1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software Foundation,
14 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15 *
16 * The Original Code is Copyright (C) 2008 Blender Foundation.
17 * All rights reserved.
18 */
19
20 /** \file
21 * \ingroup edinterface
22 */
23
24 #include <ctype.h>
25 #include <float.h>
26 #include <limits.h>
27 #include <math.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "MEM_guardedalloc.h"
32
33 #include "DNA_brush_types.h"
34 #include "DNA_curveprofile_types.h"
35 #include "DNA_scene_types.h"
36 #include "DNA_screen_types.h"
37
38 #include "BLI_linklist.h"
39 #include "BLI_listbase.h"
40 #include "BLI_math.h"
41 #include "BLI_rect.h"
42 #include "BLI_string.h"
43 #include "BLI_string_cursor_utf8.h"
44 #include "BLI_string_utf8.h"
45 #include "BLI_utildefines.h"
46
47 #include "PIL_time.h"
48
49 #include "BKE_animsys.h"
50 #include "BKE_blender_undo.h"
51 #include "BKE_brush.h"
52 #include "BKE_colorband.h"
53 #include "BKE_colortools.h"
54 #include "BKE_context.h"
55 #include "BKE_curveprofile.h"
56 #include "BKE_movieclip.h"
57 #include "BKE_paint.h"
58 #include "BKE_report.h"
59 #include "BKE_screen.h"
60 #include "BKE_tracking.h"
61 #include "BKE_unit.h"
62
63 #include "IMB_colormanagement.h"
64
65 #include "ED_screen.h"
66 #include "ED_undo.h"
67
68 #include "UI_interface.h"
69 #include "UI_view2d.h"
70
71 #include "BLF_api.h"
72
73 #include "interface_intern.h"
74
75 #include "RNA_access.h"
76
77 #include "WM_api.h"
78 #include "WM_types.h"
79 #include "wm_event_system.h"
80
81 #ifdef WITH_INPUT_IME
82 # include "BLT_lang.h"
83 # include "BLT_translation.h"
84 # include "wm_window.h"
85 #endif
86
87 /* -------------------------------------------------------------------- */
88 /** \name Feature Defines
89 *
90 * These defines allow developers to locally toggle functionality which
91 * may be useful for testing (especially conflicts in dragging).
92 * Ideally the code would be refactored to support this functionality in a less fragile way.
93 * Until then keep these defines.
94 * \{ */
95
96 /** Place the mouse at the scaled down location when un-grabbing. */
97 #define USE_CONT_MOUSE_CORRECT
98 /** Support dragging toggle buttons. */
99 #define USE_DRAG_TOGGLE
100
101 /** Support dragging multiple number buttons at once. */
102 #define USE_DRAG_MULTINUM
103
104 /** Allow dragging/editing all other selected items at once. */
105 #define USE_ALLSELECT
106
107 /**
108 * Check to avoid very small mouse-moves from jumping away from keyboard navigation,
109 * while larger mouse motion will override keyboard input, see: T34936.
110 */
111 #define USE_KEYNAV_LIMIT
112
113 /** Support dragging popups by their header. */
114 #define USE_DRAG_POPUP
115
116 /** \} */
117
118 /* -------------------------------------------------------------------- */
119 /** \name Local Defines
120 * \{ */
121
122 /**
123 * The buffer side used for password strings, where the password is stored internally,
124 * but not displayed.
125 */
126 #define UI_MAX_PASSWORD_STR 128
127
128 /**
129 * When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used,
130 * Use this as a maximum soft range for mapping cursor motion to the value.
131 * Otherwise min/max of #FLT_MAX, #INT_MAX cause small adjustments to jump to large numbers.
132 *
133 * This is needed for values such as location & dimensions which don't have a meaningful min/max,
134 * Instead of mapping cursor motion to the min/max, map the motion to the click-step.
135 *
136 * This value is multiplied by the click step to calculate a range to clamp the soft-range by.
137 * See: T68130
138 */
139 #define UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX 1000
140
141 /** \} */
142
143 /* -------------------------------------------------------------------- */
144 /** \name Local Prototypes
145 * \{ */
146
147 static int ui_do_but_EXIT(bContext *C,
148 uiBut *but,
149 struct uiHandleButtonData *data,
150 const wmEvent *event);
151 static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b);
152 static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str);
153 static void button_tooltip_timer_reset(bContext *C, uiBut *but);
154
155 #ifdef USE_KEYNAV_LIMIT
156 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event);
157 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event);
158 #endif
159
160 /** \} */
161
162 /* -------------------------------------------------------------------- */
163 /** \name Structs & Defines
164 * \{ */
165
166 #define BUTTON_FLASH_DELAY 0.020
167 #define MENU_SCROLL_INTERVAL 0.1
168 #define PIE_MENU_INTERVAL 0.01
169 #define BUTTON_AUTO_OPEN_THRESH 0.2
170 #define BUTTON_MOUSE_TOWARDS_THRESH 1.0
171 /* pixels to move the cursor to get out of keyboard navigation */
172 #define BUTTON_KEYNAV_PX_LIMIT 8
173
174 #define MENU_TOWARDS_MARGIN 20 /* margin in pixels */
175 #define MENU_TOWARDS_WIGGLE_ROOM 64 /* tolerance in pixels */
176 /* drag-lock distance threshold in pixels */
177 #define BUTTON_DRAGLOCK_THRESH 3
178
179 typedef enum uiButtonActivateType {
180 BUTTON_ACTIVATE_OVER,
181 BUTTON_ACTIVATE,
182 BUTTON_ACTIVATE_APPLY,
183 BUTTON_ACTIVATE_TEXT_EDITING,
184 BUTTON_ACTIVATE_OPEN,
185 } uiButtonActivateType;
186
187 typedef enum uiHandleButtonState {
188 BUTTON_STATE_INIT,
189 BUTTON_STATE_HIGHLIGHT,
190 BUTTON_STATE_WAIT_FLASH,
191 BUTTON_STATE_WAIT_RELEASE,
192 BUTTON_STATE_WAIT_KEY_EVENT,
193 BUTTON_STATE_NUM_EDITING,
194 BUTTON_STATE_TEXT_EDITING,
195 BUTTON_STATE_TEXT_SELECTING,
196 BUTTON_STATE_MENU_OPEN,
197 BUTTON_STATE_WAIT_DRAG,
198 BUTTON_STATE_EXIT,
199 } uiHandleButtonState;
200
201 typedef enum uiMenuScrollType {
202 MENU_SCROLL_UP,
203 MENU_SCROLL_DOWN,
204 MENU_SCROLL_TOP,
205 MENU_SCROLL_BOTTOM,
206 } uiMenuScrollType;
207
208 #ifdef USE_ALLSELECT
209
210 /* Unfortunately there's no good way handle more generally:
211 * (propagate single clicks on layer buttons to other objects) */
212 # define USE_ALLSELECT_LAYER_HACK
213
214 typedef struct uiSelectContextElem {
215 PointerRNA ptr;
216 union {
217 bool val_b;
218 int val_i;
219 float val_f;
220 };
221 } uiSelectContextElem;
222
223 typedef struct uiSelectContextStore {
224 uiSelectContextElem *elems;
225 int elems_len;
226 bool do_free;
227 bool is_enabled;
228 /* When set, simply copy values (don't apply difference).
229 * Rules are:
230 * - dragging numbers uses delta.
231 * - typing in values will assign to all. */
232 bool is_copy;
233 } uiSelectContextStore;
234
235 static bool ui_selectcontext_begin(bContext *C,
236 uiBut *but,
237 struct uiSelectContextStore *selctx_data);
238 static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data);
239 static void ui_selectcontext_apply(bContext *C,
240 uiBut *but,
241 struct uiSelectContextStore *selctx_data,
242 const double value,
243 const double value_orig);
244
245 # define IS_ALLSELECT_EVENT(event) ((event)->alt != 0)
246
247 /** just show a tinted color so users know its activated */
248 # define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE
249
250 #endif /* USE_ALLSELECT */
251
252 #ifdef USE_DRAG_MULTINUM
253
254 /**
255 * how far to drag before we check for gesture direction (in pixels),
256 * note: half the height of a button is about right... */
257 # define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
258
259 /**
260 * How far to drag horizontally
261 * before we stop checking which buttons the gesture spans (in pixels),
262 * locking down the buttons so we can drag freely without worrying about vertical movement.
263 */
264 # define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4)
265
266 /**
267 * How strict to be when detecting a vertical gesture:
268 * [0.5 == sloppy], [0.9 == strict], (unsigned dot-product).
269 *
270 * \note We should be quite strict here,
271 * since doing a vertical gesture by accident should be avoided,
272 * however with some care a user should be able to do a vertical movement without _missing_.
273 */
274 # define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f)
275
276 /* a simple version of uiHandleButtonData when accessing multiple buttons */
277 typedef struct uiButMultiState {
278 double origvalue;
279 uiBut *but;
280
281 # ifdef USE_ALLSELECT
282 uiSelectContextStore select_others;
283 # endif
284 } uiButMultiState;
285
286 typedef struct uiHandleButtonMulti {
287 enum {
288 /** gesture direction unknown, wait until mouse has moved enough... */
289 BUTTON_MULTI_INIT_UNSET = 0,
290 /** vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */
291 BUTTON_MULTI_INIT_SETUP,
292 /** flag buttons finished, apply horizontal motion to active and flagged */
293 BUTTON_MULTI_INIT_ENABLE,
294 /** vertical gesture _not_ detected, take no further action */
295 BUTTON_MULTI_INIT_DISABLE,
296 } init;
297
298 bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */
299 LinkNode *mbuts;
300 uiButStore *bs_mbuts;
301
302 bool is_proportional;
303
304 /* In some cases we directly apply the changes to multiple buttons,
305 * so we don't want to do it twice. */
306 bool skip;
307
308 /* before activating, we need to check gesture direction accumulate signed cursor movement
309 * here so we can tell if this is a vertical motion or not. */
310 float drag_dir[2];
311
312 /* values copied direct from event->x,y
313 * used to detect buttons between the current and initial mouse position */
314 int drag_start[2];
315
316 /* store x location once BUTTON_MULTI_INIT_SETUP is set,
317 * moving outside this sets BUTTON_MULTI_INIT_ENABLE */
318 int drag_lock_x;
319
320 } uiHandleButtonMulti;
321
322 #endif /* USE_DRAG_MULTINUM */
323
324 typedef struct uiHandleButtonData {
325 wmWindowManager *wm;
326 wmWindow *window;
327 ScrArea *area;
328 ARegion *region;
329
330 bool interactive;
331
332 /* overall state */
333 uiHandleButtonState state;
334 int retval;
335 /* booleans (could be made into flags) */
336 bool cancel, escapecancel;
337 bool applied, applied_interactive;
338 bool changed_cursor;
339 wmTimer *flashtimer;
340
341 /* edited value */
342 /* use 'ui_textedit_string_set' to assign new strings */
343 char *str;
344 char *origstr;
345 double value, origvalue, startvalue;
346 float vec[3], origvec[3];
347 #if 0 /* UNUSED */
348 int togdual, togonly;
349 #endif
350 ColorBand *coba;
351
352 /* tooltip */
353 uint tooltip_force : 1;
354
355 /* auto open */
356 bool used_mouse;
357 wmTimer *autoopentimer;
358
359 /* auto open (hold) */
360 wmTimer *hold_action_timer;
361
362 /* text selection/editing */
363 /* size of 'str' (including terminator) */
364 int maxlen;
365 /* Button text selection:
366 * extension direction, selextend, inside ui_do_but_TEX */
367 int sel_pos_init;
368 /* allow to realloc str/editstr and use 'maxlen' to track alloc size (maxlen + 1) */
369 bool is_str_dynamic;
370
371 /* number editing / dragging */
372 /* coords are Window/uiBlock relative (depends on the button) */
373 int draglastx, draglasty;
374 int dragstartx, dragstarty;
375 int draglastvalue;
376 int dragstartvalue;
377 bool dragchange, draglock;
378 int dragsel;
379 float dragf, dragfstart;
380 CBData *dragcbd;
381
382 /** Soft min/max with #UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX applied. */
383 float drag_map_soft_min;
384 float drag_map_soft_max;
385
386 #ifdef USE_CONT_MOUSE_CORRECT
387 /* when ungrabbing buttons which are #ui_but_is_cursor_warp(),
388 * we may want to position them.
389 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl()
390 * to get this into a usable space. */
391 float ungrab_mval[2];
392 #endif
393
394 /* menu open (watch UI_screen_free_active_but) */
395 uiPopupBlockHandle *menu;
396 int menuretval;
397
398 /* search box (watch UI_screen_free_active_but) */
399 ARegion *searchbox;
400 #ifdef USE_KEYNAV_LIMIT
401 struct uiKeyNavLock searchbox_keynav_state;
402 #endif
403
404 #ifdef USE_DRAG_MULTINUM
405 /* Multi-buttons will be updated in unison with the active button. */
406 uiHandleButtonMulti multi_data;
407 #endif
408
409 #ifdef USE_ALLSELECT
410 uiSelectContextStore select_others;
411 #endif
412
413 /* Text field undo. */
414 struct uiUndoStack_Text *undo_stack_text;
415
416 /* post activate */
417 uiButtonActivateType posttype;
418 uiBut *postbut;
419 } uiHandleButtonData;
420
421 typedef struct uiAfterFunc {
422 struct uiAfterFunc *next, *prev;
423
424 uiButHandleFunc func;
425 void *func_arg1;
426 void *func_arg2;
427
428 uiButHandleNFunc funcN;
429 void *func_argN;
430
431 uiButHandleRenameFunc rename_func;
432 void *rename_arg1;
433 void *rename_orig;
434
435 uiBlockHandleFunc handle_func;
436 void *handle_func_arg;
437 int retval;
438
439 uiMenuHandleFunc butm_func;
440 void *butm_func_arg;
441 int a2;
442
443 wmOperator *popup_op;
444 wmOperatorType *optype;
445 int opcontext;
446 PointerRNA *opptr;
447
448 PointerRNA rnapoin;
449 PropertyRNA *rnaprop;
450
451 void *search_arg;
452 uiButSearchArgFreeFn search_arg_free_fn;
453
454 bContextStore *context;
455
456 char undostr[BKE_UNDO_STR_MAX];
457 } uiAfterFunc;
458
459 static void button_activate_init(bContext *C,
460 ARegion *region,
461 uiBut *but,
462 uiButtonActivateType type);
463 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
464 static void button_activate_exit(
465 bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree);
466 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
467 static void ui_handle_button_activate(bContext *C,
468 ARegion *region,
469 uiBut *but,
470 uiButtonActivateType type);
471 static bool ui_do_but_extra_operator_icon(bContext *C,
472 uiBut *but,
473 uiHandleButtonData *data,
474 const wmEvent *event);
475 static void ui_do_but_extra_operator_icons_mousemove(uiBut *but,
476 uiHandleButtonData *data,
477 const wmEvent *event);
478
479 #ifdef USE_DRAG_MULTINUM
480 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block);
481 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but);
482 #endif
483
484 /* buttons clipboard */
485 static ColorBand but_copypaste_coba = {0};
486 static CurveMapping but_copypaste_curve = {0};
487 static bool but_copypaste_curve_alive = false;
488 static CurveProfile but_copypaste_profile = {0};
489 static bool but_copypaste_profile_alive = false;
490
491 /** \} */
492
493 /* -------------------------------------------------------------------- */
494 /** \name UI Queries
495 * \{ */
496
ui_but_is_editing(const uiBut * but)497 bool ui_but_is_editing(const uiBut *but)
498 {
499 uiHandleButtonData *data = but->active;
500 return (data && ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING));
501 }
502
503 /* assumes event type is MOUSEPAN */
ui_pan_to_scroll(const wmEvent * event,int * type,int * val)504 void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
505 {
506 static int lastdy = 0;
507 int dy = event->prevy - event->y;
508
509 /* This event should be originally from event->type,
510 * converting wrong event into wheel is bad, see T33803. */
511 BLI_assert(*type == MOUSEPAN);
512
513 /* sign differs, reset */
514 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
515 lastdy = dy;
516 }
517 else {
518 lastdy += dy;
519
520 if (abs(lastdy) > (int)UI_UNIT_Y) {
521 if (U.uiflag2 & USER_TRACKPAD_NATURAL) {
522 dy = -dy;
523 }
524
525 *val = KM_PRESS;
526
527 if (dy > 0) {
528 *type = WHEELUPMOUSE;
529 }
530 else {
531 *type = WHEELDOWNMOUSE;
532 }
533
534 lastdy = 0;
535 }
536 }
537 }
538
ui_but_find_select_in_enum__cmp(const uiBut * but_a,const uiBut * but_b)539 static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b)
540 {
541 return ((but_a->type == but_b->type) && (but_a->alignnr == but_b->alignnr) &&
542 (but_a->poin == but_b->poin) && (but_a->rnapoin.type == but_b->rnapoin.type) &&
543 (but_a->rnaprop == but_b->rnaprop));
544 }
545
546 /**
547 * Finds the pressed button in an aligned row (typically an expanded enum).
548 *
549 * \param direction: Use when there may be multiple buttons pressed.
550 */
ui_but_find_select_in_enum(uiBut * but,int direction)551 uiBut *ui_but_find_select_in_enum(uiBut *but, int direction)
552 {
553 uiBut *but_iter = but;
554 uiBut *but_found = NULL;
555 BLI_assert(ELEM(direction, -1, 1));
556
557 while ((but_iter->prev) && ui_but_find_select_in_enum__cmp(but_iter->prev, but)) {
558 but_iter = but_iter->prev;
559 }
560
561 while (but_iter && ui_but_find_select_in_enum__cmp(but_iter, but)) {
562 if (but_iter->flag & UI_SELECT) {
563 but_found = but_iter;
564 if (direction == 1) {
565 break;
566 }
567 }
568 but_iter = but_iter->next;
569 }
570
571 return but_found;
572 }
573
ui_mouse_scale_warp_factor(const bool shift)574 static float ui_mouse_scale_warp_factor(const bool shift)
575 {
576 return shift ? 0.05f : 1.0f;
577 }
578
ui_mouse_scale_warp(uiHandleButtonData * data,const float mx,const float my,float * r_mx,float * r_my,const bool shift)579 static void ui_mouse_scale_warp(uiHandleButtonData *data,
580 const float mx,
581 const float my,
582 float *r_mx,
583 float *r_my,
584 const bool shift)
585 {
586 const float fac = ui_mouse_scale_warp_factor(shift);
587
588 /* slow down the mouse, this is fairly picky */
589 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
590 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
591 }
592
593 /** \} */
594
595 /* -------------------------------------------------------------------- */
596 /** \name UI Utilities
597 * \{ */
598
599 /**
600 * Ignore mouse movements within some horizontal pixel threshold before starting to drag
601 */
ui_but_dragedit_update_mval(uiHandleButtonData * data,int mx)602 static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx)
603 {
604 if (mx == data->draglastx) {
605 return false;
606 }
607
608 if (data->draglock) {
609 if (abs(mx - data->dragstartx) <= BUTTON_DRAGLOCK_THRESH) {
610 return false;
611 }
612 #ifdef USE_DRAG_MULTINUM
613 if (ELEM(data->multi_data.init, BUTTON_MULTI_INIT_UNSET, BUTTON_MULTI_INIT_SETUP)) {
614 return false;
615 }
616 #endif
617 data->draglock = false;
618 data->dragstartx = mx; /* ignore mouse movement within drag-lock */
619 }
620
621 return true;
622 }
623
ui_rna_is_userdef(PointerRNA * ptr,PropertyRNA * prop)624 static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop)
625 {
626 /* Not very elegant, but ensures preference changes force re-save. */
627 bool tag = false;
628 if (prop && !(RNA_property_flag(prop) & PROP_NO_DEG_UPDATE)) {
629 StructRNA *base = RNA_struct_base(ptr->type);
630 if (base == NULL) {
631 base = ptr->type;
632 }
633 if (ELEM(base, &RNA_AddonPreferences, &RNA_KeyConfigPreferences, &RNA_KeyMapItem)) {
634 tag = true;
635 }
636 }
637 return tag;
638 }
639
UI_but_is_userdef(const uiBut * but)640 bool UI_but_is_userdef(const uiBut *but)
641 {
642 /* This is read-only, RNA API isn't using const when it could. */
643 return ui_rna_is_userdef((PointerRNA *)&but->rnapoin, but->rnaprop);
644 }
645
ui_rna_update_preferences_dirty(PointerRNA * ptr,PropertyRNA * prop)646 static void ui_rna_update_preferences_dirty(PointerRNA *ptr, PropertyRNA *prop)
647 {
648 if (ui_rna_is_userdef(ptr, prop)) {
649 U.runtime.is_dirty = true;
650 WM_main_add_notifier(NC_WINDOW, NULL);
651 }
652 }
653
ui_but_update_preferences_dirty(uiBut * but)654 static void ui_but_update_preferences_dirty(uiBut *but)
655 {
656 ui_rna_update_preferences_dirty(&but->rnapoin, but->rnaprop);
657 }
658
ui_afterfunc_update_preferences_dirty(uiAfterFunc * after)659 static void ui_afterfunc_update_preferences_dirty(uiAfterFunc *after)
660 {
661 ui_rna_update_preferences_dirty(&after->rnapoin, after->rnaprop);
662 }
663
664 /** \} */
665
666 /* -------------------------------------------------------------------- */
667 /** \name Button Snap Values
668 *
669 * \{ */
670
671 enum eSnapType {
672 SNAP_OFF = 0,
673 SNAP_ON,
674 SNAP_ON_SMALL,
675 };
676
ui_event_to_snap(const wmEvent * event)677 static enum eSnapType ui_event_to_snap(const wmEvent *event)
678 {
679 return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF;
680 }
681
ui_event_is_snap(const wmEvent * event)682 static bool ui_event_is_snap(const wmEvent *event)
683 {
684 return (ELEM(event->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) ||
685 ELEM(event->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY));
686 }
687
ui_color_snap_hue(const enum eSnapType snap,float * r_hue)688 static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
689 {
690 const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
691 BLI_assert(snap != SNAP_OFF);
692 *r_hue = roundf((*r_hue) * snap_increment) / snap_increment;
693 }
694
695 /** \} */
696
697 /* -------------------------------------------------------------------- */
698 /** \name Button Apply/Revert
699 * \{ */
700
701 static ListBase UIAfterFuncs = {NULL, NULL};
702
ui_afterfunc_new(void)703 static uiAfterFunc *ui_afterfunc_new(void)
704 {
705 uiAfterFunc *after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc");
706
707 BLI_addtail(&UIAfterFuncs, after);
708
709 return after;
710 }
711
712 /**
713 * For executing operators after the button is pressed.
714 * (some non operator buttons need to trigger operators), see: T37795.
715 *
716 * \note Can only call while handling buttons.
717 */
ui_handle_afterfunc_add_operator(wmOperatorType * ot,int opcontext,bool create_props)718 PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
719 {
720 PointerRNA *ptr = NULL;
721 uiAfterFunc *after = ui_afterfunc_new();
722
723 after->optype = ot;
724 after->opcontext = opcontext;
725
726 if (create_props) {
727 ptr = MEM_callocN(sizeof(PointerRNA), __func__);
728 WM_operator_properties_create_ptr(ptr, ot);
729 after->opptr = ptr;
730 }
731
732 return ptr;
733 }
734
popup_check(bContext * C,wmOperator * op)735 static void popup_check(bContext *C, wmOperator *op)
736 {
737 if (op && op->type->check) {
738 op->type->check(C, op);
739 }
740 }
741
742 /**
743 * Check if a #uiAfterFunc is needed for this button.
744 */
ui_afterfunc_check(const uiBlock * block,const uiBut * but)745 static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
746 {
747 return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop ||
748 block->handle_func || (but->type == UI_BTYPE_BUT_MENU && block->butm_func) ||
749 (block->handle && block->handle->popup_op));
750 }
751
ui_apply_but_func(bContext * C,uiBut * but)752 static void ui_apply_but_func(bContext *C, uiBut *but)
753 {
754 uiBlock *block = but->block;
755
756 /* these functions are postponed and only executed after all other
757 * handling is done, i.e. menus are closed, in order to avoid conflicts
758 * with these functions removing the buttons we are working with */
759
760 if (ui_afterfunc_check(block, but)) {
761 uiAfterFunc *after = ui_afterfunc_new();
762
763 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
764 /* exception, this will crash due to removed button otherwise */
765 but->func(C, but->func_arg1, but->func_arg2);
766 }
767 else {
768 after->func = but->func;
769 }
770
771 after->func_arg1 = but->func_arg1;
772 after->func_arg2 = but->func_arg2;
773
774 after->funcN = but->funcN;
775 after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL;
776
777 after->rename_func = but->rename_func;
778 after->rename_arg1 = but->rename_arg1;
779 after->rename_orig = but->rename_orig; /* needs free! */
780
781 after->handle_func = block->handle_func;
782 after->handle_func_arg = block->handle_func_arg;
783 after->retval = but->retval;
784
785 if (but->type == UI_BTYPE_BUT_MENU) {
786 after->butm_func = block->butm_func;
787 after->butm_func_arg = block->butm_func_arg;
788 after->a2 = but->a2;
789 }
790
791 if (block->handle) {
792 after->popup_op = block->handle->popup_op;
793 }
794
795 after->optype = but->optype;
796 after->opcontext = but->opcontext;
797 after->opptr = but->opptr;
798
799 after->rnapoin = but->rnapoin;
800 after->rnaprop = but->rnaprop;
801
802 if (but->type == UI_BTYPE_SEARCH_MENU) {
803 uiButSearch *search_but = (uiButSearch *)but;
804 after->search_arg_free_fn = search_but->arg_free_fn;
805 after->search_arg = search_but->arg;
806 search_but->arg_free_fn = NULL;
807 search_but->arg = NULL;
808 }
809
810 if (but->context) {
811 after->context = CTX_store_copy(but->context);
812 }
813
814 but->optype = NULL;
815 but->opcontext = 0;
816 but->opptr = NULL;
817 }
818 }
819
820 /* typically call ui_apply_but_undo(), ui_apply_but_autokey() */
ui_apply_but_undo(uiBut * but)821 static void ui_apply_but_undo(uiBut *but)
822 {
823 if (but->flag & UI_BUT_UNDO) {
824 const char *str = NULL;
825 size_t str_len_clip = SIZE_MAX - 1;
826 bool skip_undo = false;
827
828 /* define which string to use for undo */
829 if (but->type == UI_BTYPE_MENU) {
830 str = but->drawstr;
831 str_len_clip = ui_but_drawstr_len_without_sep_char(but);
832 }
833 else if (but->drawstr[0]) {
834 str = but->drawstr;
835 str_len_clip = ui_but_drawstr_len_without_sep_char(but);
836 }
837 else {
838 str = but->tip;
839 str_len_clip = ui_but_tip_len_only_first_line(but);
840 }
841
842 /* fallback, else we don't get an undo! */
843 if (str == NULL || str[0] == '\0' || str_len_clip == 0) {
844 str = "Unknown Action";
845 }
846
847 /* Optionally override undo when undo system doesn't support storing properties. */
848 if (but->rnapoin.owner_id) {
849 /* Exception for renaming ID data, we always need undo pushes in this case,
850 * because undo systems track data by their ID, see: T67002. */
851 extern PropertyRNA rna_ID_name;
852 /* Exception for active shape-key, since changing this in edit-mode updates
853 * the shape key from object mode data. */
854 extern PropertyRNA rna_Object_active_shape_key_index;
855 if (ELEM(but->rnaprop, &rna_ID_name, &rna_Object_active_shape_key_index)) {
856 /* pass */
857 }
858 else {
859 ID *id = but->rnapoin.owner_id;
860 if (!ED_undo_is_legacy_compatible_for_property(but->block->evil_C, id)) {
861 skip_undo = true;
862 }
863 }
864 }
865
866 if (skip_undo == false) {
867 /* XXX: disable all undo pushes from UI changes from sculpt mode as they cause memfile undo
868 * steps to be written which cause lag: T71434. */
869 if (BKE_paintmode_get_active_from_context(but->block->evil_C) == PAINT_MODE_SCULPT) {
870 skip_undo = true;
871 }
872 }
873
874 if (skip_undo) {
875 str = "";
876 }
877
878 /* delayed, after all other funcs run, popups are closed, etc */
879 uiAfterFunc *after = ui_afterfunc_new();
880 BLI_strncpy(after->undostr, str, min_zz(str_len_clip + 1, sizeof(after->undostr)));
881 }
882 }
883
ui_apply_but_autokey(bContext * C,uiBut * but)884 static void ui_apply_but_autokey(bContext *C, uiBut *but)
885 {
886 Scene *scene = CTX_data_scene(C);
887
888 /* try autokey */
889 ui_but_anim_autokey(C, but, scene, scene->r.cfra);
890
891 /* make a little report about what we've done! */
892 if (but->rnaprop) {
893 char *buf;
894
895 if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) {
896 return;
897 }
898
899 buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex);
900 if (buf) {
901 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf);
902 MEM_freeN(buf);
903
904 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL);
905 }
906 }
907 }
908
ui_apply_but_funcs_after(bContext * C)909 static void ui_apply_but_funcs_after(bContext *C)
910 {
911 /* copy to avoid recursive calls */
912 ListBase funcs = UIAfterFuncs;
913 BLI_listbase_clear(&UIAfterFuncs);
914
915 LISTBASE_FOREACH_MUTABLE (uiAfterFunc *, afterf, &funcs) {
916 uiAfterFunc after = *afterf; /* copy to avoid memleak on exit() */
917 BLI_freelinkN(&funcs, afterf);
918
919 if (after.context) {
920 CTX_store_set(C, after.context);
921 }
922
923 if (after.popup_op) {
924 popup_check(C, after.popup_op);
925 }
926
927 PointerRNA opptr;
928 if (after.opptr) {
929 /* free in advance to avoid leak on exit */
930 opptr = *after.opptr;
931 MEM_freeN(after.opptr);
932 }
933
934 if (after.optype) {
935 WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL);
936 }
937
938 if (after.opptr) {
939 WM_operator_properties_free(&opptr);
940 }
941
942 if (after.rnapoin.data) {
943 RNA_property_update(C, &after.rnapoin, after.rnaprop);
944 }
945
946 if (after.context) {
947 CTX_store_set(C, NULL);
948 CTX_store_free(after.context);
949 }
950
951 if (after.func) {
952 after.func(C, after.func_arg1, after.func_arg2);
953 }
954 if (after.funcN) {
955 after.funcN(C, after.func_argN, after.func_arg2);
956 }
957 if (after.func_argN) {
958 MEM_freeN(after.func_argN);
959 }
960
961 if (after.handle_func) {
962 after.handle_func(C, after.handle_func_arg, after.retval);
963 }
964 if (after.butm_func) {
965 after.butm_func(C, after.butm_func_arg, after.a2);
966 }
967
968 if (after.rename_func) {
969 after.rename_func(C, after.rename_arg1, after.rename_orig);
970 }
971 if (after.rename_orig) {
972 MEM_freeN(after.rename_orig);
973 }
974
975 if (after.search_arg_free_fn) {
976 after.search_arg_free_fn(after.search_arg);
977 }
978
979 ui_afterfunc_update_preferences_dirty(&after);
980
981 if (after.undostr[0]) {
982 ED_undo_push(C, after.undostr);
983 }
984 }
985 }
986
ui_apply_but_BUT(bContext * C,uiBut * but,uiHandleButtonData * data)987 static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
988 {
989 ui_apply_but_func(C, but);
990
991 data->retval = but->retval;
992 data->applied = true;
993 }
994
ui_apply_but_BUTM(bContext * C,uiBut * but,uiHandleButtonData * data)995 static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
996 {
997 ui_but_value_set(but, but->hardmin);
998 ui_apply_but_func(C, but);
999
1000 data->retval = but->retval;
1001 data->applied = true;
1002 }
1003
ui_apply_but_BLOCK(bContext * C,uiBut * but,uiHandleButtonData * data)1004 static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
1005 {
1006 if (but->type == UI_BTYPE_MENU) {
1007 ui_but_value_set(but, data->value);
1008 }
1009
1010 ui_but_update_edited(but);
1011 ui_apply_but_func(C, but);
1012 data->retval = but->retval;
1013 data->applied = true;
1014 }
1015
ui_apply_but_TOG(bContext * C,uiBut * but,uiHandleButtonData * data)1016 static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
1017 {
1018 const double value = ui_but_value_get(but);
1019 int value_toggle;
1020 if (but->bit) {
1021 value_toggle = UI_BITBUT_VALUE_TOGGLED((int)value, but->bitnr);
1022 }
1023 else {
1024 value_toggle = (value == 0.0);
1025 if (ELEM(but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N)) {
1026 value_toggle = !value_toggle;
1027 }
1028 }
1029
1030 ui_but_value_set(but, (double)value_toggle);
1031 if (but->type == UI_BTYPE_ICON_TOGGLE || but->type == UI_BTYPE_ICON_TOGGLE_N) {
1032 ui_but_update_edited(but);
1033 }
1034
1035 ui_apply_but_func(C, but);
1036
1037 data->retval = but->retval;
1038 data->applied = true;
1039 }
1040
ui_apply_but_ROW(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data)1041 static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
1042 {
1043 ui_but_value_set(but, but->hardmax);
1044
1045 ui_apply_but_func(C, but);
1046
1047 /* states of other row buttons */
1048 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
1049 if (bt != but && bt->poin == but->poin && ELEM(bt->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) {
1050 ui_but_update_edited(bt);
1051 }
1052 }
1053
1054 data->retval = but->retval;
1055 data->applied = true;
1056 }
1057
ui_apply_but_TEX(bContext * C,uiBut * but,uiHandleButtonData * data)1058 static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
1059 {
1060 if (!data->str) {
1061 return;
1062 }
1063
1064 ui_but_string_set(C, but, data->str);
1065 ui_but_update_edited(but);
1066
1067 /* give butfunc a copy of the original text too.
1068 * feature used for bone renaming, channels, etc.
1069 * afterfunc frees rename_orig */
1070 if (data->origstr && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) {
1071 /* In this case, we need to keep origstr available,
1072 * to restore real org string in case we cancel after having typed something already. */
1073 but->rename_orig = BLI_strdup(data->origstr);
1074 }
1075 /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */
1076 else if (ui_afterfunc_check(but->block, but)) {
1077 but->rename_orig = data->origstr;
1078 data->origstr = NULL;
1079 }
1080
1081 void *orig_arg2 = but->func_arg2;
1082
1083 /* If arg2 isn't in use already, pass the active search item through it. */
1084 if ((but->func_arg2 == NULL) && (but->type == UI_BTYPE_SEARCH_MENU)) {
1085 uiButSearch *search_but = (uiButSearch *)but;
1086 but->func_arg2 = search_but->item_active;
1087 }
1088
1089 ui_apply_but_func(C, but);
1090
1091 but->func_arg2 = orig_arg2;
1092
1093 data->retval = but->retval;
1094 data->applied = true;
1095 }
1096
ui_apply_but_TAB(bContext * C,uiBut * but,uiHandleButtonData * data)1097 static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data)
1098 {
1099 if (data->str) {
1100 ui_but_string_set(C, but, data->str);
1101 ui_but_update_edited(but);
1102 }
1103 else {
1104 ui_but_value_set(but, but->hardmax);
1105 ui_apply_but_func(C, but);
1106 }
1107
1108 data->retval = but->retval;
1109 data->applied = true;
1110 }
1111
ui_apply_but_NUM(bContext * C,uiBut * but,uiHandleButtonData * data)1112 static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
1113 {
1114 if (data->str) {
1115 if (ui_but_string_set(C, but, data->str)) {
1116 data->value = ui_but_value_get(but);
1117 }
1118 else {
1119 data->cancel = true;
1120 return;
1121 }
1122 }
1123 else {
1124 ui_but_value_set(but, data->value);
1125 }
1126
1127 ui_but_update_edited(but);
1128 ui_apply_but_func(C, but);
1129
1130 data->retval = but->retval;
1131 data->applied = true;
1132 }
1133
ui_apply_but_VEC(bContext * C,uiBut * but,uiHandleButtonData * data)1134 static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
1135 {
1136 ui_but_v3_set(but, data->vec);
1137 ui_but_update_edited(but);
1138 ui_apply_but_func(C, but);
1139
1140 data->retval = but->retval;
1141 data->applied = true;
1142 }
1143
ui_apply_but_COLORBAND(bContext * C,uiBut * but,uiHandleButtonData * data)1144 static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
1145 {
1146 ui_apply_but_func(C, but);
1147 data->retval = but->retval;
1148 data->applied = true;
1149 }
1150
ui_apply_but_CURVE(bContext * C,uiBut * but,uiHandleButtonData * data)1151 static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
1152 {
1153 ui_apply_but_func(C, but);
1154 data->retval = but->retval;
1155 data->applied = true;
1156 }
1157
ui_apply_but_CURVEPROFILE(bContext * C,uiBut * but,uiHandleButtonData * data)1158 static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data)
1159 {
1160 ui_apply_but_func(C, but);
1161 data->retval = but->retval;
1162 data->applied = true;
1163 }
1164
1165 /** \} */
1166
1167 /* -------------------------------------------------------------------- */
1168 /** \name Button Drag Multi-Number
1169 * \{ */
1170
1171 #ifdef USE_DRAG_MULTINUM
1172
1173 /* small multi-but api */
ui_multibut_add(uiHandleButtonData * data,uiBut * but)1174 static void ui_multibut_add(uiHandleButtonData *data, uiBut *but)
1175 {
1176 BLI_assert(but->flag & UI_BUT_DRAG_MULTI);
1177 BLI_assert(data->multi_data.has_mbuts);
1178
1179 uiButMultiState *mbut_state = MEM_callocN(sizeof(*mbut_state), __func__);
1180 mbut_state->but = but;
1181 mbut_state->origvalue = ui_but_value_get(but);
1182 # ifdef USE_ALLSELECT
1183 mbut_state->select_others.is_copy = data->select_others.is_copy;
1184 # endif
1185
1186 BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state);
1187
1188 UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but);
1189 }
1190
ui_multibut_lookup(uiHandleButtonData * data,const uiBut * but)1191 static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but)
1192 {
1193 for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) {
1194 uiButMultiState *mbut_state = l->link;
1195
1196 if (mbut_state->but == but) {
1197 return mbut_state;
1198 }
1199 }
1200
1201 return NULL;
1202 }
1203
ui_multibut_restore(bContext * C,uiHandleButtonData * data,uiBlock * block)1204 static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block)
1205 {
1206 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1207 if (but->flag & UI_BUT_DRAG_MULTI) {
1208 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1209 if (mbut_state) {
1210 ui_but_value_set(but, mbut_state->origvalue);
1211
1212 # ifdef USE_ALLSELECT
1213 if (mbut_state->select_others.elems_len > 0) {
1214 ui_selectcontext_apply(
1215 C, but, &mbut_state->select_others, mbut_state->origvalue, mbut_state->origvalue);
1216 }
1217 # else
1218 UNUSED_VARS(C);
1219 # endif
1220 }
1221 }
1222 }
1223 }
1224
ui_multibut_free(uiHandleButtonData * data,uiBlock * block)1225 static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block)
1226 {
1227 # ifdef USE_ALLSELECT
1228 if (data->multi_data.mbuts) {
1229 LinkNode *list = data->multi_data.mbuts;
1230 while (list) {
1231 LinkNode *next = list->next;
1232 uiButMultiState *mbut_state = list->link;
1233
1234 if (mbut_state->select_others.elems) {
1235 MEM_freeN(mbut_state->select_others.elems);
1236 }
1237
1238 MEM_freeN(list->link);
1239 MEM_freeN(list);
1240 list = next;
1241 }
1242 }
1243 # else
1244 BLI_linklist_freeN(data->multi_data.mbuts);
1245 # endif
1246
1247 data->multi_data.mbuts = NULL;
1248
1249 if (data->multi_data.bs_mbuts) {
1250 UI_butstore_free(block, data->multi_data.bs_mbuts);
1251 data->multi_data.bs_mbuts = NULL;
1252 }
1253 }
1254
ui_multibut_states_tag(uiBut * but_active,uiHandleButtonData * data,const wmEvent * event)1255 static bool ui_multibut_states_tag(uiBut *but_active,
1256 uiHandleButtonData *data,
1257 const wmEvent *event)
1258 {
1259 float seg[2][2];
1260 bool changed = false;
1261
1262 seg[0][0] = data->multi_data.drag_start[0];
1263 seg[0][1] = data->multi_data.drag_start[1];
1264
1265 seg[1][0] = event->x;
1266 seg[1][1] = event->y;
1267
1268 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1269
1270 ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]);
1271 ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]);
1272
1273 data->multi_data.has_mbuts = false;
1274
1275 /* follow ui_but_find_mouse_over_ex logic */
1276 LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) {
1277 bool drag_prev = false;
1278 bool drag_curr = false;
1279
1280 /* re-set each time */
1281 if (but->flag & UI_BUT_DRAG_MULTI) {
1282 but->flag &= ~UI_BUT_DRAG_MULTI;
1283 drag_prev = true;
1284 }
1285
1286 if (ui_but_is_interactive(but, false)) {
1287
1288 /* drag checks */
1289 if (but_active != but) {
1290 if (ui_but_is_compatible(but_active, but)) {
1291
1292 BLI_assert(but->active == NULL);
1293
1294 /* finally check for overlap */
1295 if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) {
1296
1297 but->flag |= UI_BUT_DRAG_MULTI;
1298 data->multi_data.has_mbuts = true;
1299 drag_curr = true;
1300 }
1301 }
1302 }
1303 }
1304
1305 changed |= (drag_prev != drag_curr);
1306 }
1307
1308 return changed;
1309 }
1310
ui_multibut_states_create(uiBut * but_active,uiHandleButtonData * data)1311 static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
1312 {
1313 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
1314 BLI_assert(data->multi_data.has_mbuts);
1315
1316 data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
1317
1318 LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) {
1319 if (but->flag & UI_BUT_DRAG_MULTI) {
1320 ui_multibut_add(data, but);
1321 }
1322 }
1323
1324 /* edit buttons proportionally to eachother
1325 * note: if we mix buttons which are proportional and others which are not,
1326 * this may work a bit strangely */
1327 if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) ||
1328 ELEM(but_active->unit_type, RNA_SUBTYPE_UNIT_VALUE(PROP_UNIT_LENGTH))) {
1329 if (data->origvalue != 0.0) {
1330 data->multi_data.is_proportional = true;
1331 }
1332 }
1333 }
1334
ui_multibut_states_apply(bContext * C,uiHandleButtonData * data,uiBlock * block)1335 static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
1336 {
1337 ARegion *region = data->region;
1338 const double value_delta = data->value - data->origvalue;
1339 const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) :
1340 0.0;
1341
1342 BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE);
1343 BLI_assert(data->multi_data.skip == false);
1344
1345 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1346 if (!(but->flag & UI_BUT_DRAG_MULTI)) {
1347 continue;
1348 }
1349
1350 uiButMultiState *mbut_state = ui_multibut_lookup(data, but);
1351
1352 if (mbut_state == NULL) {
1353 /* Highly unlikely. */
1354 printf("%s: Can't find button\n", __func__);
1355 }
1356
1357 void *active_back;
1358 ui_but_execute_begin(C, region, but, &active_back);
1359
1360 # ifdef USE_ALLSELECT
1361 if (data->select_others.is_enabled) {
1362 /* init once! */
1363 if (mbut_state->select_others.elems_len == 0) {
1364 ui_selectcontext_begin(C, but, &mbut_state->select_others);
1365 }
1366 if (mbut_state->select_others.elems_len == 0) {
1367 mbut_state->select_others.elems_len = -1;
1368 }
1369 }
1370
1371 /* Needed so we apply the right deltas. */
1372 but->active->origvalue = mbut_state->origvalue;
1373 but->active->select_others = mbut_state->select_others;
1374 but->active->select_others.do_free = false;
1375 # endif
1376
1377 BLI_assert(active_back == NULL);
1378 /* No need to check 'data->state' here. */
1379 if (data->str) {
1380 /* Entering text (set all). */
1381 but->active->value = data->value;
1382 ui_but_string_set(C, but, data->str);
1383 }
1384 else {
1385 /* Dragging (use delta). */
1386 if (data->multi_data.is_proportional) {
1387 but->active->value = mbut_state->origvalue * value_scale;
1388 }
1389 else {
1390 but->active->value = mbut_state->origvalue + value_delta;
1391 }
1392
1393 /* Clamp based on soft limits, see T40154. */
1394 CLAMP(but->active->value, (double)but->softmin, (double)but->softmax);
1395 }
1396
1397 ui_but_execute_end(C, region, but, active_back);
1398 }
1399 }
1400
1401 #endif /* USE_DRAG_MULTINUM */
1402
1403 /** \} */
1404
1405 /* -------------------------------------------------------------------- */
1406 /** \name Button Drag Toggle
1407 * \{ */
1408
1409 #ifdef USE_DRAG_TOGGLE
1410
1411 /* Helpers that wrap boolean functions, to support different kinds of buttons. */
1412
ui_drag_toggle_but_is_supported(const uiBut * but)1413 static bool ui_drag_toggle_but_is_supported(const uiBut *but)
1414 {
1415 if (but->flag & UI_BUT_DISABLED) {
1416 return false;
1417 }
1418 if (ui_but_is_bool(but)) {
1419 return true;
1420 }
1421 if (UI_but_is_decorator(but)) {
1422 return ELEM(but->icon,
1423 ICON_DECORATE,
1424 ICON_DECORATE_KEYFRAME,
1425 ICON_DECORATE_ANIMATE,
1426 ICON_DECORATE_OVERRIDE);
1427 }
1428 return false;
1429 }
1430
1431 /* Button pushed state to compare if other buttons match. Can be more
1432 * then just true or false for toggle buttons with more than 2 states. */
ui_drag_toggle_but_pushed_state(bContext * C,uiBut * but)1433 static int ui_drag_toggle_but_pushed_state(bContext *C, uiBut *but)
1434 {
1435 if (but->rnapoin.data == NULL && but->poin == NULL && but->icon) {
1436 if (but->pushed_state_func) {
1437 return but->pushed_state_func(C, but->pushed_state_arg);
1438 }
1439 /* Assume icon identifies a unique state, for buttons that
1440 * work through functions callbacks and don't have an boolean
1441 * value that indicates the state. */
1442 return but->icon + but->iconadd;
1443 }
1444 if (ui_but_is_bool(but)) {
1445 return ui_but_is_pushed(but);
1446 }
1447 return 0;
1448 }
1449
1450 typedef struct uiDragToggleHandle {
1451 /* init */
1452 int pushed_state;
1453 float but_cent_start[2];
1454
1455 bool is_xy_lock_init;
1456 bool xy_lock[2];
1457
1458 int xy_init[2];
1459 int xy_last[2];
1460 } uiDragToggleHandle;
1461
ui_drag_toggle_set_xy_xy(bContext * C,ARegion * region,const int pushed_state,const int xy_src[2],const int xy_dst[2])1462 static bool ui_drag_toggle_set_xy_xy(
1463 bContext *C, ARegion *region, const int pushed_state, const int xy_src[2], const int xy_dst[2])
1464 {
1465 /* popups such as layers won't re-evaluate on redraw */
1466 const bool do_check = (region->regiontype == RGN_TYPE_TEMPORARY);
1467 bool changed = false;
1468
1469 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
1470 float xy_a_block[2] = {UNPACK2(xy_src)};
1471 float xy_b_block[2] = {UNPACK2(xy_dst)};
1472
1473 ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]);
1474 ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]);
1475
1476 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
1477 /* Note: ctrl is always true here because (at least for now)
1478 * we always want to consider text control in this case, even when not embossed. */
1479 if (ui_but_is_interactive(but, true)) {
1480 if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
1481
1482 /* execute the button */
1483 if (ui_drag_toggle_but_is_supported(but)) {
1484 /* is it pressed? */
1485 const int pushed_state_but = ui_drag_toggle_but_pushed_state(C, but);
1486 if (pushed_state_but != pushed_state) {
1487 UI_but_execute(C, region, but);
1488 if (do_check) {
1489 ui_but_update_edited(but);
1490 }
1491 if (U.runtime.is_dirty == false) {
1492 ui_but_update_preferences_dirty(but);
1493 }
1494 changed = true;
1495 }
1496 }
1497 /* done */
1498 }
1499 }
1500 }
1501 }
1502 if (changed) {
1503 /* apply now, not on release (or if handlers are canceled for whatever reason) */
1504 ui_apply_but_funcs_after(C);
1505 }
1506
1507 return changed;
1508 }
1509
ui_drag_toggle_set(bContext * C,uiDragToggleHandle * drag_info,const int xy_input[2])1510 static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
1511 {
1512 ARegion *region = CTX_wm_region(C);
1513 bool do_draw = false;
1514
1515 /**
1516 * Initialize Locking:
1517 *
1518 * Check if we need to initialize the lock axis by finding if the first
1519 * button we mouse over is X or Y aligned, then lock the mouse to that axis after.
1520 */
1521 if (drag_info->is_xy_lock_init == false) {
1522 /* first store the buttons original coords */
1523 uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true);
1524
1525 if (but) {
1526 if (but->flag & UI_BUT_DRAG_LOCK) {
1527 const float but_cent_new[2] = {
1528 BLI_rctf_cent_x(&but->rect),
1529 BLI_rctf_cent_y(&but->rect),
1530 };
1531
1532 /* check if this is a different button,
1533 * chances are high the button wont move about :) */
1534 if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
1535 if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
1536 fabsf(drag_info->but_cent_start[1] - but_cent_new[1])) {
1537 drag_info->xy_lock[0] = true;
1538 }
1539 else {
1540 drag_info->xy_lock[1] = true;
1541 }
1542 drag_info->is_xy_lock_init = true;
1543 }
1544 }
1545 else {
1546 drag_info->is_xy_lock_init = true;
1547 }
1548 }
1549 }
1550 /* done with axis locking */
1551
1552 int xy[2];
1553 xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0];
1554 xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1];
1555
1556 /* touch all buttons between last mouse coord and this one */
1557 do_draw = ui_drag_toggle_set_xy_xy(C, region, drag_info->pushed_state, drag_info->xy_last, xy);
1558
1559 if (do_draw) {
1560 ED_region_tag_redraw(region);
1561 }
1562
1563 copy_v2_v2_int(drag_info->xy_last, xy);
1564 }
1565
ui_handler_region_drag_toggle_remove(bContext * UNUSED (C),void * userdata)1566 static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata)
1567 {
1568 uiDragToggleHandle *drag_info = userdata;
1569 MEM_freeN(drag_info);
1570 }
1571
ui_handler_region_drag_toggle(bContext * C,const wmEvent * event,void * userdata)1572 static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
1573 {
1574 uiDragToggleHandle *drag_info = userdata;
1575 bool done = false;
1576
1577 switch (event->type) {
1578 case LEFTMOUSE: {
1579 if (event->val == KM_RELEASE) {
1580 done = true;
1581 }
1582 break;
1583 }
1584 case MOUSEMOVE: {
1585 ui_drag_toggle_set(C, drag_info, &event->x);
1586 break;
1587 }
1588 }
1589
1590 if (done) {
1591 wmWindow *win = CTX_wm_window(C);
1592 ARegion *region = CTX_wm_region(C);
1593 uiBut *but = ui_but_find_mouse_over_ex(
1594 region, drag_info->xy_init[0], drag_info->xy_init[1], true);
1595
1596 if (but) {
1597 ui_apply_but_undo(but);
1598 }
1599
1600 WM_event_remove_ui_handler(&win->modalhandlers,
1601 ui_handler_region_drag_toggle,
1602 ui_handler_region_drag_toggle_remove,
1603 drag_info,
1604 false);
1605 ui_handler_region_drag_toggle_remove(C, drag_info);
1606
1607 WM_event_add_mousemove(win);
1608 return WM_UI_HANDLER_BREAK;
1609 }
1610 return WM_UI_HANDLER_CONTINUE;
1611 }
1612
ui_but_is_drag_toggle(const uiBut * but)1613 static bool ui_but_is_drag_toggle(const uiBut *but)
1614 {
1615 return ((ui_drag_toggle_but_is_supported(but) == true) &&
1616 /* menu check is importnt so the button dragged over isn't removed instantly */
1617 (ui_block_is_menu(but->block) == false));
1618 }
1619
1620 #endif /* USE_DRAG_TOGGLE */
1621
1622 #ifdef USE_ALLSELECT
1623
ui_selectcontext_begin(bContext * C,uiBut * but,uiSelectContextStore * selctx_data)1624 static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore *selctx_data)
1625 {
1626 PointerRNA lptr, idptr;
1627 PropertyRNA *lprop;
1628 bool success = false;
1629
1630 char *path = NULL;
1631 ListBase lb = {NULL};
1632
1633 PointerRNA ptr = but->rnapoin;
1634 PropertyRNA *prop = but->rnaprop;
1635 const int index = but->rnaindex;
1636
1637 /* for now don't support whole colors */
1638 if (index == -1) {
1639 return false;
1640 }
1641
1642 /* if there is a valid property that is editable... */
1643 if (ptr.data && prop) {
1644 bool use_path_from_id;
1645
1646 /* some facts we want to know */
1647 const bool is_array = RNA_property_array_check(prop);
1648 const int rna_type = RNA_property_type(prop);
1649
1650 if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) &&
1651 !BLI_listbase_is_empty(&lb)) {
1652 selctx_data->elems_len = BLI_listbase_count(&lb);
1653 selctx_data->elems = MEM_mallocN(sizeof(uiSelectContextElem) * selctx_data->elems_len,
1654 __func__);
1655 int i;
1656 LISTBASE_FOREACH_INDEX (CollectionPointerLink *, link, &lb, i) {
1657 if (i >= selctx_data->elems_len) {
1658 break;
1659 }
1660 uiSelectContextElem *other = &selctx_data->elems[i];
1661 /* TODO,. de-duplicate copy_to_selected_button */
1662 if (link->ptr.data != ptr.data) {
1663 if (use_path_from_id) {
1664 /* Path relative to ID. */
1665 lprop = NULL;
1666 RNA_id_pointer_create(link->ptr.owner_id, &idptr);
1667 RNA_path_resolve_property(&idptr, path, &lptr, &lprop);
1668 }
1669 else if (path) {
1670 /* Path relative to elements from list. */
1671 lprop = NULL;
1672 RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop);
1673 }
1674 else {
1675 lptr = link->ptr;
1676 lprop = prop;
1677 }
1678
1679 /* lptr might not be the same as link->ptr! */
1680 if ((lptr.data != ptr.data) && (lprop == prop) && RNA_property_editable(&lptr, lprop)) {
1681 other->ptr = lptr;
1682 if (is_array) {
1683 if (rna_type == PROP_FLOAT) {
1684 other->val_f = RNA_property_float_get_index(&lptr, lprop, index);
1685 }
1686 else if (rna_type == PROP_INT) {
1687 other->val_i = RNA_property_int_get_index(&lptr, lprop, index);
1688 }
1689 /* ignored for now */
1690 # if 0
1691 else if (rna_type == PROP_BOOLEAN) {
1692 other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index);
1693 }
1694 # endif
1695 }
1696 else {
1697 if (rna_type == PROP_FLOAT) {
1698 other->val_f = RNA_property_float_get(&lptr, lprop);
1699 }
1700 else if (rna_type == PROP_INT) {
1701 other->val_i = RNA_property_int_get(&lptr, lprop);
1702 }
1703 /* ignored for now */
1704 # if 0
1705 else if (rna_type == PROP_BOOLEAN) {
1706 other->val_b = RNA_property_boolean_get(&lptr, lprop);
1707 }
1708 else if (rna_type == PROP_ENUM) {
1709 other->val_i = RNA_property_enum_get(&lptr, lprop);
1710 }
1711 # endif
1712 }
1713
1714 continue;
1715 }
1716 }
1717
1718 selctx_data->elems_len -= 1;
1719 i -= 1;
1720 }
1721
1722 success = (selctx_data->elems_len != 0);
1723 }
1724 }
1725
1726 if (selctx_data->elems_len == 0) {
1727 MEM_SAFE_FREE(selctx_data->elems);
1728 }
1729
1730 MEM_SAFE_FREE(path);
1731 BLI_freelistN(&lb);
1732
1733 /* caller can clear */
1734 selctx_data->do_free = true;
1735
1736 if (success) {
1737 but->flag |= UI_BUT_IS_SELECT_CONTEXT;
1738 }
1739
1740 return success;
1741 }
1742
ui_selectcontext_end(uiBut * but,uiSelectContextStore * selctx_data)1743 static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data)
1744 {
1745 if (selctx_data->do_free) {
1746 if (selctx_data->elems) {
1747 MEM_freeN(selctx_data->elems);
1748 }
1749 }
1750
1751 but->flag &= ~UI_BUT_IS_SELECT_CONTEXT;
1752 }
1753
ui_selectcontext_apply(bContext * C,uiBut * but,uiSelectContextStore * selctx_data,const double value,const double value_orig)1754 static void ui_selectcontext_apply(bContext *C,
1755 uiBut *but,
1756 uiSelectContextStore *selctx_data,
1757 const double value,
1758 const double value_orig)
1759 {
1760 if (selctx_data->elems) {
1761 PropertyRNA *prop = but->rnaprop;
1762 PropertyRNA *lprop = but->rnaprop;
1763 const int index = but->rnaindex;
1764 const bool use_delta = (selctx_data->is_copy == false);
1765
1766 union {
1767 bool b;
1768 int i;
1769 float f;
1770 PointerRNA p;
1771 } delta, min, max;
1772
1773 const bool is_array = RNA_property_array_check(prop);
1774 const int rna_type = RNA_property_type(prop);
1775
1776 if (rna_type == PROP_FLOAT) {
1777 delta.f = use_delta ? (value - value_orig) : value;
1778 RNA_property_float_range(&but->rnapoin, prop, &min.f, &max.f);
1779 }
1780 else if (rna_type == PROP_INT) {
1781 delta.i = use_delta ? ((int)value - (int)value_orig) : (int)value;
1782 RNA_property_int_range(&but->rnapoin, prop, &min.i, &max.i);
1783 }
1784 else if (rna_type == PROP_ENUM) {
1785 /* Not a delta in fact. */
1786 delta.i = RNA_property_enum_get(&but->rnapoin, prop);
1787 }
1788 else if (rna_type == PROP_BOOLEAN) {
1789 if (is_array) {
1790 /* Not a delta in fact. */
1791 delta.b = RNA_property_boolean_get_index(&but->rnapoin, prop, index);
1792 }
1793 else {
1794 /* Not a delta in fact. */
1795 delta.b = RNA_property_boolean_get(&but->rnapoin, prop);
1796 }
1797 }
1798 else if (rna_type == PROP_POINTER) {
1799 /* Not a delta in fact. */
1800 delta.p = RNA_property_pointer_get(&but->rnapoin, prop);
1801 }
1802
1803 # ifdef USE_ALLSELECT_LAYER_HACK
1804 /* make up for not having 'handle_layer_buttons' */
1805 {
1806 const PropertySubType subtype = RNA_property_subtype(prop);
1807
1808 if ((rna_type == PROP_BOOLEAN) && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) && is_array &&
1809 /* could check for 'handle_layer_buttons' */
1810 but->func) {
1811 wmWindow *win = CTX_wm_window(C);
1812 if (!win->eventstate->shift) {
1813 const int len = RNA_property_array_length(&but->rnapoin, prop);
1814 bool *tmparray = MEM_callocN(sizeof(bool) * len, __func__);
1815
1816 tmparray[index] = true;
1817
1818 for (int i = 0; i < selctx_data->elems_len; i++) {
1819 uiSelectContextElem *other = &selctx_data->elems[i];
1820 PointerRNA lptr = other->ptr;
1821 RNA_property_boolean_set_array(&lptr, lprop, tmparray);
1822 RNA_property_update(C, &lptr, lprop);
1823 }
1824
1825 MEM_freeN(tmparray);
1826
1827 return;
1828 }
1829 }
1830 }
1831 # endif
1832
1833 for (int i = 0; i < selctx_data->elems_len; i++) {
1834 uiSelectContextElem *other = &selctx_data->elems[i];
1835 PointerRNA lptr = other->ptr;
1836
1837 if (rna_type == PROP_FLOAT) {
1838 float other_value = use_delta ? (other->val_f + delta.f) : delta.f;
1839 CLAMP(other_value, min.f, max.f);
1840 if (is_array) {
1841 RNA_property_float_set_index(&lptr, lprop, index, other_value);
1842 }
1843 else {
1844 RNA_property_float_set(&lptr, lprop, other_value);
1845 }
1846 }
1847 else if (rna_type == PROP_INT) {
1848 int other_value = use_delta ? (other->val_i + delta.i) : delta.i;
1849 CLAMP(other_value, min.i, max.i);
1850 if (is_array) {
1851 RNA_property_int_set_index(&lptr, lprop, index, other_value);
1852 }
1853 else {
1854 RNA_property_int_set(&lptr, lprop, other_value);
1855 }
1856 }
1857 else if (rna_type == PROP_BOOLEAN) {
1858 const bool other_value = delta.b;
1859 if (is_array) {
1860 RNA_property_boolean_set_index(&lptr, lprop, index, other_value);
1861 }
1862 else {
1863 RNA_property_boolean_set(&lptr, lprop, delta.b);
1864 }
1865 }
1866 else if (rna_type == PROP_ENUM) {
1867 const int other_value = delta.i;
1868 BLI_assert(!is_array);
1869 RNA_property_enum_set(&lptr, lprop, other_value);
1870 }
1871 else if (rna_type == PROP_POINTER) {
1872 const PointerRNA other_value = delta.p;
1873 RNA_property_pointer_set(&lptr, lprop, other_value, NULL);
1874 }
1875
1876 RNA_property_update(C, &lptr, prop);
1877 }
1878 }
1879 }
1880
1881 #endif /* USE_ALLSELECT */
1882
1883 /** \} */
1884
1885 /* -------------------------------------------------------------------- */
1886 /** \name Button Drag
1887 * \{ */
1888
ui_but_drag_init(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)1889 static bool ui_but_drag_init(bContext *C,
1890 uiBut *but,
1891 uiHandleButtonData *data,
1892 const wmEvent *event)
1893 {
1894 /* prevent other WM gestures to start while we try to drag */
1895 WM_gestures_remove(CTX_wm_window(C));
1896
1897 /* Clamp the maximum to half the UI unit size so a high user preference
1898 * doesn't require the user to drag more than half the default button height. */
1899 const int drag_threshold = min_ii(
1900 WM_event_drag_threshold(event),
1901 (int)((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block)));
1902
1903 if (abs(data->dragstartx - event->x) + abs(data->dragstarty - event->y) > drag_threshold) {
1904 button_activate_state(C, but, BUTTON_STATE_EXIT);
1905 data->cancel = true;
1906 #ifdef USE_DRAG_TOGGLE
1907 if (ui_drag_toggle_but_is_supported(but)) {
1908 uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1909 ARegion *region_prev;
1910
1911 /* call here because regular mouse-up event wont run,
1912 * typically 'button_activate_exit()' handles this */
1913 ui_apply_but_autokey(C, but);
1914
1915 drag_info->pushed_state = ui_drag_toggle_but_pushed_state(C, but);
1916 drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
1917 drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
1918 copy_v2_v2_int(drag_info->xy_init, &event->x);
1919 copy_v2_v2_int(drag_info->xy_last, &event->x);
1920
1921 /* needed for toggle drag on popups */
1922 region_prev = CTX_wm_region(C);
1923 CTX_wm_region_set(C, data->region);
1924
1925 WM_event_add_ui_handler(C,
1926 &data->window->modalhandlers,
1927 ui_handler_region_drag_toggle,
1928 ui_handler_region_drag_toggle_remove,
1929 drag_info,
1930 WM_HANDLER_BLOCKING);
1931
1932 CTX_wm_region_set(C, region_prev);
1933
1934 /* Initialize alignment for single row/column regions,
1935 * otherwise we use the relative position of the first other button dragged over. */
1936 if (ELEM(data->region->regiontype,
1937 RGN_TYPE_NAV_BAR,
1938 RGN_TYPE_HEADER,
1939 RGN_TYPE_TOOL_HEADER,
1940 RGN_TYPE_FOOTER)) {
1941 const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment);
1942 int lock_axis = -1;
1943
1944 if (ELEM(region_alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
1945 lock_axis = 0;
1946 }
1947 else if (ELEM(region_alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
1948 lock_axis = 1;
1949 }
1950 if (lock_axis != -1) {
1951 drag_info->xy_lock[lock_axis] = true;
1952 drag_info->is_xy_lock_init = true;
1953 }
1954 }
1955 }
1956 else
1957 #endif
1958 if (but->type == UI_BTYPE_COLOR) {
1959 bool valid = false;
1960 uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
1961
1962 /* TODO support more button pointer types */
1963 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
1964 ui_but_v3_get(but, drag_info->color);
1965 drag_info->gamma_corrected = true;
1966 valid = true;
1967 }
1968 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
1969 ui_but_v3_get(but, drag_info->color);
1970 drag_info->gamma_corrected = false;
1971 valid = true;
1972 }
1973 else if (ELEM(but->pointype, UI_BUT_POIN_FLOAT, UI_BUT_POIN_CHAR)) {
1974 ui_but_v3_get(but, drag_info->color);
1975 copy_v3_v3(drag_info->color, (float *)but->poin);
1976 valid = true;
1977 }
1978
1979 if (valid) {
1980 WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA);
1981 }
1982 else {
1983 MEM_freeN(drag_info);
1984 return false;
1985 }
1986 }
1987 else {
1988 wmDrag *drag = WM_event_start_drag(
1989 C, but->icon, but->dragtype, but->dragpoin, ui_but_value_get(but), WM_DRAG_NOP);
1990
1991 if (but->imb) {
1992 WM_event_drag_image(drag,
1993 but->imb,
1994 but->imb_scale,
1995 BLI_rctf_size_x(&but->rect),
1996 BLI_rctf_size_y(&but->rect));
1997 }
1998 }
1999 return true;
2000 }
2001
2002 return false;
2003 }
2004
2005 /** \} */
2006
2007 /* -------------------------------------------------------------------- */
2008 /** \name Button Apply
2009 * \{ */
2010
ui_apply_but_IMAGE(bContext * C,uiBut * but,uiHandleButtonData * data)2011 static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
2012 {
2013 ui_apply_but_func(C, but);
2014 data->retval = but->retval;
2015 data->applied = true;
2016 }
2017
ui_apply_but_HISTOGRAM(bContext * C,uiBut * but,uiHandleButtonData * data)2018 static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
2019 {
2020 ui_apply_but_func(C, but);
2021 data->retval = but->retval;
2022 data->applied = true;
2023 }
2024
ui_apply_but_WAVEFORM(bContext * C,uiBut * but,uiHandleButtonData * data)2025 static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
2026 {
2027 ui_apply_but_func(C, but);
2028 data->retval = but->retval;
2029 data->applied = true;
2030 }
2031
ui_apply_but_TRACKPREVIEW(bContext * C,uiBut * but,uiHandleButtonData * data)2032 static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
2033 {
2034 ui_apply_but_func(C, but);
2035 data->retval = but->retval;
2036 data->applied = true;
2037 }
2038
ui_apply_but(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const bool interactive)2039 static void ui_apply_but(
2040 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
2041 {
2042 const eButType but_type = but->type; /* Store as const to quiet maybe uninitialized warning. */
2043
2044 data->retval = 0;
2045
2046 /* if we cancel and have not applied yet, there is nothing to do,
2047 * otherwise we have to restore the original value again */
2048 if (data->cancel) {
2049 if (!data->applied) {
2050 return;
2051 }
2052
2053 if (data->str) {
2054 MEM_freeN(data->str);
2055 }
2056 data->str = data->origstr;
2057 data->origstr = NULL;
2058 data->value = data->origvalue;
2059 copy_v3_v3(data->vec, data->origvec);
2060 /* postpone clearing origdata */
2061 }
2062 else {
2063 /* We avoid applying interactive edits a second time
2064 * at the end with the #uiHandleButtonData.applied_interactive flag. */
2065 if (interactive) {
2066 data->applied_interactive = true;
2067 }
2068 else if (data->applied_interactive) {
2069 return;
2070 }
2071
2072 #ifdef USE_ALLSELECT
2073 # ifdef USE_DRAG_MULTINUM
2074 if (but->flag & UI_BUT_DRAG_MULTI) {
2075 /* pass */
2076 }
2077 else
2078 # endif
2079 if (data->select_others.elems_len == 0) {
2080 wmWindow *win = CTX_wm_window(C);
2081 /* may have been enabled before activating */
2082 if (data->select_others.is_enabled || IS_ALLSELECT_EVENT(win->eventstate)) {
2083 ui_selectcontext_begin(C, but, &data->select_others);
2084 data->select_others.is_enabled = true;
2085 }
2086 }
2087 if (data->select_others.elems_len == 0) {
2088 /* dont check again */
2089 data->select_others.elems_len = -1;
2090 }
2091 #endif
2092 }
2093
2094 /* ensures we are writing actual values */
2095 char *editstr = but->editstr;
2096 double *editval = but->editval;
2097 float *editvec = but->editvec;
2098 ColorBand *editcoba;
2099 CurveMapping *editcumap;
2100 CurveProfile *editprofile;
2101 if (but_type == UI_BTYPE_COLORBAND) {
2102 uiButColorBand *but_coba = (uiButColorBand *)but;
2103 editcoba = but_coba->edit_coba;
2104 }
2105 else if (but_type == UI_BTYPE_CURVE) {
2106 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2107 editcumap = but_cumap->edit_cumap;
2108 }
2109 else if (but_type == UI_BTYPE_CURVEPROFILE) {
2110 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2111 editprofile = but_profile->edit_profile;
2112 }
2113 but->editstr = NULL;
2114 but->editval = NULL;
2115 but->editvec = NULL;
2116 if (but_type == UI_BTYPE_COLORBAND) {
2117 uiButColorBand *but_coba = (uiButColorBand *)but;
2118 but_coba->edit_coba = NULL;
2119 }
2120 else if (but_type == UI_BTYPE_CURVE) {
2121 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2122 but_cumap->edit_cumap = NULL;
2123 }
2124 else if (but_type == UI_BTYPE_CURVEPROFILE) {
2125 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2126 but_profile->edit_profile = NULL;
2127 }
2128
2129 /* handle different types */
2130 switch (but_type) {
2131 case UI_BTYPE_BUT:
2132 case UI_BTYPE_DECORATOR:
2133 ui_apply_but_BUT(C, but, data);
2134 break;
2135 case UI_BTYPE_TEXT:
2136 case UI_BTYPE_SEARCH_MENU:
2137 ui_apply_but_TEX(C, but, data);
2138 break;
2139 case UI_BTYPE_BUT_TOGGLE:
2140 case UI_BTYPE_TOGGLE:
2141 case UI_BTYPE_TOGGLE_N:
2142 case UI_BTYPE_ICON_TOGGLE:
2143 case UI_BTYPE_ICON_TOGGLE_N:
2144 case UI_BTYPE_CHECKBOX:
2145 case UI_BTYPE_CHECKBOX_N:
2146 ui_apply_but_TOG(C, but, data);
2147 break;
2148 case UI_BTYPE_ROW:
2149 case UI_BTYPE_LISTROW:
2150 ui_apply_but_ROW(C, block, but, data);
2151 break;
2152 case UI_BTYPE_TAB:
2153 ui_apply_but_TAB(C, but, data);
2154 break;
2155 case UI_BTYPE_SCROLL:
2156 case UI_BTYPE_GRIP:
2157 case UI_BTYPE_NUM:
2158 case UI_BTYPE_NUM_SLIDER:
2159 ui_apply_but_NUM(C, but, data);
2160 break;
2161 case UI_BTYPE_MENU:
2162 case UI_BTYPE_BLOCK:
2163 case UI_BTYPE_PULLDOWN:
2164 ui_apply_but_BLOCK(C, but, data);
2165 break;
2166 case UI_BTYPE_COLOR:
2167 if (data->cancel) {
2168 ui_apply_but_VEC(C, but, data);
2169 }
2170 else {
2171 ui_apply_but_BLOCK(C, but, data);
2172 }
2173 break;
2174 case UI_BTYPE_BUT_MENU:
2175 ui_apply_but_BUTM(C, but, data);
2176 break;
2177 case UI_BTYPE_UNITVEC:
2178 case UI_BTYPE_HSVCUBE:
2179 case UI_BTYPE_HSVCIRCLE:
2180 ui_apply_but_VEC(C, but, data);
2181 break;
2182 case UI_BTYPE_COLORBAND:
2183 ui_apply_but_COLORBAND(C, but, data);
2184 break;
2185 case UI_BTYPE_CURVE:
2186 ui_apply_but_CURVE(C, but, data);
2187 break;
2188 case UI_BTYPE_CURVEPROFILE:
2189 ui_apply_but_CURVEPROFILE(C, but, data);
2190 break;
2191 case UI_BTYPE_KEY_EVENT:
2192 case UI_BTYPE_HOTKEY_EVENT:
2193 ui_apply_but_BUT(C, but, data);
2194 break;
2195 case UI_BTYPE_IMAGE:
2196 ui_apply_but_IMAGE(C, but, data);
2197 break;
2198 case UI_BTYPE_HISTOGRAM:
2199 ui_apply_but_HISTOGRAM(C, but, data);
2200 break;
2201 case UI_BTYPE_WAVEFORM:
2202 ui_apply_but_WAVEFORM(C, but, data);
2203 break;
2204 case UI_BTYPE_TRACK_PREVIEW:
2205 ui_apply_but_TRACKPREVIEW(C, but, data);
2206 break;
2207 default:
2208 break;
2209 }
2210
2211 #ifdef USE_DRAG_MULTINUM
2212 if (data->multi_data.has_mbuts) {
2213 if ((data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) && (data->multi_data.skip == false)) {
2214 if (data->cancel) {
2215 ui_multibut_restore(C, data, block);
2216 }
2217 else {
2218 ui_multibut_states_apply(C, data, block);
2219 }
2220 }
2221 }
2222 #endif
2223
2224 #ifdef USE_ALLSELECT
2225 ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue);
2226 #endif
2227
2228 if (data->cancel) {
2229 data->origvalue = 0.0;
2230 zero_v3(data->origvec);
2231 }
2232
2233 but->editstr = editstr;
2234 but->editval = editval;
2235 but->editvec = editvec;
2236 if (but_type == UI_BTYPE_COLORBAND) {
2237 uiButColorBand *but_coba = (uiButColorBand *)but;
2238 but_coba->edit_coba = editcoba;
2239 }
2240 else if (but_type == UI_BTYPE_CURVE) {
2241 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2242 but_cumap->edit_cumap = editcumap;
2243 }
2244 else if (but_type == UI_BTYPE_CURVEPROFILE) {
2245 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2246 but_profile->edit_profile = editprofile;
2247 }
2248 }
2249
2250 /** \} */
2251
2252 /* -------------------------------------------------------------------- */
2253 /** \name Button Drop Event
2254 * \{ */
2255
2256 /* only call if event type is EVT_DROP */
ui_but_drop(bContext * C,const wmEvent * event,uiBut * but,uiHandleButtonData * data)2257 static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data)
2258 {
2259 ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */
2260
2261 LISTBASE_FOREACH (wmDrag *, wmd, drags) {
2262 if (wmd->type == WM_DRAG_ID) {
2263 /* align these types with UI_but_active_drop_name */
2264 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2265 ID *id = WM_drag_ID(wmd, 0);
2266
2267 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2268
2269 ui_textedit_string_set(but, data, id->name + 2);
2270
2271 if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) {
2272 but->changed = true;
2273 ui_searchbox_update(C, data->searchbox, but, true);
2274 }
2275
2276 button_activate_state(C, but, BUTTON_STATE_EXIT);
2277 }
2278 }
2279 }
2280 }
2281
2282 /** \} */
2283
2284 /* -------------------------------------------------------------------- */
2285 /** \name Button Copy & Paste
2286 * \{ */
2287
ui_but_get_pasted_text_from_clipboard(char ** buf_paste,int * buf_len)2288 static void ui_but_get_pasted_text_from_clipboard(char **buf_paste, int *buf_len)
2289 {
2290 /* get only first line even if the clipboard contains multiple lines */
2291 int length;
2292 char *text = WM_clipboard_text_get_firstline(false, &length);
2293
2294 if (text) {
2295 *buf_paste = text;
2296 *buf_len = length;
2297 }
2298 else {
2299 *buf_paste = MEM_callocN(sizeof(char), __func__);
2300 *buf_len = 0;
2301 }
2302 }
2303
get_but_property_array_length(uiBut * but)2304 static int get_but_property_array_length(uiBut *but)
2305 {
2306 return RNA_property_array_length(&but->rnapoin, but->rnaprop);
2307 }
2308
ui_but_set_float_array(bContext * C,uiBut * but,uiHandleButtonData * data,float * values,int array_length)2309 static void ui_but_set_float_array(
2310 bContext *C, uiBut *but, uiHandleButtonData *data, float *values, int array_length)
2311 {
2312 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2313
2314 for (int i = 0; i < array_length; i++) {
2315 RNA_property_float_set_index(&but->rnapoin, but->rnaprop, i, values[i]);
2316 }
2317 if (data) {
2318 if (but->type == UI_BTYPE_UNITVEC) {
2319 BLI_assert(array_length == 3);
2320 copy_v3_v3(data->vec, values);
2321 }
2322 else {
2323 data->value = values[but->rnaindex];
2324 }
2325 }
2326
2327 button_activate_state(C, but, BUTTON_STATE_EXIT);
2328 }
2329
float_array_to_string(float * values,int array_length,char * output,int output_len_max)2330 static void float_array_to_string(float *values,
2331 int array_length,
2332 char *output,
2333 int output_len_max)
2334 {
2335 /* to avoid buffer overflow attacks; numbers are quite arbitrary */
2336 BLI_assert(output_len_max > 15);
2337 output_len_max -= 10;
2338
2339 int current_index = 0;
2340 output[current_index] = '[';
2341 current_index++;
2342
2343 for (int i = 0; i < array_length; i++) {
2344 int length = BLI_snprintf(
2345 output + current_index, output_len_max - current_index, "%f", values[i]);
2346 current_index += length;
2347
2348 if (i < array_length - 1) {
2349 if (current_index < output_len_max) {
2350 output[current_index + 0] = ',';
2351 output[current_index + 1] = ' ';
2352 current_index += 2;
2353 }
2354 }
2355 }
2356
2357 output[current_index + 0] = ']';
2358 output[current_index + 1] = '\0';
2359 }
2360
ui_but_copy_numeric_array(uiBut * but,char * output,int output_len_max)2361 static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_len_max)
2362 {
2363 const int array_length = get_but_property_array_length(but);
2364 float *values = alloca(array_length * sizeof(float));
2365 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values);
2366 float_array_to_string(values, array_length, output, output_len_max);
2367 }
2368
parse_float_array(char * text,float * values,int expected_length)2369 static bool parse_float_array(char *text, float *values, int expected_length)
2370 {
2371 /* can parse max 4 floats for now */
2372 BLI_assert(0 <= expected_length && expected_length <= 4);
2373
2374 float v[5];
2375 const int actual_length = sscanf(
2376 text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]);
2377
2378 if (actual_length == expected_length) {
2379 memcpy(values, v, sizeof(float) * expected_length);
2380 return true;
2381 }
2382 return false;
2383 }
2384
ui_but_paste_numeric_array(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2385 static void ui_but_paste_numeric_array(bContext *C,
2386 uiBut *but,
2387 uiHandleButtonData *data,
2388 char *buf_paste)
2389 {
2390 const int array_length = get_but_property_array_length(but);
2391 if (array_length > 4) {
2392 /* not supported for now */
2393 return;
2394 }
2395
2396 float *values = alloca(sizeof(float) * array_length);
2397
2398 if (parse_float_array(buf_paste, values, array_length)) {
2399 ui_but_set_float_array(C, but, data, values, array_length);
2400 }
2401 else {
2402 WM_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]");
2403 }
2404 }
2405
ui_but_copy_numeric_value(uiBut * but,char * output,int output_len_max)2406 static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_len_max)
2407 {
2408 /* Get many decimal places, then strip trailing zeros.
2409 * note: too high values start to give strange results */
2410 ui_but_string_get_ex(but, output, output_len_max, UI_PRECISION_FLOAT_MAX, false, NULL);
2411 BLI_str_rstrip_float_zero(output, '\0');
2412 }
2413
ui_but_paste_numeric_value(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2414 static void ui_but_paste_numeric_value(bContext *C,
2415 uiBut *but,
2416 uiHandleButtonData *data,
2417 char *buf_paste)
2418 {
2419 double value;
2420 if (ui_but_string_eval_number(C, but, buf_paste, &value)) {
2421 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2422 data->value = value;
2423 ui_but_string_set(C, but, buf_paste);
2424 button_activate_state(C, but, BUTTON_STATE_EXIT);
2425 }
2426 else {
2427 WM_report(RPT_ERROR, "Expected a number");
2428 }
2429 }
2430
ui_but_paste_normalized_vector(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2431 static void ui_but_paste_normalized_vector(bContext *C,
2432 uiBut *but,
2433 uiHandleButtonData *data,
2434 char *buf_paste)
2435 {
2436 float xyz[3];
2437 if (parse_float_array(buf_paste, xyz, 3)) {
2438 if (normalize_v3(xyz) == 0.0f) {
2439 /* better set Z up then have a zero vector */
2440 xyz[2] = 1.0;
2441 }
2442 ui_but_set_float_array(C, but, data, xyz, 3);
2443 }
2444 else {
2445 WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
2446 }
2447 }
2448
ui_but_copy_color(uiBut * but,char * output,int output_len_max)2449 static void ui_but_copy_color(uiBut *but, char *output, int output_len_max)
2450 {
2451 float rgba[4];
2452
2453 if (but->rnaprop && get_but_property_array_length(but) == 4) {
2454 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2455 }
2456 else {
2457 rgba[3] = 1.0f;
2458 }
2459
2460 ui_but_v3_get(but, rgba);
2461
2462 /* convert to linear color to do compatible copy between gamma and non-gamma */
2463 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2464 srgb_to_linearrgb_v3_v3(rgba, rgba);
2465 }
2466
2467 float_array_to_string(rgba, 4, output, output_len_max);
2468 }
2469
ui_but_paste_color(bContext * C,uiBut * but,char * buf_paste)2470 static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste)
2471 {
2472 float rgba[4];
2473 if (parse_float_array(buf_paste, rgba, 4)) {
2474 if (but->rnaprop) {
2475 /* Assume linear colors in buffer. */
2476 if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
2477 linearrgb_to_srgb_v3_v3(rgba, rgba);
2478 }
2479
2480 /* Some color properties are RGB, not RGBA. */
2481 const int array_len = get_but_property_array_length(but);
2482 BLI_assert(ELEM(array_len, 3, 4));
2483 ui_but_set_float_array(C, but, NULL, rgba, array_len);
2484 }
2485 }
2486 else {
2487 WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2488 }
2489 }
2490
ui_but_copy_text(uiBut * but,char * output,int output_len_max)2491 static void ui_but_copy_text(uiBut *but, char *output, int output_len_max)
2492 {
2493 ui_but_string_get(but, output, output_len_max);
2494 }
2495
ui_but_paste_text(bContext * C,uiBut * but,uiHandleButtonData * data,char * buf_paste)2496 static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
2497 {
2498 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
2499 ui_textedit_string_set(but, but->active, buf_paste);
2500
2501 if (but->type == UI_BTYPE_SEARCH_MENU) {
2502 but->changed = true;
2503 ui_searchbox_update(C, data->searchbox, but, true);
2504 }
2505
2506 button_activate_state(C, but, BUTTON_STATE_EXIT);
2507 }
2508
ui_but_copy_colorband(uiBut * but)2509 static void ui_but_copy_colorband(uiBut *but)
2510 {
2511 if (but->poin != NULL) {
2512 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
2513 }
2514 }
2515
ui_but_paste_colorband(bContext * C,uiBut * but,uiHandleButtonData * data)2516 static void ui_but_paste_colorband(bContext *C, uiBut *but, uiHandleButtonData *data)
2517 {
2518 if (but_copypaste_coba.tot != 0) {
2519 if (!but->poin) {
2520 but->poin = MEM_callocN(sizeof(ColorBand), "colorband");
2521 }
2522
2523 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2524 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
2525 button_activate_state(C, but, BUTTON_STATE_EXIT);
2526 }
2527 }
2528
ui_but_copy_curvemapping(uiBut * but)2529 static void ui_but_copy_curvemapping(uiBut *but)
2530 {
2531 if (but->poin != NULL) {
2532 but_copypaste_curve_alive = true;
2533 BKE_curvemapping_free_data(&but_copypaste_curve);
2534 BKE_curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *)but->poin);
2535 }
2536 }
2537
ui_but_paste_curvemapping(bContext * C,uiBut * but)2538 static void ui_but_paste_curvemapping(bContext *C, uiBut *but)
2539 {
2540 if (but_copypaste_curve_alive) {
2541 if (!but->poin) {
2542 but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping");
2543 }
2544
2545 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2546 BKE_curvemapping_free_data((CurveMapping *)but->poin);
2547 BKE_curvemapping_copy_data((CurveMapping *)but->poin, &but_copypaste_curve);
2548 button_activate_state(C, but, BUTTON_STATE_EXIT);
2549 }
2550 }
2551
ui_but_copy_CurveProfile(uiBut * but)2552 static void ui_but_copy_CurveProfile(uiBut *but)
2553 {
2554 if (but->poin != NULL) {
2555 but_copypaste_profile_alive = true;
2556 BKE_curveprofile_free_data(&but_copypaste_profile);
2557 BKE_curveprofile_copy_data(&but_copypaste_profile, (CurveProfile *)but->poin);
2558 }
2559 }
2560
ui_but_paste_CurveProfile(bContext * C,uiBut * but)2561 static void ui_but_paste_CurveProfile(bContext *C, uiBut *but)
2562 {
2563 if (but_copypaste_profile_alive) {
2564 if (!but->poin) {
2565 but->poin = MEM_callocN(sizeof(CurveProfile), "CurveProfile");
2566 }
2567
2568 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
2569 BKE_curveprofile_free_data((CurveProfile *)but->poin);
2570 BKE_curveprofile_copy_data((CurveProfile *)but->poin, &but_copypaste_profile);
2571 button_activate_state(C, but, BUTTON_STATE_EXIT);
2572 }
2573 }
2574
ui_but_copy_operator(bContext * C,uiBut * but,char * output,int output_len_max)2575 static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max)
2576 {
2577 PointerRNA *opptr = UI_but_operator_ptr_get(but);
2578
2579 char *str;
2580 str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr);
2581 BLI_strncpy(output, str, output_len_max);
2582 MEM_freeN(str);
2583 }
2584
ui_but_copy_menu(uiBut * but,char * output,int output_len_max)2585 static bool ui_but_copy_menu(uiBut *but, char *output, int output_len_max)
2586 {
2587 MenuType *mt = UI_but_menutype_get(but);
2588 if (mt) {
2589 BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
2590 return true;
2591 }
2592 return false;
2593 }
2594
ui_but_copy_popover(uiBut * but,char * output,int output_len_max)2595 static bool ui_but_copy_popover(uiBut *but, char *output, int output_len_max)
2596 {
2597 PanelType *pt = UI_but_paneltype_get(but);
2598 if (pt) {
2599 BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_panel(name=\"%s\")", pt->idname);
2600 return true;
2601 }
2602 return false;
2603 }
2604
ui_but_copy(bContext * C,uiBut * but,const bool copy_array)2605 static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array)
2606 {
2607 if (ui_but_contains_password(but)) {
2608 return;
2609 }
2610
2611 /* Arbitrary large value (allow for paths: 'PATH_MAX') */
2612 char buf[4096] = {0};
2613 const int buf_max_len = sizeof(buf);
2614
2615 /* Left false for copying internal data (color-band for eg). */
2616 bool is_buf_set = false;
2617
2618 const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL);
2619
2620 switch (but->type) {
2621 case UI_BTYPE_NUM:
2622 case UI_BTYPE_NUM_SLIDER:
2623 if (!has_required_data) {
2624 break;
2625 }
2626 if (copy_array && ui_but_has_array_value(but)) {
2627 ui_but_copy_numeric_array(but, buf, buf_max_len);
2628 }
2629 else {
2630 ui_but_copy_numeric_value(but, buf, buf_max_len);
2631 }
2632 is_buf_set = true;
2633 break;
2634
2635 case UI_BTYPE_UNITVEC:
2636 if (!has_required_data) {
2637 break;
2638 }
2639 ui_but_copy_numeric_array(but, buf, buf_max_len);
2640 is_buf_set = true;
2641 break;
2642
2643 case UI_BTYPE_COLOR:
2644 if (!has_required_data) {
2645 break;
2646 }
2647 ui_but_copy_color(but, buf, buf_max_len);
2648 is_buf_set = true;
2649 break;
2650
2651 case UI_BTYPE_TEXT:
2652 case UI_BTYPE_SEARCH_MENU:
2653 if (!has_required_data) {
2654 break;
2655 }
2656 ui_but_copy_text(but, buf, buf_max_len);
2657 is_buf_set = true;
2658 break;
2659
2660 case UI_BTYPE_COLORBAND:
2661 ui_but_copy_colorband(but);
2662 break;
2663
2664 case UI_BTYPE_CURVE:
2665 ui_but_copy_curvemapping(but);
2666 break;
2667
2668 case UI_BTYPE_CURVEPROFILE:
2669 ui_but_copy_CurveProfile(but);
2670 break;
2671
2672 case UI_BTYPE_BUT:
2673 if (!but->optype) {
2674 break;
2675 }
2676 ui_but_copy_operator(C, but, buf, buf_max_len);
2677 is_buf_set = true;
2678 break;
2679
2680 case UI_BTYPE_MENU:
2681 case UI_BTYPE_PULLDOWN:
2682 if (ui_but_copy_menu(but, buf, buf_max_len)) {
2683 is_buf_set = true;
2684 }
2685 break;
2686 case UI_BTYPE_POPOVER:
2687 if (ui_but_copy_popover(but, buf, buf_max_len)) {
2688 is_buf_set = true;
2689 }
2690 break;
2691
2692 default:
2693 break;
2694 }
2695
2696 if (is_buf_set) {
2697 WM_clipboard_text_set(buf, 0);
2698 }
2699 }
2700
ui_but_paste(bContext * C,uiBut * but,uiHandleButtonData * data,const bool paste_array)2701 static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array)
2702 {
2703 BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
2704
2705 int buf_paste_len = 0;
2706 char *buf_paste;
2707 ui_but_get_pasted_text_from_clipboard(&buf_paste, &buf_paste_len);
2708
2709 const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL);
2710
2711 switch (but->type) {
2712 case UI_BTYPE_NUM:
2713 case UI_BTYPE_NUM_SLIDER:
2714 if (!has_required_data) {
2715 break;
2716 }
2717 if (paste_array && ui_but_has_array_value(but)) {
2718 ui_but_paste_numeric_array(C, but, data, buf_paste);
2719 }
2720 else {
2721 ui_but_paste_numeric_value(C, but, data, buf_paste);
2722 }
2723 break;
2724
2725 case UI_BTYPE_UNITVEC:
2726 if (!has_required_data) {
2727 break;
2728 }
2729 ui_but_paste_normalized_vector(C, but, data, buf_paste);
2730 break;
2731
2732 case UI_BTYPE_COLOR:
2733 if (!has_required_data) {
2734 break;
2735 }
2736 ui_but_paste_color(C, but, buf_paste);
2737 break;
2738
2739 case UI_BTYPE_TEXT:
2740 case UI_BTYPE_SEARCH_MENU:
2741 if (!has_required_data) {
2742 break;
2743 }
2744 ui_but_paste_text(C, but, data, buf_paste);
2745 break;
2746
2747 case UI_BTYPE_COLORBAND:
2748 ui_but_paste_colorband(C, but, data);
2749 break;
2750
2751 case UI_BTYPE_CURVE:
2752 ui_but_paste_curvemapping(C, but);
2753 break;
2754
2755 case UI_BTYPE_CURVEPROFILE:
2756 ui_but_paste_CurveProfile(C, but);
2757 break;
2758
2759 default:
2760 break;
2761 }
2762
2763 MEM_freeN((void *)buf_paste);
2764 }
2765
ui_but_clipboard_free(void)2766 void ui_but_clipboard_free(void)
2767 {
2768 BKE_curvemapping_free_data(&but_copypaste_curve);
2769 BKE_curveprofile_free_data(&but_copypaste_profile);
2770 }
2771
2772 /** \} */
2773
2774 /* -------------------------------------------------------------------- */
2775 /** \name Button Text Password
2776 *
2777 * Functions to convert password strings that should not be displayed
2778 * to asterisk representation (e.g. 'mysecretpasswd' -> '*************')
2779 *
2780 * It converts every UTF-8 character to an asterisk, and also remaps
2781 * the cursor position and selection start/end.
2782 *
2783 * \note remapping is used, because password could contain UTF-8 characters.
2784 *
2785 * \{ */
2786
ui_text_position_from_hidden(uiBut * but,int pos)2787 static int ui_text_position_from_hidden(uiBut *but, int pos)
2788 {
2789 const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2790 const char *strpos = butstr;
2791 for (int i = 0; i < pos; i++) {
2792 strpos = BLI_str_find_next_char_utf8(strpos, NULL);
2793 }
2794
2795 return (strpos - butstr);
2796 }
2797
ui_text_position_to_hidden(uiBut * but,int pos)2798 static int ui_text_position_to_hidden(uiBut *but, int pos)
2799 {
2800 const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2801 return BLI_strnlen_utf8(butstr, pos);
2802 }
2803
ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR],uiBut * but,const bool restore)2804 void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR],
2805 uiBut *but,
2806 const bool restore)
2807 {
2808 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
2809 return;
2810 }
2811
2812 char *butstr = (but->editstr) ? but->editstr : but->drawstr;
2813
2814 if (restore) {
2815 /* restore original string */
2816 BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR);
2817
2818 /* remap cursor positions */
2819 if (but->pos >= 0) {
2820 but->pos = ui_text_position_from_hidden(but, but->pos);
2821 but->selsta = ui_text_position_from_hidden(but, but->selsta);
2822 but->selend = ui_text_position_from_hidden(but, but->selend);
2823 }
2824 }
2825 else {
2826 /* convert text to hidden text using asterisks (e.g. pass -> ****) */
2827 const size_t len = BLI_strlen_utf8(butstr);
2828
2829 /* remap cursor positions */
2830 if (but->pos >= 0) {
2831 but->pos = ui_text_position_to_hidden(but, but->pos);
2832 but->selsta = ui_text_position_to_hidden(but, but->selsta);
2833 but->selend = ui_text_position_to_hidden(but, but->selend);
2834 }
2835
2836 /* save original string */
2837 BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR);
2838 memset(butstr, '*', len);
2839 butstr[len] = '\0';
2840 }
2841 }
2842
2843 /** \} */
2844
2845 /* -------------------------------------------------------------------- */
2846 /** \name Button Text Selection/Editing
2847 * \{ */
2848
ui_but_active_string_clear_and_exit(bContext * C,uiBut * but)2849 void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but)
2850 {
2851 if (!but->active) {
2852 return;
2853 }
2854
2855 /* most likely NULL, but let's check, and give it temp zero string */
2856 if (!but->active->str) {
2857 but->active->str = MEM_callocN(1, "temp str");
2858 }
2859 but->active->str[0] = 0;
2860
2861 ui_apply_but_TEX(C, but, but->active);
2862 button_activate_state(C, but, BUTTON_STATE_EXIT);
2863 }
2864
ui_textedit_string_ensure_max_length(uiBut * but,uiHandleButtonData * data,int maxlen)2865 static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen)
2866 {
2867 BLI_assert(data->is_str_dynamic);
2868 BLI_assert(data->str == but->editstr);
2869
2870 if (maxlen > data->maxlen) {
2871 data->str = but->editstr = MEM_reallocN(data->str, sizeof(char) * maxlen);
2872 data->maxlen = maxlen;
2873 }
2874 }
2875
ui_textedit_string_set(uiBut * but,uiHandleButtonData * data,const char * str)2876 static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str)
2877 {
2878 if (data->is_str_dynamic) {
2879 ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1);
2880 }
2881
2882 if (UI_but_is_utf8(but)) {
2883 BLI_strncpy_utf8(data->str, str, data->maxlen);
2884 }
2885 else {
2886 BLI_strncpy(data->str, str, data->maxlen);
2887 }
2888 }
2889
ui_textedit_delete_selection(uiBut * but,uiHandleButtonData * data)2890 static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
2891 {
2892 char *str = data->str;
2893 const int len = strlen(str);
2894 bool changed = false;
2895 if (but->selsta != but->selend && len) {
2896 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
2897 changed = true;
2898 }
2899
2900 but->pos = but->selend = but->selsta;
2901 return changed;
2902 }
2903
ui_textedit_set_cursor_pos_foreach_glyph(const char * UNUSED (str),const size_t str_step_ofs,const rcti * glyph_step_bounds,const int UNUSED (glyph_advance_x),const rctf * glyph_bounds,const int UNUSED (glyph_bearing[2]),void * user_data)2904 static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str),
2905 const size_t str_step_ofs,
2906 const rcti *glyph_step_bounds,
2907 const int UNUSED(glyph_advance_x),
2908 const rctf *glyph_bounds,
2909 const int UNUSED(glyph_bearing[2]),
2910 void *user_data)
2911 {
2912 int *cursor_data = user_data;
2913 const float center = glyph_step_bounds->xmin + (BLI_rctf_size_x(glyph_bounds) / 2.0f);
2914 if (cursor_data[0] < center) {
2915 cursor_data[1] = str_step_ofs;
2916 return false;
2917 }
2918 return true;
2919 }
2920
2921 /**
2922 * \param x: Screen space cursor location - #wmEvent.x
2923 *
2924 * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too.
2925 */
ui_textedit_set_cursor_pos(uiBut * but,uiHandleButtonData * data,const float x)2926 static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x)
2927 {
2928 /* XXX pass on as arg. */
2929 uiFontStyle fstyle = UI_style_get()->widget;
2930 const float aspect = but->block->aspect;
2931
2932 float startx = but->rect.xmin;
2933 float starty_dummy = 0.0f;
2934 char password_str[UI_MAX_PASSWORD_STR];
2935 /* treat 'str_last' as null terminator for str, no need to modify in-place */
2936 const char *str = but->editstr, *str_last;
2937
2938 ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy);
2939
2940 ui_fontscale(&fstyle.points, aspect);
2941
2942 UI_fontstyle_set(&fstyle);
2943
2944 if (fstyle.kerning == 1) {
2945 /* for BLF_width */
2946 BLF_enable(fstyle.uifont_id, BLF_KERNING_DEFAULT);
2947 }
2948
2949 ui_but_text_password_hide(password_str, but, false);
2950
2951 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
2952 if (but->flag & UI_HAS_ICON) {
2953 startx += UI_DPI_ICON_SIZE / aspect;
2954 }
2955 }
2956 startx += (UI_TEXT_MARGIN_X * U.widget_unit) / aspect;
2957
2958 /* mouse dragged outside the widget to the left */
2959 if (x < startx) {
2960 int i = but->ofs;
2961
2962 str_last = &str[but->ofs];
2963
2964 while (i > 0) {
2965 if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) {
2966 /* 0.25 == scale factor for less sensitivity */
2967 if (BLF_width(fstyle.uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) {
2968 break;
2969 }
2970 }
2971 else {
2972 break; /* unlikely but possible */
2973 }
2974 }
2975 but->ofs = i;
2976 but->pos = but->ofs;
2977 }
2978 /* mouse inside the widget, mouse coords mapped in widget space */
2979 else {
2980 str_last = &str[but->ofs];
2981 const int str_last_len = strlen(str_last);
2982 const int x_pos = (int)(x - startx);
2983 int glyph_data[2] = {
2984 x_pos, /* horizontal position to test. */
2985 -1, /* Write the character offset here. */
2986 };
2987 BLF_boundbox_foreach_glyph(fstyle.uifont_id,
2988 str + but->ofs,
2989 INT_MAX,
2990 ui_textedit_set_cursor_pos_foreach_glyph,
2991 glyph_data);
2992 /* If value untouched then we are to the right. */
2993 if (glyph_data[1] == -1) {
2994 glyph_data[1] = str_last_len;
2995 }
2996 but->pos = glyph_data[1] + but->ofs;
2997 }
2998
2999 if (fstyle.kerning == 1) {
3000 BLF_disable(fstyle.uifont_id, BLF_KERNING_DEFAULT);
3001 }
3002
3003 ui_but_text_password_hide(password_str, but, true);
3004 }
3005
ui_textedit_set_cursor_select(uiBut * but,uiHandleButtonData * data,const float x)3006 static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
3007 {
3008 ui_textedit_set_cursor_pos(but, data, x);
3009
3010 but->selsta = but->pos;
3011 but->selend = data->sel_pos_init;
3012 if (but->selend < but->selsta) {
3013 SWAP(short, but->selsta, but->selend);
3014 }
3015
3016 ui_but_update(but);
3017 }
3018
3019 /**
3020 * This is used for both utf8 and ascii
3021 *
3022 * For unicode buttons, \a buf is treated as unicode.
3023 */
ui_textedit_insert_buf(uiBut * but,uiHandleButtonData * data,const char * buf,int buf_len)3024 static bool ui_textedit_insert_buf(uiBut *but,
3025 uiHandleButtonData *data,
3026 const char *buf,
3027 int buf_len)
3028 {
3029 int len = strlen(data->str);
3030 const int len_new = len - (but->selend - but->selsta) + 1;
3031 bool changed = false;
3032
3033 if (data->is_str_dynamic) {
3034 ui_textedit_string_ensure_max_length(but, data, len_new + buf_len);
3035 }
3036
3037 if (len_new <= data->maxlen) {
3038 char *str = data->str;
3039 size_t step = buf_len;
3040
3041 /* type over the current selection */
3042 if ((but->selend - but->selsta) > 0) {
3043 changed = ui_textedit_delete_selection(but, data);
3044 len = strlen(str);
3045 }
3046
3047 if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) {
3048 if (UI_but_is_utf8(but)) {
3049 /* shorten 'step' to a utf8 aligned size that fits */
3050 BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step);
3051 }
3052 else {
3053 step = data->maxlen - (len + 1);
3054 }
3055 }
3056
3057 if (step && (len + step < data->maxlen)) {
3058 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
3059 memcpy(&str[but->pos], buf, step * sizeof(char));
3060 but->pos += step;
3061 changed = true;
3062 }
3063 }
3064
3065 return changed;
3066 }
3067
ui_textedit_insert_ascii(uiBut * but,uiHandleButtonData * data,char ascii)3068 static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii)
3069 {
3070 const char buf[2] = {ascii, '\0'};
3071
3072 if (UI_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) {
3073 printf(
3074 "%s: entering invalid ascii char into an ascii key (%d)\n", __func__, (int)(uchar)ascii);
3075
3076 return false;
3077 }
3078
3079 /* in some cases we want to allow invalid utf8 chars */
3080 return ui_textedit_insert_buf(but, data, buf, 1);
3081 }
3082
ui_textedit_move(uiBut * but,uiHandleButtonData * data,eStrCursorJumpDirection direction,const bool select,eStrCursorJumpType jump)3083 static void ui_textedit_move(uiBut *but,
3084 uiHandleButtonData *data,
3085 eStrCursorJumpDirection direction,
3086 const bool select,
3087 eStrCursorJumpType jump)
3088 {
3089 const char *str = data->str;
3090 const int len = strlen(str);
3091 const int pos_prev = but->pos;
3092 const bool has_sel = (but->selend - but->selsta) > 0;
3093
3094 ui_but_update(but);
3095
3096 /* special case, quit selection and set cursor */
3097 if (has_sel && !select) {
3098 if (jump == STRCUR_JUMP_ALL) {
3099 but->selsta = but->selend = but->pos = direction ? len : 0;
3100 }
3101 else {
3102 if (direction) {
3103 but->selsta = but->pos = but->selend;
3104 }
3105 else {
3106 but->pos = but->selend = but->selsta;
3107 }
3108 }
3109 data->sel_pos_init = but->pos;
3110 }
3111 else {
3112 int pos_i = but->pos;
3113 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
3114 but->pos = pos_i;
3115
3116 if (select) {
3117 if (has_sel == false) {
3118 data->sel_pos_init = pos_prev;
3119 }
3120 but->selsta = but->pos;
3121 but->selend = data->sel_pos_init;
3122 }
3123 if (but->selend < but->selsta) {
3124 SWAP(short, but->selsta, but->selend);
3125 }
3126 }
3127 }
3128
ui_textedit_delete(uiBut * but,uiHandleButtonData * data,int direction,eStrCursorJumpType jump)3129 static bool ui_textedit_delete(uiBut *but,
3130 uiHandleButtonData *data,
3131 int direction,
3132 eStrCursorJumpType jump)
3133 {
3134 char *str = data->str;
3135 const int len = strlen(str);
3136
3137 bool changed = false;
3138
3139 if (jump == STRCUR_JUMP_ALL) {
3140 if (len) {
3141 changed = true;
3142 }
3143 str[0] = '\0';
3144 but->pos = 0;
3145 }
3146 else if (direction) { /* delete */
3147 if ((but->selend - but->selsta) > 0) {
3148 changed = ui_textedit_delete_selection(but, data);
3149 }
3150 else if (but->pos >= 0 && but->pos < len) {
3151 int pos = but->pos;
3152 int step;
3153 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
3154 step = pos - but->pos;
3155 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step));
3156 changed = true;
3157 }
3158 }
3159 else { /* backspace */
3160 if (len != 0) {
3161 if ((but->selend - but->selsta) > 0) {
3162 changed = ui_textedit_delete_selection(but, data);
3163 }
3164 else if (but->pos > 0) {
3165 int pos = but->pos;
3166 int step;
3167
3168 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
3169 step = but->pos - pos;
3170 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
3171 but->pos -= step;
3172 changed = true;
3173 }
3174 }
3175 }
3176
3177 return changed;
3178 }
3179
ui_textedit_autocomplete(bContext * C,uiBut * but,uiHandleButtonData * data)3180 static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
3181 {
3182 char *str = data->str;
3183
3184 int changed;
3185 if (data->searchbox) {
3186 changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str);
3187 }
3188 else {
3189 changed = but->autocomplete_func(C, str, but->autofunc_arg);
3190 }
3191
3192 but->pos = strlen(str);
3193 but->selsta = but->selend = but->pos;
3194
3195 return changed;
3196 }
3197
3198 /* mode for ui_textedit_copypaste() */
3199 enum {
3200 UI_TEXTEDIT_PASTE = 1,
3201 UI_TEXTEDIT_COPY,
3202 UI_TEXTEDIT_CUT,
3203 };
3204
ui_textedit_copypaste(uiBut * but,uiHandleButtonData * data,const int mode)3205 static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode)
3206 {
3207 bool changed = false;
3208
3209 /* paste */
3210 if (mode == UI_TEXTEDIT_PASTE) {
3211 /* extract the first line from the clipboard */
3212 int buf_len;
3213 char *pbuf = WM_clipboard_text_get_firstline(false, &buf_len);
3214
3215 if (pbuf) {
3216 if (UI_but_is_utf8(but)) {
3217 buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len);
3218 }
3219
3220 ui_textedit_insert_buf(but, data, pbuf, buf_len);
3221
3222 changed = true;
3223
3224 MEM_freeN(pbuf);
3225 }
3226 }
3227 /* cut & copy */
3228 else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
3229 /* copy the contents to the copypaste buffer */
3230 const int sellen = but->selend - but->selsta;
3231 char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste");
3232
3233 BLI_strncpy(buf, data->str + but->selsta, sellen + 1);
3234 WM_clipboard_text_set(buf, 0);
3235 MEM_freeN(buf);
3236
3237 /* for cut only, delete the selection afterwards */
3238 if (mode == UI_TEXTEDIT_CUT) {
3239 if ((but->selend - but->selsta) > 0) {
3240 changed = ui_textedit_delete_selection(but, data);
3241 }
3242 }
3243 }
3244
3245 return changed;
3246 }
3247
3248 #ifdef WITH_INPUT_IME
3249 /* enable ime, and set up uibut ime data */
ui_textedit_ime_begin(wmWindow * win,uiBut * UNUSED (but))3250 static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
3251 {
3252 /* XXX Is this really needed? */
3253 int x, y;
3254
3255 BLI_assert(win->ime_data == NULL);
3256
3257 /* enable IME and position to cursor, it's a trick */
3258 x = win->eventstate->x;
3259 /* flip y and move down a bit, prevent the IME panel cover the edit button */
3260 y = win->eventstate->y - 12;
3261
3262 wm_window_IME_begin(win, x, y, 0, 0, true);
3263 }
3264
3265 /* disable ime, and clear uibut ime data */
ui_textedit_ime_end(wmWindow * win,uiBut * UNUSED (but))3266 static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but))
3267 {
3268 wm_window_IME_end(win);
3269 }
3270
ui_but_ime_reposition(uiBut * but,int x,int y,bool complete)3271 void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete)
3272 {
3273 BLI_assert(but->active);
3274
3275 ui_region_to_window(but->active->region, &x, &y);
3276 wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete);
3277 }
3278
ui_but_ime_data_get(uiBut * but)3279 wmIMEData *ui_but_ime_data_get(uiBut *but)
3280 {
3281 if (but->active && but->active->window) {
3282 return but->active->window->ime_data;
3283 }
3284 else {
3285 return NULL;
3286 }
3287 }
3288 #endif /* WITH_INPUT_IME */
3289
ui_textedit_begin(bContext * C,uiBut * but,uiHandleButtonData * data)3290 static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
3291 {
3292 wmWindow *win = data->window;
3293 const bool is_num_but = ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER);
3294 bool no_zero_strip = false;
3295
3296 if (data->str) {
3297 MEM_freeN(data->str);
3298 data->str = NULL;
3299 }
3300
3301 #ifdef USE_DRAG_MULTINUM
3302 /* this can happen from multi-drag */
3303 if (data->applied_interactive) {
3304 /* remove any small changes so canceling edit doesn't restore invalid value: T40538 */
3305 data->cancel = true;
3306 ui_apply_but(C, but->block, but, data, true);
3307 data->cancel = false;
3308
3309 data->applied_interactive = false;
3310 }
3311 #endif
3312
3313 #ifdef USE_ALLSELECT
3314 if (is_num_but) {
3315 if (IS_ALLSELECT_EVENT(win->eventstate)) {
3316 data->select_others.is_enabled = true;
3317 data->select_others.is_copy = true;
3318 }
3319 }
3320 #endif
3321
3322 /* retrieve string */
3323 data->maxlen = ui_but_string_get_max_length(but);
3324 if (data->maxlen != 0) {
3325 data->str = MEM_callocN(sizeof(char) * data->maxlen, "textedit str");
3326 /* We do not want to truncate precision to default here, it's nice to show value,
3327 * not to edit it - way too much precision is lost then. */
3328 ui_but_string_get_ex(
3329 but, data->str, data->maxlen, UI_PRECISION_FLOAT_MAX, true, &no_zero_strip);
3330 }
3331 else {
3332 data->is_str_dynamic = true;
3333 data->str = ui_but_string_get_dynamic(but, &data->maxlen);
3334 }
3335
3336 if (ui_but_is_float(but) && !ui_but_is_unit(but) && !ui_but_anim_expression_get(but, NULL, 0) &&
3337 !no_zero_strip) {
3338 BLI_str_rstrip_float_zero(data->str, '\0');
3339 }
3340
3341 if (is_num_but) {
3342 BLI_assert(data->is_str_dynamic == false);
3343 ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen);
3344 }
3345
3346 /* won't change from now on */
3347 const int len = strlen(data->str);
3348
3349 data->origstr = BLI_strdupn(data->str, len);
3350 data->sel_pos_init = 0;
3351
3352 /* set cursor pos to the end of the text */
3353 but->editstr = data->str;
3354 but->pos = len;
3355 but->selsta = 0;
3356 but->selend = len;
3357
3358 /* Initialize undo history tracking. */
3359 data->undo_stack_text = ui_textedit_undo_stack_create();
3360 ui_textedit_undo_push(data->undo_stack_text, but->editstr, but->pos);
3361
3362 /* optional searchbox */
3363 if (but->type == UI_BTYPE_SEARCH_MENU) {
3364 uiButSearch *search_but = (uiButSearch *)but;
3365
3366 data->searchbox = search_but->popup_create_fn(C, data->region, search_but);
3367 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
3368 }
3369
3370 /* reset alert flag (avoid confusion, will refresh on exit) */
3371 but->flag &= ~UI_BUT_REDALERT;
3372
3373 ui_but_update(but);
3374
3375 WM_cursor_modal_set(win, WM_CURSOR_TEXT_EDIT);
3376
3377 #ifdef WITH_INPUT_IME
3378 if (is_num_but == false && BLT_lang_is_ime_supported()) {
3379 ui_textedit_ime_begin(win, but);
3380 }
3381 #endif
3382 }
3383
ui_textedit_end(bContext * C,uiBut * but,uiHandleButtonData * data)3384 static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
3385 {
3386 wmWindow *win = data->window;
3387
3388 if (but) {
3389 if (UI_but_is_utf8(but)) {
3390 const int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
3391 /* not a file?, strip non utf-8 chars */
3392 if (strip) {
3393 /* wont happen often so isn't that annoying to keep it here for a while */
3394 printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip);
3395 }
3396 }
3397
3398 if (data->searchbox) {
3399 if (data->cancel == false) {
3400 if ((ui_searchbox_apply(but, data->searchbox) == false) &&
3401 (ui_searchbox_find_index(data->searchbox, but->editstr) == -1)) {
3402 data->cancel = true;
3403
3404 /* ensure menu (popup) too is closed! */
3405 data->escapecancel = true;
3406
3407 WM_reportf(RPT_ERROR, "Failed to find '%s'", but->editstr);
3408 WM_report_banner_show();
3409 }
3410 }
3411
3412 ui_searchbox_free(C, data->searchbox);
3413 data->searchbox = NULL;
3414 }
3415
3416 but->editstr = NULL;
3417 but->pos = -1;
3418 }
3419
3420 WM_cursor_modal_restore(win);
3421
3422 /* Free text undo history text blocks. */
3423 ui_textedit_undo_stack_destroy(data->undo_stack_text);
3424 data->undo_stack_text = NULL;
3425
3426 #ifdef WITH_INPUT_IME
3427 if (win->ime_data) {
3428 ui_textedit_ime_end(win, but);
3429 }
3430 #endif
3431 }
3432
ui_textedit_next_but(uiBlock * block,uiBut * actbut,uiHandleButtonData * data)3433 static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
3434 {
3435 /* label and roundbox can overlap real buttons (backdrops...) */
3436 if (ELEM(actbut->type,
3437 UI_BTYPE_LABEL,
3438 UI_BTYPE_SEPR,
3439 UI_BTYPE_SEPR_LINE,
3440 UI_BTYPE_ROUNDBOX,
3441 UI_BTYPE_LISTBOX)) {
3442 return;
3443 }
3444
3445 for (uiBut *but = actbut->next; but; but = but->next) {
3446 if (ui_but_is_editable_as_text(but)) {
3447 if (!(but->flag & UI_BUT_DISABLED)) {
3448 data->postbut = but;
3449 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3450 return;
3451 }
3452 }
3453 }
3454 for (uiBut *but = block->buttons.first; but != actbut; but = but->next) {
3455 if (ui_but_is_editable_as_text(but)) {
3456 if (!(but->flag & UI_BUT_DISABLED)) {
3457 data->postbut = but;
3458 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3459 return;
3460 }
3461 }
3462 }
3463 }
3464
ui_textedit_prev_but(uiBlock * block,uiBut * actbut,uiHandleButtonData * data)3465 static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
3466 {
3467 /* label and roundbox can overlap real buttons (backdrops...) */
3468 if (ELEM(actbut->type,
3469 UI_BTYPE_LABEL,
3470 UI_BTYPE_SEPR,
3471 UI_BTYPE_SEPR_LINE,
3472 UI_BTYPE_ROUNDBOX,
3473 UI_BTYPE_LISTBOX)) {
3474 return;
3475 }
3476
3477 for (uiBut *but = actbut->prev; but; but = but->prev) {
3478 if (ui_but_is_editable_as_text(but)) {
3479 if (!(but->flag & UI_BUT_DISABLED)) {
3480 data->postbut = but;
3481 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3482 return;
3483 }
3484 }
3485 }
3486 for (uiBut *but = block->buttons.last; but != actbut; but = but->prev) {
3487 if (ui_but_is_editable_as_text(but)) {
3488 if (!(but->flag & UI_BUT_DISABLED)) {
3489 data->postbut = but;
3490 data->posttype = BUTTON_ACTIVATE_TEXT_EDITING;
3491 return;
3492 }
3493 }
3494 }
3495 }
3496
ui_do_but_textedit(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)3497 static void ui_do_but_textedit(
3498 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
3499 {
3500 int retval = WM_UI_HANDLER_CONTINUE;
3501 bool changed = false, inbox = false, update = false, skip_undo_push = false;
3502
3503 #ifdef WITH_INPUT_IME
3504 wmWindow *win = CTX_wm_window(C);
3505 wmIMEData *ime_data = win->ime_data;
3506 const bool is_ime_composing = ime_data && ime_data->is_ime_composing;
3507 #else
3508 const bool is_ime_composing = false;
3509 #endif
3510
3511 switch (event->type) {
3512 case MOUSEMOVE:
3513 case MOUSEPAN:
3514 if (data->searchbox) {
3515 #ifdef USE_KEYNAV_LIMIT
3516 if ((event->type == MOUSEMOVE) &&
3517 ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event)) {
3518 /* pass */
3519 }
3520 else {
3521 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3522 }
3523 #else
3524 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3525 #endif
3526 }
3527 ui_do_but_extra_operator_icons_mousemove(but, data, event);
3528
3529 break;
3530 case RIGHTMOUSE:
3531 case EVT_ESCKEY:
3532 if (event->val == KM_PRESS) {
3533 /* Support search context menu. */
3534 if (event->type == RIGHTMOUSE) {
3535 if (data->searchbox) {
3536 if (ui_searchbox_event(C, data->searchbox, but, data->region, event)) {
3537 /* Only break if the event was handled. */
3538 break;
3539 }
3540 }
3541 }
3542
3543 #ifdef WITH_INPUT_IME
3544 /* skips button handling since it is not wanted */
3545 if (is_ime_composing) {
3546 break;
3547 }
3548 #endif
3549 data->cancel = true;
3550 data->escapecancel = true;
3551 button_activate_state(C, but, BUTTON_STATE_EXIT);
3552 retval = WM_UI_HANDLER_BREAK;
3553 }
3554 break;
3555 case LEFTMOUSE: {
3556 /* Allow clicks on extra icons while editing. */
3557 if (ui_do_but_extra_operator_icon(C, but, data, event)) {
3558 break;
3559 }
3560
3561 const bool had_selection = but->selsta != but->selend;
3562
3563 /* exit on LMB only on RELEASE for searchbox, to mimic other popups,
3564 * and allow multiple menu levels */
3565 if (data->searchbox) {
3566 inbox = ui_searchbox_inside(data->searchbox, event->x, event->y);
3567 }
3568
3569 /* for double click: we do a press again for when you first click on button
3570 * (selects all text, no cursor pos) */
3571 if (event->val == KM_PRESS || event->val == KM_DBL_CLICK) {
3572 float mx = event->x;
3573 float my = event->y;
3574 ui_window_to_block_fl(data->region, block, &mx, &my);
3575
3576 if (ui_but_contains_pt(but, mx, my)) {
3577 ui_textedit_set_cursor_pos(but, data, event->x);
3578 but->selsta = but->selend = but->pos;
3579 data->sel_pos_init = but->pos;
3580
3581 button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING);
3582 retval = WM_UI_HANDLER_BREAK;
3583 }
3584 else if (inbox == false) {
3585 /* if searchbox, click outside will cancel */
3586 if (data->searchbox) {
3587 data->cancel = data->escapecancel = true;
3588 }
3589 button_activate_state(C, but, BUTTON_STATE_EXIT);
3590 retval = WM_UI_HANDLER_BREAK;
3591 }
3592 }
3593
3594 /* only select a word in button if there was no selection before */
3595 if (event->val == KM_DBL_CLICK && had_selection == false) {
3596 ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM);
3597 ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM);
3598 retval = WM_UI_HANDLER_BREAK;
3599 changed = true;
3600 }
3601 else if (inbox) {
3602 /* if we allow activation on key press,
3603 * it gives problems launching operators T35713. */
3604 if (event->val == KM_RELEASE) {
3605 button_activate_state(C, but, BUTTON_STATE_EXIT);
3606 retval = WM_UI_HANDLER_BREAK;
3607 }
3608 }
3609 break;
3610 }
3611 }
3612
3613 if (event->val == KM_PRESS && !is_ime_composing) {
3614 switch (event->type) {
3615 case EVT_VKEY:
3616 case EVT_XKEY:
3617 case EVT_CKEY:
3618 if (IS_EVENT_MOD(event, ctrl, oskey)) {
3619 if (event->type == EVT_VKEY) {
3620 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE);
3621 }
3622 else if (event->type == EVT_CKEY) {
3623 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_COPY);
3624 }
3625 else if (event->type == EVT_XKEY) {
3626 changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_CUT);
3627 }
3628
3629 retval = WM_UI_HANDLER_BREAK;
3630 }
3631 break;
3632 case EVT_RIGHTARROWKEY:
3633 ui_textedit_move(but,
3634 data,
3635 STRCUR_DIR_NEXT,
3636 event->shift != 0,
3637 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3638 retval = WM_UI_HANDLER_BREAK;
3639 break;
3640 case EVT_LEFTARROWKEY:
3641 ui_textedit_move(but,
3642 data,
3643 STRCUR_DIR_PREV,
3644 event->shift != 0,
3645 event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3646 retval = WM_UI_HANDLER_BREAK;
3647 break;
3648 case WHEELDOWNMOUSE:
3649 case EVT_DOWNARROWKEY:
3650 if (data->searchbox) {
3651 #ifdef USE_KEYNAV_LIMIT
3652 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
3653 #endif
3654 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3655 break;
3656 }
3657 if (event->type == WHEELDOWNMOUSE) {
3658 break;
3659 }
3660 ATTR_FALLTHROUGH;
3661 case EVT_ENDKEY:
3662 ui_textedit_move(but, data, STRCUR_DIR_NEXT, event->shift != 0, STRCUR_JUMP_ALL);
3663 retval = WM_UI_HANDLER_BREAK;
3664 break;
3665 case WHEELUPMOUSE:
3666 case EVT_UPARROWKEY:
3667 if (data->searchbox) {
3668 #ifdef USE_KEYNAV_LIMIT
3669 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
3670 #endif
3671 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3672 break;
3673 }
3674 if (event->type == WHEELUPMOUSE) {
3675 break;
3676 }
3677 ATTR_FALLTHROUGH;
3678 case EVT_HOMEKEY:
3679 ui_textedit_move(but, data, STRCUR_DIR_PREV, event->shift != 0, STRCUR_JUMP_ALL);
3680 retval = WM_UI_HANDLER_BREAK;
3681 break;
3682 case EVT_PADENTER:
3683 case EVT_RETKEY:
3684 button_activate_state(C, but, BUTTON_STATE_EXIT);
3685 retval = WM_UI_HANDLER_BREAK;
3686 break;
3687 case EVT_DELKEY:
3688 changed = ui_textedit_delete(
3689 but, data, 1, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3690 retval = WM_UI_HANDLER_BREAK;
3691 break;
3692
3693 case EVT_BACKSPACEKEY:
3694 changed = ui_textedit_delete(
3695 but, data, 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE);
3696 retval = WM_UI_HANDLER_BREAK;
3697 break;
3698
3699 case EVT_AKEY:
3700
3701 /* Ctrl + A: Select all */
3702 #if defined(__APPLE__)
3703 /* OSX uses cmd-a systemwide, so add it */
3704 if ((event->oskey && !IS_EVENT_MOD(event, shift, alt, ctrl)) ||
3705 (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)))
3706 #else
3707 if (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey))
3708 #endif
3709 {
3710 ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_ALL);
3711 ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_ALL);
3712 retval = WM_UI_HANDLER_BREAK;
3713 }
3714 break;
3715
3716 case EVT_TABKEY:
3717 /* there is a key conflict here, we can't tab with autocomplete */
3718 if (but->autocomplete_func || data->searchbox) {
3719 const int autocomplete = ui_textedit_autocomplete(C, but, data);
3720 changed = autocomplete != AUTOCOMPLETE_NO_MATCH;
3721
3722 if (autocomplete == AUTOCOMPLETE_FULL_MATCH) {
3723 button_activate_state(C, but, BUTTON_STATE_EXIT);
3724 }
3725 }
3726 /* the hotkey here is not well defined, was G.qual so we check all */
3727 else if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
3728 ui_textedit_prev_but(block, but, data);
3729 button_activate_state(C, but, BUTTON_STATE_EXIT);
3730 }
3731 else {
3732 ui_textedit_next_but(block, but, data);
3733 button_activate_state(C, but, BUTTON_STATE_EXIT);
3734 }
3735 retval = WM_UI_HANDLER_BREAK;
3736 break;
3737 case EVT_ZKEY: {
3738 /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */
3739
3740 const bool is_redo = (event->shift != 0);
3741 if (
3742 #if defined(__APPLE__)
3743 (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) ||
3744 #endif
3745 (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) {
3746 int undo_pos;
3747 const char *undo_str = ui_textedit_undo(
3748 data->undo_stack_text, is_redo ? 1 : -1, &undo_pos);
3749 if (undo_str != NULL) {
3750 ui_textedit_string_set(but, data, undo_str);
3751
3752 /* Set the cursor & clear selection. */
3753 but->pos = undo_pos;
3754 but->selsta = but->pos;
3755 but->selend = but->pos;
3756 changed = true;
3757 }
3758 retval = WM_UI_HANDLER_BREAK;
3759 skip_undo_push = true;
3760 }
3761 break;
3762 }
3763 }
3764
3765 if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)
3766 #ifdef WITH_INPUT_IME
3767 && !is_ime_composing && (!WM_event_is_ime_switch(event) || !BLT_lang_is_ime_supported())
3768 #endif
3769 ) {
3770 char ascii = event->ascii;
3771 const char *utf8_buf = event->utf8_buf;
3772
3773 /* exception that's useful for number buttons, some keyboard
3774 * numpads have a comma instead of a period */
3775 if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* could use data->min*/
3776 if (event->type == EVT_PADPERIOD && ascii == ',') {
3777 ascii = '.';
3778 utf8_buf = NULL; /* force ascii fallback */
3779 }
3780 }
3781
3782 if (utf8_buf && utf8_buf[0]) {
3783 const int utf8_buf_len = BLI_str_utf8_size(utf8_buf);
3784 BLI_assert(utf8_buf_len != -1);
3785 changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len);
3786 }
3787 else {
3788 changed = ui_textedit_insert_ascii(but, data, ascii);
3789 }
3790
3791 retval = WM_UI_HANDLER_BREAK;
3792 }
3793 /* textbutton with this flag: do live update (e.g. for search buttons) */
3794 if (but->flag & UI_BUT_TEXTEDIT_UPDATE) {
3795 update = true;
3796 }
3797 }
3798
3799 #ifdef WITH_INPUT_IME
3800 if (event->type == WM_IME_COMPOSITE_START || event->type == WM_IME_COMPOSITE_EVENT) {
3801 changed = true;
3802
3803 if (event->type == WM_IME_COMPOSITE_START && but->selend > but->selsta) {
3804 ui_textedit_delete_selection(but, data);
3805 }
3806 if (event->type == WM_IME_COMPOSITE_EVENT && ime_data->result_len) {
3807 ui_textedit_insert_buf(but, data, ime_data->str_result, ime_data->result_len);
3808 }
3809 }
3810 else if (event->type == WM_IME_COMPOSITE_END) {
3811 changed = true;
3812 }
3813 #endif
3814
3815 if (changed) {
3816 /* The undo stack may be NULL if an event exits editing. */
3817 if ((skip_undo_push == false) && (data->undo_stack_text != NULL)) {
3818 ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos);
3819 }
3820
3821 /* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */
3822 if (update && data->interactive) {
3823 ui_apply_but(C, block, but, data, true);
3824 }
3825 else {
3826 ui_but_update_edited(but);
3827 }
3828 but->changed = true;
3829
3830 if (data->searchbox) {
3831 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
3832 }
3833 }
3834
3835 if (changed || (retval == WM_UI_HANDLER_BREAK)) {
3836 ED_region_tag_redraw(data->region);
3837 }
3838 }
3839
ui_do_but_textedit_select(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)3840 static void ui_do_but_textedit_select(
3841 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
3842 {
3843 int retval = WM_UI_HANDLER_CONTINUE;
3844
3845 switch (event->type) {
3846 case MOUSEMOVE: {
3847 int mx = event->x;
3848 int my = event->y;
3849 ui_window_to_block(data->region, block, &mx, &my);
3850
3851 ui_textedit_set_cursor_select(but, data, event->x);
3852 retval = WM_UI_HANDLER_BREAK;
3853 break;
3854 }
3855 case LEFTMOUSE:
3856 if (event->val == KM_RELEASE) {
3857 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
3858 }
3859 retval = WM_UI_HANDLER_BREAK;
3860 break;
3861 }
3862
3863 if (retval == WM_UI_HANDLER_BREAK) {
3864 ui_but_update(but);
3865 ED_region_tag_redraw(data->region);
3866 }
3867 }
3868
3869 /** \} */
3870
3871 /* -------------------------------------------------------------------- */
3872 /** \name Button Number Editing (various types)
3873 * \{ */
3874
ui_numedit_begin(uiBut * but,uiHandleButtonData * data)3875 static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
3876 {
3877 if (but->type == UI_BTYPE_CURVE) {
3878 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
3879 but_cumap->edit_cumap = (CurveMapping *)but->poin;
3880 }
3881 else if (but->type == UI_BTYPE_CURVEPROFILE) {
3882 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
3883 but_profile->edit_profile = (CurveProfile *)but->poin;
3884 }
3885 else if (but->type == UI_BTYPE_COLORBAND) {
3886 uiButColorBand *but_coba = (uiButColorBand *)but;
3887 data->coba = (ColorBand *)but->poin;
3888 but_coba->edit_coba = data->coba;
3889 }
3890 else if (ELEM(but->type,
3891 UI_BTYPE_UNITVEC,
3892 UI_BTYPE_HSVCUBE,
3893 UI_BTYPE_HSVCIRCLE,
3894 UI_BTYPE_COLOR)) {
3895 ui_but_v3_get(but, data->origvec);
3896 copy_v3_v3(data->vec, data->origvec);
3897 but->editvec = data->vec;
3898 }
3899 else {
3900 float softrange, softmin, softmax;
3901
3902 data->startvalue = ui_but_value_get(but);
3903 data->origvalue = data->startvalue;
3904 data->value = data->origvalue;
3905 but->editval = &data->value;
3906
3907 softmin = but->softmin;
3908 softmax = but->softmax;
3909 softrange = softmax - softmin;
3910
3911 if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) {
3912 uiButNumber *number_but = (uiButNumber *)but;
3913 /* Use a minimum so we have a predictable range,
3914 * otherwise some float buttons get a large range. */
3915 const float value_step_float_min = 0.1f;
3916 const bool is_float = ui_but_is_float(but);
3917 const double value_step = is_float ?
3918 (double)(number_but->step_size * UI_PRECISION_FLOAT_SCALE) :
3919 (int)number_but->step_size;
3920 const float drag_map_softrange_max = UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX * UI_DPI_FAC;
3921 const float softrange_max = min_ff(
3922 softrange,
3923 2 * (is_float ? min_ff(value_step, value_step_float_min) *
3924 (drag_map_softrange_max / value_step_float_min) :
3925 drag_map_softrange_max));
3926
3927 if (softrange > softrange_max) {
3928 /* Center around the value, keeping in the real soft min/max range. */
3929 softmin = data->origvalue - (softrange_max / 2);
3930 softmax = data->origvalue + (softrange_max / 2);
3931 if (!isfinite(softmin)) {
3932 softmin = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX);
3933 }
3934 if (!isfinite(softmax)) {
3935 softmax = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX);
3936 }
3937
3938 if (softmin < but->softmin) {
3939 softmin = but->softmin;
3940 softmax = softmin + softrange_max;
3941 }
3942 else if (softmax > but->softmax) {
3943 softmax = but->softmax;
3944 softmin = softmax - softrange_max;
3945 }
3946
3947 /* Can happen at extreme values. */
3948 if (UNLIKELY(softmin == softmax)) {
3949 if (data->origvalue > 0.0) {
3950 softmin = nextafterf(softmin, -FLT_MAX);
3951 }
3952 else {
3953 softmax = nextafterf(softmax, FLT_MAX);
3954 }
3955 }
3956
3957 softrange = softmax - softmin;
3958 }
3959 }
3960
3961 data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange;
3962 data->dragf = data->dragfstart;
3963
3964 data->drag_map_soft_min = softmin;
3965 data->drag_map_soft_max = softmax;
3966 }
3967
3968 data->dragchange = false;
3969 data->draglock = true;
3970 }
3971
ui_numedit_end(uiBut * but,uiHandleButtonData * data)3972 static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
3973 {
3974 but->editval = NULL;
3975 but->editvec = NULL;
3976 if (but->type == UI_BTYPE_COLORBAND) {
3977 uiButColorBand *but_coba = (uiButColorBand *)but;
3978 but_coba->edit_coba = NULL;
3979 }
3980 else if (but->type == UI_BTYPE_CURVE) {
3981 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
3982 but_cumap->edit_cumap = NULL;
3983 }
3984 else if (but->type == UI_BTYPE_CURVEPROFILE) {
3985 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
3986 but_profile->edit_profile = NULL;
3987 }
3988 data->dragstartx = 0;
3989 data->draglastx = 0;
3990 data->dragchange = false;
3991 data->dragcbd = NULL;
3992 data->dragsel = 0;
3993 }
3994
ui_numedit_apply(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data)3995 static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
3996 {
3997 if (data->interactive) {
3998 ui_apply_but(C, block, but, data, true);
3999 }
4000 else {
4001 ui_but_update(but);
4002 }
4003
4004 ED_region_tag_redraw(data->region);
4005 }
4006
ui_but_extra_operator_icon_apply(bContext * C,uiBut * but,uiButExtraOpIcon * op_icon)4007 static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon)
4008 {
4009 if (but->active->interactive) {
4010 ui_apply_but(C, but->block, but, but->active, true);
4011 }
4012 button_activate_state(C, but, BUTTON_STATE_EXIT);
4013 WM_operator_name_call_ptr(C,
4014 op_icon->optype_params->optype,
4015 op_icon->optype_params->opcontext,
4016 op_icon->optype_params->opptr);
4017
4018 /* Force recreation of extra operator icons (pseudo update). */
4019 ui_but_extra_operator_icons_free(but);
4020
4021 WM_event_add_mousemove(CTX_wm_window(C));
4022 }
4023
4024 /** \} */
4025
4026 /* -------------------------------------------------------------------- */
4027 /** \name Menu/Popup Begin/End (various popup types)
4028 * \{ */
4029
ui_block_open_begin(bContext * C,uiBut * but,uiHandleButtonData * data)4030 static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
4031 {
4032 uiBlockCreateFunc func = NULL;
4033 uiBlockHandleCreateFunc handlefunc = NULL;
4034 uiMenuCreateFunc menufunc = NULL;
4035 uiMenuCreateFunc popoverfunc = NULL;
4036 void *arg = NULL;
4037
4038 switch (but->type) {
4039 case UI_BTYPE_BLOCK:
4040 case UI_BTYPE_PULLDOWN:
4041 if (but->menu_create_func) {
4042 menufunc = but->menu_create_func;
4043 arg = but->poin;
4044 }
4045 else {
4046 func = but->block_create_func;
4047 arg = but->poin ? but->poin : but->func_argN;
4048 }
4049 break;
4050 case UI_BTYPE_MENU:
4051 case UI_BTYPE_POPOVER:
4052 BLI_assert(but->menu_create_func);
4053 if ((but->type == UI_BTYPE_POPOVER) || ui_but_menu_draw_as_popover(but)) {
4054 popoverfunc = but->menu_create_func;
4055 }
4056 else {
4057 menufunc = but->menu_create_func;
4058 }
4059 arg = but->poin;
4060 break;
4061 case UI_BTYPE_COLOR:
4062 ui_but_v3_get(but, data->origvec);
4063 copy_v3_v3(data->vec, data->origvec);
4064 but->editvec = data->vec;
4065
4066 if (ui_but_menu_draw_as_popover(but)) {
4067 popoverfunc = but->menu_create_func;
4068 }
4069 else {
4070 handlefunc = ui_block_func_COLOR;
4071 }
4072 arg = but;
4073 break;
4074
4075 /* quiet warnings for unhandled types */
4076 default:
4077 break;
4078 }
4079
4080 if (func || handlefunc) {
4081 data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg, NULL);
4082 if (but->block->handle) {
4083 data->menu->popup = but->block->handle->popup;
4084 }
4085 }
4086 else if (menufunc) {
4087 data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg);
4088 if (but->block->handle) {
4089 data->menu->popup = but->block->handle->popup;
4090 }
4091 }
4092 else if (popoverfunc) {
4093 data->menu = ui_popover_panel_create(C, data->region, but, popoverfunc, arg);
4094 if (but->block->handle) {
4095 data->menu->popup = but->block->handle->popup;
4096 }
4097 }
4098
4099 #ifdef USE_ALLSELECT
4100 {
4101 if (IS_ALLSELECT_EVENT(data->window->eventstate)) {
4102 data->select_others.is_enabled = true;
4103 }
4104 }
4105 #endif
4106
4107 /* this makes adjacent blocks auto open from now on */
4108 // if (but->block->auto_open == 0) {
4109 // but->block->auto_open = 1;
4110 //}
4111 }
4112
ui_block_open_end(bContext * C,uiBut * but,uiHandleButtonData * data)4113 static void ui_block_open_end(bContext *C, uiBut *but, uiHandleButtonData *data)
4114 {
4115 if (but) {
4116 but->editval = NULL;
4117 but->editvec = NULL;
4118
4119 but->block->auto_open_last = PIL_check_seconds_timer();
4120 }
4121
4122 if (data->menu) {
4123 ui_popup_block_free(C, data->menu);
4124 data->menu = NULL;
4125 }
4126 }
4127
ui_but_menu_direction(uiBut * but)4128 int ui_but_menu_direction(uiBut *but)
4129 {
4130 uiHandleButtonData *data = but->active;
4131
4132 if (data && data->menu) {
4133 return data->menu->direction;
4134 }
4135
4136 return 0;
4137 }
4138
4139 /**
4140 * Hack for #uiList #UI_BTYPE_LISTROW buttons to "give" events to overlaying #UI_BTYPE_TEXT
4141 * buttons (Ctrl-Click rename feature & co).
4142 */
ui_but_list_row_text_activate(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event,uiButtonActivateType activate_type)4143 static uiBut *ui_but_list_row_text_activate(bContext *C,
4144 uiBut *but,
4145 uiHandleButtonData *data,
4146 const wmEvent *event,
4147 uiButtonActivateType activate_type)
4148 {
4149 ARegion *region = CTX_wm_region(C);
4150 uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true);
4151
4152 if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) {
4153 /* exit listrow */
4154 data->cancel = true;
4155 button_activate_exit(C, but, data, false, false);
4156
4157 /* Activate the text button. */
4158 button_activate_init(C, region, labelbut, activate_type);
4159
4160 return labelbut;
4161 }
4162 return NULL;
4163 }
4164
4165 /** \} */
4166
4167 /* -------------------------------------------------------------------- */
4168 /** \name Events for Various Button Types
4169 * \{ */
4170
ui_but_extra_operator_icon_mouse_over_get(uiBut * but,uiHandleButtonData * data,const wmEvent * event)4171 static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but,
4172 uiHandleButtonData *data,
4173 const wmEvent *event)
4174 {
4175 float xmax = but->rect.xmax;
4176 const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */
4177 int x = event->x, y = event->y;
4178
4179 ui_window_to_block(data->region, but->block, &x, &y);
4180 if (!BLI_rctf_isect_pt(&but->rect, x, y)) {
4181 return NULL;
4182 }
4183
4184 /* Inverse order, from right to left. */
4185 LISTBASE_FOREACH_BACKWARD (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) {
4186 if ((x > (xmax - icon_size)) && x < xmax) {
4187 return op_icon;
4188 }
4189 xmax -= icon_size;
4190 }
4191
4192 return NULL;
4193 }
4194
ui_do_but_extra_operator_icon(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4195 static bool ui_do_but_extra_operator_icon(bContext *C,
4196 uiBut *but,
4197 uiHandleButtonData *data,
4198 const wmEvent *event)
4199 {
4200 uiButExtraOpIcon *op_icon = ui_but_extra_operator_icon_mouse_over_get(but, data, event);
4201
4202 if (!op_icon) {
4203 return false;
4204 }
4205
4206 /* Only act on release, avoids some glitches. */
4207 if (event->val != KM_RELEASE) {
4208 /* Still swallow events on the icon. */
4209 return true;
4210 }
4211
4212 ED_region_tag_redraw(data->region);
4213 button_tooltip_timer_reset(C, but);
4214
4215 ui_but_extra_operator_icon_apply(C, but, op_icon);
4216 /* Note: 'but', 'data' may now be freed, don't access. */
4217
4218 return true;
4219 }
4220
ui_do_but_extra_operator_icons_mousemove(uiBut * but,uiHandleButtonData * data,const wmEvent * event)4221 static void ui_do_but_extra_operator_icons_mousemove(uiBut *but,
4222 uiHandleButtonData *data,
4223 const wmEvent *event)
4224 {
4225 uiButExtraOpIcon *old_highlighted = NULL;
4226
4227 /* Unset highlighting of all first. */
4228 LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) {
4229 if (op_icon->highlighted) {
4230 old_highlighted = op_icon;
4231 }
4232 op_icon->highlighted = false;
4233 }
4234
4235 uiButExtraOpIcon *hovered = ui_but_extra_operator_icon_mouse_over_get(but, data, event);
4236
4237 if (hovered) {
4238 hovered->highlighted = true;
4239 }
4240
4241 if (old_highlighted != hovered) {
4242 ED_region_tag_redraw_no_rebuild(data->region);
4243 }
4244 }
4245
4246 #ifdef USE_DRAG_TOGGLE
4247 /* Shared by any button that supports drag-toggle. */
ui_do_but_ANY_drag_toggle(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event,int * r_retval)4248 static bool ui_do_but_ANY_drag_toggle(
4249 bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, int *r_retval)
4250 {
4251 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4252 if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) {
4253 # if 0 /* UNUSED */
4254 data->togdual = event->ctrl;
4255 data->togonly = !event->shift;
4256 # endif
4257 ui_apply_but(C, but->block, but, data, true);
4258 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
4259 data->dragstartx = event->x;
4260 data->dragstarty = event->y;
4261 *r_retval = WM_UI_HANDLER_BREAK;
4262 return true;
4263 }
4264 }
4265 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
4266 /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into
4267 * its own function */
4268 data->applied = false;
4269 *r_retval = ui_do_but_EXIT(C, but, data, event);
4270 return true;
4271 }
4272 return false;
4273 }
4274 #endif /* USE_DRAG_TOGGLE */
4275
ui_do_but_BUT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4276 static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4277 {
4278 #ifdef USE_DRAG_TOGGLE
4279 {
4280 int retval;
4281 if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4282 return retval;
4283 }
4284 }
4285 #endif
4286
4287 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4288 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
4289 button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE);
4290 return WM_UI_HANDLER_BREAK;
4291 }
4292 if (event->type == LEFTMOUSE && event->val == KM_RELEASE && but->block->handle) {
4293 /* regular buttons will be 'UI_SELECT', menu items 'UI_ACTIVE' */
4294 if (!(but->flag & (UI_SELECT | UI_ACTIVE))) {
4295 data->cancel = true;
4296 }
4297 button_activate_state(C, but, BUTTON_STATE_EXIT);
4298 return WM_UI_HANDLER_BREAK;
4299 }
4300 if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4301 button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH);
4302 return WM_UI_HANDLER_BREAK;
4303 }
4304 }
4305 else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
4306 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
4307 if (!(but->flag & UI_SELECT)) {
4308 data->cancel = true;
4309 }
4310 button_activate_state(C, but, BUTTON_STATE_EXIT);
4311 return WM_UI_HANDLER_BREAK;
4312 }
4313 }
4314
4315 return WM_UI_HANDLER_CONTINUE;
4316 }
4317
ui_do_but_HOTKEYEVT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4318 static int ui_do_but_HOTKEYEVT(bContext *C,
4319 uiBut *but,
4320 uiHandleButtonData *data,
4321 const wmEvent *event)
4322 {
4323 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4324 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4325 but->drawstr[0] = 0;
4326 but->modifier_key = 0;
4327 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
4328 return WM_UI_HANDLER_BREAK;
4329 }
4330 }
4331 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
4332 if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
4333 return WM_UI_HANDLER_CONTINUE;
4334 }
4335 if (event->type == EVT_UNKNOWNKEY) {
4336 WM_report(RPT_WARNING, "Unsupported key: Unknown");
4337 return WM_UI_HANDLER_CONTINUE;
4338 }
4339 if (event->type == EVT_CAPSLOCKKEY) {
4340 WM_report(RPT_WARNING, "Unsupported key: CapsLock");
4341 return WM_UI_HANDLER_CONTINUE;
4342 }
4343
4344 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
4345 /* only cancel if click outside the button */
4346 if (ui_but_contains_point_px(but, but->active->region, event->x, event->y) == false) {
4347 /* data->cancel doesn't work, this button opens immediate */
4348 if (but->flag & UI_BUT_IMMEDIATE) {
4349 ui_but_value_set(but, 0);
4350 }
4351 else {
4352 data->cancel = true;
4353 }
4354 button_activate_state(C, but, BUTTON_STATE_EXIT);
4355 return WM_UI_HANDLER_BREAK;
4356 }
4357 }
4358
4359 /* always set */
4360 but->modifier_key = 0;
4361 if (event->shift) {
4362 but->modifier_key |= KM_SHIFT;
4363 }
4364 if (event->alt) {
4365 but->modifier_key |= KM_ALT;
4366 }
4367 if (event->ctrl) {
4368 but->modifier_key |= KM_CTRL;
4369 }
4370 if (event->oskey) {
4371 but->modifier_key |= KM_OSKEY;
4372 }
4373
4374 ui_but_update(but);
4375 ED_region_tag_redraw(data->region);
4376
4377 if (event->val == KM_PRESS) {
4378 if (ISHOTKEY(event->type) && (event->type != EVT_ESCKEY)) {
4379 if (WM_key_event_string(event->type, false)[0]) {
4380 ui_but_value_set(but, event->type);
4381 }
4382 else {
4383 data->cancel = true;
4384 }
4385
4386 button_activate_state(C, but, BUTTON_STATE_EXIT);
4387 return WM_UI_HANDLER_BREAK;
4388 }
4389 if (event->type == EVT_ESCKEY) {
4390 if (event->val == KM_PRESS) {
4391 data->cancel = true;
4392 data->escapecancel = true;
4393 button_activate_state(C, but, BUTTON_STATE_EXIT);
4394 }
4395 }
4396 }
4397 }
4398
4399 return WM_UI_HANDLER_CONTINUE;
4400 }
4401
ui_do_but_KEYEVT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4402 static int ui_do_but_KEYEVT(bContext *C,
4403 uiBut *but,
4404 uiHandleButtonData *data,
4405 const wmEvent *event)
4406 {
4407 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4408 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4409 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
4410 return WM_UI_HANDLER_BREAK;
4411 }
4412 }
4413 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
4414 if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
4415 return WM_UI_HANDLER_CONTINUE;
4416 }
4417
4418 if (event->val == KM_PRESS) {
4419 if (WM_key_event_string(event->type, false)[0]) {
4420 ui_but_value_set(but, event->type);
4421 }
4422 else {
4423 data->cancel = true;
4424 }
4425
4426 button_activate_state(C, but, BUTTON_STATE_EXIT);
4427 }
4428 }
4429
4430 return WM_UI_HANDLER_CONTINUE;
4431 }
4432
ui_do_but_TAB(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4433 static int ui_do_but_TAB(
4434 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4435 {
4436 const bool is_property = (but->rnaprop != NULL);
4437
4438 #ifdef USE_DRAG_TOGGLE
4439 if (is_property) {
4440 int retval;
4441 if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4442 return retval;
4443 }
4444 }
4445 #endif
4446
4447 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4448 const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0;
4449
4450 if (is_property && ELEM(rna_type, PROP_POINTER, PROP_STRING) && (but->custom_data != NULL) &&
4451 (event->type == LEFTMOUSE) && ((event->val == KM_DBL_CLICK) || event->ctrl)) {
4452 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
4453 return WM_UI_HANDLER_BREAK;
4454 }
4455 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY)) {
4456 const int event_val = (is_property) ? KM_PRESS : KM_CLICK;
4457 if (event->val == event_val) {
4458 button_activate_state(C, but, BUTTON_STATE_EXIT);
4459 return WM_UI_HANDLER_BREAK;
4460 }
4461 }
4462 }
4463 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
4464 ui_do_but_textedit(C, block, but, data, event);
4465 return WM_UI_HANDLER_BREAK;
4466 }
4467 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
4468 ui_do_but_textedit_select(C, block, but, data, event);
4469 return WM_UI_HANDLER_BREAK;
4470 }
4471
4472 return WM_UI_HANDLER_CONTINUE;
4473 }
4474
ui_do_but_TEX(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4475 static int ui_do_but_TEX(
4476 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4477 {
4478 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4479 if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) &&
4480 event->val == KM_PRESS) {
4481 if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && (!UI_but_is_utf8(but))) {
4482 /* pass - allow filesel, enter to execute */
4483 }
4484 else if (but->emboss == UI_EMBOSS_NONE && !event->ctrl) {
4485 /* pass */
4486 }
4487 else {
4488 if (!ui_but_extra_operator_icon_mouse_over_get(but, data, event)) {
4489 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
4490 }
4491 return WM_UI_HANDLER_BREAK;
4492 }
4493 }
4494 }
4495 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
4496 ui_do_but_textedit(C, block, but, data, event);
4497 return WM_UI_HANDLER_BREAK;
4498 }
4499 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
4500 ui_do_but_textedit_select(C, block, but, data, event);
4501 return WM_UI_HANDLER_BREAK;
4502 }
4503
4504 return WM_UI_HANDLER_CONTINUE;
4505 }
4506
ui_do_but_SEARCH_UNLINK(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4507 static int ui_do_but_SEARCH_UNLINK(
4508 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4509 {
4510 /* unlink icon is on right */
4511 if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY)) {
4512 /* doing this on KM_PRESS calls eyedropper after clicking unlink icon */
4513 if ((event->val == KM_RELEASE) && ui_do_but_extra_operator_icon(C, but, data, event)) {
4514 return WM_UI_HANDLER_BREAK;
4515 }
4516 }
4517 return ui_do_but_TEX(C, block, but, data, event);
4518 }
4519
ui_do_but_TOG(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4520 static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4521 {
4522 #ifdef USE_DRAG_TOGGLE
4523 {
4524 int retval;
4525 if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4526 return retval;
4527 }
4528 }
4529 #endif
4530
4531 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4532 bool do_activate = false;
4533 if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY)) {
4534 if (event->val == KM_PRESS) {
4535 do_activate = true;
4536 }
4537 }
4538 else if (event->type == LEFTMOUSE) {
4539 if (ui_block_is_menu(but->block)) {
4540 /* Behave like other menu items. */
4541 do_activate = (event->val == KM_RELEASE);
4542 }
4543 else {
4544 /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */
4545 do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK);
4546 }
4547 }
4548
4549 if (do_activate) {
4550 #if 0 /* UNUSED */
4551 data->togdual = event->ctrl;
4552 data->togonly = !event->shift;
4553 #endif
4554 button_activate_state(C, but, BUTTON_STATE_EXIT);
4555 return WM_UI_HANDLER_BREAK;
4556 }
4557 if (ELEM(event->type, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
4558 /* Support alt+wheel on expanded enum rows */
4559 if (but->type == UI_BTYPE_ROW) {
4560 const int direction = (event->type == WHEELDOWNMOUSE) ? -1 : 1;
4561 uiBut *but_select = ui_but_find_select_in_enum(but, direction);
4562 if (but_select) {
4563 uiBut *but_other = (direction == -1) ? but_select->next : but_select->prev;
4564 if (but_other && ui_but_find_select_in_enum__cmp(but, but_other)) {
4565 ARegion *region = data->region;
4566
4567 data->cancel = true;
4568 button_activate_exit(C, but, data, false, false);
4569
4570 /* Activate the text button. */
4571 button_activate_init(C, region, but_other, BUTTON_ACTIVATE_OVER);
4572 data = but_other->active;
4573 if (data) {
4574 ui_apply_but(C, but->block, but_other, but_other->active, true);
4575 button_activate_exit(C, but_other, data, false, false);
4576
4577 /* restore active button */
4578 button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
4579 }
4580 else {
4581 /* shouldn't happen */
4582 BLI_assert(0);
4583 }
4584 }
4585 }
4586 return WM_UI_HANDLER_BREAK;
4587 }
4588 }
4589 }
4590 return WM_UI_HANDLER_CONTINUE;
4591 }
4592
ui_do_but_EXIT(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4593 static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4594 {
4595 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4596
4597 /* first handle click on icondrag type button */
4598 if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && but->dragpoin) {
4599 if (ui_but_contains_point_px_icon(but, data->region, event)) {
4600
4601 /* tell the button to wait and keep checking further events to
4602 * see if it should start dragging */
4603 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
4604 data->dragstartx = event->x;
4605 data->dragstarty = event->y;
4606 return WM_UI_HANDLER_CONTINUE;
4607 }
4608 }
4609 #ifdef USE_DRAG_TOGGLE
4610 if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_is_drag_toggle(but)) {
4611 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
4612 data->dragstartx = event->x;
4613 data->dragstarty = event->y;
4614 return WM_UI_HANDLER_CONTINUE;
4615 }
4616 #endif
4617
4618 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4619 int ret = WM_UI_HANDLER_BREAK;
4620 /* XXX (a bit ugly) Special case handling for filebrowser drag button */
4621 if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) {
4622 ret = WM_UI_HANDLER_CONTINUE;
4623 }
4624 button_activate_state(C, but, BUTTON_STATE_EXIT);
4625 return ret;
4626 }
4627 }
4628 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
4629
4630 /* this function also ends state */
4631 if (ui_but_drag_init(C, but, data, event)) {
4632 return WM_UI_HANDLER_BREAK;
4633 }
4634
4635 /* If the mouse has been pressed and released, getting to
4636 * this point without triggering a drag, then clear the
4637 * drag state for this button and continue to pass on the event */
4638 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
4639 button_activate_state(C, but, BUTTON_STATE_EXIT);
4640 return WM_UI_HANDLER_CONTINUE;
4641 }
4642
4643 /* while waiting for a drag to be triggered, always block
4644 * other events from getting handled */
4645 return WM_UI_HANDLER_BREAK;
4646 }
4647
4648 return WM_UI_HANDLER_CONTINUE;
4649 }
4650
4651 /* var names match ui_numedit_but_NUM */
ui_numedit_apply_snapf(uiBut * but,float tempf,float softmin,float softmax,const enum eSnapType snap)4652 static float ui_numedit_apply_snapf(
4653 uiBut *but, float tempf, float softmin, float softmax, const enum eSnapType snap)
4654 {
4655 if (tempf == softmin || tempf == softmax || snap == SNAP_OFF) {
4656 /* pass */
4657 }
4658 else {
4659 float softrange = softmax - softmin;
4660 float fac = 1.0f;
4661
4662 if (ui_but_is_unit(but)) {
4663 UnitSettings *unit = but->block->unit;
4664 const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but));
4665
4666 if (BKE_unit_is_valid(unit->system, unit_type)) {
4667 fac = (float)BKE_unit_base_scalar(unit->system, unit_type);
4668 if (ELEM(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) {
4669 fac /= unit->scale_length;
4670 }
4671 }
4672 }
4673
4674 if (fac != 1.0f) {
4675 /* snap in unit-space */
4676 tempf /= fac;
4677 /* softmin /= fac; */ /* UNUSED */
4678 /* softmax /= fac; */ /* UNUSED */
4679 softrange /= fac;
4680 }
4681
4682 /* workaround, too high snapping values */
4683 /* snapping by 10's for float buttons is quite annoying (location, scale...),
4684 * but allow for rotations */
4685 if (softrange >= 21.0f) {
4686 UnitSettings *unit = but->block->unit;
4687 const int unit_type = UI_but_unit_type_get(but);
4688 if ((unit_type == PROP_UNIT_ROTATION) && (unit->system_rotation != USER_UNIT_ROT_RADIANS)) {
4689 /* pass (degrees)*/
4690 }
4691 else {
4692 softrange = 20.0f;
4693 }
4694 }
4695
4696 if (snap == SNAP_ON) {
4697 if (softrange < 2.10f) {
4698 tempf = roundf(tempf * 10.0f) * 0.1f;
4699 }
4700 else if (softrange < 21.0f) {
4701 tempf = roundf(tempf);
4702 }
4703 else {
4704 tempf = roundf(tempf * 0.1f) * 10.0f;
4705 }
4706 }
4707 else if (snap == SNAP_ON_SMALL) {
4708 if (softrange < 2.10f) {
4709 tempf = roundf(tempf * 100.0f) * 0.01f;
4710 }
4711 else if (softrange < 21.0f) {
4712 tempf = roundf(tempf * 10.0f) * 0.1f;
4713 }
4714 else {
4715 tempf = roundf(tempf);
4716 }
4717 }
4718 else {
4719 BLI_assert(0);
4720 }
4721
4722 if (fac != 1.0f) {
4723 tempf *= fac;
4724 }
4725 }
4726
4727 return tempf;
4728 }
4729
ui_numedit_apply_snap(int temp,float softmin,float softmax,const enum eSnapType snap)4730 static float ui_numedit_apply_snap(int temp,
4731 float softmin,
4732 float softmax,
4733 const enum eSnapType snap)
4734 {
4735 if (temp == softmin || temp == softmax) {
4736 return temp;
4737 }
4738
4739 switch (snap) {
4740 case SNAP_OFF:
4741 break;
4742 case SNAP_ON:
4743 temp = 10 * (temp / 10);
4744 break;
4745 case SNAP_ON_SMALL:
4746 temp = 100 * (temp / 100);
4747 break;
4748 }
4749
4750 return temp;
4751 }
4752
ui_numedit_but_NUM(uiButNumber * number_but,uiHandleButtonData * data,int mx,const bool is_motion,const enum eSnapType snap,float fac)4753 static bool ui_numedit_but_NUM(uiButNumber *number_but,
4754 uiHandleButtonData *data,
4755 int mx,
4756 const bool is_motion,
4757 const enum eSnapType snap,
4758 float fac)
4759 {
4760 uiBut *but = &number_but->but;
4761 float deler, tempf;
4762 int lvalue, temp;
4763 bool changed = false;
4764 const bool is_float = ui_but_is_float(but);
4765
4766 /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
4767 if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) {
4768 return changed;
4769 }
4770
4771 if (ui_but_is_cursor_warp(but)) {
4772 const float softmin = but->softmin;
4773 const float softmax = but->softmax;
4774 const float softrange = softmax - softmin;
4775
4776 /* Mouse location isn't screen clamped to the screen so use a linear mapping
4777 * 2px == 1-int, or 1px == 1-ClickStep */
4778 if (is_float) {
4779 fac *= 0.01f * number_but->step_size;
4780 tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac);
4781 tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap);
4782
4783 #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
4784 if (tempf < softmin) {
4785 data->dragstartx -= (softmin - tempf) / fac;
4786 tempf = softmin;
4787 }
4788 else if (tempf > softmax) {
4789 data->dragstartx += (tempf - softmax) / fac;
4790 tempf = softmax;
4791 }
4792 #else
4793 CLAMP(tempf, softmin, softmax);
4794 #endif
4795
4796 if (tempf != (float)data->value) {
4797 data->dragchange = true;
4798 data->value = tempf;
4799 changed = true;
4800 }
4801 }
4802 else {
4803 if (softrange > 256) {
4804 fac = 1.0;
4805 } /* 1px == 1 */
4806 else if (softrange > 32) {
4807 fac = 1.0 / 2.0;
4808 } /* 2px == 1 */
4809 else {
4810 fac = 1.0 / 16.0;
4811 } /* 16px == 1? */
4812
4813 temp = data->startvalue + (((double)mx - data->dragstartx) * (double)fac);
4814 temp = ui_numedit_apply_snap(temp, softmin, softmax, snap);
4815
4816 #if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
4817 if (temp < softmin) {
4818 data->dragstartx -= (softmin - temp) / fac;
4819 temp = softmin;
4820 }
4821 else if (temp > softmax) {
4822 data->dragstartx += (temp - softmax) / fac;
4823 temp = softmax;
4824 }
4825 #else
4826 CLAMP(temp, softmin, softmax);
4827 #endif
4828
4829 if (temp != data->value) {
4830 data->dragchange = true;
4831 data->value = temp;
4832 changed = true;
4833 }
4834 }
4835
4836 data->draglastx = mx;
4837 }
4838 else {
4839 /* Use 'but->softmin', 'but->softmax' when clamping values. */
4840 const float softmin = data->drag_map_soft_min;
4841 const float softmax = data->drag_map_soft_max;
4842 const float softrange = softmax - softmin;
4843
4844 float non_linear_range_limit;
4845 float non_linear_pixel_map;
4846 float non_linear_scale;
4847
4848 /* Use a non-linear mapping of the mouse drag especially for large floats
4849 * (normal behavior) */
4850 deler = 500;
4851 if (is_float) {
4852 /* not needed for smaller float buttons */
4853 non_linear_range_limit = 11.0f;
4854 non_linear_pixel_map = 500.0f;
4855 }
4856 else {
4857 /* only scale large int buttons */
4858 non_linear_range_limit = 129.0f;
4859 /* larger for ints, we dont need to fine tune them */
4860 non_linear_pixel_map = 250.0f;
4861
4862 /* prevent large ranges from getting too out of control */
4863 if (softrange > 600) {
4864 deler = powf(softrange, 0.75f);
4865 }
4866 else if (softrange < 25) {
4867 deler = 50.0;
4868 }
4869 else if (softrange < 100) {
4870 deler = 100.0;
4871 }
4872 }
4873 deler /= fac;
4874
4875 if (softrange > non_linear_range_limit) {
4876 non_linear_scale = (float)abs(mx - data->dragstartx) / non_linear_pixel_map;
4877 }
4878 else {
4879 non_linear_scale = 1.0f;
4880 }
4881
4882 if (is_float == false) {
4883 /* at minimum, moving cursor 2 pixels should change an int button. */
4884 CLAMP_MIN(non_linear_scale, 0.5f * UI_DPI_FAC);
4885 }
4886
4887 data->dragf += (((float)(mx - data->draglastx)) / deler) * non_linear_scale;
4888
4889 if (but->softmin == softmin) {
4890 CLAMP_MIN(data->dragf, 0.0f);
4891 }
4892 if (but->softmax == softmax) {
4893 CLAMP_MAX(data->dragf, 1.0f);
4894 }
4895
4896 data->draglastx = mx;
4897 tempf = (softmin + data->dragf * softrange);
4898
4899 if (!is_float) {
4900 temp = round_fl_to_int(tempf);
4901
4902 temp = ui_numedit_apply_snap(temp, but->softmin, but->softmax, snap);
4903
4904 CLAMP(temp, but->softmin, but->softmax);
4905 lvalue = (int)data->value;
4906
4907 if (temp != lvalue) {
4908 data->dragchange = true;
4909 data->value = (double)temp;
4910 changed = true;
4911 }
4912 }
4913 else {
4914 temp = 0;
4915 tempf = ui_numedit_apply_snapf(but, tempf, but->softmin, but->softmax, snap);
4916
4917 CLAMP(tempf, but->softmin, but->softmax);
4918
4919 if (tempf != (float)data->value) {
4920 data->dragchange = true;
4921 data->value = tempf;
4922 changed = true;
4923 }
4924 }
4925 }
4926
4927 return changed;
4928 }
4929
ui_numedit_set_active(uiBut * but)4930 static void ui_numedit_set_active(uiBut *but)
4931 {
4932 const int oldflag = but->drawflag;
4933 but->drawflag &= ~(UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT);
4934
4935 uiHandleButtonData *data = but->active;
4936 if (!data) {
4937 return;
4938 }
4939
4940 /* Ignore once we start dragging. */
4941 if (data->dragchange == false) {
4942 const float handle_width = min_ff(BLI_rctf_size_x(&but->rect) / 3,
4943 BLI_rctf_size_y(&but->rect) * 0.7f);
4944 /* we can click on the side arrows to increment/decrement,
4945 * or click inside to edit the value directly */
4946 int mx = data->window->eventstate->x;
4947 int my = data->window->eventstate->y;
4948 ui_window_to_block(data->region, but->block, &mx, &my);
4949
4950 if (mx < (but->rect.xmin + handle_width)) {
4951 but->drawflag |= UI_BUT_ACTIVE_LEFT;
4952 }
4953 else if (mx > (but->rect.xmax - handle_width)) {
4954 but->drawflag |= UI_BUT_ACTIVE_RIGHT;
4955 }
4956 }
4957
4958 /* Don't change the cursor once pressed. */
4959 if ((but->flag & UI_SELECT) == 0) {
4960 if ((but->drawflag & UI_BUT_ACTIVE_LEFT) || (but->drawflag & UI_BUT_ACTIVE_RIGHT)) {
4961 if (data->changed_cursor) {
4962 WM_cursor_modal_restore(data->window);
4963 data->changed_cursor = false;
4964 }
4965 }
4966 else {
4967 if (data->changed_cursor == false) {
4968 WM_cursor_modal_set(data->window, WM_CURSOR_X_MOVE);
4969 data->changed_cursor = true;
4970 }
4971 }
4972 }
4973
4974 if (but->drawflag != oldflag) {
4975 ED_region_tag_redraw(data->region);
4976 }
4977 }
4978
ui_do_but_NUM(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)4979 static int ui_do_but_NUM(
4980 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4981 {
4982 uiButNumber *number_but = (uiButNumber *)but;
4983 int click = 0;
4984 int retval = WM_UI_HANDLER_CONTINUE;
4985
4986 /* mouse location scaled to fit the UI */
4987 int mx = event->x;
4988 int my = event->y;
4989 /* mouse location kept at screen pixel coords */
4990 const int screen_mx = event->x;
4991
4992 BLI_assert(but->type == UI_BTYPE_NUM);
4993
4994 ui_window_to_block(data->region, block, &mx, &my);
4995 ui_numedit_set_active(but);
4996
4997 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4998 int type = event->type, val = event->val;
4999
5000 if (type == MOUSEPAN) {
5001 ui_pan_to_scroll(event, &type, &val);
5002 }
5003
5004 /* XXX hardcoded keymap check.... */
5005 if (type == MOUSEPAN && event->ctrl) {
5006 /* allow accumulating values, otherwise scrolling gets preference */
5007 retval = WM_UI_HANDLER_BREAK;
5008 }
5009 else if (type == WHEELDOWNMOUSE && event->ctrl) {
5010 mx = but->rect.xmin;
5011 but->drawflag &= ~UI_BUT_ACTIVE_RIGHT;
5012 but->drawflag |= UI_BUT_ACTIVE_LEFT;
5013 click = 1;
5014 }
5015 else if (type == WHEELUPMOUSE && event->ctrl) {
5016 mx = but->rect.xmax;
5017 but->drawflag &= ~UI_BUT_ACTIVE_LEFT;
5018 but->drawflag |= UI_BUT_ACTIVE_RIGHT;
5019 click = 1;
5020 }
5021 else if (event->val == KM_PRESS) {
5022 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) {
5023 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5024 retval = WM_UI_HANDLER_BREAK;
5025 }
5026 else if (event->type == LEFTMOUSE) {
5027 data->dragstartx = data->draglastx = ui_but_is_cursor_warp(but) ? screen_mx : mx;
5028 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5029 retval = WM_UI_HANDLER_BREAK;
5030 }
5031 else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5032 click = 1;
5033 }
5034 else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) {
5035 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5036 data->value = -data->value;
5037 button_activate_state(C, but, BUTTON_STATE_EXIT);
5038 retval = WM_UI_HANDLER_BREAK;
5039 }
5040
5041 #ifdef USE_DRAG_MULTINUM
5042 copy_v2_v2_int(data->multi_data.drag_start, &event->x);
5043 #endif
5044 }
5045 }
5046 else if (data->state == BUTTON_STATE_NUM_EDITING) {
5047 if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
5048 if (event->val == KM_PRESS) {
5049 data->cancel = true;
5050 data->escapecancel = true;
5051 button_activate_state(C, but, BUTTON_STATE_EXIT);
5052 }
5053 }
5054 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5055 if (data->dragchange) {
5056 #ifdef USE_DRAG_MULTINUM
5057 /* if we started multibutton but didn't drag, then edit */
5058 if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
5059 click = 1;
5060 }
5061 else
5062 #endif
5063 {
5064 button_activate_state(C, but, BUTTON_STATE_EXIT);
5065 }
5066 }
5067 else {
5068 click = 1;
5069 }
5070 }
5071 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5072 const bool is_motion = (event->type == MOUSEMOVE);
5073 const enum eSnapType snap = ui_event_to_snap(event);
5074 float fac;
5075
5076 #ifdef USE_DRAG_MULTINUM
5077 data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
5078 data->multi_data.drag_dir[1] += abs(data->draglasty - my);
5079 #endif
5080
5081 fac = 1.0f;
5082 if (event->shift) {
5083 fac /= 10.0f;
5084 }
5085
5086 if (ui_numedit_but_NUM(number_but,
5087 data,
5088 (ui_but_is_cursor_warp(but) ? screen_mx : mx),
5089 is_motion,
5090 snap,
5091 fac)) {
5092 ui_numedit_apply(C, block, but, data);
5093 }
5094 #ifdef USE_DRAG_MULTINUM
5095 else if (data->multi_data.has_mbuts) {
5096 if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
5097 ui_multibut_states_apply(C, data, block);
5098 }
5099 }
5100 #endif
5101 }
5102 retval = WM_UI_HANDLER_BREAK;
5103 }
5104 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
5105 ui_do_but_textedit(C, block, but, data, event);
5106 retval = WM_UI_HANDLER_BREAK;
5107 }
5108 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
5109 ui_do_but_textedit_select(C, block, but, data, event);
5110 retval = WM_UI_HANDLER_BREAK;
5111 }
5112
5113 if (click) {
5114 /* we can click on the side arrows to increment/decrement,
5115 * or click inside to edit the value directly */
5116
5117 if (!ui_but_is_float(but)) {
5118 /* Integer Value. */
5119 if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) {
5120 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5121
5122 const int value_step = (int)number_but->step_size;
5123 BLI_assert(value_step > 0);
5124 const int softmin = round_fl_to_int_clamp(but->softmin);
5125 const int softmax = round_fl_to_int_clamp(but->softmax);
5126 const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ?
5127 (double)max_ii(softmin, (int)data->value - value_step) :
5128 (double)min_ii(softmax, (int)data->value + value_step);
5129 if (value_test != data->value) {
5130 data->value = (double)value_test;
5131 }
5132 else {
5133 data->cancel = true;
5134 }
5135 button_activate_state(C, but, BUTTON_STATE_EXIT);
5136 }
5137 else {
5138 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5139 }
5140 }
5141 else {
5142 /* Float Value. */
5143 if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) {
5144 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5145
5146 const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE;
5147 BLI_assert(value_step > 0.0f);
5148 const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ?
5149 (double)max_ff(but->softmin,
5150 (float)(data->value - value_step)) :
5151 (double)min_ff(but->softmax,
5152 (float)(data->value + value_step));
5153 if (value_test != data->value) {
5154 data->value = value_test;
5155 }
5156 else {
5157 data->cancel = true;
5158 }
5159 button_activate_state(C, but, BUTTON_STATE_EXIT);
5160 }
5161 else {
5162 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5163 }
5164 }
5165
5166 retval = WM_UI_HANDLER_BREAK;
5167 }
5168
5169 data->draglastx = mx;
5170 data->draglasty = my;
5171
5172 return retval;
5173 }
5174
ui_numedit_but_SLI(uiBut * but,uiHandleButtonData * data,int mx,const bool is_horizontal,const bool is_motion,const bool snap,const bool shift)5175 static bool ui_numedit_but_SLI(uiBut *but,
5176 uiHandleButtonData *data,
5177 int mx,
5178 const bool is_horizontal,
5179 const bool is_motion,
5180 const bool snap,
5181 const bool shift)
5182 {
5183 float cursor_x_range, f, tempf, softmin, softmax, softrange;
5184 int temp, lvalue;
5185 bool changed = false;
5186 float mx_fl, my_fl;
5187
5188 /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
5189 if ((but->type != UI_BTYPE_SCROLL) && (is_motion || data->draglock) &&
5190 (ui_but_dragedit_update_mval(data, mx) == false)) {
5191 return changed;
5192 }
5193
5194 softmin = but->softmin;
5195 softmax = but->softmax;
5196 softrange = softmax - softmin;
5197
5198 /* yes, 'mx' as both x/y is intentional */
5199 ui_mouse_scale_warp(data, mx, mx, &mx_fl, &my_fl, shift);
5200
5201 if (but->type == UI_BTYPE_NUM_SLIDER) {
5202 cursor_x_range = BLI_rctf_size_x(&but->rect);
5203 }
5204 else if (but->type == UI_BTYPE_SCROLL) {
5205 const float size = (is_horizontal) ? BLI_rctf_size_x(&but->rect) :
5206 -BLI_rctf_size_y(&but->rect);
5207 cursor_x_range = size * (but->softmax - but->softmin) /
5208 (but->softmax - but->softmin + but->a1);
5209 }
5210 else {
5211 const float offs = (BLI_rctf_size_y(&but->rect) / 2.0f);
5212 cursor_x_range = (BLI_rctf_size_x(&but->rect) - offs);
5213 }
5214
5215 f = (mx_fl - data->dragstartx) / cursor_x_range + data->dragfstart;
5216 CLAMP(f, 0.0f, 1.0f);
5217
5218 /* deal with mouse correction */
5219 #ifdef USE_CONT_MOUSE_CORRECT
5220 if (ui_but_is_cursor_warp(but)) {
5221 /* OK but can go outside bounds */
5222 if (is_horizontal) {
5223 data->ungrab_mval[0] = but->rect.xmin + (f * cursor_x_range);
5224 data->ungrab_mval[1] = BLI_rctf_cent_y(&but->rect);
5225 }
5226 else {
5227 data->ungrab_mval[1] = but->rect.ymin + (f * cursor_x_range);
5228 data->ungrab_mval[0] = BLI_rctf_cent_x(&but->rect);
5229 }
5230 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
5231 }
5232 #endif
5233 /* done correcting mouse */
5234
5235 tempf = softmin + f * softrange;
5236 temp = round_fl_to_int(tempf);
5237
5238 if (snap) {
5239 if (tempf == softmin || tempf == softmax) {
5240 /* pass */
5241 }
5242 else if (ui_but_is_float(but)) {
5243
5244 if (shift) {
5245 if (tempf == softmin || tempf == softmax) {
5246 }
5247 else if (softrange < 2.10f) {
5248 tempf = roundf(tempf * 100.0f) * 0.01f;
5249 }
5250 else if (softrange < 21.0f) {
5251 tempf = roundf(tempf * 10.0f) * 0.1f;
5252 }
5253 else {
5254 tempf = roundf(tempf);
5255 }
5256 }
5257 else {
5258 if (softrange < 2.10f) {
5259 tempf = roundf(tempf * 10.0f) * 0.1f;
5260 }
5261 else if (softrange < 21.0f) {
5262 tempf = roundf(tempf);
5263 }
5264 else {
5265 tempf = roundf(tempf * 0.1f) * 10.0f;
5266 }
5267 }
5268 }
5269 else {
5270 temp = 10 * (temp / 10);
5271 tempf = temp;
5272 }
5273 }
5274
5275 if (!ui_but_is_float(but)) {
5276 lvalue = round(data->value);
5277
5278 CLAMP(temp, softmin, softmax);
5279
5280 if (temp != lvalue) {
5281 data->value = temp;
5282 data->dragchange = true;
5283 changed = true;
5284 }
5285 }
5286 else {
5287 CLAMP(tempf, softmin, softmax);
5288
5289 if (tempf != (float)data->value) {
5290 data->value = tempf;
5291 data->dragchange = true;
5292 changed = true;
5293 }
5294 }
5295
5296 return changed;
5297 }
5298
ui_do_but_SLI(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5299 static int ui_do_but_SLI(
5300 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5301 {
5302 int click = 0;
5303 int retval = WM_UI_HANDLER_CONTINUE;
5304
5305 int mx = event->x;
5306 int my = event->y;
5307 ui_window_to_block(data->region, block, &mx, &my);
5308
5309 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5310 int type = event->type, val = event->val;
5311
5312 if (type == MOUSEPAN) {
5313 ui_pan_to_scroll(event, &type, &val);
5314 }
5315
5316 /* XXX hardcoded keymap check.... */
5317 if (type == MOUSEPAN && event->ctrl) {
5318 /* allow accumulating values, otherwise scrolling gets preference */
5319 retval = WM_UI_HANDLER_BREAK;
5320 }
5321 else if (type == WHEELDOWNMOUSE && event->ctrl) {
5322 mx = but->rect.xmin;
5323 click = 2;
5324 }
5325 else if (type == WHEELUPMOUSE && event->ctrl) {
5326 mx = but->rect.xmax;
5327 click = 2;
5328 }
5329 else if (event->val == KM_PRESS) {
5330 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) {
5331 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5332 retval = WM_UI_HANDLER_BREAK;
5333 }
5334 #ifndef USE_ALLSELECT
5335 /* alt-click on sides to get "arrows" like in UI_BTYPE_NUM buttons,
5336 * and match wheel usage above */
5337 else if (event->type == LEFTMOUSE && event->alt) {
5338 int halfpos = BLI_rctf_cent_x(&but->rect);
5339 click = 2;
5340 if (mx < halfpos) {
5341 mx = but->rect.xmin;
5342 }
5343 else {
5344 mx = but->rect.xmax;
5345 }
5346 }
5347 #endif
5348 else if (event->type == LEFTMOUSE) {
5349 data->dragstartx = mx;
5350 data->draglastx = mx;
5351 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5352 retval = WM_UI_HANDLER_BREAK;
5353 }
5354 else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5355 click = 1;
5356 }
5357 else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) {
5358 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5359 data->value = -data->value;
5360 button_activate_state(C, but, BUTTON_STATE_EXIT);
5361 retval = WM_UI_HANDLER_BREAK;
5362 }
5363 }
5364 #ifdef USE_DRAG_MULTINUM
5365 copy_v2_v2_int(data->multi_data.drag_start, &event->x);
5366 #endif
5367 }
5368 else if (data->state == BUTTON_STATE_NUM_EDITING) {
5369 if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
5370 if (event->val == KM_PRESS) {
5371 data->cancel = true;
5372 data->escapecancel = true;
5373 button_activate_state(C, but, BUTTON_STATE_EXIT);
5374 }
5375 }
5376 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5377 if (data->dragchange) {
5378 #ifdef USE_DRAG_MULTINUM
5379 /* if we started multibutton but didn't drag, then edit */
5380 if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
5381 click = 1;
5382 }
5383 else
5384 #endif
5385 {
5386 button_activate_state(C, but, BUTTON_STATE_EXIT);
5387 }
5388 }
5389 else {
5390 #ifdef USE_CONT_MOUSE_CORRECT
5391 /* reset! */
5392 copy_v2_fl(data->ungrab_mval, FLT_MAX);
5393 #endif
5394 click = 1;
5395 }
5396 }
5397 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5398 const bool is_motion = (event->type == MOUSEMOVE);
5399 #ifdef USE_DRAG_MULTINUM
5400 data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
5401 data->multi_data.drag_dir[1] += abs(data->draglasty - my);
5402 #endif
5403 if (ui_numedit_but_SLI(
5404 but, data, mx, true, is_motion, event->ctrl != 0, event->shift != 0)) {
5405 ui_numedit_apply(C, block, but, data);
5406 }
5407
5408 #ifdef USE_DRAG_MULTINUM
5409 else if (data->multi_data.has_mbuts) {
5410 if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) {
5411 ui_multibut_states_apply(C, data, block);
5412 }
5413 }
5414 #endif
5415 }
5416 retval = WM_UI_HANDLER_BREAK;
5417 }
5418 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
5419 ui_do_but_textedit(C, block, but, data, event);
5420 retval = WM_UI_HANDLER_BREAK;
5421 }
5422 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
5423 ui_do_but_textedit_select(C, block, but, data, event);
5424 retval = WM_UI_HANDLER_BREAK;
5425 }
5426
5427 if (click) {
5428 if (click == 2) {
5429 /* nudge slider to the left or right */
5430 float f, tempf, softmin, softmax, softrange;
5431 int temp;
5432
5433 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5434
5435 softmin = but->softmin;
5436 softmax = but->softmax;
5437 softrange = softmax - softmin;
5438
5439 tempf = data->value;
5440 temp = (int)data->value;
5441
5442 #if 0
5443 if (but->type == SLI) {
5444 /* same as below */
5445 f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect));
5446 }
5447 else
5448 #endif
5449 {
5450 f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect));
5451 }
5452
5453 f = softmin + f * softrange;
5454
5455 if (!ui_but_is_float(but)) {
5456 if (f < temp) {
5457 temp--;
5458 }
5459 else {
5460 temp++;
5461 }
5462
5463 if (temp >= softmin && temp <= softmax) {
5464 data->value = temp;
5465 }
5466 else {
5467 data->cancel = true;
5468 }
5469 }
5470 else {
5471 if (f < tempf) {
5472 tempf -= 0.01f;
5473 }
5474 else {
5475 tempf += 0.01f;
5476 }
5477
5478 if (tempf >= softmin && tempf <= softmax) {
5479 data->value = tempf;
5480 }
5481 else {
5482 data->cancel = true;
5483 }
5484 }
5485
5486 button_activate_state(C, but, BUTTON_STATE_EXIT);
5487 retval = WM_UI_HANDLER_BREAK;
5488 }
5489 else {
5490 /* edit the value directly */
5491 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
5492 retval = WM_UI_HANDLER_BREAK;
5493 }
5494 }
5495
5496 data->draglastx = mx;
5497 data->draglasty = my;
5498
5499 return retval;
5500 }
5501
ui_do_but_SCROLL(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5502 static int ui_do_but_SCROLL(
5503 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5504 {
5505 int retval = WM_UI_HANDLER_CONTINUE;
5506 const bool horizontal = (BLI_rctf_size_x(&but->rect) > BLI_rctf_size_y(&but->rect));
5507
5508 int mx = event->x;
5509 int my = event->y;
5510 ui_window_to_block(data->region, block, &mx, &my);
5511
5512 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5513 if (event->val == KM_PRESS) {
5514 if (event->type == LEFTMOUSE) {
5515 if (horizontal) {
5516 data->dragstartx = mx;
5517 data->draglastx = mx;
5518 }
5519 else {
5520 data->dragstartx = my;
5521 data->draglastx = my;
5522 }
5523 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5524 retval = WM_UI_HANDLER_BREAK;
5525 }
5526 }
5527 }
5528 else if (data->state == BUTTON_STATE_NUM_EDITING) {
5529 if (event->type == EVT_ESCKEY) {
5530 if (event->val == KM_PRESS) {
5531 data->cancel = true;
5532 data->escapecancel = true;
5533 button_activate_state(C, but, BUTTON_STATE_EXIT);
5534 }
5535 }
5536 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5537 button_activate_state(C, but, BUTTON_STATE_EXIT);
5538 }
5539 else if (event->type == MOUSEMOVE) {
5540 const bool is_motion = (event->type == MOUSEMOVE);
5541 if (ui_numedit_but_SLI(
5542 but, data, (horizontal) ? mx : my, horizontal, is_motion, false, false)) {
5543 ui_numedit_apply(C, block, but, data);
5544 }
5545 }
5546
5547 retval = WM_UI_HANDLER_BREAK;
5548 }
5549
5550 return retval;
5551 }
5552
ui_do_but_GRIP(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5553 static int ui_do_but_GRIP(
5554 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5555 {
5556 int retval = WM_UI_HANDLER_CONTINUE;
5557 const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
5558
5559 /* Note: Having to store org point in window space and recompute it to block "space" each time
5560 * is not ideal, but this is a way to hack around behavior of ui_window_to_block(), which
5561 * returns different results when the block is inside a panel or not...
5562 * See T37739.
5563 */
5564
5565 int mx = event->x;
5566 int my = event->y;
5567 ui_window_to_block(data->region, block, &mx, &my);
5568
5569 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5570 if (event->val == KM_PRESS) {
5571 if (event->type == LEFTMOUSE) {
5572 data->dragstartx = event->x;
5573 data->dragstarty = event->y;
5574 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5575 retval = WM_UI_HANDLER_BREAK;
5576 }
5577 }
5578 }
5579 else if (data->state == BUTTON_STATE_NUM_EDITING) {
5580 if (event->type == EVT_ESCKEY) {
5581 if (event->val == KM_PRESS) {
5582 data->cancel = true;
5583 data->escapecancel = true;
5584 button_activate_state(C, but, BUTTON_STATE_EXIT);
5585 }
5586 }
5587 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5588 button_activate_state(C, but, BUTTON_STATE_EXIT);
5589 }
5590 else if (event->type == MOUSEMOVE) {
5591 int dragstartx = data->dragstartx;
5592 int dragstarty = data->dragstarty;
5593 ui_window_to_block(data->region, block, &dragstartx, &dragstarty);
5594 data->value = data->origvalue + (horizontal ? mx - dragstartx : dragstarty - my);
5595 ui_numedit_apply(C, block, but, data);
5596 }
5597
5598 retval = WM_UI_HANDLER_BREAK;
5599 }
5600
5601 return retval;
5602 }
5603
ui_do_but_LISTROW(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5604 static int ui_do_but_LISTROW(bContext *C,
5605 uiBut *but,
5606 uiHandleButtonData *data,
5607 const wmEvent *event)
5608 {
5609 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5610 /* hack to pass on ctrl+click and double click to overlapping text
5611 * editing field for editing list item names
5612 */
5613 if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS &&
5614 event->ctrl) ||
5615 (event->type == LEFTMOUSE && event->val == KM_DBL_CLICK)) {
5616 uiBut *labelbut = ui_but_list_row_text_activate(
5617 C, but, data, event, BUTTON_ACTIVATE_TEXT_EDITING);
5618 if (labelbut) {
5619 /* Nothing else to do. */
5620 return WM_UI_HANDLER_BREAK;
5621 }
5622 }
5623 }
5624
5625 return ui_do_but_EXIT(C, but, data, event);
5626 }
5627
ui_do_but_BLOCK(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5628 static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5629 {
5630 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5631
5632 /* first handle click on icondrag type button */
5633 if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) {
5634 if (ui_but_contains_point_px_icon(but, data->region, event)) {
5635 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5636 data->dragstartx = event->x;
5637 data->dragstarty = event->y;
5638 return WM_UI_HANDLER_BREAK;
5639 }
5640 }
5641 #ifdef USE_DRAG_TOGGLE
5642 if (event->type == LEFTMOUSE && event->val == KM_PRESS && (ui_but_is_drag_toggle(but))) {
5643 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5644 data->dragstartx = event->x;
5645 data->dragstarty = event->y;
5646 return WM_UI_HANDLER_BREAK;
5647 }
5648 #endif
5649 /* regular open menu */
5650 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5651 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5652 return WM_UI_HANDLER_BREAK;
5653 }
5654 if (ui_but_supports_cycling(but)) {
5655 if (ELEM(event->type, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
5656 const int direction = (event->type == WHEELDOWNMOUSE) ? 1 : -1;
5657
5658 data->value = ui_but_menu_step(but, direction);
5659
5660 button_activate_state(C, but, BUTTON_STATE_EXIT);
5661 ui_apply_but(C, but->block, but, data, true);
5662
5663 /* Button's state need to be changed to EXIT so moving mouse away from this mouse
5664 * wouldn't lead to cancel changes made to this button, but changing state to EXIT also
5665 * makes no button active for a while which leads to triggering operator when doing fast
5666 * scrolling mouse wheel. using post activate stuff from button allows to make button be
5667 * active again after checking for all all that mouse leave and cancel stuff, so quick
5668 * scroll wouldn't be an issue anymore. Same goes for scrolling wheel in another
5669 * direction below (sergey).
5670 */
5671 data->postbut = but;
5672 data->posttype = BUTTON_ACTIVATE_OVER;
5673
5674 /* without this, a new interface that draws as result of the menu change
5675 * won't register that the mouse is over it, eg:
5676 * Alt+MouseWheel over the render slots, without this,
5677 * the slot menu fails to switch a second time.
5678 *
5679 * The active state of the button could be maintained some other way
5680 * and remove this mousemove event.
5681 */
5682 WM_event_add_mousemove(data->window);
5683
5684 return WM_UI_HANDLER_BREAK;
5685 }
5686 }
5687 }
5688 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
5689
5690 /* this function also ends state */
5691 if (ui_but_drag_init(C, but, data, event)) {
5692 return WM_UI_HANDLER_BREAK;
5693 }
5694
5695 /* outside icon quit, not needed if drag activated */
5696 if (0 == ui_but_contains_point_px_icon(but, data->region, event)) {
5697 button_activate_state(C, but, BUTTON_STATE_EXIT);
5698 data->cancel = true;
5699 return WM_UI_HANDLER_BREAK;
5700 }
5701
5702 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5703 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5704 return WM_UI_HANDLER_BREAK;
5705 }
5706 }
5707
5708 return WM_UI_HANDLER_CONTINUE;
5709 }
5710
ui_numedit_but_UNITVEC(uiBut * but,uiHandleButtonData * data,int mx,int my,const enum eSnapType snap)5711 static bool ui_numedit_but_UNITVEC(
5712 uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap)
5713 {
5714 float mrad;
5715 bool changed = true;
5716
5717 /* button is presumed square */
5718 /* if mouse moves outside of sphere, it does negative normal */
5719
5720 /* note that both data->vec and data->origvec should be normalized
5721 * else we'll get a harmless but annoying jump when first clicking */
5722
5723 float *fp = data->origvec;
5724 const float rad = BLI_rctf_size_x(&but->rect);
5725 const float radsq = rad * rad;
5726
5727 int mdx, mdy;
5728 if (fp[2] > 0.0f) {
5729 mdx = (rad * fp[0]);
5730 mdy = (rad * fp[1]);
5731 }
5732 else if (fp[2] > -1.0f) {
5733 mrad = rad / sqrtf(fp[0] * fp[0] + fp[1] * fp[1]);
5734
5735 mdx = 2.0f * mrad * fp[0] - (rad * fp[0]);
5736 mdy = 2.0f * mrad * fp[1] - (rad * fp[1]);
5737 }
5738 else {
5739 mdx = mdy = 0;
5740 }
5741
5742 float dx = (float)(mx + mdx - data->dragstartx);
5743 float dy = (float)(my + mdy - data->dragstarty);
5744
5745 fp = data->vec;
5746 mrad = dx * dx + dy * dy;
5747 if (mrad < radsq) { /* inner circle */
5748 fp[0] = dx;
5749 fp[1] = dy;
5750 fp[2] = sqrtf(radsq - dx * dx - dy * dy);
5751 }
5752 else { /* outer circle */
5753
5754 mrad = rad / sqrtf(mrad); /* veclen */
5755
5756 dx *= (2.0f * mrad - 1.0f);
5757 dy *= (2.0f * mrad - 1.0f);
5758
5759 mrad = dx * dx + dy * dy;
5760 if (mrad < radsq) {
5761 fp[0] = dx;
5762 fp[1] = dy;
5763 fp[2] = -sqrtf(radsq - dx * dx - dy * dy);
5764 }
5765 }
5766 normalize_v3(fp);
5767
5768 if (snap != SNAP_OFF) {
5769 const int snap_steps = (snap == SNAP_ON) ? 4 : 12; /* 45 or 15 degree increments */
5770 const float snap_steps_angle = M_PI / snap_steps;
5771 float angle, angle_snap;
5772
5773 /* round each axis of 'fp' to the next increment
5774 * do this in "angle" space - this gives increments of same size */
5775 for (int i = 0; i < 3; i++) {
5776 angle = asinf(fp[i]);
5777 angle_snap = roundf((angle / snap_steps_angle)) * snap_steps_angle;
5778 fp[i] = sinf(angle_snap);
5779 }
5780 normalize_v3(fp);
5781 changed = !compare_v3v3(fp, data->origvec, FLT_EPSILON);
5782 }
5783
5784 data->draglastx = mx;
5785 data->draglasty = my;
5786
5787 return changed;
5788 }
5789
ui_palette_set_active(uiButColor * color_but)5790 static void ui_palette_set_active(uiButColor *color_but)
5791 {
5792 if (color_but->is_pallete_color) {
5793 Palette *palette = (Palette *)color_but->but.rnapoin.owner_id;
5794 PaletteColor *color = color_but->but.rnapoin.data;
5795 palette->active_color = BLI_findindex(&palette->colors, color);
5796 }
5797 }
5798
ui_do_but_COLOR(bContext * C,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5799 static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5800 {
5801 BLI_assert(but->type == UI_BTYPE_COLOR);
5802 uiButColor *color_but = (uiButColor *)but;
5803
5804 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5805 /* first handle click on icondrag type button */
5806 if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) {
5807 ui_palette_set_active(color_but);
5808 if (ui_but_contains_point_px_icon(but, data->region, event)) {
5809 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5810 data->dragstartx = event->x;
5811 data->dragstarty = event->y;
5812 return WM_UI_HANDLER_BREAK;
5813 }
5814 }
5815 #ifdef USE_DRAG_TOGGLE
5816 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
5817 ui_palette_set_active(color_but);
5818 button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
5819 data->dragstartx = event->x;
5820 data->dragstarty = event->y;
5821 return WM_UI_HANDLER_BREAK;
5822 }
5823 #endif
5824 /* regular open menu */
5825 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5826 ui_palette_set_active(color_but);
5827 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5828 return WM_UI_HANDLER_BREAK;
5829 }
5830 if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) {
5831 ColorPicker *cpicker = but->custom_data;
5832 float hsv_static[3] = {0.0f};
5833 float *hsv = cpicker ? cpicker->color_data : hsv_static;
5834 float col[3];
5835
5836 ui_but_v3_get(but, col);
5837 rgb_to_hsv_compat_v(col, hsv);
5838
5839 if (event->type == WHEELDOWNMOUSE) {
5840 hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f);
5841 }
5842 else if (event->type == WHEELUPMOUSE) {
5843 hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
5844 }
5845 else {
5846 const float fac = 0.005 * (event->y - event->prevy);
5847 hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f);
5848 }
5849
5850 hsv_to_rgb_v(hsv, data->vec);
5851 ui_but_v3_set(but, data->vec);
5852
5853 button_activate_state(C, but, BUTTON_STATE_EXIT);
5854 ui_apply_but(C, but->block, but, data, true);
5855 return WM_UI_HANDLER_BREAK;
5856 }
5857 if (color_but->is_pallete_color && (event->type == EVT_DELKEY) && (event->val == KM_PRESS)) {
5858 Palette *palette = (Palette *)but->rnapoin.owner_id;
5859 PaletteColor *color = but->rnapoin.data;
5860
5861 BKE_palette_color_remove(palette, color);
5862
5863 button_activate_state(C, but, BUTTON_STATE_EXIT);
5864
5865 /* this is risky. it works OK for now,
5866 * but if it gives trouble we should delay execution */
5867 but->rnapoin = PointerRNA_NULL;
5868 but->rnaprop = NULL;
5869
5870 return WM_UI_HANDLER_BREAK;
5871 }
5872 }
5873 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
5874
5875 /* this function also ends state */
5876 if (ui_but_drag_init(C, but, data, event)) {
5877 return WM_UI_HANDLER_BREAK;
5878 }
5879
5880 /* outside icon quit, not needed if drag activated */
5881 if (0 == ui_but_contains_point_px_icon(but, data->region, event)) {
5882 button_activate_state(C, but, BUTTON_STATE_EXIT);
5883 data->cancel = true;
5884 return WM_UI_HANDLER_BREAK;
5885 }
5886
5887 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5888 if (color_but->is_pallete_color) {
5889 if (!event->ctrl) {
5890 float color[3];
5891 Paint *paint = BKE_paint_get_active_from_context(C);
5892 Brush *brush = BKE_paint_brush(paint);
5893
5894 if (brush->flag & BRUSH_USE_GRADIENT) {
5895 float *target = &brush->gradient->data[brush->gradient->cur].r;
5896
5897 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
5898 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
5899 IMB_colormanagement_srgb_to_scene_linear_v3(target);
5900 }
5901 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
5902 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target);
5903 }
5904 }
5905 else {
5906 Scene *scene = CTX_data_scene(C);
5907 bool updated = false;
5908
5909 if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) {
5910 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
5911 BKE_brush_color_set(scene, brush, color);
5912 updated = true;
5913 }
5914 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
5915 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color);
5916 IMB_colormanagement_scene_linear_to_srgb_v3(color);
5917 BKE_brush_color_set(scene, brush, color);
5918 updated = true;
5919 }
5920
5921 if (updated) {
5922 PointerRNA brush_ptr;
5923 PropertyRNA *brush_color_prop;
5924
5925 RNA_id_pointer_create(&brush->id, &brush_ptr);
5926 brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
5927 RNA_property_update(C, &brush_ptr, brush_color_prop);
5928 }
5929 }
5930
5931 button_activate_state(C, but, BUTTON_STATE_EXIT);
5932 }
5933 else {
5934 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5935 }
5936 }
5937 else {
5938 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
5939 }
5940 return WM_UI_HANDLER_BREAK;
5941 }
5942 }
5943
5944 return WM_UI_HANDLER_CONTINUE;
5945 }
5946
ui_do_but_UNITVEC(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)5947 static int ui_do_but_UNITVEC(
5948 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5949 {
5950 int mx = event->x;
5951 int my = event->y;
5952 ui_window_to_block(data->region, block, &mx, &my);
5953
5954 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5955 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
5956 const enum eSnapType snap = ui_event_to_snap(event);
5957 data->dragstartx = mx;
5958 data->dragstarty = my;
5959 data->draglastx = mx;
5960 data->draglasty = my;
5961 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
5962
5963 /* also do drag the first time */
5964 if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) {
5965 ui_numedit_apply(C, block, but, data);
5966 }
5967
5968 return WM_UI_HANDLER_BREAK;
5969 }
5970 }
5971 else if (data->state == BUTTON_STATE_NUM_EDITING) {
5972 if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5973 if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
5974 const enum eSnapType snap = ui_event_to_snap(event);
5975 if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) {
5976 ui_numedit_apply(C, block, but, data);
5977 }
5978 }
5979 }
5980 else if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
5981 if (event->val == KM_PRESS) {
5982 data->cancel = true;
5983 data->escapecancel = true;
5984 button_activate_state(C, but, BUTTON_STATE_EXIT);
5985 }
5986 }
5987 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5988 button_activate_state(C, but, BUTTON_STATE_EXIT);
5989 }
5990
5991 return WM_UI_HANDLER_BREAK;
5992 }
5993
5994 return WM_UI_HANDLER_CONTINUE;
5995 }
5996
5997 /* scales a vector so no axis exceeds max
5998 * (could become BLI_math func) */
clamp_axis_max_v3(float v[3],const float max)5999 static void clamp_axis_max_v3(float v[3], const float max)
6000 {
6001 const float v_max = max_fff(v[0], v[1], v[2]);
6002 if (v_max > max) {
6003 mul_v3_fl(v, max / v_max);
6004 if (v[0] > max) {
6005 v[0] = max;
6006 }
6007 if (v[1] > max) {
6008 v[1] = max;
6009 }
6010 if (v[2] > max) {
6011 v[2] = max;
6012 }
6013 }
6014 }
6015
ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube * hsv_but,const float rgb[3],float hsv[3])6016 static void ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube *hsv_but,
6017 const float rgb[3],
6018 float hsv[3])
6019 {
6020 if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6021 rgb_to_hsl_compat_v(rgb, hsv);
6022 }
6023 else {
6024 rgb_to_hsv_compat_v(rgb, hsv);
6025 }
6026 }
6027
ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube * hsv_but,const float rgb[3],float hsv[3])6028 static void ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube *hsv_but,
6029 const float rgb[3],
6030 float hsv[3])
6031 {
6032 if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6033 rgb_to_hsl_v(rgb, hsv);
6034 }
6035 else {
6036 rgb_to_hsv_v(rgb, hsv);
6037 }
6038 }
6039
ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube * hsv_but,const float hsv[3],float rgb[3])6040 static void ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube *hsv_but,
6041 const float hsv[3],
6042 float rgb[3])
6043 {
6044 if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6045 hsl_to_rgb_v(hsv, rgb);
6046 }
6047 else {
6048 hsv_to_rgb_v(hsv, rgb);
6049 }
6050 }
6051
ui_numedit_but_HSVCUBE(uiBut * but,uiHandleButtonData * data,int mx,int my,const enum eSnapType snap,const bool shift)6052 static bool ui_numedit_but_HSVCUBE(uiBut *but,
6053 uiHandleButtonData *data,
6054 int mx,
6055 int my,
6056 const enum eSnapType snap,
6057 const bool shift)
6058 {
6059 const uiButHSVCube *hsv_but = (uiButHSVCube *)but;
6060 ColorPicker *cpicker = but->custom_data;
6061 float *hsv = cpicker->color_data;
6062 float rgb[3];
6063 float x, y;
6064 float mx_fl, my_fl;
6065 const bool changed = true;
6066
6067 ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
6068
6069 #ifdef USE_CONT_MOUSE_CORRECT
6070 if (ui_but_is_cursor_warp(but)) {
6071 /* OK but can go outside bounds */
6072 data->ungrab_mval[0] = mx_fl;
6073 data->ungrab_mval[1] = my_fl;
6074 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
6075 }
6076 #endif
6077
6078 ui_but_v3_get(but, rgb);
6079 ui_scene_linear_to_color_picker_space(but, rgb);
6080
6081 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6082
6083 /* only apply the delta motion, not absolute */
6084 if (shift) {
6085 rcti rect_i;
6086 float xpos, ypos, hsvo[3];
6087
6088 BLI_rcti_rctf_copy(&rect_i, &but->rect);
6089
6090 /* calculate original hsv again */
6091 copy_v3_v3(rgb, data->origvec);
6092 ui_scene_linear_to_color_picker_space(but, rgb);
6093
6094 copy_v3_v3(hsvo, hsv);
6095
6096 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsvo);
6097
6098 /* and original position */
6099 ui_hsvcube_pos_from_vals(hsv_but, &rect_i, hsvo, &xpos, &ypos);
6100
6101 mx_fl = xpos - (data->dragstartx - mx_fl);
6102 my_fl = ypos - (data->dragstarty - my_fl);
6103 }
6104
6105 /* relative position within box */
6106 x = ((float)mx_fl - but->rect.xmin) / BLI_rctf_size_x(&but->rect);
6107 y = ((float)my_fl - but->rect.ymin) / BLI_rctf_size_y(&but->rect);
6108 CLAMP(x, 0.0f, 1.0f);
6109 CLAMP(y, 0.0f, 1.0f);
6110
6111 switch (hsv_but->gradient_type) {
6112 case UI_GRAD_SV:
6113 hsv[1] = x;
6114 hsv[2] = y;
6115 break;
6116 case UI_GRAD_HV:
6117 hsv[0] = x;
6118 hsv[2] = y;
6119 break;
6120 case UI_GRAD_HS:
6121 hsv[0] = x;
6122 hsv[1] = y;
6123 break;
6124 case UI_GRAD_H:
6125 hsv[0] = x;
6126 break;
6127 case UI_GRAD_S:
6128 hsv[1] = x;
6129 break;
6130 case UI_GRAD_V:
6131 hsv[2] = x;
6132 break;
6133 case UI_GRAD_L_ALT:
6134 hsv[2] = y;
6135 break;
6136 case UI_GRAD_V_ALT: {
6137 /* vertical 'value' strip */
6138 const float min = but->softmin, max = but->softmax;
6139 /* exception only for value strip - use the range set in but->min/max */
6140 hsv[2] = y * (max - min) + min;
6141 break;
6142 }
6143 default:
6144 BLI_assert(0);
6145 break;
6146 }
6147
6148 if (snap != SNAP_OFF) {
6149 if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) {
6150 ui_color_snap_hue(snap, &hsv[0]);
6151 }
6152 }
6153
6154 ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb);
6155 ui_color_picker_to_scene_linear_space(but, rgb);
6156
6157 /* clamp because with color conversion we can exceed range T34295. */
6158 if (hsv_but->gradient_type == UI_GRAD_V_ALT) {
6159 clamp_axis_max_v3(rgb, but->softmax);
6160 }
6161
6162 copy_v3_v3(data->vec, rgb);
6163
6164 data->draglastx = mx;
6165 data->draglasty = my;
6166
6167 return changed;
6168 }
6169
6170 #ifdef WITH_INPUT_NDOF
ui_ndofedit_but_HSVCUBE(uiButHSVCube * hsv_but,uiHandleButtonData * data,const wmNDOFMotionData * ndof,const enum eSnapType snap,const bool shift)6171 static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but,
6172 uiHandleButtonData *data,
6173 const wmNDOFMotionData *ndof,
6174 const enum eSnapType snap,
6175 const bool shift)
6176 {
6177 ColorPicker *cpicker = hsv_but->but.custom_data;
6178 float *hsv = cpicker->color_data;
6179 const float hsv_v_max = max_ff(hsv[2], hsv_but->but.softmax);
6180 float rgb[3];
6181 const float sensitivity = (shift ? 0.15f : 0.3f) * ndof->dt;
6182
6183 ui_but_v3_get(&hsv_but->but, rgb);
6184 ui_scene_linear_to_color_picker_space(&hsv_but->but, rgb);
6185 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6186
6187 switch (hsv_but->gradient_type) {
6188 case UI_GRAD_SV:
6189 hsv[1] += ndof->rvec[2] * sensitivity;
6190 hsv[2] += ndof->rvec[0] * sensitivity;
6191 break;
6192 case UI_GRAD_HV:
6193 hsv[0] += ndof->rvec[2] * sensitivity;
6194 hsv[2] += ndof->rvec[0] * sensitivity;
6195 break;
6196 case UI_GRAD_HS:
6197 hsv[0] += ndof->rvec[2] * sensitivity;
6198 hsv[1] += ndof->rvec[0] * sensitivity;
6199 break;
6200 case UI_GRAD_H:
6201 hsv[0] += ndof->rvec[2] * sensitivity;
6202 break;
6203 case UI_GRAD_S:
6204 hsv[1] += ndof->rvec[2] * sensitivity;
6205 break;
6206 case UI_GRAD_V:
6207 hsv[2] += ndof->rvec[2] * sensitivity;
6208 break;
6209 case UI_GRAD_V_ALT:
6210 case UI_GRAD_L_ALT:
6211 /* vertical 'value' strip */
6212
6213 /* exception only for value strip - use the range set in but->min/max */
6214 hsv[2] += ndof->rvec[0] * sensitivity;
6215
6216 CLAMP(hsv[2], hsv_but->but.softmin, hsv_but->but.softmax);
6217 break;
6218 default:
6219 BLI_assert(!"invalid hsv type");
6220 break;
6221 }
6222
6223 if (snap != SNAP_OFF) {
6224 if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) {
6225 ui_color_snap_hue(snap, &hsv[0]);
6226 }
6227 }
6228
6229 /* ndof specific: the changes above aren't clamping */
6230 hsv_clamp_v(hsv, hsv_v_max);
6231
6232 ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb);
6233 ui_color_picker_to_scene_linear_space(&hsv_but->but, rgb);
6234
6235 copy_v3_v3(data->vec, rgb);
6236 ui_but_v3_set(&hsv_but->but, data->vec);
6237 }
6238 #endif /* WITH_INPUT_NDOF */
6239
ui_do_but_HSVCUBE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6240 static int ui_do_but_HSVCUBE(
6241 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6242 {
6243 uiButHSVCube *hsv_but = (uiButHSVCube *)but;
6244 int mx = event->x;
6245 int my = event->y;
6246 ui_window_to_block(data->region, block, &mx, &my);
6247
6248 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6249 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6250 const enum eSnapType snap = ui_event_to_snap(event);
6251
6252 data->dragstartx = mx;
6253 data->dragstarty = my;
6254 data->draglastx = mx;
6255 data->draglasty = my;
6256 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6257
6258 /* also do drag the first time */
6259 if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) {
6260 ui_numedit_apply(C, block, but, data);
6261 }
6262
6263 return WM_UI_HANDLER_BREAK;
6264 }
6265 #ifdef WITH_INPUT_NDOF
6266 if (event->type == NDOF_MOTION) {
6267 const wmNDOFMotionData *ndof = event->customdata;
6268 const enum eSnapType snap = ui_event_to_snap(event);
6269
6270 ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->shift != 0);
6271
6272 button_activate_state(C, but, BUTTON_STATE_EXIT);
6273 ui_apply_but(C, but->block, but, data, true);
6274
6275 return WM_UI_HANDLER_BREAK;
6276 }
6277 #endif /* WITH_INPUT_NDOF */
6278 /* XXX hardcoded keymap check.... */
6279 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
6280 if (ELEM(hsv_but->gradient_type, UI_GRAD_V_ALT, UI_GRAD_L_ALT)) {
6281 int len;
6282
6283 /* reset only value */
6284
6285 len = RNA_property_array_length(&but->rnapoin, but->rnaprop);
6286 if (ELEM(len, 3, 4)) {
6287 float rgb[3], def_hsv[3];
6288 float def[4];
6289 ColorPicker *cpicker = but->custom_data;
6290 float *hsv = cpicker->color_data;
6291
6292 RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def);
6293 ui_rgb_to_color_picker_HSVCUBE_v(hsv_but, def, def_hsv);
6294
6295 ui_but_v3_get(but, rgb);
6296 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6297
6298 def_hsv[0] = hsv[0];
6299 def_hsv[1] = hsv[1];
6300
6301 ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, def_hsv, rgb);
6302 ui_but_v3_set(but, rgb);
6303
6304 RNA_property_update(C, &but->rnapoin, but->rnaprop);
6305 return WM_UI_HANDLER_BREAK;
6306 }
6307 }
6308 }
6309 }
6310 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6311 if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
6312 if (event->val == KM_PRESS) {
6313 data->cancel = true;
6314 data->escapecancel = true;
6315 button_activate_state(C, but, BUTTON_STATE_EXIT);
6316 }
6317 }
6318 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
6319 if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
6320 const enum eSnapType snap = ui_event_to_snap(event);
6321
6322 if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) {
6323 ui_numedit_apply(C, block, but, data);
6324 }
6325 }
6326 }
6327 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6328 button_activate_state(C, but, BUTTON_STATE_EXIT);
6329 }
6330
6331 return WM_UI_HANDLER_BREAK;
6332 }
6333
6334 return WM_UI_HANDLER_CONTINUE;
6335 }
6336
ui_numedit_but_HSVCIRCLE(uiBut * but,uiHandleButtonData * data,float mx,float my,const enum eSnapType snap,const bool shift)6337 static bool ui_numedit_but_HSVCIRCLE(uiBut *but,
6338 uiHandleButtonData *data,
6339 float mx,
6340 float my,
6341 const enum eSnapType snap,
6342 const bool shift)
6343 {
6344 const bool changed = true;
6345 ColorPicker *cpicker = but->custom_data;
6346 float *hsv = cpicker->color_data;
6347
6348 float mx_fl, my_fl;
6349 ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
6350
6351 #ifdef USE_CONT_MOUSE_CORRECT
6352 if (ui_but_is_cursor_warp(but)) {
6353 /* OK but can go outside bounds */
6354 data->ungrab_mval[0] = mx_fl;
6355 data->ungrab_mval[1] = my_fl;
6356 { /* clamp */
6357 const float radius = min_ff(BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)) / 2.0f;
6358 const float cent[2] = {BLI_rctf_cent_x(&but->rect), BLI_rctf_cent_y(&but->rect)};
6359 const float len = len_v2v2(cent, data->ungrab_mval);
6360 if (len > radius) {
6361 dist_ensure_v2_v2fl(data->ungrab_mval, cent, radius);
6362 }
6363 }
6364 }
6365 #endif
6366
6367 rcti rect;
6368 BLI_rcti_rctf_copy(&rect, &but->rect);
6369
6370 float rgb[3];
6371 ui_but_v3_get(but, rgb);
6372 ui_scene_linear_to_color_picker_space(but, rgb);
6373 ui_rgb_to_color_picker_compat_v(rgb, hsv);
6374
6375 /* exception, when using color wheel in 'locked' value state:
6376 * allow choosing a hue for black values, by giving a tiny increment */
6377 if (cpicker->use_color_lock) {
6378 if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */
6379 if (hsv[2] == 0.f) {
6380 hsv[2] = 0.0001f;
6381 }
6382 }
6383 else {
6384 if (hsv[2] == 0.0f) {
6385 hsv[2] = 0.0001f;
6386 }
6387 if (hsv[2] >= 0.9999f) {
6388 hsv[2] = 0.9999f;
6389 }
6390 }
6391 }
6392
6393 /* only apply the delta motion, not absolute */
6394 if (shift) {
6395 float xpos, ypos, hsvo[3], rgbo[3];
6396
6397 /* calculate original hsv again */
6398 copy_v3_v3(hsvo, hsv);
6399 copy_v3_v3(rgbo, data->origvec);
6400 ui_scene_linear_to_color_picker_space(but, rgbo);
6401 ui_rgb_to_color_picker_compat_v(rgbo, hsvo);
6402
6403 /* and original position */
6404 ui_hsvcircle_pos_from_vals(cpicker, &rect, hsvo, &xpos, &ypos);
6405
6406 mx_fl = xpos - (data->dragstartx - mx_fl);
6407 my_fl = ypos - (data->dragstarty - my_fl);
6408 }
6409
6410 ui_hsvcircle_vals_from_pos(&rect, mx_fl, my_fl, hsv, hsv + 1);
6411
6412 if ((cpicker->use_color_cubic) && (U.color_picker_type == USER_CP_CIRCLE_HSV)) {
6413 hsv[1] = 1.0f - sqrt3f(1.0f - hsv[1]);
6414 }
6415
6416 if (snap != SNAP_OFF) {
6417 ui_color_snap_hue(snap, &hsv[0]);
6418 }
6419
6420 ui_color_picker_to_rgb_v(hsv, rgb);
6421
6422 if ((cpicker->use_luminosity_lock)) {
6423 if (!is_zero_v3(rgb)) {
6424 normalize_v3_length(rgb, cpicker->luminosity_lock_value);
6425 }
6426 }
6427
6428 ui_color_picker_to_scene_linear_space(but, rgb);
6429 ui_but_v3_set(but, rgb);
6430
6431 data->draglastx = mx;
6432 data->draglasty = my;
6433
6434 return changed;
6435 }
6436
6437 #ifdef WITH_INPUT_NDOF
ui_ndofedit_but_HSVCIRCLE(uiBut * but,uiHandleButtonData * data,const wmNDOFMotionData * ndof,const enum eSnapType snap,const bool shift)6438 static void ui_ndofedit_but_HSVCIRCLE(uiBut *but,
6439 uiHandleButtonData *data,
6440 const wmNDOFMotionData *ndof,
6441 const enum eSnapType snap,
6442 const bool shift)
6443 {
6444 ColorPicker *cpicker = but->custom_data;
6445 float *hsv = cpicker->color_data;
6446 float rgb[3];
6447 float phi, r /*, sqr */ /* UNUSED */, v[2];
6448 const float sensitivity = (shift ? 0.06f : 0.3f) * ndof->dt;
6449
6450 ui_but_v3_get(but, rgb);
6451 ui_scene_linear_to_color_picker_space(but, rgb);
6452 ui_rgb_to_color_picker_compat_v(rgb, hsv);
6453
6454 /* Convert current color on hue/sat disc to circular coordinates phi, r */
6455 phi = fmodf(hsv[0] + 0.25f, 1.0f) * -2.0f * (float)M_PI;
6456 r = hsv[1];
6457 /* sqr = r > 0.0f ? sqrtf(r) : 1; */ /* UNUSED */
6458
6459 /* Convert to 2d vectors */
6460 v[0] = r * cosf(phi);
6461 v[1] = r * sinf(phi);
6462
6463 /* Use ndof device y and x rotation to move the vector in 2d space */
6464 v[0] += ndof->rvec[2] * sensitivity;
6465 v[1] += ndof->rvec[0] * sensitivity;
6466
6467 /* convert back to polar coords on circle */
6468 phi = atan2f(v[0], v[1]) / (2.0f * (float)M_PI) + 0.5f;
6469
6470 /* use ndof Y rotation to additionally rotate hue */
6471 phi += ndof->rvec[1] * sensitivity * 0.5f;
6472 r = len_v2(v);
6473
6474 /* convert back to hsv values, in range [0,1] */
6475 hsv[0] = phi;
6476 hsv[1] = r;
6477
6478 /* exception, when using color wheel in 'locked' value state:
6479 * allow choosing a hue for black values, by giving a tiny increment */
6480 if (cpicker->use_color_lock) {
6481 if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */
6482 if (hsv[2] == 0.f) {
6483 hsv[2] = 0.0001f;
6484 }
6485 }
6486 else {
6487 if (hsv[2] == 0.f) {
6488 hsv[2] = 0.0001f;
6489 }
6490 if (hsv[2] == 1.f) {
6491 hsv[2] = 0.9999f;
6492 }
6493 }
6494 }
6495
6496 if (snap != SNAP_OFF) {
6497 ui_color_snap_hue(snap, &hsv[0]);
6498 }
6499
6500 hsv_clamp_v(hsv, FLT_MAX);
6501
6502 ui_color_picker_to_rgb_v(hsv, data->vec);
6503
6504 if (cpicker->use_luminosity_lock) {
6505 if (!is_zero_v3(data->vec)) {
6506 normalize_v3_length(data->vec, cpicker->luminosity_lock_value);
6507 }
6508 }
6509
6510 ui_color_picker_to_scene_linear_space(but, data->vec);
6511 ui_but_v3_set(but, data->vec);
6512 }
6513 #endif /* WITH_INPUT_NDOF */
6514
ui_do_but_HSVCIRCLE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6515 static int ui_do_but_HSVCIRCLE(
6516 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6517 {
6518 ColorPicker *cpicker = but->custom_data;
6519 float *hsv = cpicker->color_data;
6520 int mx = event->x;
6521 int my = event->y;
6522 ui_window_to_block(data->region, block, &mx, &my);
6523
6524 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6525 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6526 const enum eSnapType snap = ui_event_to_snap(event);
6527 data->dragstartx = mx;
6528 data->dragstarty = my;
6529 data->draglastx = mx;
6530 data->draglasty = my;
6531 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6532
6533 /* also do drag the first time */
6534 if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) {
6535 ui_numedit_apply(C, block, but, data);
6536 }
6537
6538 return WM_UI_HANDLER_BREAK;
6539 }
6540 #ifdef WITH_INPUT_NDOF
6541 if (event->type == NDOF_MOTION) {
6542 const enum eSnapType snap = ui_event_to_snap(event);
6543 const wmNDOFMotionData *ndof = event->customdata;
6544
6545 ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->shift != 0);
6546
6547 button_activate_state(C, but, BUTTON_STATE_EXIT);
6548 ui_apply_but(C, but->block, but, data, true);
6549
6550 return WM_UI_HANDLER_BREAK;
6551 }
6552 #endif /* WITH_INPUT_NDOF */
6553 /* XXX hardcoded keymap check.... */
6554 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
6555 int len;
6556
6557 /* reset only saturation */
6558
6559 len = RNA_property_array_length(&but->rnapoin, but->rnaprop);
6560 if (len >= 3) {
6561 float rgb[3], def_hsv[3];
6562 float *def;
6563 def = MEM_callocN(sizeof(float) * len, "reset_defaults - float");
6564
6565 RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def);
6566 ui_color_picker_to_rgb_v(def, def_hsv);
6567
6568 ui_but_v3_get(but, rgb);
6569 ui_rgb_to_color_picker_compat_v(rgb, hsv);
6570
6571 def_hsv[0] = hsv[0];
6572 def_hsv[2] = hsv[2];
6573
6574 hsv_to_rgb_v(def_hsv, rgb);
6575 ui_but_v3_set(but, rgb);
6576
6577 RNA_property_update(C, &but->rnapoin, but->rnaprop);
6578
6579 MEM_freeN(def);
6580 }
6581 return WM_UI_HANDLER_BREAK;
6582 }
6583 }
6584 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6585 if (event->type == EVT_ESCKEY || event->type == RIGHTMOUSE) {
6586 if (event->val == KM_PRESS) {
6587 data->cancel = true;
6588 data->escapecancel = true;
6589 button_activate_state(C, but, BUTTON_STATE_EXIT);
6590 }
6591 }
6592 /* XXX hardcoded keymap check.... */
6593 else if (event->type == WHEELDOWNMOUSE) {
6594 hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f);
6595 ui_but_hsv_set(but); /* converts to rgb */
6596 ui_numedit_apply(C, block, but, data);
6597 }
6598 else if (event->type == WHEELUPMOUSE) {
6599 hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
6600 ui_but_hsv_set(but); /* converts to rgb */
6601 ui_numedit_apply(C, block, but, data);
6602 }
6603 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
6604 if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
6605 const enum eSnapType snap = ui_event_to_snap(event);
6606
6607 if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) {
6608 ui_numedit_apply(C, block, but, data);
6609 }
6610 }
6611 }
6612 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6613 button_activate_state(C, but, BUTTON_STATE_EXIT);
6614 }
6615 return WM_UI_HANDLER_BREAK;
6616 }
6617
6618 return WM_UI_HANDLER_CONTINUE;
6619 }
6620
ui_numedit_but_COLORBAND(uiBut * but,uiHandleButtonData * data,int mx)6621 static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int mx)
6622 {
6623 bool changed = false;
6624
6625 if (data->draglastx == mx) {
6626 return changed;
6627 }
6628
6629 if (data->coba->tot == 0) {
6630 return changed;
6631 }
6632
6633 const float dx = ((float)(mx - data->draglastx)) / BLI_rctf_size_x(&but->rect);
6634 data->dragcbd->pos += dx;
6635 CLAMP(data->dragcbd->pos, 0.0f, 1.0f);
6636
6637 BKE_colorband_update_sort(data->coba);
6638 data->dragcbd = data->coba->data + data->coba->cur; /* because qsort */
6639
6640 data->draglastx = mx;
6641 changed = true;
6642
6643 return changed;
6644 }
6645
ui_do_but_COLORBAND(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6646 static int ui_do_but_COLORBAND(
6647 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6648 {
6649 int mx = event->x;
6650 int my = event->y;
6651 ui_window_to_block(data->region, block, &mx, &my);
6652
6653 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6654 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6655 ColorBand *coba = (ColorBand *)but->poin;
6656
6657 if (event->ctrl) {
6658 /* insert new key on mouse location */
6659 const float pos = ((float)(mx - but->rect.xmin)) / BLI_rctf_size_x(&but->rect);
6660 BKE_colorband_element_add(coba, pos);
6661 button_activate_state(C, but, BUTTON_STATE_EXIT);
6662 }
6663 else {
6664 CBData *cbd;
6665 /* ignore zoom-level for mindist */
6666 int mindist = (50 * UI_DPI_FAC) * block->aspect;
6667 int xco;
6668 data->dragstartx = mx;
6669 data->dragstarty = my;
6670 data->draglastx = mx;
6671 data->draglasty = my;
6672
6673 /* activate new key when mouse is close */
6674 int a;
6675 for (a = 0, cbd = coba->data; a < coba->tot; a++, cbd++) {
6676 xco = but->rect.xmin + (cbd->pos * BLI_rctf_size_x(&but->rect));
6677 xco = abs(xco - mx);
6678 if (a == coba->cur) {
6679 /* Selected one disadvantage. */
6680 xco += 5;
6681 }
6682 if (xco < mindist) {
6683 coba->cur = a;
6684 mindist = xco;
6685 }
6686 }
6687
6688 data->dragcbd = coba->data + coba->cur;
6689 data->dragfstart = data->dragcbd->pos;
6690 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6691 }
6692
6693 return WM_UI_HANDLER_BREAK;
6694 }
6695 }
6696 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6697 if (event->type == MOUSEMOVE) {
6698 if (mx != data->draglastx || my != data->draglasty) {
6699 if (ui_numedit_but_COLORBAND(but, data, mx)) {
6700 ui_numedit_apply(C, block, but, data);
6701 }
6702 }
6703 }
6704 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6705 button_activate_state(C, but, BUTTON_STATE_EXIT);
6706 }
6707 else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
6708 if (event->val == KM_PRESS) {
6709 data->dragcbd->pos = data->dragfstart;
6710 BKE_colorband_update_sort(data->coba);
6711 data->cancel = true;
6712 data->escapecancel = true;
6713 button_activate_state(C, but, BUTTON_STATE_EXIT);
6714 }
6715 }
6716 return WM_UI_HANDLER_BREAK;
6717 }
6718
6719 return WM_UI_HANDLER_CONTINUE;
6720 }
6721
ui_numedit_but_CURVE(uiBlock * block,uiBut * but,uiHandleButtonData * data,int evtx,int evty,bool snap,const bool shift)6722 static bool ui_numedit_but_CURVE(uiBlock *block,
6723 uiBut *but,
6724 uiHandleButtonData *data,
6725 int evtx,
6726 int evty,
6727 bool snap,
6728 const bool shift)
6729 {
6730 CurveMapping *cumap = (CurveMapping *)but->poin;
6731 CurveMap *cuma = cumap->cm + cumap->cur;
6732 CurveMapPoint *cmp = cuma->curve;
6733 bool changed = false;
6734
6735 /* evtx evty and drag coords are absolute mousecoords,
6736 * prevents errors when editing when layout changes */
6737 int mx = evtx;
6738 int my = evty;
6739 ui_window_to_block(data->region, block, &mx, &my);
6740 int dragx = data->draglastx;
6741 int dragy = data->draglasty;
6742 ui_window_to_block(data->region, block, &dragx, &dragy);
6743
6744 const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&cumap->curr);
6745 const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&cumap->curr);
6746
6747 if (snap) {
6748 float d[2];
6749
6750 d[0] = mx - data->dragstartx;
6751 d[1] = my - data->dragstarty;
6752
6753 if (len_squared_v2(d) < (3.0f * 3.0f)) {
6754 snap = false;
6755 }
6756 }
6757
6758 float fx = (mx - dragx) / zoomx;
6759 float fy = (my - dragy) / zoomy;
6760
6761 if (data->dragsel != -1) {
6762 CurveMapPoint *cmp_last = NULL;
6763 const float mval_factor = ui_mouse_scale_warp_factor(shift);
6764 bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
6765
6766 fx *= mval_factor;
6767 fy *= mval_factor;
6768
6769 for (int a = 0; a < cuma->totpoint; a++) {
6770 if (cmp[a].flag & CUMA_SELECT) {
6771 const float origx = cmp[a].x, origy = cmp[a].y;
6772 cmp[a].x += fx;
6773 cmp[a].y += fy;
6774 if (snap) {
6775 cmp[a].x = 0.125f * roundf(8.0f * cmp[a].x);
6776 cmp[a].y = 0.125f * roundf(8.0f * cmp[a].y);
6777 }
6778 if (cmp[a].x != origx || cmp[a].y != origy) {
6779 moved_point = true;
6780 }
6781
6782 cmp_last = &cmp[a];
6783 }
6784 }
6785
6786 BKE_curvemapping_changed(cumap, false);
6787
6788 if (moved_point) {
6789 data->draglastx = evtx;
6790 data->draglasty = evty;
6791 changed = true;
6792
6793 #ifdef USE_CONT_MOUSE_CORRECT
6794 /* note: using 'cmp_last' is weak since there may be multiple points selected,
6795 * but in practice this isnt really an issue */
6796 if (ui_but_is_cursor_warp(but)) {
6797 /* OK but can go outside bounds */
6798 data->ungrab_mval[0] = but->rect.xmin + ((cmp_last->x - cumap->curr.xmin) * zoomx);
6799 data->ungrab_mval[1] = but->rect.ymin + ((cmp_last->y - cumap->curr.ymin) * zoomy);
6800 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
6801 }
6802 #endif
6803 }
6804
6805 data->dragchange = true; /* mark for selection */
6806 }
6807 else {
6808 /* clamp for clip */
6809 if (cumap->flag & CUMA_DO_CLIP) {
6810 if (cumap->curr.xmin - fx < cumap->clipr.xmin) {
6811 fx = cumap->curr.xmin - cumap->clipr.xmin;
6812 }
6813 else if (cumap->curr.xmax - fx > cumap->clipr.xmax) {
6814 fx = cumap->curr.xmax - cumap->clipr.xmax;
6815 }
6816 if (cumap->curr.ymin - fy < cumap->clipr.ymin) {
6817 fy = cumap->curr.ymin - cumap->clipr.ymin;
6818 }
6819 else if (cumap->curr.ymax - fy > cumap->clipr.ymax) {
6820 fy = cumap->curr.ymax - cumap->clipr.ymax;
6821 }
6822 }
6823
6824 cumap->curr.xmin -= fx;
6825 cumap->curr.ymin -= fy;
6826 cumap->curr.xmax -= fx;
6827 cumap->curr.ymax -= fy;
6828
6829 data->draglastx = evtx;
6830 data->draglasty = evty;
6831
6832 changed = true;
6833 }
6834
6835 return changed;
6836 }
6837
ui_do_but_CURVE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)6838 static int ui_do_but_CURVE(
6839 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6840 {
6841 bool changed = false;
6842 Scene *scene = CTX_data_scene(C);
6843 ViewLayer *view_layer = CTX_data_view_layer(C);
6844
6845 int mx = event->x;
6846 int my = event->y;
6847 ui_window_to_block(data->region, block, &mx, &my);
6848
6849 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6850 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6851 CurveMapping *cumap = (CurveMapping *)but->poin;
6852 CurveMap *cuma = cumap->cm + cumap->cur;
6853 const float m_xy[2] = {mx, my};
6854 float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius */
6855 int sel = -1;
6856
6857 if (event->ctrl) {
6858 float f_xy[2];
6859 BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
6860
6861 BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]);
6862 BKE_curvemapping_changed(cumap, false);
6863 changed = true;
6864 }
6865
6866 /* check for selecting of a point */
6867 CurveMapPoint *cmp = cuma->curve; /* ctrl adds point, new malloc */
6868 for (int a = 0; a < cuma->totpoint; a++) {
6869 float f_xy[2];
6870 BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[a].x);
6871 const float dist_sq = len_squared_v2v2(m_xy, f_xy);
6872 if (dist_sq < dist_min_sq) {
6873 sel = a;
6874 dist_min_sq = dist_sq;
6875 }
6876 }
6877
6878 if (sel == -1) {
6879 float f_xy[2], f_xy_prev[2];
6880
6881 /* if the click didn't select anything, check if it's clicked on the
6882 * curve itself, and if so, add a point */
6883 cmp = cuma->table;
6884
6885 BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[0].x);
6886
6887 /* with 160px height 8px should translate to the old 0.05 coefficient at no zoom */
6888 dist_min_sq = square_f(U.dpi_fac * 8.0f);
6889
6890 /* loop through the curve segment table and find what's near the mouse. */
6891 for (int i = 1; i <= CM_TABLE; i++) {
6892 copy_v2_v2(f_xy_prev, f_xy);
6893 BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[i].x);
6894
6895 if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
6896 BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
6897
6898 BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]);
6899 BKE_curvemapping_changed(cumap, false);
6900
6901 changed = true;
6902
6903 /* reset cmp back to the curve points again,
6904 * rather than drawing segments */
6905 cmp = cuma->curve;
6906
6907 /* find newly added point and make it 'sel' */
6908 for (int a = 0; a < cuma->totpoint; a++) {
6909 if (cmp[a].x == f_xy[0]) {
6910 sel = a;
6911 }
6912 }
6913 break;
6914 }
6915 }
6916 }
6917
6918 if (sel != -1) {
6919 /* ok, we move a point */
6920 /* deselect all if this one is deselect. except if we hold shift */
6921 if (!event->shift) {
6922 for (int a = 0; a < cuma->totpoint; a++) {
6923 cmp[a].flag &= ~CUMA_SELECT;
6924 }
6925 cmp[sel].flag |= CUMA_SELECT;
6926 }
6927 else {
6928 cmp[sel].flag ^= CUMA_SELECT;
6929 }
6930 }
6931 else {
6932 /* move the view */
6933 data->cancel = true;
6934 }
6935
6936 data->dragsel = sel;
6937
6938 data->dragstartx = event->x;
6939 data->dragstarty = event->y;
6940 data->draglastx = event->x;
6941 data->draglasty = event->y;
6942
6943 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
6944 return WM_UI_HANDLER_BREAK;
6945 }
6946 }
6947 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6948 if (event->type == MOUSEMOVE) {
6949 if (event->x != data->draglastx || event->y != data->draglasty) {
6950
6951 if (ui_numedit_but_CURVE(
6952 block, but, data, event->x, event->y, event->ctrl != 0, event->shift != 0)) {
6953 ui_numedit_apply(C, block, but, data);
6954 }
6955 }
6956 }
6957 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6958 if (data->dragsel != -1) {
6959 CurveMapping *cumap = (CurveMapping *)but->poin;
6960 CurveMap *cuma = cumap->cm + cumap->cur;
6961 CurveMapPoint *cmp = cuma->curve;
6962
6963 if (data->dragchange == false) {
6964 /* deselect all, select one */
6965 if (!event->shift) {
6966 for (int a = 0; a < cuma->totpoint; a++) {
6967 cmp[a].flag &= ~CUMA_SELECT;
6968 }
6969 cmp[data->dragsel].flag |= CUMA_SELECT;
6970 }
6971 }
6972 else {
6973 BKE_curvemapping_changed(cumap, true); /* remove doubles */
6974 BKE_paint_invalidate_cursor_overlay(scene, view_layer, cumap);
6975 }
6976 }
6977
6978 button_activate_state(C, but, BUTTON_STATE_EXIT);
6979 }
6980
6981 return WM_UI_HANDLER_BREAK;
6982 }
6983
6984 /* UNUSED but keep for now */
6985 (void)changed;
6986
6987 return WM_UI_HANDLER_CONTINUE;
6988 }
6989
6990 /* Same as ui_numedit_but_CURVE with some smaller changes. */
ui_numedit_but_CURVEPROFILE(uiBlock * block,uiBut * but,uiHandleButtonData * data,int evtx,int evty,bool snap,const bool shift)6991 static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
6992 uiBut *but,
6993 uiHandleButtonData *data,
6994 int evtx,
6995 int evty,
6996 bool snap,
6997 const bool shift)
6998 {
6999 CurveProfile *profile = (CurveProfile *)but->poin;
7000 CurveProfilePoint *pts = profile->path;
7001 bool changed = false;
7002
7003 /* evtx evty and drag coords are absolute mousecoords,
7004 * prevents errors when editing when layout changes */
7005 int mx = evtx;
7006 int my = evty;
7007 ui_window_to_block(data->region, block, &mx, &my);
7008 int dragx = data->draglastx;
7009 int dragy = data->draglasty;
7010 ui_window_to_block(data->region, block, &dragx, &dragy);
7011
7012 const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect);
7013 const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect);
7014
7015 if (snap) {
7016 float d[2] = {mx - data->dragstartx, data->dragstarty};
7017
7018 if (len_squared_v2(d) < (9.0f * U.dpi_fac)) {
7019 snap = false;
7020 }
7021 }
7022
7023 float fx = (mx - dragx) / zoomx;
7024 float fy = (my - dragy) / zoomy;
7025
7026 if (data->dragsel != -1) {
7027 float last_x, last_y;
7028 const float mval_factor = ui_mouse_scale_warp_factor(shift);
7029 bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
7030
7031 fx *= mval_factor;
7032 fy *= mval_factor;
7033
7034 /* Move all selected points. */
7035 const float delta[2] = {fx, fy};
7036 for (int a = 0; a < profile->path_len; a++) {
7037 /* Don't move the last and first control points. */
7038 if (pts[a].flag & PROF_SELECT) {
7039 moved_point |= BKE_curveprofile_move_point(profile, &pts[a], snap, delta);
7040 last_x = pts[a].x;
7041 last_y = pts[a].y;
7042 }
7043 else {
7044 /* Move handles when they're selected but the control point isn't. */
7045 if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H1_SELECT) {
7046 moved_point |= BKE_curveprofile_move_handle(&pts[a], true, snap, delta);
7047 last_x = pts[a].h1_loc[0];
7048 last_y = pts[a].h1_loc[1];
7049 }
7050 if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H2_SELECT) {
7051 moved_point |= BKE_curveprofile_move_handle(&pts[a], false, snap, delta);
7052 last_x = pts[a].h2_loc[0];
7053 last_y = pts[a].h2_loc[1];
7054 }
7055 }
7056 }
7057
7058 BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
7059
7060 if (moved_point) {
7061 data->draglastx = evtx;
7062 data->draglasty = evty;
7063 changed = true;
7064 #ifdef USE_CONT_MOUSE_CORRECT
7065 /* note: using 'cmp_last' is weak since there may be multiple points selected,
7066 * but in practice this isnt really an issue */
7067 if (ui_but_is_cursor_warp(but)) {
7068 /* OK but can go outside bounds */
7069 data->ungrab_mval[0] = but->rect.xmin + ((last_x - profile->view_rect.xmin) * zoomx);
7070 data->ungrab_mval[1] = but->rect.ymin + ((last_y - profile->view_rect.ymin) * zoomy);
7071 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
7072 }
7073 #endif
7074 }
7075 data->dragchange = true; /* mark for selection */
7076 }
7077 else {
7078 /* Clamp the view rect when clipping is on. */
7079 if (profile->flag & PROF_USE_CLIP) {
7080 if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) {
7081 fx = profile->view_rect.xmin - profile->clip_rect.xmin;
7082 }
7083 else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) {
7084 fx = profile->view_rect.xmax - profile->clip_rect.xmax;
7085 }
7086 if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) {
7087 fy = profile->view_rect.ymin - profile->clip_rect.ymin;
7088 }
7089 else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) {
7090 fy = profile->view_rect.ymax - profile->clip_rect.ymax;
7091 }
7092 }
7093
7094 profile->view_rect.xmin -= fx;
7095 profile->view_rect.ymin -= fy;
7096 profile->view_rect.xmax -= fx;
7097 profile->view_rect.ymax -= fy;
7098
7099 data->draglastx = evtx;
7100 data->draglasty = evty;
7101
7102 changed = true;
7103 }
7104
7105 return changed;
7106 }
7107
7108 /**
7109 * Helper for #ui_do_but_CURVEPROFILE. Used to tell whether to select a control point's handles.
7110 */
point_draw_handles(CurveProfilePoint * point)7111 static bool point_draw_handles(CurveProfilePoint *point)
7112 {
7113 return (point->flag & PROF_SELECT &&
7114 (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) ||
7115 ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT);
7116 }
7117
7118 /**
7119 * Interaction for curve profile widget.
7120 * \note Uses hardcoded keys rather than the keymap.
7121 */
ui_do_but_CURVEPROFILE(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7122 static int ui_do_but_CURVEPROFILE(
7123 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7124 {
7125 CurveProfile *profile = (CurveProfile *)but->poin;
7126 int mx = event->x;
7127 int my = event->y;
7128
7129 ui_window_to_block(data->region, block, &mx, &my);
7130
7131 /* Move selected control points. */
7132 if (event->type == EVT_GKEY && event->val == KM_RELEASE) {
7133 data->dragstartx = mx;
7134 data->dragstarty = my;
7135 data->draglastx = mx;
7136 data->draglasty = my;
7137 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7138 return WM_UI_HANDLER_BREAK;
7139 }
7140
7141 /* Delete selected control points. */
7142 if (event->type == EVT_XKEY && event->val == KM_RELEASE) {
7143 BKE_curveprofile_remove_by_flag(profile, PROF_SELECT);
7144 BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
7145 button_activate_state(C, but, BUTTON_STATE_EXIT);
7146 return WM_UI_HANDLER_BREAK;
7147 }
7148
7149 /* Selecting, adding, and starting point movements. */
7150 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7151 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7152 const float m_xy[2] = {mx, my};
7153
7154 if (event->ctrl) {
7155 float f_xy[2];
7156 BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
7157
7158 BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
7159 BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
7160 }
7161
7162 /* Check for selecting of a point by finding closest point in radius. */
7163 CurveProfilePoint *pts = profile->path;
7164 float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */
7165 int i_selected = -1;
7166 short selection_type = 0; /* For handle selection. */
7167 for (int i = 0; i < profile->path_len; i++) {
7168 float f_xy[2];
7169 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
7170 float dist_sq = len_squared_v2v2(m_xy, f_xy);
7171 if (dist_sq < dist_min_sq) {
7172 i_selected = i;
7173 selection_type = PROF_SELECT;
7174 dist_min_sq = dist_sq;
7175 }
7176
7177 /* Also select handles if the point is selected and it has the right handle type. */
7178 if (point_draw_handles(&pts[i])) {
7179 if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
7180 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h1_loc);
7181 dist_sq = len_squared_v2v2(m_xy, f_xy);
7182 if (dist_sq < dist_min_sq) {
7183 i_selected = i;
7184 selection_type = PROF_H1_SELECT;
7185 dist_min_sq = dist_sq;
7186 }
7187 }
7188 if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) {
7189 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h2_loc);
7190 dist_sq = len_squared_v2v2(m_xy, f_xy);
7191 if (dist_sq < dist_min_sq) {
7192 i_selected = i;
7193 selection_type = PROF_H2_SELECT;
7194 dist_min_sq = dist_sq;
7195 }
7196 }
7197 }
7198 }
7199
7200 /* Add a point if the click was close to the path but not a control point or handle. */
7201 if (i_selected == -1) {
7202 float f_xy[2], f_xy_prev[2];
7203 CurveProfilePoint *table = profile->table;
7204 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[0].x);
7205
7206 dist_min_sq = square_f(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */
7207
7208 /* Loop through the path's high resolution table and find what's near the click. */
7209 for (int i = 1; i <= PROF_TABLE_LEN(profile->path_len); i++) {
7210 copy_v2_v2(f_xy_prev, f_xy);
7211 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x);
7212
7213 if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
7214 BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
7215
7216 CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
7217 BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
7218
7219 /* Get the index of the newly added point. */
7220 i_selected = (int)(new_pt - profile->path);
7221 BLI_assert(i_selected >= 0 && i_selected <= profile->path_len);
7222 selection_type = PROF_SELECT;
7223 break;
7224 }
7225 }
7226 }
7227
7228 /* Change the flag for the point(s) if one was selected or added. */
7229 if (i_selected != -1) {
7230 /* Deselect all if this one is deselected, except if we hold shift. */
7231 if (event->shift) {
7232 pts[i_selected].flag ^= selection_type;
7233 }
7234 else {
7235 for (int i = 0; i < profile->path_len; i++) {
7236 // pts[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
7237 profile->path[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
7238 }
7239 profile->path[i_selected].flag |= selection_type;
7240 }
7241 }
7242 else {
7243 /* Move the view. */
7244 data->cancel = true;
7245 }
7246
7247 data->dragsel = i_selected;
7248
7249 data->dragstartx = mx;
7250 data->dragstarty = my;
7251 data->draglastx = mx;
7252 data->draglasty = my;
7253
7254 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7255 return WM_UI_HANDLER_BREAK;
7256 }
7257 }
7258 else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */
7259 if (event->type == MOUSEMOVE) {
7260 if (mx != data->draglastx || my != data->draglasty) {
7261 if (ui_numedit_but_CURVEPROFILE(
7262 block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) {
7263 ui_numedit_apply(C, block, but, data);
7264 }
7265 }
7266 }
7267 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7268 /* Finish move. */
7269 if (data->dragsel != -1) {
7270
7271 if (data->dragchange == false) {
7272 /* Deselect all, select one. */
7273 }
7274 else {
7275 /* Remove doubles, clip after move. */
7276 BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP);
7277 }
7278 }
7279 button_activate_state(C, but, BUTTON_STATE_EXIT);
7280 }
7281 return WM_UI_HANDLER_BREAK;
7282 }
7283
7284 return WM_UI_HANDLER_CONTINUE;
7285 }
7286
ui_numedit_but_HISTOGRAM(uiBut * but,uiHandleButtonData * data,int mx,int my)7287 static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my)
7288 {
7289 Histogram *hist = (Histogram *)but->poin;
7290 const bool changed = true;
7291 const float dy = my - data->draglasty;
7292
7293 /* scale histogram values (dy / 10 for better control) */
7294 const float yfac = min_ff(pow2f(hist->ymax), 1.0f) * 0.5f;
7295 hist->ymax += (dy * 0.1f) * yfac;
7296
7297 /* 0.1 allows us to see HDR colors up to 10 */
7298 CLAMP(hist->ymax, 0.1f, 100.f);
7299
7300 data->draglastx = mx;
7301 data->draglasty = my;
7302
7303 return changed;
7304 }
7305
ui_do_but_HISTOGRAM(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7306 static int ui_do_but_HISTOGRAM(
7307 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7308 {
7309 int mx = event->x;
7310 int my = event->y;
7311 ui_window_to_block(data->region, block, &mx, &my);
7312
7313 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7314 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7315 data->dragstartx = mx;
7316 data->dragstarty = my;
7317 data->draglastx = mx;
7318 data->draglasty = my;
7319 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7320
7321 /* also do drag the first time */
7322 if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) {
7323 ui_numedit_apply(C, block, but, data);
7324 }
7325
7326 return WM_UI_HANDLER_BREAK;
7327 }
7328 /* XXX hardcoded keymap check.... */
7329 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
7330 Histogram *hist = (Histogram *)but->poin;
7331 hist->ymax = 1.f;
7332
7333 button_activate_state(C, but, BUTTON_STATE_EXIT);
7334 return WM_UI_HANDLER_BREAK;
7335 }
7336 }
7337 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7338 if (event->type == EVT_ESCKEY) {
7339 if (event->val == KM_PRESS) {
7340 data->cancel = true;
7341 data->escapecancel = true;
7342 button_activate_state(C, but, BUTTON_STATE_EXIT);
7343 }
7344 }
7345 else if (event->type == MOUSEMOVE) {
7346 if (mx != data->draglastx || my != data->draglasty) {
7347 if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) {
7348 ui_numedit_apply(C, block, but, data);
7349 }
7350 }
7351 }
7352 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7353 button_activate_state(C, but, BUTTON_STATE_EXIT);
7354 }
7355 return WM_UI_HANDLER_BREAK;
7356 }
7357
7358 return WM_UI_HANDLER_CONTINUE;
7359 }
7360
ui_numedit_but_WAVEFORM(uiBut * but,uiHandleButtonData * data,int mx,int my)7361 static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx, int my)
7362 {
7363 Scopes *scopes = (Scopes *)but->poin;
7364 const bool changed = true;
7365
7366 const float dy = my - data->draglasty;
7367
7368 /* scale waveform values */
7369 scopes->wavefrm_yfac += dy / 200.0f;
7370
7371 CLAMP(scopes->wavefrm_yfac, 0.5f, 2.0f);
7372
7373 data->draglastx = mx;
7374 data->draglasty = my;
7375
7376 return changed;
7377 }
7378
ui_do_but_WAVEFORM(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7379 static int ui_do_but_WAVEFORM(
7380 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7381 {
7382 int mx = event->x;
7383 int my = event->y;
7384 ui_window_to_block(data->region, block, &mx, &my);
7385
7386 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7387 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7388 data->dragstartx = mx;
7389 data->dragstarty = my;
7390 data->draglastx = mx;
7391 data->draglasty = my;
7392 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7393
7394 /* also do drag the first time */
7395 if (ui_numedit_but_WAVEFORM(but, data, mx, my)) {
7396 ui_numedit_apply(C, block, but, data);
7397 }
7398
7399 return WM_UI_HANDLER_BREAK;
7400 }
7401 /* XXX hardcoded keymap check.... */
7402 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
7403 Scopes *scopes = (Scopes *)but->poin;
7404 scopes->wavefrm_yfac = 1.f;
7405
7406 button_activate_state(C, but, BUTTON_STATE_EXIT);
7407 return WM_UI_HANDLER_BREAK;
7408 }
7409 }
7410 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7411 if (event->type == EVT_ESCKEY) {
7412 if (event->val == KM_PRESS) {
7413 data->cancel = true;
7414 data->escapecancel = true;
7415 button_activate_state(C, but, BUTTON_STATE_EXIT);
7416 }
7417 }
7418 else if (event->type == MOUSEMOVE) {
7419 if (mx != data->draglastx || my != data->draglasty) {
7420 if (ui_numedit_but_WAVEFORM(but, data, mx, my)) {
7421 ui_numedit_apply(C, block, but, data);
7422 }
7423 }
7424 }
7425 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7426 button_activate_state(C, but, BUTTON_STATE_EXIT);
7427 }
7428 return WM_UI_HANDLER_BREAK;
7429 }
7430
7431 return WM_UI_HANDLER_CONTINUE;
7432 }
7433
ui_numedit_but_TRACKPREVIEW(bContext * C,uiBut * but,uiHandleButtonData * data,int mx,int my,const bool shift)7434 static bool ui_numedit_but_TRACKPREVIEW(
7435 bContext *C, uiBut *but, uiHandleButtonData *data, int mx, int my, const bool shift)
7436 {
7437 MovieClipScopes *scopes = (MovieClipScopes *)but->poin;
7438 const bool changed = true;
7439
7440 float dx = mx - data->draglastx;
7441 float dy = my - data->draglasty;
7442
7443 if (shift) {
7444 dx /= 5.0f;
7445 dy /= 5.0f;
7446 }
7447
7448 if (!scopes->track_locked) {
7449 const MovieClip *clip = CTX_data_edit_movieclip(C);
7450 const int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, scopes->framenr);
7451 if (scopes->marker->framenr != clip_framenr) {
7452 scopes->marker = BKE_tracking_marker_ensure(scopes->track, clip_framenr);
7453 }
7454
7455 scopes->marker->flag &= ~(MARKER_DISABLED | MARKER_TRACKED);
7456 scopes->marker->pos[0] += -dx * scopes->slide_scale[0] / BLI_rctf_size_x(&but->block->rect);
7457 scopes->marker->pos[1] += -dy * scopes->slide_scale[1] / BLI_rctf_size_y(&but->block->rect);
7458
7459 WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL);
7460 }
7461
7462 scopes->ok = 0;
7463
7464 data->draglastx = mx;
7465 data->draglasty = my;
7466
7467 return changed;
7468 }
7469
ui_do_but_TRACKPREVIEW(bContext * C,uiBlock * block,uiBut * but,uiHandleButtonData * data,const wmEvent * event)7470 static int ui_do_but_TRACKPREVIEW(
7471 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7472 {
7473 int mx = event->x;
7474 int my = event->y;
7475 ui_window_to_block(data->region, block, &mx, &my);
7476
7477 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7478 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7479 data->dragstartx = mx;
7480 data->dragstarty = my;
7481 data->draglastx = mx;
7482 data->draglasty = my;
7483 button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
7484
7485 /* also do drag the first time */
7486 if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) {
7487 ui_numedit_apply(C, block, but, data);
7488 }
7489
7490 return WM_UI_HANDLER_BREAK;
7491 }
7492 }
7493 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7494 if (event->type == EVT_ESCKEY) {
7495 if (event->val == KM_PRESS) {
7496 data->cancel = true;
7497 data->escapecancel = true;
7498 button_activate_state(C, but, BUTTON_STATE_EXIT);
7499 }
7500 }
7501 else if (event->type == MOUSEMOVE) {
7502 if (mx != data->draglastx || my != data->draglasty) {
7503 if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) {
7504 ui_numedit_apply(C, block, but, data);
7505 }
7506 }
7507 }
7508 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7509 button_activate_state(C, but, BUTTON_STATE_EXIT);
7510 }
7511 return WM_UI_HANDLER_BREAK;
7512 }
7513
7514 return WM_UI_HANDLER_CONTINUE;
7515 }
7516
ui_do_button(bContext * C,uiBlock * block,uiBut * but,const wmEvent * event)7517 static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *event)
7518 {
7519 uiHandleButtonData *data = but->active;
7520 int retval = WM_UI_HANDLER_CONTINUE;
7521
7522 const bool is_disabled = but->flag & UI_BUT_DISABLED;
7523
7524 /* if but->pointype is set, but->poin should be too */
7525 BLI_assert(!but->pointype || but->poin);
7526
7527 /* Only hard-coded stuff here, button interactions with configurable
7528 * keymaps are handled using operators (see #ED_keymap_ui). */
7529
7530 if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) {
7531
7532 /* handle copy and paste */
7533 bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) &&
7534 !event->shift;
7535 const bool do_copy = event->type == EVT_CKEY && is_press_ctrl_but_no_shift;
7536 const bool do_paste = event->type == EVT_VKEY && is_press_ctrl_but_no_shift;
7537
7538 /* Specific handling for listrows, we try to find their overlapping tex button. */
7539 if ((do_copy || do_paste) && but->type == UI_BTYPE_LISTROW) {
7540 uiBut *labelbut = ui_but_list_row_text_activate(C, but, data, event, BUTTON_ACTIVATE_OVER);
7541 if (labelbut) {
7542 but = labelbut;
7543 data = but->active;
7544 }
7545 }
7546
7547 /* do copy first, because it is the only allowed operator when disabled */
7548 if (do_copy) {
7549 ui_but_copy(C, but, event->alt);
7550 return WM_UI_HANDLER_BREAK;
7551 }
7552
7553 /* handle menu */
7554 if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
7555 (event->val == KM_PRESS)) {
7556 /* RMB has two options now */
7557 if (ui_popup_context_menu_for_button(C, but)) {
7558 return WM_UI_HANDLER_BREAK;
7559 }
7560 }
7561
7562 if (is_disabled) {
7563 return WM_UI_HANDLER_CONTINUE;
7564 }
7565
7566 if (do_paste) {
7567 ui_but_paste(C, but, data, event->alt);
7568 return WM_UI_HANDLER_BREAK;
7569 }
7570
7571 /* handle drop */
7572 if (event->type == EVT_DROP) {
7573 ui_but_drop(C, event, but, data);
7574 }
7575
7576 if ((data->state == BUTTON_STATE_HIGHLIGHT) &&
7577 ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) &&
7578 (event->val == KM_RELEASE) &&
7579 /* Only returns true if the event was handled. */
7580 ui_do_but_extra_operator_icon(C, but, data, event)) {
7581 return WM_UI_HANDLER_BREAK;
7582 }
7583 }
7584
7585 if (but->flag & UI_BUT_DISABLED) {
7586 return WM_UI_HANDLER_BREAK;
7587 }
7588
7589 switch (but->type) {
7590 case UI_BTYPE_BUT:
7591 case UI_BTYPE_DECORATOR:
7592 retval = ui_do_but_BUT(C, but, data, event);
7593 break;
7594 case UI_BTYPE_KEY_EVENT:
7595 retval = ui_do_but_KEYEVT(C, but, data, event);
7596 break;
7597 case UI_BTYPE_HOTKEY_EVENT:
7598 retval = ui_do_but_HOTKEYEVT(C, but, data, event);
7599 break;
7600 case UI_BTYPE_TAB:
7601 retval = ui_do_but_TAB(C, block, but, data, event);
7602 break;
7603 case UI_BTYPE_BUT_TOGGLE:
7604 case UI_BTYPE_TOGGLE:
7605 case UI_BTYPE_ICON_TOGGLE:
7606 case UI_BTYPE_ICON_TOGGLE_N:
7607 case UI_BTYPE_TOGGLE_N:
7608 case UI_BTYPE_CHECKBOX:
7609 case UI_BTYPE_CHECKBOX_N:
7610 case UI_BTYPE_ROW:
7611 retval = ui_do_but_TOG(C, but, data, event);
7612 break;
7613 case UI_BTYPE_SCROLL:
7614 retval = ui_do_but_SCROLL(C, block, but, data, event);
7615 break;
7616 case UI_BTYPE_GRIP:
7617 retval = ui_do_but_GRIP(C, block, but, data, event);
7618 break;
7619 case UI_BTYPE_NUM:
7620 retval = ui_do_but_NUM(C, block, but, data, event);
7621 break;
7622 case UI_BTYPE_NUM_SLIDER:
7623 retval = ui_do_but_SLI(C, block, but, data, event);
7624 break;
7625 case UI_BTYPE_LISTBOX:
7626 /* Nothing to do! */
7627 break;
7628 case UI_BTYPE_LISTROW:
7629 retval = ui_do_but_LISTROW(C, but, data, event);
7630 break;
7631 case UI_BTYPE_ROUNDBOX:
7632 case UI_BTYPE_LABEL:
7633 case UI_BTYPE_IMAGE:
7634 case UI_BTYPE_PROGRESS_BAR:
7635 case UI_BTYPE_NODE_SOCKET:
7636 retval = ui_do_but_EXIT(C, but, data, event);
7637 break;
7638 case UI_BTYPE_HISTOGRAM:
7639 retval = ui_do_but_HISTOGRAM(C, block, but, data, event);
7640 break;
7641 case UI_BTYPE_WAVEFORM:
7642 retval = ui_do_but_WAVEFORM(C, block, but, data, event);
7643 break;
7644 case UI_BTYPE_VECTORSCOPE:
7645 /* Nothing to do! */
7646 break;
7647 case UI_BTYPE_TEXT:
7648 case UI_BTYPE_SEARCH_MENU:
7649 if ((but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) {
7650 retval = ui_do_but_SEARCH_UNLINK(C, block, but, data, event);
7651 if (retval & WM_UI_HANDLER_BREAK) {
7652 break;
7653 }
7654 }
7655 retval = ui_do_but_TEX(C, block, but, data, event);
7656 break;
7657 case UI_BTYPE_MENU:
7658 case UI_BTYPE_POPOVER:
7659 case UI_BTYPE_BLOCK:
7660 case UI_BTYPE_PULLDOWN:
7661 retval = ui_do_but_BLOCK(C, but, data, event);
7662 break;
7663 case UI_BTYPE_BUT_MENU:
7664 retval = ui_do_but_BUT(C, but, data, event);
7665 break;
7666 case UI_BTYPE_COLOR:
7667 retval = ui_do_but_COLOR(C, but, data, event);
7668 break;
7669 case UI_BTYPE_UNITVEC:
7670 retval = ui_do_but_UNITVEC(C, block, but, data, event);
7671 break;
7672 case UI_BTYPE_COLORBAND:
7673 retval = ui_do_but_COLORBAND(C, block, but, data, event);
7674 break;
7675 case UI_BTYPE_CURVE:
7676 retval = ui_do_but_CURVE(C, block, but, data, event);
7677 break;
7678 case UI_BTYPE_CURVEPROFILE:
7679 retval = ui_do_but_CURVEPROFILE(C, block, but, data, event);
7680 break;
7681 case UI_BTYPE_HSVCUBE:
7682 retval = ui_do_but_HSVCUBE(C, block, but, data, event);
7683 break;
7684 case UI_BTYPE_HSVCIRCLE:
7685 retval = ui_do_but_HSVCIRCLE(C, block, but, data, event);
7686 break;
7687 case UI_BTYPE_TRACK_PREVIEW:
7688 retval = ui_do_but_TRACKPREVIEW(C, block, but, data, event);
7689 break;
7690
7691 /* quiet warnings for unhandled types */
7692 case UI_BTYPE_SEPR:
7693 case UI_BTYPE_SEPR_LINE:
7694 case UI_BTYPE_SEPR_SPACER:
7695 case UI_BTYPE_EXTRA:
7696 break;
7697 }
7698
7699 #ifdef USE_DRAG_MULTINUM
7700 if (data) {
7701 if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) ||
7702 /* if we started dragging, progress on any event */
7703 (data->multi_data.init == BUTTON_MULTI_INIT_SETUP)) {
7704 if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) &&
7705 ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) {
7706 /* initialize! */
7707 if (data->multi_data.init == BUTTON_MULTI_INIT_UNSET) {
7708 /* --> (BUTTON_MULTI_INIT_SETUP | BUTTON_MULTI_INIT_DISABLE) */
7709
7710 const float margin_y = DRAG_MULTINUM_THRESHOLD_DRAG_Y / sqrtf(block->aspect);
7711
7712 /* check if we have a vertical gesture */
7713 if (len_squared_v2(data->multi_data.drag_dir) > (margin_y * margin_y)) {
7714 const float dir_nor_y[2] = {0.0, 1.0f};
7715 float dir_nor_drag[2];
7716
7717 normalize_v2_v2(dir_nor_drag, data->multi_data.drag_dir);
7718
7719 if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) {
7720 data->multi_data.init = BUTTON_MULTI_INIT_SETUP;
7721 data->multi_data.drag_lock_x = event->x;
7722 }
7723 else {
7724 data->multi_data.init = BUTTON_MULTI_INIT_DISABLE;
7725 }
7726 }
7727 }
7728 else if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
7729 /* --> (BUTTON_MULTI_INIT_ENABLE) */
7730 const float margin_x = DRAG_MULTINUM_THRESHOLD_DRAG_X / sqrtf(block->aspect);
7731 /* check if we're dont setting buttons */
7732 if ((data->str &&
7733 ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) ||
7734 ((abs(data->multi_data.drag_lock_x - event->x) > margin_x) &&
7735 /* just to be sure, check we're dragging more hoz then virt */
7736 abs(event->prevx - event->x) > abs(event->prevy - event->y))) {
7737 if (data->multi_data.has_mbuts) {
7738 ui_multibut_states_create(but, data);
7739 data->multi_data.init = BUTTON_MULTI_INIT_ENABLE;
7740 }
7741 else {
7742 data->multi_data.init = BUTTON_MULTI_INIT_DISABLE;
7743 }
7744 }
7745 }
7746
7747 if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) {
7748 if (ui_multibut_states_tag(but, data, event)) {
7749 ED_region_tag_redraw(data->region);
7750 }
7751 }
7752 }
7753 }
7754 }
7755 #endif /* USE_DRAG_MULTINUM */
7756
7757 return retval;
7758 }
7759
7760 /** \} */
7761
7762 /* -------------------------------------------------------------------- */
7763 /** \name Button Tool Tip
7764 * \{ */
7765
ui_blocks_set_tooltips(ARegion * region,const bool enable)7766 static void ui_blocks_set_tooltips(ARegion *region, const bool enable)
7767 {
7768 if (!region) {
7769 return;
7770 }
7771
7772 /* we disabled buttons when when they were already shown, and
7773 * re-enable them on mouse move */
7774 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
7775 block->tooltipdisabled = !enable;
7776 }
7777 }
7778
7779 /**
7780 * Recreate tooltip (use to update dynamic tips)
7781 */
UI_but_tooltip_refresh(bContext * C,uiBut * but)7782 void UI_but_tooltip_refresh(bContext *C, uiBut *but)
7783 {
7784 uiHandleButtonData *data = but->active;
7785 if (data) {
7786 bScreen *screen = WM_window_get_active_screen(data->window);
7787 if (screen->tool_tip && screen->tool_tip->region) {
7788 WM_tooltip_refresh(C, data->window);
7789 }
7790 }
7791 }
7792
7793 /**
7794 * Removes tool-tip timer from active but
7795 * (meaning tool-tip is disabled until it's re-enabled again).
7796 */
UI_but_tooltip_timer_remove(bContext * C,uiBut * but)7797 void UI_but_tooltip_timer_remove(bContext *C, uiBut *but)
7798 {
7799 uiHandleButtonData *data = but->active;
7800 if (data) {
7801 if (data->autoopentimer) {
7802 WM_event_remove_timer(data->wm, data->window, data->autoopentimer);
7803 data->autoopentimer = NULL;
7804 }
7805
7806 if (data->window) {
7807 WM_tooltip_clear(C, data->window);
7808 }
7809 }
7810 }
7811
ui_but_tooltip_init(bContext * C,ARegion * region,int * pass,double * r_pass_delay,bool * r_exit_on_event)7812 static ARegion *ui_but_tooltip_init(
7813 bContext *C, ARegion *region, int *pass, double *r_pass_delay, bool *r_exit_on_event)
7814 {
7815 bool is_label = false;
7816 if (*pass == 1) {
7817 is_label = true;
7818 (*pass)--;
7819 (*r_pass_delay) = UI_TOOLTIP_DELAY - UI_TOOLTIP_DELAY_LABEL;
7820 }
7821
7822 uiBut *but = UI_region_active_but_get(region);
7823 *r_exit_on_event = false;
7824 if (but) {
7825 return UI_tooltip_create_from_button(C, region, but, is_label);
7826 }
7827 return NULL;
7828 }
7829
button_tooltip_timer_reset(bContext * C,uiBut * but)7830 static void button_tooltip_timer_reset(bContext *C, uiBut *but)
7831 {
7832 wmWindowManager *wm = CTX_wm_manager(C);
7833 uiHandleButtonData *data = but->active;
7834
7835 WM_tooltip_timer_clear(C, data->window);
7836
7837 if ((U.flag & USER_TOOLTIPS) || (data->tooltip_force)) {
7838 if (!but->block->tooltipdisabled) {
7839 if (!wm->drags.first) {
7840 const bool is_label = UI_but_has_tooltip_label(but);
7841 const double delay = is_label ? UI_TOOLTIP_DELAY_LABEL : UI_TOOLTIP_DELAY;
7842 WM_tooltip_timer_init_ex(
7843 C, data->window, data->area, data->region, ui_but_tooltip_init, delay);
7844 if (is_label) {
7845 bScreen *screen = WM_window_get_active_screen(data->window);
7846 if (screen->tool_tip) {
7847 screen->tool_tip->pass = 1;
7848 }
7849 }
7850 }
7851 }
7852 }
7853 }
7854
7855 /** \} */
7856
7857 /* -------------------------------------------------------------------- */
7858 /** \name Button State Handling
7859 * \{ */
7860
button_modal_state(uiHandleButtonState state)7861 static bool button_modal_state(uiHandleButtonState state)
7862 {
7863 return ELEM(state,
7864 BUTTON_STATE_WAIT_RELEASE,
7865 BUTTON_STATE_WAIT_KEY_EVENT,
7866 BUTTON_STATE_NUM_EDITING,
7867 BUTTON_STATE_TEXT_EDITING,
7868 BUTTON_STATE_TEXT_SELECTING,
7869 BUTTON_STATE_MENU_OPEN);
7870 }
7871
button_activate_state(bContext * C,uiBut * but,uiHandleButtonState state)7872 static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state)
7873 {
7874 uiHandleButtonData *data = but->active;
7875 if (data->state == state) {
7876 return;
7877 }
7878
7879 /* highlight has timers for tooltips and auto open */
7880 if (state == BUTTON_STATE_HIGHLIGHT) {
7881 but->flag &= ~UI_SELECT;
7882
7883 button_tooltip_timer_reset(C, but);
7884
7885 /* automatic open pulldown block timer */
7886 if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER) ||
7887 /* Menu button types may draw as popovers, check for this case
7888 * ignoring other kinds of menus (mainly enums). (see T66538). */
7889 ((but->type == UI_BTYPE_MENU) &&
7890 (UI_but_paneltype_get(but) || ui_but_menu_draw_as_popover(but)))) {
7891 if (data->used_mouse && !data->autoopentimer) {
7892 int time;
7893
7894 if (but->block->auto_open == true) { /* test for toolbox */
7895 time = 1;
7896 }
7897 else if ((but->block->flag & UI_BLOCK_LOOP && but->type != UI_BTYPE_BLOCK) ||
7898 (but->block->auto_open == true)) {
7899 time = 5 * U.menuthreshold2;
7900 }
7901 else if (U.uiflag & USER_MENUOPENAUTO) {
7902 time = 5 * U.menuthreshold1;
7903 }
7904 else {
7905 time = -1; /* do nothing */
7906 }
7907
7908 if (time >= 0) {
7909 data->autoopentimer = WM_event_add_timer(
7910 data->wm, data->window, TIMER, 0.02 * (double)time);
7911 }
7912 }
7913 }
7914 }
7915 else {
7916 but->flag |= UI_SELECT;
7917 UI_but_tooltip_timer_remove(C, but);
7918 }
7919
7920 /* text editing */
7921 if (state == BUTTON_STATE_TEXT_EDITING && data->state != BUTTON_STATE_TEXT_SELECTING) {
7922 ui_textedit_begin(C, but, data);
7923 }
7924 else if (data->state == BUTTON_STATE_TEXT_EDITING && state != BUTTON_STATE_TEXT_SELECTING) {
7925 ui_textedit_end(C, but, data);
7926 }
7927 else if (data->state == BUTTON_STATE_TEXT_SELECTING && state != BUTTON_STATE_TEXT_EDITING) {
7928 ui_textedit_end(C, but, data);
7929 }
7930
7931 /* number editing */
7932 if (state == BUTTON_STATE_NUM_EDITING) {
7933 if (ui_but_is_cursor_warp(but)) {
7934 WM_cursor_grab_enable(CTX_wm_window(C), WM_CURSOR_WRAP_XY, true, NULL);
7935 }
7936 ui_numedit_begin(but, data);
7937 }
7938 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7939 ui_numedit_end(but, data);
7940
7941 if (but->flag & UI_BUT_DRIVEN) {
7942 /* Only warn when editing stepping/dragging the value.
7943 * No warnings should show for editing driver expressions though!
7944 */
7945 if (state != BUTTON_STATE_TEXT_EDITING) {
7946 WM_report(RPT_INFO,
7947 "Can't edit driven number value, see graph editor for the driver setup.");
7948 }
7949 }
7950
7951 if (ui_but_is_cursor_warp(but)) {
7952
7953 #ifdef USE_CONT_MOUSE_CORRECT
7954 /* stereo3d has issues with changing cursor location so rather avoid */
7955 if (data->ungrab_mval[0] != FLT_MAX && !WM_stereo3d_enabled(data->window, false)) {
7956 int mouse_ungrab_xy[2];
7957 ui_block_to_window_fl(
7958 data->region, but->block, &data->ungrab_mval[0], &data->ungrab_mval[1]);
7959 mouse_ungrab_xy[0] = data->ungrab_mval[0];
7960 mouse_ungrab_xy[1] = data->ungrab_mval[1];
7961
7962 WM_cursor_grab_disable(data->window, mouse_ungrab_xy);
7963 }
7964 else {
7965 WM_cursor_grab_disable(data->window, NULL);
7966 }
7967 #else
7968 WM_cursor_grab_disable(data->window, NULL);
7969 #endif
7970 }
7971 }
7972 /* menu open */
7973 if (state == BUTTON_STATE_MENU_OPEN) {
7974 ui_block_open_begin(C, but, data);
7975 }
7976 else if (data->state == BUTTON_STATE_MENU_OPEN) {
7977 ui_block_open_end(C, but, data);
7978 }
7979
7980 /* add a short delay before exiting, to ensure there is some feedback */
7981 if (state == BUTTON_STATE_WAIT_FLASH) {
7982 data->flashtimer = WM_event_add_timer(data->wm, data->window, TIMER, BUTTON_FLASH_DELAY);
7983 }
7984 else if (data->flashtimer) {
7985 WM_event_remove_timer(data->wm, data->window, data->flashtimer);
7986 data->flashtimer = NULL;
7987 }
7988
7989 /* add hold timer if it's used */
7990 if (state == BUTTON_STATE_WAIT_RELEASE && (but->hold_func != NULL)) {
7991 data->hold_action_timer = WM_event_add_timer(
7992 data->wm, data->window, TIMER, BUTTON_AUTO_OPEN_THRESH);
7993 }
7994 else if (data->hold_action_timer) {
7995 WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
7996 data->hold_action_timer = NULL;
7997 }
7998
7999 /* add a blocking ui handler at the window handler for blocking, modal states
8000 * but not for popups, because we already have a window level handler*/
8001 if (!(but->block->handle && but->block->handle->popup)) {
8002 if (button_modal_state(state)) {
8003 if (!button_modal_state(data->state)) {
8004 WM_event_add_ui_handler(
8005 C, &data->window->modalhandlers, ui_handler_region_menu, NULL, data, 0);
8006 }
8007 }
8008 else {
8009 if (button_modal_state(data->state)) {
8010 /* true = postpone free */
8011 WM_event_remove_ui_handler(
8012 &data->window->modalhandlers, ui_handler_region_menu, NULL, data, true);
8013 }
8014 }
8015 }
8016
8017 /* wait for mousemove to enable drag */
8018 if (state == BUTTON_STATE_WAIT_DRAG) {
8019 but->flag &= ~UI_SELECT;
8020 }
8021
8022 data->state = state;
8023
8024 if (state != BUTTON_STATE_EXIT) {
8025 /* When objects for eg. are removed, running ui_but_update() can access
8026 * the removed data - so disable update on exit. Also in case of
8027 * highlight when not in a popup menu, we remove because data used in
8028 * button below popup might have been removed by action of popup. Needs
8029 * a more reliable solution... */
8030 if (state != BUTTON_STATE_HIGHLIGHT || (but->block->flag & UI_BLOCK_LOOP)) {
8031 ui_but_update(but);
8032 }
8033 }
8034
8035 /* redraw */
8036 ED_region_tag_redraw_no_rebuild(data->region);
8037 }
8038
button_activate_init(bContext * C,ARegion * region,uiBut * but,uiButtonActivateType type)8039 static void button_activate_init(bContext *C,
8040 ARegion *region,
8041 uiBut *but,
8042 uiButtonActivateType type)
8043 {
8044 /* Only ever one active button! */
8045 BLI_assert(ui_region_find_active_but(region) == NULL);
8046
8047 /* setup struct */
8048 uiHandleButtonData *data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData");
8049 data->wm = CTX_wm_manager(C);
8050 data->window = CTX_wm_window(C);
8051 data->area = CTX_wm_area(C);
8052 BLI_assert(region != NULL);
8053 data->region = region;
8054
8055 #ifdef USE_CONT_MOUSE_CORRECT
8056 copy_v2_fl(data->ungrab_mval, FLT_MAX);
8057 #endif
8058
8059 if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_CURVEPROFILE, UI_BTYPE_SEARCH_MENU)) {
8060 /* XXX curve is temp */
8061 }
8062 else {
8063 if ((but->flag & UI_BUT_UPDATE_DELAY) == 0) {
8064 data->interactive = true;
8065 }
8066 }
8067
8068 data->state = BUTTON_STATE_INIT;
8069
8070 /* activate button */
8071 but->flag |= UI_ACTIVE;
8072
8073 but->active = data;
8074
8075 /* we disable auto_open in the block after a threshold, because we still
8076 * want to allow auto opening adjacent menus even if no button is activated
8077 * in between going over to the other button, but only for a short while */
8078 if (type == BUTTON_ACTIVATE_OVER && but->block->auto_open == true) {
8079 if (but->block->auto_open_last + BUTTON_AUTO_OPEN_THRESH < PIL_check_seconds_timer()) {
8080 but->block->auto_open = false;
8081 }
8082 }
8083
8084 if (type == BUTTON_ACTIVATE_OVER) {
8085 data->used_mouse = true;
8086 }
8087 button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
8088
8089 /* activate right away */
8090 if (but->flag & UI_BUT_IMMEDIATE) {
8091 if (but->type == UI_BTYPE_HOTKEY_EVENT) {
8092 button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT);
8093 }
8094 /* .. more to be added here */
8095 }
8096
8097 if (type == BUTTON_ACTIVATE_OPEN) {
8098 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
8099
8100 /* activate first button in submenu */
8101 if (data->menu && data->menu->region) {
8102 ARegion *subar = data->menu->region;
8103 uiBlock *subblock = subar->uiblocks.first;
8104 uiBut *subbut;
8105
8106 if (subblock) {
8107 subbut = ui_but_first(subblock);
8108
8109 if (subbut) {
8110 ui_handle_button_activate(C, subar, subbut, BUTTON_ACTIVATE);
8111 }
8112 }
8113 }
8114 }
8115 else if (type == BUTTON_ACTIVATE_TEXT_EDITING) {
8116 button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
8117 }
8118 else if (type == BUTTON_ACTIVATE_APPLY) {
8119 button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH);
8120 }
8121
8122 if (but->type == UI_BTYPE_GRIP) {
8123 const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
8124 WM_cursor_modal_set(data->window, horizontal ? WM_CURSOR_X_MOVE : WM_CURSOR_Y_MOVE);
8125 }
8126 else if (but->type == UI_BTYPE_NUM) {
8127 ui_numedit_set_active(but);
8128 }
8129
8130 if (UI_but_has_tooltip_label(but)) {
8131 /* Show a label for this button. */
8132 bScreen *screen = WM_window_get_active_screen(data->window);
8133 if ((PIL_check_seconds_timer() - WM_tooltip_time_closed()) < 0.1) {
8134 WM_tooltip_immediate_init(C, CTX_wm_window(C), data->area, region, ui_but_tooltip_init);
8135 if (screen->tool_tip) {
8136 screen->tool_tip->pass = 1;
8137 }
8138 }
8139 }
8140 }
8141
button_activate_exit(bContext * C,uiBut * but,uiHandleButtonData * data,const bool mousemove,const bool onfree)8142 static void button_activate_exit(
8143 bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree)
8144 {
8145 wmWindow *win = data->window;
8146 uiBlock *block = but->block;
8147
8148 if (but->type == UI_BTYPE_GRIP) {
8149 WM_cursor_modal_restore(win);
8150 }
8151
8152 /* ensure we are in the exit state */
8153 if (data->state != BUTTON_STATE_EXIT) {
8154 button_activate_state(C, but, BUTTON_STATE_EXIT);
8155 }
8156
8157 /* apply the button action or value */
8158 if (!onfree) {
8159 ui_apply_but(C, block, but, data, false);
8160 }
8161
8162 #ifdef USE_DRAG_MULTINUM
8163 if (data->multi_data.has_mbuts) {
8164 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
8165 if (bt->flag & UI_BUT_DRAG_MULTI) {
8166 bt->flag &= ~UI_BUT_DRAG_MULTI;
8167
8168 if (!data->cancel) {
8169 ui_apply_but_autokey(C, bt);
8170 }
8171 }
8172 }
8173
8174 ui_multibut_free(data, block);
8175 }
8176 #endif
8177
8178 /* if this button is in a menu, this will set the button return
8179 * value to the button value and the menu return value to ok, the
8180 * menu return value will be picked up and the menu will close */
8181 if (block->handle && !(block->flag & UI_BLOCK_KEEP_OPEN)) {
8182 if (!data->cancel || data->escapecancel) {
8183 uiPopupBlockHandle *menu;
8184
8185 menu = block->handle;
8186 menu->butretval = data->retval;
8187 menu->menuretval = (data->cancel) ? UI_RETURN_CANCEL : UI_RETURN_OK;
8188 }
8189 }
8190
8191 if (!onfree && !data->cancel) {
8192 /* autokey & undo push */
8193 ui_apply_but_undo(but);
8194 ui_apply_but_autokey(C, but);
8195
8196 #ifdef USE_ALLSELECT
8197 {
8198 /* only RNA from this button is used */
8199 uiBut but_temp = *but;
8200 uiSelectContextStore *selctx_data = &data->select_others;
8201 for (int i = 0; i < selctx_data->elems_len; i++) {
8202 uiSelectContextElem *other = &selctx_data->elems[i];
8203 but_temp.rnapoin = other->ptr;
8204 ui_apply_but_autokey(C, &but_temp);
8205 }
8206 }
8207 #endif
8208
8209 /* popup menu memory */
8210 if (block->flag & UI_BLOCK_POPUP_MEMORY) {
8211 ui_popup_menu_memory_set(block, but);
8212 }
8213
8214 if (U.runtime.is_dirty == false) {
8215 ui_but_update_preferences_dirty(but);
8216 }
8217 }
8218
8219 /* disable tooltips until mousemove + last active flag */
8220 LISTBASE_FOREACH (uiBlock *, block_iter, &data->region->uiblocks) {
8221 LISTBASE_FOREACH (uiBut *, bt, &block_iter->buttons) {
8222 bt->flag &= ~UI_BUT_LAST_ACTIVE;
8223 }
8224
8225 block_iter->tooltipdisabled = 1;
8226 }
8227
8228 ui_blocks_set_tooltips(data->region, false);
8229
8230 /* clean up */
8231 if (data->str) {
8232 MEM_freeN(data->str);
8233 }
8234 if (data->origstr) {
8235 MEM_freeN(data->origstr);
8236 }
8237
8238 #ifdef USE_ALLSELECT
8239 ui_selectcontext_end(but, &data->select_others);
8240 #endif
8241
8242 if (data->changed_cursor) {
8243 WM_cursor_modal_restore(data->window);
8244 }
8245
8246 /* redraw and refresh (for popups) */
8247 ED_region_tag_redraw_no_rebuild(data->region);
8248 ED_region_tag_refresh_ui(data->region);
8249
8250 /* clean up button */
8251 if (but->active) {
8252 MEM_freeN(but->active);
8253 but->active = NULL;
8254 }
8255
8256 but->flag &= ~(UI_ACTIVE | UI_SELECT);
8257 but->flag |= UI_BUT_LAST_ACTIVE;
8258 if (!onfree) {
8259 ui_but_update(but);
8260 }
8261
8262 /* adds empty mousemove in queue for re-init handler, in case mouse is
8263 * still over a button. We cannot just check for this ourselves because
8264 * at this point the mouse may be over a button in another region */
8265 if (mousemove) {
8266 WM_event_add_mousemove(CTX_wm_window(C));
8267 }
8268 }
8269
ui_but_active_free(const bContext * C,uiBut * but)8270 void ui_but_active_free(const bContext *C, uiBut *but)
8271 {
8272 /* this gets called when the button somehow disappears while it is still
8273 * active, this is bad for user interaction, but we need to handle this
8274 * case cleanly anyway in case it happens */
8275 if (but->active) {
8276 uiHandleButtonData *data = but->active;
8277 data->cancel = true;
8278 button_activate_exit((bContext *)C, but, data, false, true);
8279 }
8280 }
8281
8282 /* returns the active button with an optional checking function */
ui_context_button_active(ARegion * region,bool (* but_check_cb)(uiBut *))8283 static uiBut *ui_context_button_active(ARegion *region, bool (*but_check_cb)(uiBut *))
8284 {
8285 uiBut *but_found = NULL;
8286
8287 while (region) {
8288 uiBut *activebut = NULL;
8289
8290 /* find active button */
8291 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
8292 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
8293 if (but->active) {
8294 activebut = but;
8295 }
8296 else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) {
8297 activebut = but;
8298 }
8299 }
8300 }
8301
8302 if (activebut && (but_check_cb == NULL || but_check_cb(activebut))) {
8303 uiHandleButtonData *data = activebut->active;
8304
8305 but_found = activebut;
8306
8307 /* recurse into opened menu, like colorpicker case */
8308 if (data && data->menu && (region != data->menu->region)) {
8309 region = data->menu->region;
8310 }
8311 else {
8312 return but_found;
8313 }
8314 }
8315 else {
8316 /* no active button */
8317 return but_found;
8318 }
8319 }
8320
8321 return but_found;
8322 }
8323
ui_context_rna_button_active_test(uiBut * but)8324 static bool ui_context_rna_button_active_test(uiBut *but)
8325 {
8326 return (but->rnapoin.data != NULL);
8327 }
ui_context_rna_button_active(const bContext * C)8328 static uiBut *ui_context_rna_button_active(const bContext *C)
8329 {
8330 return ui_context_button_active(CTX_wm_region(C), ui_context_rna_button_active_test);
8331 }
8332
UI_context_active_but_get(const bContext * C)8333 uiBut *UI_context_active_but_get(const bContext *C)
8334 {
8335 return ui_context_button_active(CTX_wm_region(C), NULL);
8336 }
8337
8338 /*
8339 * Version of #UI_context_active_get() that uses the result of #CTX_wm_menu()
8340 * if set. Does not traverse into parent menus, which may be wanted in some
8341 * cases.
8342 */
UI_context_active_but_get_respect_menu(const bContext * C)8343 uiBut *UI_context_active_but_get_respect_menu(const bContext *C)
8344 {
8345 ARegion *ar_menu = CTX_wm_menu(C);
8346 return ui_context_button_active(ar_menu ? ar_menu : CTX_wm_region(C), NULL);
8347 }
8348
UI_region_active_but_get(ARegion * region)8349 uiBut *UI_region_active_but_get(ARegion *region)
8350 {
8351 return ui_context_button_active(region, NULL);
8352 }
8353
UI_region_but_find_rect_over(const ARegion * region,const rcti * rect_px)8354 uiBut *UI_region_but_find_rect_over(const ARegion *region, const rcti *rect_px)
8355 {
8356 return ui_but_find_rect_over(region, rect_px);
8357 }
8358
UI_region_block_find_mouse_over(const struct ARegion * region,const int xy[2],bool only_clip)8359 uiBlock *UI_region_block_find_mouse_over(const struct ARegion *region,
8360 const int xy[2],
8361 bool only_clip)
8362 {
8363 return ui_block_find_mouse_over_ex(region, xy[0], xy[1], only_clip);
8364 }
8365
8366 /**
8367 * Version of #UI_context_active_but_get that also returns RNA property info.
8368 * Helper function for insert keyframe, reset to default, etc operators.
8369 *
8370 * \return active button, NULL if none found or if it doesn't contain valid RNA data.
8371 */
UI_context_active_but_prop_get(const bContext * C,struct PointerRNA * r_ptr,struct PropertyRNA ** r_prop,int * r_index)8372 uiBut *UI_context_active_but_prop_get(const bContext *C,
8373 struct PointerRNA *r_ptr,
8374 struct PropertyRNA **r_prop,
8375 int *r_index)
8376 {
8377 uiBut *activebut = ui_context_rna_button_active(C);
8378
8379 if (activebut && activebut->rnapoin.data) {
8380 *r_ptr = activebut->rnapoin;
8381 *r_prop = activebut->rnaprop;
8382 *r_index = activebut->rnaindex;
8383 }
8384 else {
8385 memset(r_ptr, 0, sizeof(*r_ptr));
8386 *r_prop = NULL;
8387 *r_index = 0;
8388 }
8389
8390 return activebut;
8391 }
8392
UI_context_active_but_prop_handle(bContext * C)8393 void UI_context_active_but_prop_handle(bContext *C)
8394 {
8395 uiBut *activebut = ui_context_rna_button_active(C);
8396 if (activebut) {
8397 /* TODO, look into a better way to handle the button change
8398 * currently this is mainly so reset defaults works for the
8399 * operator redo panel - campbell */
8400 uiBlock *block = activebut->block;
8401 if (block->handle_func) {
8402 block->handle_func(C, block->handle_func_arg, activebut->retval);
8403 }
8404 }
8405 }
8406
UI_context_active_but_clear(bContext * C,wmWindow * win,ARegion * region)8407 void UI_context_active_but_clear(bContext *C, wmWindow *win, ARegion *region)
8408 {
8409 wm_event_handler_ui_cancel_ex(C, win, region, false);
8410 }
8411
UI_context_active_operator_get(const struct bContext * C)8412 wmOperator *UI_context_active_operator_get(const struct bContext *C)
8413 {
8414 ARegion *region_ctx = CTX_wm_region(C);
8415
8416 /* background mode */
8417 if (region_ctx == NULL) {
8418 return NULL;
8419 }
8420
8421 /* scan active regions ui */
8422 LISTBASE_FOREACH (uiBlock *, block, ®ion_ctx->uiblocks) {
8423 if (block->ui_operator) {
8424 return block->ui_operator;
8425 }
8426 }
8427
8428 /* scan popups */
8429 {
8430 bScreen *screen = CTX_wm_screen(C);
8431
8432 LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
8433 if (region == region_ctx) {
8434 continue;
8435 }
8436 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
8437 if (block->ui_operator) {
8438 return block->ui_operator;
8439 }
8440 }
8441 }
8442 }
8443
8444 return NULL;
8445 }
8446
8447 /* helper function for insert keyframe, reset to default, etc operators */
UI_context_update_anim_flag(const bContext * C)8448 void UI_context_update_anim_flag(const bContext *C)
8449 {
8450 Scene *scene = CTX_data_scene(C);
8451 ARegion *region = CTX_wm_region(C);
8452 struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
8453 const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(
8454 depsgraph, (scene) ? scene->r.cfra : 0.0f);
8455
8456 while (region) {
8457 /* find active button */
8458 uiBut *activebut = NULL;
8459
8460 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
8461 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
8462 ui_but_anim_flag(but, &anim_eval_context);
8463 ui_but_override_flag(CTX_data_main(C), but);
8464 if (UI_but_is_decorator(but)) {
8465 ui_but_anim_decorate_update_from_flag((uiButDecorator *)but);
8466 }
8467
8468 ED_region_tag_redraw(region);
8469
8470 if (but->active) {
8471 activebut = but;
8472 }
8473 else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) {
8474 activebut = but;
8475 }
8476 }
8477 }
8478
8479 if (activebut) {
8480 /* always recurse into opened menu, so all buttons update (like colorpicker) */
8481 uiHandleButtonData *data = activebut->active;
8482 if (data && data->menu) {
8483 region = data->menu->region;
8484 }
8485 else {
8486 return;
8487 }
8488 }
8489 else {
8490 /* no active button */
8491 return;
8492 }
8493 }
8494 }
8495
8496 /** \} */
8497
8498 /* -------------------------------------------------------------------- */
8499 /** \name Button Activation Handling
8500 * \{ */
8501
ui_but_find_open_event(ARegion * region,const wmEvent * event)8502 static uiBut *ui_but_find_open_event(ARegion *region, const wmEvent *event)
8503 {
8504 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
8505 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
8506 if (but == event->customdata) {
8507 return but;
8508 }
8509 }
8510 }
8511 return NULL;
8512 }
8513
ui_handle_button_over(bContext * C,const wmEvent * event,ARegion * region)8514 static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *region)
8515 {
8516 if (event->type == MOUSEMOVE) {
8517 uiBut *but = ui_but_find_mouse_over(region, event);
8518 if (but) {
8519 button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8520
8521 if (event->alt && but->active) {
8522 /* display tooltips if holding alt on mouseover when tooltips are off in prefs */
8523 but->active->tooltip_force = true;
8524 }
8525 }
8526 }
8527 else if (event->type == EVT_BUT_OPEN) {
8528 uiBut *but = ui_but_find_open_event(region, event);
8529 if (but) {
8530 button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8531 ui_do_button(C, but->block, but, event);
8532 }
8533 }
8534
8535 return WM_UI_HANDLER_CONTINUE;
8536 }
8537
8538 /**
8539 * Exported to interface.c: #UI_but_active_only()
8540 * \note The region is only for the button.
8541 * The context needs to be set by the caller.
8542 */
ui_but_activate_event(bContext * C,ARegion * region,uiBut * but)8543 void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but)
8544 {
8545 wmWindow *win = CTX_wm_window(C);
8546
8547 button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8548
8549 wmEvent event;
8550 wm_event_init_from_window(win, &event);
8551 event.type = EVT_BUT_OPEN;
8552 event.val = KM_PRESS;
8553 event.is_repeat = false;
8554 event.customdata = but;
8555 event.customdatafree = false;
8556
8557 ui_do_button(C, but->block, but, &event);
8558 }
8559
8560 /**
8561 * Simulate moving the mouse over a button (or navigating to it with arrow keys).
8562 *
8563 * exported so menus can start with a highlighted button,
8564 * even if the mouse isnt over it
8565 */
ui_but_activate_over(bContext * C,ARegion * region,uiBut * but)8566 void ui_but_activate_over(bContext *C, ARegion *region, uiBut *but)
8567 {
8568 button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8569 }
8570
ui_but_execute_begin(struct bContext * UNUSED (C),struct ARegion * region,uiBut * but,void ** active_back)8571 void ui_but_execute_begin(struct bContext *UNUSED(C),
8572 struct ARegion *region,
8573 uiBut *but,
8574 void **active_back)
8575 {
8576 BLI_assert(region != NULL);
8577 BLI_assert(BLI_findindex(®ion->uiblocks, but->block) != -1);
8578 /* note: ideally we would not have to change 'but->active' however
8579 * some functions we call don't use data (as they should be doing) */
8580 uiHandleButtonData *data;
8581 *active_back = but->active;
8582 data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData_Fake");
8583 but->active = data;
8584 BLI_assert(region != NULL);
8585 data->region = region;
8586 }
8587
ui_but_execute_end(struct bContext * C,struct ARegion * UNUSED (region),uiBut * but,void * active_back)8588 void ui_but_execute_end(struct bContext *C,
8589 struct ARegion *UNUSED(region),
8590 uiBut *but,
8591 void *active_back)
8592 {
8593 ui_apply_but(C, but->block, but, but->active, true);
8594
8595 if ((but->flag & UI_BUT_DRAG_MULTI) == 0) {
8596 ui_apply_but_autokey(C, but);
8597 }
8598 /* use onfree event so undo is handled by caller and apply is already done above */
8599 button_activate_exit((bContext *)C, but, but->active, false, true);
8600 but->active = active_back;
8601 }
8602
ui_handle_button_activate(bContext * C,ARegion * region,uiBut * but,uiButtonActivateType type)8603 static void ui_handle_button_activate(bContext *C,
8604 ARegion *region,
8605 uiBut *but,
8606 uiButtonActivateType type)
8607 {
8608 uiBut *oldbut = ui_region_find_active_but(region);
8609 if (oldbut) {
8610 uiHandleButtonData *data = oldbut->active;
8611 data->cancel = true;
8612 button_activate_exit(C, oldbut, data, false, false);
8613 }
8614
8615 button_activate_init(C, region, but, type);
8616 }
8617
8618 /**
8619 * Use for key accelerator or default key to activate the button even if its not active.
8620 */
ui_handle_button_activate_by_type(bContext * C,ARegion * region,uiBut * but)8621 static bool ui_handle_button_activate_by_type(bContext *C, ARegion *region, uiBut *but)
8622 {
8623 if (but->type == UI_BTYPE_BUT_MENU) {
8624 /* mainly for operator buttons */
8625 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_APPLY);
8626 }
8627 else if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) {
8628 /* open sub-menus (like right arrow key) */
8629 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN);
8630 }
8631 else if (but->type == UI_BTYPE_MENU) {
8632 /* activate menu items */
8633 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE);
8634 }
8635 else {
8636 #ifdef DEBUG
8637 printf("%s: error, unhandled type: %u\n", __func__, but->type);
8638 #endif
8639 return false;
8640 }
8641 return true;
8642 }
8643
8644 /** \} */
8645
8646 /* -------------------------------------------------------------------- */
8647 /** \name Handle Events for Activated Buttons
8648 * \{ */
8649
ui_button_value_default(uiBut * but,double * r_value)8650 static bool ui_button_value_default(uiBut *but, double *r_value)
8651 {
8652 if (but->rnaprop != NULL && ui_but_is_rna_valid(but)) {
8653 const int type = RNA_property_type(but->rnaprop);
8654 if (ELEM(type, PROP_FLOAT, PROP_INT)) {
8655 double default_value;
8656 switch (type) {
8657 case PROP_INT:
8658 if (RNA_property_array_check(but->rnaprop)) {
8659 default_value = (double)RNA_property_int_get_default_index(
8660 &but->rnapoin, but->rnaprop, but->rnaindex);
8661 }
8662 else {
8663 default_value = (double)RNA_property_int_get_default(&but->rnapoin, but->rnaprop);
8664 }
8665 break;
8666 case PROP_FLOAT:
8667 if (RNA_property_array_check(but->rnaprop)) {
8668 default_value = (double)RNA_property_float_get_default_index(
8669 &but->rnapoin, but->rnaprop, but->rnaindex);
8670 }
8671 else {
8672 default_value = (double)RNA_property_float_get_default(&but->rnapoin, but->rnaprop);
8673 }
8674 break;
8675 }
8676 *r_value = default_value;
8677 return true;
8678 }
8679 }
8680 return false;
8681 }
8682
ui_handle_button_event(bContext * C,const wmEvent * event,uiBut * but)8683 static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
8684 {
8685 uiHandleButtonData *data = but->active;
8686 const uiHandleButtonState state_orig = data->state;
8687
8688 uiBlock *block = but->block;
8689 ARegion *region = data->region;
8690
8691 int retval = WM_UI_HANDLER_CONTINUE;
8692
8693 if (data->state == BUTTON_STATE_HIGHLIGHT) {
8694 switch (event->type) {
8695 case WINDEACTIVATE:
8696 case EVT_BUT_CANCEL:
8697 data->cancel = true;
8698 button_activate_state(C, but, BUTTON_STATE_EXIT);
8699 break;
8700 #ifdef USE_UI_POPOVER_ONCE
8701 case LEFTMOUSE: {
8702 if (event->val == KM_RELEASE) {
8703 if (block->flag & UI_BLOCK_POPOVER_ONCE) {
8704 if (!(but->flag & UI_BUT_DISABLED)) {
8705 if (ui_but_is_popover_once_compat(but)) {
8706 data->cancel = false;
8707 button_activate_state(C, but, BUTTON_STATE_EXIT);
8708 retval = WM_UI_HANDLER_BREAK;
8709 /* Cancel because this `but` handles all events and we don't want
8710 * the parent button's update function to do anything.
8711 *
8712 * Causes issues with buttons defined by #uiItemFullR_with_popover. */
8713 block->handle->menuretval = UI_RETURN_CANCEL;
8714 }
8715 else if (ui_but_is_editable_as_text(but)) {
8716 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_TEXT_EDITING);
8717 retval = WM_UI_HANDLER_BREAK;
8718 }
8719 }
8720 }
8721 }
8722 break;
8723 }
8724 #endif
8725 case MOUSEMOVE: {
8726 uiBut *but_other = ui_but_find_mouse_over(region, event);
8727 bool exit = false;
8728
8729 /* always deactivate button for pie menus,
8730 * else moving to blank space will leave activated */
8731 if ((!ui_block_is_menu(block) || ui_block_is_pie_menu(block)) &&
8732 !ui_but_contains_point_px(but, region, event->x, event->y)) {
8733 exit = true;
8734 }
8735 else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) {
8736 exit = true;
8737 }
8738
8739 if (exit) {
8740 data->cancel = true;
8741 button_activate_state(C, but, BUTTON_STATE_EXIT);
8742 }
8743 else if (event->x != event->prevx || event->y != event->prevy) {
8744 /* re-enable tooltip on mouse move */
8745 ui_blocks_set_tooltips(region, true);
8746 button_tooltip_timer_reset(C, but);
8747 }
8748
8749 /* Update extra icons states. */
8750 ui_do_but_extra_operator_icons_mousemove(but, data, event);
8751
8752 break;
8753 }
8754 case TIMER: {
8755 /* Handle menu auto open timer. */
8756 if (event->customdata == data->autoopentimer) {
8757 WM_event_remove_timer(data->wm, data->window, data->autoopentimer);
8758 data->autoopentimer = NULL;
8759
8760 if (ui_but_contains_point_px(but, region, event->x, event->y) || but->active) {
8761 button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
8762 }
8763 }
8764
8765 break;
8766 }
8767 /* XXX hardcoded keymap check... but anyway,
8768 * while view changes, tooltips should be removed */
8769 case WHEELUPMOUSE:
8770 case WHEELDOWNMOUSE:
8771 case MIDDLEMOUSE:
8772 case MOUSEPAN:
8773 UI_but_tooltip_timer_remove(C, but);
8774 ATTR_FALLTHROUGH;
8775 default:
8776 break;
8777 }
8778
8779 /* handle button type specific events */
8780 retval = ui_do_button(C, block, but, event);
8781 }
8782 else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
8783 switch (event->type) {
8784 case WINDEACTIVATE:
8785 data->cancel = true;
8786 button_activate_state(C, but, BUTTON_STATE_EXIT);
8787 break;
8788
8789 case TIMER: {
8790 if (event->customdata == data->hold_action_timer) {
8791 if (true) {
8792 data->cancel = true;
8793 button_activate_state(C, but, BUTTON_STATE_EXIT);
8794 }
8795 else {
8796 /* Do this so we can still mouse-up, closing the menu and running the button.
8797 * This is nice to support but there are times when the button gets left pressed.
8798 * Keep disabled for now. */
8799 WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
8800 data->hold_action_timer = NULL;
8801 }
8802 retval = WM_UI_HANDLER_CONTINUE;
8803 but->hold_func(C, data->region, but);
8804 }
8805 break;
8806 }
8807 case MOUSEMOVE: {
8808 /* deselect the button when moving the mouse away */
8809 /* also de-activate for buttons that only show highlights */
8810 if (ui_but_contains_point_px(but, region, event->x, event->y)) {
8811
8812 /* Drag on a hold button (used in the toolbar) now opens it immediately. */
8813 if (data->hold_action_timer) {
8814 if (but->flag & UI_SELECT) {
8815 if (len_manhattan_v2v2_int(&event->x, &event->prevx) <=
8816 WM_EVENT_CURSOR_MOTION_THRESHOLD) {
8817 /* pass */
8818 }
8819 else {
8820 WM_event_remove_timer(data->wm, data->window, data->hold_action_timer);
8821 data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, 0.0f);
8822 }
8823 }
8824 }
8825
8826 if (!(but->flag & UI_SELECT)) {
8827 but->flag |= (UI_SELECT | UI_ACTIVE);
8828 data->cancel = false;
8829 ED_region_tag_redraw_no_rebuild(data->region);
8830 }
8831 }
8832 else {
8833 if (but->flag & UI_SELECT) {
8834 but->flag &= ~(UI_SELECT | UI_ACTIVE);
8835 data->cancel = true;
8836 ED_region_tag_redraw_no_rebuild(data->region);
8837 }
8838 }
8839 break;
8840 }
8841 default:
8842 /* otherwise catch mouse release event */
8843 ui_do_button(C, block, but, event);
8844 break;
8845 }
8846
8847 retval = WM_UI_HANDLER_BREAK;
8848 }
8849 else if (data->state == BUTTON_STATE_WAIT_FLASH) {
8850 switch (event->type) {
8851 case TIMER: {
8852 if (event->customdata == data->flashtimer) {
8853 button_activate_state(C, but, BUTTON_STATE_EXIT);
8854 }
8855 break;
8856 }
8857 }
8858
8859 retval = WM_UI_HANDLER_CONTINUE;
8860 }
8861 else if (data->state == BUTTON_STATE_MENU_OPEN) {
8862 /* check for exit because of mouse-over another button */
8863 switch (event->type) {
8864 case MOUSEMOVE: {
8865 uiBut *bt;
8866
8867 if (data->menu && data->menu->region) {
8868 if (ui_region_contains_point_px(data->menu->region, event->x, event->y)) {
8869 break;
8870 }
8871 }
8872
8873 bt = ui_but_find_mouse_over(region, event);
8874
8875 if (bt && bt->active != data) {
8876 if (but->type != UI_BTYPE_COLOR) { /* exception */
8877 data->cancel = true;
8878 }
8879 button_activate_state(C, but, BUTTON_STATE_EXIT);
8880 }
8881 break;
8882 }
8883 case RIGHTMOUSE: {
8884 if (event->val == KM_PRESS) {
8885 uiBut *bt = ui_but_find_mouse_over(region, event);
8886 if (bt && bt->active == data) {
8887 button_activate_state(C, bt, BUTTON_STATE_HIGHLIGHT);
8888 }
8889 }
8890 break;
8891 }
8892 }
8893
8894 ui_do_button(C, block, but, event);
8895 retval = WM_UI_HANDLER_CONTINUE;
8896 }
8897 else {
8898 retval = ui_do_button(C, block, but, event);
8899 // retval = WM_UI_HANDLER_BREAK; XXX why ?
8900 }
8901
8902 /* may have been re-allocated above (eyedropper for eg) */
8903 data = but->active;
8904 if (data && data->state == BUTTON_STATE_EXIT) {
8905 uiBut *post_but = data->postbut;
8906 const uiButtonActivateType post_type = data->posttype;
8907
8908 /* Reset the button value when empty text is typed. */
8909 if ((data->cancel == false) && (data->str != NULL) && (data->str[0] == '\0') &&
8910 (but->rnaprop && ELEM(RNA_property_type(but->rnaprop), PROP_FLOAT, PROP_INT))) {
8911 MEM_SAFE_FREE(data->str);
8912 ui_button_value_default(but, &data->value);
8913
8914 #ifdef USE_DRAG_MULTINUM
8915 if (data->multi_data.mbuts) {
8916 for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) {
8917 uiButMultiState *state = l->link;
8918 uiBut *but_iter = state->but;
8919 double default_value;
8920
8921 if (ui_button_value_default(but_iter, &default_value)) {
8922 ui_but_value_set(but_iter, default_value);
8923 }
8924 }
8925 }
8926 data->multi_data.skip = true;
8927 #endif
8928 }
8929
8930 button_activate_exit(C, but, data, (post_but == NULL), false);
8931
8932 /* for jumping to the next button with tab while text editing */
8933 if (post_but) {
8934 /* The post_but still has previous ranges (without the changes in active button considered),
8935 * needs refreshing the ranges. */
8936 ui_but_range_set_soft(post_but);
8937 ui_but_range_set_hard(post_but);
8938
8939 button_activate_init(C, region, post_but, post_type);
8940 }
8941 else if (!((event->type == EVT_BUT_CANCEL) && (event->val == 1))) {
8942 /* XXX issue is because WM_event_add_mousemove(wm) is a bad hack and not reliable,
8943 * if that gets coded better this bypass can go away too.
8944 *
8945 * This is needed to make sure if a button was active,
8946 * it stays active while the mouse is over it.
8947 * This avoids adding mousemoves, see: T33466. */
8948 if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT, BUTTON_STATE_WAIT_DRAG)) {
8949 if (ui_but_find_mouse_over(region, event) == but) {
8950 button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
8951 }
8952 }
8953 }
8954 }
8955
8956 return retval;
8957 }
8958
ui_handle_list_event(bContext * C,const wmEvent * event,ARegion * region,uiBut * listbox)8959 static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox)
8960 {
8961 int retval = WM_UI_HANDLER_CONTINUE;
8962 int type = event->type, val = event->val;
8963 bool redraw = false;
8964
8965 uiList *ui_list = listbox->custom_data;
8966 if (!ui_list || !ui_list->dyn_data) {
8967 return retval;
8968 }
8969 uiListDyn *dyn_data = ui_list->dyn_data;
8970
8971 int mx = event->x;
8972 int my = event->y;
8973 ui_window_to_block(region, listbox->block, &mx, &my);
8974
8975 /* Convert pan to scroll-wheel. */
8976 if (type == MOUSEPAN) {
8977 ui_pan_to_scroll(event, &type, &val);
8978
8979 /* If type still is mouse-pan, we call it handled, since delta-y accumulate. */
8980 /* also see wm_event_system.c do_wheel_ui hack */
8981 if (type == MOUSEPAN) {
8982 retval = WM_UI_HANDLER_BREAK;
8983 }
8984 }
8985
8986 if (val == KM_PRESS) {
8987 if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) &&
8988 !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) ||
8989 ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl &&
8990 !IS_EVENT_MOD(event, shift, alt, oskey)))) {
8991 const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop);
8992 int value, min, max, inc;
8993
8994 /* activate up/down the list */
8995 value = value_orig;
8996 if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) {
8997 inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? 1 : -1;
8998 }
8999 else {
9000 inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? -1 : 1;
9001 }
9002
9003 if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) {
9004 /* If we have a display order different from
9005 * collection order, we have some work! */
9006 int *org_order = MEM_mallocN(dyn_data->items_shown * sizeof(int), __func__);
9007 const int *new_order = dyn_data->items_filter_neworder;
9008 int org_idx = -1, len = dyn_data->items_len;
9009 int current_idx = -1;
9010 const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE;
9011
9012 for (int i = 0; i < len; i++) {
9013 if (!dyn_data->items_filter_flags ||
9014 ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) {
9015 org_order[new_order ? new_order[++org_idx] : ++org_idx] = i;
9016 if (i == value) {
9017 current_idx = new_order ? new_order[org_idx] : org_idx;
9018 }
9019 }
9020 else if (i == value && org_idx >= 0) {
9021 current_idx = -(new_order ? new_order[org_idx] : org_idx) - 1;
9022 }
9023 }
9024 /* Now, org_order maps displayed indices to real indices,
9025 * and current_idx either contains the displayed index of active value (positive),
9026 * or its more-nearest one (negated).
9027 */
9028 if (current_idx < 0) {
9029 current_idx = (current_idx * -1) + (inc < 0 ? inc : inc - 1);
9030 }
9031 else {
9032 current_idx += inc;
9033 }
9034 CLAMP(current_idx, 0, dyn_data->items_shown - 1);
9035 value = org_order[current_idx];
9036 MEM_freeN(org_order);
9037 }
9038 else {
9039 value += inc;
9040 }
9041
9042 CLAMP(value, 0, dyn_data->items_len - 1);
9043
9044 RNA_property_int_range(&listbox->rnapoin, listbox->rnaprop, &min, &max);
9045 CLAMP(value, min, max);
9046
9047 if (value != value_orig) {
9048 RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value);
9049 RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
9050
9051 ui_apply_but_undo(listbox);
9052
9053 ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
9054 redraw = true;
9055 }
9056 retval = WM_UI_HANDLER_BREAK;
9057 }
9058 else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->shift) {
9059 /* We now have proper grip, but keep this anyway! */
9060 if (ui_list->list_grip < (dyn_data->visual_height_min - UI_LIST_AUTO_SIZE_THRESHOLD)) {
9061 ui_list->list_grip = dyn_data->visual_height;
9062 }
9063 ui_list->list_grip += (type == WHEELUPMOUSE) ? -1 : 1;
9064
9065 ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
9066
9067 redraw = true;
9068 retval = WM_UI_HANDLER_BREAK;
9069 }
9070 else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
9071 if (dyn_data->height > dyn_data->visual_height) {
9072 /* list template will clamp */
9073 ui_list->list_scroll += (type == WHEELUPMOUSE) ? -1 : 1;
9074
9075 redraw = true;
9076 retval = WM_UI_HANDLER_BREAK;
9077 }
9078 }
9079 }
9080
9081 if (redraw) {
9082 ED_region_tag_redraw(region);
9083 ED_region_tag_refresh_ui(region);
9084 }
9085
9086 return retval;
9087 }
9088
ui_handle_button_return_submenu(bContext * C,const wmEvent * event,uiBut * but)9089 static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but)
9090 {
9091 uiHandleButtonData *data = but->active;
9092 uiPopupBlockHandle *menu = data->menu;
9093
9094 /* copy over return values from the closing menu */
9095 if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_UPDATE)) {
9096 if (but->type == UI_BTYPE_COLOR) {
9097 copy_v3_v3(data->vec, menu->retvec);
9098 }
9099 else if (but->type == UI_BTYPE_MENU) {
9100 data->value = menu->retvalue;
9101 }
9102 }
9103
9104 if (menu->menuretval & UI_RETURN_UPDATE) {
9105 if (data->interactive) {
9106 ui_apply_but(C, but->block, but, data, true);
9107 }
9108 else {
9109 ui_but_update(but);
9110 }
9111
9112 menu->menuretval = 0;
9113 }
9114
9115 /* now change button state or exit, which will close the submenu */
9116 if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_CANCEL)) {
9117 if (menu->menuretval != UI_RETURN_OK) {
9118 data->cancel = true;
9119 }
9120
9121 button_activate_exit(C, but, data, true, false);
9122 }
9123 else if (menu->menuretval & UI_RETURN_OUT) {
9124 if (event->type == MOUSEMOVE &&
9125 ui_but_contains_point_px(but, data->region, event->x, event->y)) {
9126 button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
9127 }
9128 else {
9129 if (ISKEYBOARD(event->type)) {
9130 /* keyboard menu hierarchy navigation, going back to previous level */
9131 but->active->used_mouse = false;
9132 button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
9133 }
9134 else {
9135 data->cancel = true;
9136 button_activate_exit(C, but, data, true, false);
9137 }
9138 }
9139 }
9140 }
9141
9142 /** \} */
9143
9144 /* -------------------------------------------------------------------- */
9145 /** \name Menu Towards (mouse motion logic)
9146 * \{ */
9147
9148 /**
9149 * Function used to prevent losing the open menu when using nested pull-downs,
9150 * when moving mouse towards the pull-down menu over other buttons that could
9151 * steal the highlight from the current button, only checks:
9152 *
9153 * - while mouse moves in triangular area defined old mouse position and
9154 * left/right side of new menu.
9155 * - only for 1 second.
9156 */
9157
ui_mouse_motion_towards_init_ex(uiPopupBlockHandle * menu,const int xy[2],const bool force)9158 static void ui_mouse_motion_towards_init_ex(uiPopupBlockHandle *menu,
9159 const int xy[2],
9160 const bool force)
9161 {
9162 BLI_assert(((uiBlock *)menu->region->uiblocks.first)->flag &
9163 (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER));
9164
9165 if (!menu->dotowards || force) {
9166 menu->dotowards = true;
9167 menu->towards_xy[0] = xy[0];
9168 menu->towards_xy[1] = xy[1];
9169
9170 if (force) {
9171 menu->towardstime = DBL_MAX; /* unlimited time */
9172 }
9173 else {
9174 menu->towardstime = PIL_check_seconds_timer();
9175 }
9176 }
9177 }
9178
ui_mouse_motion_towards_init(uiPopupBlockHandle * menu,const int xy[2])9179 static void ui_mouse_motion_towards_init(uiPopupBlockHandle *menu, const int xy[2])
9180 {
9181 ui_mouse_motion_towards_init_ex(menu, xy, false);
9182 }
9183
ui_mouse_motion_towards_reinit(uiPopupBlockHandle * menu,const int xy[2])9184 static void ui_mouse_motion_towards_reinit(uiPopupBlockHandle *menu, const int xy[2])
9185 {
9186 ui_mouse_motion_towards_init_ex(menu, xy, true);
9187 }
9188
ui_mouse_motion_towards_check(uiBlock * block,uiPopupBlockHandle * menu,const int xy[2],const bool use_wiggle_room)9189 static bool ui_mouse_motion_towards_check(uiBlock *block,
9190 uiPopupBlockHandle *menu,
9191 const int xy[2],
9192 const bool use_wiggle_room)
9193 {
9194 BLI_assert(block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER));
9195
9196 /* annoying fix for T36269, this is a bit odd but in fact works quite well
9197 * don't mouse-out of a menu if another menu has been created after it.
9198 * if this causes problems we could remove it and check on a different fix - campbell */
9199 if (menu->region->next) {
9200 /* am I the last menu (test) */
9201 ARegion *region = menu->region->next;
9202 do {
9203 uiBlock *block_iter = region->uiblocks.first;
9204 if (block_iter && ui_block_is_menu(block_iter)) {
9205 return true;
9206 }
9207 } while ((region = region->next));
9208 }
9209 /* annoying fix end! */
9210
9211 if (!menu->dotowards) {
9212 return false;
9213 }
9214
9215 float oldp[2] = {menu->towards_xy[0], menu->towards_xy[1]};
9216 const float newp[2] = {xy[0], xy[1]};
9217 if (len_squared_v2v2(oldp, newp) < (4.0f * 4.0f)) {
9218 return menu->dotowards;
9219 }
9220
9221 /* verify that we are moving towards one of the edges of the
9222 * menu block, in other words, in the triangle formed by the
9223 * initial mouse location and two edge points. */
9224 rctf rect_px;
9225 ui_block_to_window_rctf(menu->region, block, &rect_px, &block->rect);
9226
9227 const float margin = MENU_TOWARDS_MARGIN;
9228
9229 const float p1[2] = {rect_px.xmin - margin, rect_px.ymin - margin};
9230 const float p2[2] = {rect_px.xmax + margin, rect_px.ymin - margin};
9231 const float p3[2] = {rect_px.xmax + margin, rect_px.ymax + margin};
9232 const float p4[2] = {rect_px.xmin - margin, rect_px.ymax + margin};
9233
9234 /* allow for some wiggle room, if the user moves a few pixels away,
9235 * don't immediately quit (only for top level menus) */
9236 if (use_wiggle_room) {
9237 const float cent[2] = {BLI_rctf_cent_x(&rect_px), BLI_rctf_cent_y(&rect_px)};
9238 float delta[2];
9239
9240 sub_v2_v2v2(delta, oldp, cent);
9241 normalize_v2_length(delta, MENU_TOWARDS_WIGGLE_ROOM);
9242 add_v2_v2(oldp, delta);
9243 }
9244
9245 bool closer = (isect_point_tri_v2(newp, oldp, p1, p2) ||
9246 isect_point_tri_v2(newp, oldp, p2, p3) ||
9247 isect_point_tri_v2(newp, oldp, p3, p4) || isect_point_tri_v2(newp, oldp, p4, p1));
9248
9249 if (!closer) {
9250 menu->dotowards = false;
9251 }
9252
9253 /* 1 second timer */
9254 if (PIL_check_seconds_timer() - menu->towardstime > BUTTON_MOUSE_TOWARDS_THRESH) {
9255 menu->dotowards = false;
9256 }
9257
9258 return menu->dotowards;
9259 }
9260
9261 #ifdef USE_KEYNAV_LIMIT
ui_mouse_motion_keynav_init(struct uiKeyNavLock * keynav,const wmEvent * event)9262 static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event)
9263 {
9264 keynav->is_keynav = true;
9265 copy_v2_v2_int(keynav->event_xy, &event->x);
9266 }
9267 /**
9268 * Return true if keyinput isn't blocking mouse-motion,
9269 * or if the mouse-motion is enough to disable keyinput.
9270 */
ui_mouse_motion_keynav_test(struct uiKeyNavLock * keynav,const wmEvent * event)9271 static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event)
9272 {
9273 if (keynav->is_keynav &&
9274 (len_manhattan_v2v2_int(keynav->event_xy, &event->x) > BUTTON_KEYNAV_PX_LIMIT)) {
9275 keynav->is_keynav = false;
9276 }
9277
9278 return keynav->is_keynav;
9279 }
9280 #endif /* USE_KEYNAV_LIMIT */
9281
9282 /** \} */
9283
9284 /* -------------------------------------------------------------------- */
9285 /** \name Menu Scroll
9286 * \{ */
9287
ui_menu_scroll_test(uiBlock * block,int my)9288 static char ui_menu_scroll_test(uiBlock *block, int my)
9289 {
9290 if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) {
9291 if (block->flag & UI_BLOCK_CLIPTOP) {
9292 if (my > block->rect.ymax - UI_MENU_SCROLL_MOUSE) {
9293 return 't';
9294 }
9295 }
9296 if (block->flag & UI_BLOCK_CLIPBOTTOM) {
9297 if (my < block->rect.ymin + UI_MENU_SCROLL_MOUSE) {
9298 return 'b';
9299 }
9300 }
9301 }
9302 return 0;
9303 }
9304
ui_menu_scroll_apply_offset_y(ARegion * region,uiBlock * block,float dy)9305 static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float dy)
9306 {
9307 BLI_assert(dy != 0.0f);
9308
9309 if (ui_block_is_menu(block)) {
9310 if (dy < 0.0f) {
9311 /* Stop at top item, extra 0.5 UI_UNIT_Y makes it snap nicer. */
9312 float ymax = -FLT_MAX;
9313 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
9314 ymax = max_ff(ymax, bt->rect.ymax);
9315 }
9316 if (ymax + dy - UI_UNIT_Y * 0.5f < block->rect.ymax - UI_MENU_SCROLL_PAD) {
9317 dy = block->rect.ymax - ymax - UI_MENU_SCROLL_PAD;
9318 }
9319 }
9320 else {
9321 /* Stop at bottom item, extra 0.5 UI_UNIT_Y makes it snap nicer. */
9322 float ymin = FLT_MAX;
9323 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
9324 ymin = min_ff(ymin, bt->rect.ymin);
9325 }
9326 if (ymin + dy + UI_UNIT_Y * 0.5f > block->rect.ymin + UI_MENU_SCROLL_PAD) {
9327 dy = block->rect.ymin - ymin + UI_MENU_SCROLL_PAD;
9328 }
9329 }
9330 }
9331
9332 /* remember scroll offset for refreshes */
9333 block->handle->scrolloffset += dy;
9334
9335 /* apply scroll offset */
9336 LISTBASE_FOREACH (uiBut *, bt, &block->buttons) {
9337 bt->rect.ymin += dy;
9338 bt->rect.ymax += dy;
9339 }
9340
9341 /* set flags again */
9342 ui_popup_block_scrolltest(block);
9343
9344 ED_region_tag_redraw(region);
9345 }
9346
9347 /** Scroll to activated button. */
ui_menu_scroll_to_but(ARegion * region,uiBlock * block,uiBut * but_target)9348 static bool ui_menu_scroll_to_but(ARegion *region, uiBlock *block, uiBut *but_target)
9349 {
9350 float dy = 0.0;
9351 if (block->flag & UI_BLOCK_CLIPTOP) {
9352 if (but_target->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) {
9353 dy = block->rect.ymax - but_target->rect.ymax - UI_MENU_SCROLL_ARROW;
9354 }
9355 }
9356 if (block->flag & UI_BLOCK_CLIPBOTTOM) {
9357 if (but_target->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) {
9358 dy = block->rect.ymin - but_target->rect.ymin + UI_MENU_SCROLL_ARROW;
9359 }
9360 }
9361 if (dy != 0.0f) {
9362 ui_menu_scroll_apply_offset_y(region, block, dy);
9363 return true;
9364 }
9365 return false;
9366 }
9367
9368 /** Scroll to y location (in block space, see #ui_window_to_block). */
ui_menu_scroll_to_y(ARegion * region,uiBlock * block,int y)9369 static bool ui_menu_scroll_to_y(ARegion *region, uiBlock *block, int y)
9370 {
9371 const char test = ui_menu_scroll_test(block, y);
9372 float dy = 0.0f;
9373 if (test == 't') {
9374 dy = -UI_UNIT_Y; /* scroll to the top */
9375 }
9376 else if (test == 'b') {
9377 dy = UI_UNIT_Y; /* scroll to the bottom */
9378 }
9379 if (dy != 0.0f) {
9380 ui_menu_scroll_apply_offset_y(region, block, dy);
9381 return true;
9382 }
9383 return false;
9384 }
9385
ui_menu_scroll_step(ARegion * region,uiBlock * block,const int scroll_dir)9386 static bool ui_menu_scroll_step(ARegion *region, uiBlock *block, const int scroll_dir)
9387 {
9388 int my;
9389 if (scroll_dir == 1) {
9390 if ((block->flag & UI_BLOCK_CLIPTOP) == 0) {
9391 return false;
9392 }
9393 my = block->rect.ymax + UI_UNIT_Y;
9394 }
9395 else if (scroll_dir == -1) {
9396 if ((block->flag & UI_BLOCK_CLIPBOTTOM) == 0) {
9397 return false;
9398 }
9399 my = block->rect.ymin - UI_UNIT_Y;
9400 }
9401 else {
9402 BLI_assert(0);
9403 return false;
9404 }
9405
9406 return ui_menu_scroll_to_y(region, block, my);
9407 }
9408
9409 /** \} */
9410
9411 /* -------------------------------------------------------------------- */
9412 /** \name Menu Event Handling
9413 * \{ */
9414
ui_region_auto_open_clear(ARegion * region)9415 static void ui_region_auto_open_clear(ARegion *region)
9416 {
9417 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
9418 block->auto_open = false;
9419 }
9420 }
9421
9422 /**
9423 * Special function to handle nested menus.
9424 * let the parent menu get the event.
9425 *
9426 * This allows a menu to be open,
9427 * but send key events to the parent if there's no active buttons.
9428 *
9429 * Without this keyboard navigation from menu's wont work.
9430 */
ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle * menu,const uiBut * but,const int level,const int retval)9431 static bool ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle *menu,
9432 const uiBut *but,
9433 const int level,
9434 const int retval)
9435 {
9436 if ((level != 0) && (but == NULL)) {
9437 menu->menuretval = UI_RETURN_OUT | UI_RETURN_OUT_PARENT;
9438 (void)retval; /* so release builds with strict flags are happy as well */
9439 BLI_assert(retval == WM_UI_HANDLER_CONTINUE);
9440 return true;
9441 }
9442 return false;
9443 }
9444
ui_handle_menu_button(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu)9445 static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
9446 {
9447 ARegion *region = menu->region;
9448 uiBut *but = ui_region_find_active_but(region);
9449
9450 if (but) {
9451 /* Its possible there is an active menu item NOT under the mouse,
9452 * in this case ignore mouse clicks outside the button (but Enter etc is accepted) */
9453 if (event->val == KM_RELEASE) {
9454 /* pass, needed so we can exit active menu-items when click-dragging out of them */
9455 }
9456 else if (but->type == UI_BTYPE_SEARCH_MENU) {
9457 /* Pass, needed so search popup can have RMB context menu.
9458 * This may be useful for other interactions which happen in the search popup
9459 * without being directly over the search button. */
9460 }
9461 else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) {
9462 /* pass, skip for dialogs */
9463 }
9464 else if (!ui_region_contains_point_px(but->active->region, event->x, event->y)) {
9465 /* pass, needed to click-exit outside of non-flaoting menus */
9466 ui_region_auto_open_clear(but->active->region);
9467 }
9468 else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) &&
9469 ISMOUSE(event->type)) {
9470 if (!ui_but_contains_point_px(but, but->active->region, event->x, event->y)) {
9471 but = NULL;
9472 }
9473 }
9474 }
9475
9476 int retval;
9477 if (but) {
9478 ScrArea *ctx_area = CTX_wm_area(C);
9479 ARegion *ctx_region = CTX_wm_region(C);
9480
9481 if (menu->ctx_area) {
9482 CTX_wm_area_set(C, menu->ctx_area);
9483 }
9484 if (menu->ctx_region) {
9485 CTX_wm_region_set(C, menu->ctx_region);
9486 }
9487
9488 retval = ui_handle_button_event(C, event, but);
9489
9490 if (menu->ctx_area) {
9491 CTX_wm_area_set(C, ctx_area);
9492 }
9493 if (menu->ctx_region) {
9494 CTX_wm_region_set(C, ctx_region);
9495 }
9496 }
9497 else {
9498 retval = ui_handle_button_over(C, event, region);
9499 }
9500
9501 return retval;
9502 }
9503
ui_block_calc_pie_segment(uiBlock * block,const float event_xy[2])9504 float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2])
9505 {
9506 float seg1[2];
9507
9508 if (block->pie_data.flags & UI_PIE_INITIAL_DIRECTION) {
9509 copy_v2_v2(seg1, block->pie_data.pie_center_init);
9510 }
9511 else {
9512 copy_v2_v2(seg1, block->pie_data.pie_center_spawned);
9513 }
9514
9515 float seg2[2];
9516 sub_v2_v2v2(seg2, event_xy, seg1);
9517
9518 const float len = normalize_v2_v2(block->pie_data.pie_dir, seg2);
9519
9520 if (len < U.pie_menu_threshold * U.dpi_fac) {
9521 block->pie_data.flags |= UI_PIE_INVALID_DIR;
9522 }
9523 else {
9524 block->pie_data.flags &= ~UI_PIE_INVALID_DIR;
9525 }
9526
9527 return len;
9528 }
9529
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)9530 static int ui_handle_menu_event(bContext *C,
9531 const wmEvent *event,
9532 uiPopupBlockHandle *menu,
9533 int level,
9534 const bool is_parent_inside,
9535 const bool is_parent_menu,
9536 const bool is_floating)
9537 {
9538 uiBut *but;
9539 ARegion *region = menu->region;
9540 uiBlock *block = region->uiblocks.first;
9541
9542 int retval = WM_UI_HANDLER_CONTINUE;
9543
9544 int mx = event->x;
9545 int my = event->y;
9546 ui_window_to_block(region, block, &mx, &my);
9547
9548 /* check if mouse is inside block */
9549 const bool inside = BLI_rctf_isect_pt(&block->rect, mx, my);
9550 /* check for title dragging */
9551 const bool inside_title = inside && ((my + (UI_UNIT_Y * 1.5f)) > block->rect.ymax);
9552
9553 /* if there's an active modal button, don't check events or outside, except for search menu */
9554 but = ui_region_find_active_but(region);
9555
9556 #ifdef USE_DRAG_POPUP
9557 if (menu->is_grab) {
9558 if (event->type == LEFTMOUSE) {
9559 menu->is_grab = false;
9560 retval = WM_UI_HANDLER_BREAK;
9561 }
9562 else {
9563 if (event->type == MOUSEMOVE) {
9564 int mdiff[2];
9565
9566 sub_v2_v2v2_int(mdiff, &event->x, menu->grab_xy_prev);
9567 copy_v2_v2_int(menu->grab_xy_prev, &event->x);
9568
9569 add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff);
9570
9571 ui_popup_translate(region, mdiff);
9572 }
9573
9574 return retval;
9575 }
9576 }
9577 #endif
9578
9579 if (but && button_modal_state(but->active->state)) {
9580 if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
9581 /* if a button is activated modal, always reset the start mouse
9582 * position of the towards mechanism to avoid losing focus,
9583 * and don't handle events */
9584 ui_mouse_motion_towards_reinit(menu, &event->x);
9585 }
9586 }
9587 else if (event->type == TIMER) {
9588 if (event->customdata == menu->scrolltimer) {
9589 ui_menu_scroll_to_y(region, block, my);
9590 }
9591 }
9592 else {
9593 /* for ui_mouse_motion_towards_block */
9594 if (event->type == MOUSEMOVE) {
9595 if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
9596 ui_mouse_motion_towards_init(menu, &event->x);
9597 }
9598
9599 /* add menu scroll timer, if needed */
9600 if (ui_menu_scroll_test(block, my)) {
9601 if (menu->scrolltimer == NULL) {
9602 menu->scrolltimer = WM_event_add_timer(
9603 CTX_wm_manager(C), CTX_wm_window(C), TIMER, MENU_SCROLL_INTERVAL);
9604 }
9605 }
9606 }
9607
9608 /* first block own event func */
9609 if (block->block_event_func && block->block_event_func(C, block, event)) {
9610 /* pass */
9611 } /* events not for active search menu button */
9612 else {
9613 int act = 0;
9614
9615 switch (event->type) {
9616
9617 /* Closing sub-levels of pull-downs.
9618 *
9619 * The actual event is handled by the button under the cursor.
9620 * This is done so we can right click on menu items even when they have sub-menus open.
9621 */
9622 case RIGHTMOUSE:
9623 if (inside == false) {
9624 if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
9625 if (block->saferct.first) {
9626 /* Currently right clicking on a top level pull-down (typically in the header)
9627 * just closes the menu and doesn't support immediately handling the RMB event.
9628 *
9629 * To support we would need UI_RETURN_OUT_PARENT to be handled by
9630 * top-level buttons, not just menus. Note that this isn't very important
9631 * since it's easy to manually close these menus by clicking on them. */
9632 menu->menuretval = (level > 0 && is_parent_inside) ? UI_RETURN_OUT_PARENT :
9633 UI_RETURN_OUT;
9634 }
9635 }
9636 retval = WM_UI_HANDLER_BREAK;
9637 }
9638 break;
9639
9640 /* Closing sub-levels of pull-downs. */
9641 case EVT_LEFTARROWKEY:
9642 if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
9643 if (block->saferct.first) {
9644 menu->menuretval = UI_RETURN_OUT;
9645 }
9646 }
9647
9648 retval = WM_UI_HANDLER_BREAK;
9649 break;
9650
9651 /* Opening sub-levels of pull-downs. */
9652 case EVT_RIGHTARROWKEY:
9653 if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
9654
9655 if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9656 break;
9657 }
9658
9659 but = ui_region_find_active_but(region);
9660
9661 if (!but) {
9662 /* no item active, we make first active */
9663 if (block->direction & UI_DIR_UP) {
9664 but = ui_but_last(block);
9665 }
9666 else {
9667 but = ui_but_first(block);
9668 }
9669 }
9670
9671 if (but && ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) {
9672 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN);
9673 }
9674 }
9675
9676 retval = WM_UI_HANDLER_BREAK;
9677 break;
9678
9679 case WHEELUPMOUSE:
9680 case WHEELDOWNMOUSE:
9681 case MOUSEPAN: {
9682 if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
9683 /* pass */
9684 }
9685 else if (!ui_block_is_menu(block)) {
9686 int type = event->type;
9687 int val = event->val;
9688
9689 /* Convert pan to scroll-wheel. */
9690 if (type == MOUSEPAN) {
9691 ui_pan_to_scroll(event, &type, &val);
9692 }
9693
9694 if (type != MOUSEPAN) {
9695 const int scroll_dir = (type == WHEELUPMOUSE) ? 1 : -1;
9696 if (ui_menu_scroll_step(region, block, scroll_dir)) {
9697 if (but) {
9698 but->active->cancel = true;
9699 button_activate_exit(C, but, but->active, false, false);
9700 }
9701 WM_event_add_mousemove(CTX_wm_window(C));
9702 }
9703 }
9704 break;
9705 }
9706 ATTR_FALLTHROUGH;
9707 }
9708 case EVT_UPARROWKEY:
9709 case EVT_DOWNARROWKEY:
9710 case EVT_PAGEUPKEY:
9711 case EVT_PAGEDOWNKEY:
9712 case EVT_HOMEKEY:
9713 case EVT_ENDKEY:
9714 /* Arrow-keys: only handle for block_loop blocks. */
9715 if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) {
9716 /* pass */
9717 }
9718 else if (inside || (block->flag & UI_BLOCK_LOOP)) {
9719 int type = event->type;
9720 int val = event->val;
9721
9722 /* Convert pan to scroll-wheel. */
9723 if (type == MOUSEPAN) {
9724 ui_pan_to_scroll(event, &type, &val);
9725 }
9726
9727 if (val == KM_PRESS) {
9728 /* Determine scroll operation. */
9729 uiMenuScrollType scrolltype;
9730 const bool ui_block_flipped = (block->flag & UI_BLOCK_IS_FLIP) != 0;
9731
9732 if (ELEM(type, EVT_PAGEUPKEY, EVT_HOMEKEY)) {
9733 scrolltype = ui_block_flipped ? MENU_SCROLL_TOP : MENU_SCROLL_BOTTOM;
9734 }
9735 else if (ELEM(type, EVT_PAGEDOWNKEY, EVT_ENDKEY)) {
9736 scrolltype = ui_block_flipped ? MENU_SCROLL_BOTTOM : MENU_SCROLL_TOP;
9737 }
9738 else if (ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE)) {
9739 scrolltype = ui_block_flipped ? MENU_SCROLL_UP : MENU_SCROLL_DOWN;
9740 }
9741 else {
9742 scrolltype = ui_block_flipped ? MENU_SCROLL_DOWN : MENU_SCROLL_UP;
9743 }
9744
9745 if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9746 break;
9747 }
9748
9749 #ifdef USE_KEYNAV_LIMIT
9750 ui_mouse_motion_keynav_init(&menu->keynav_state, event);
9751 #endif
9752
9753 but = ui_region_find_active_but(region);
9754 if (but) {
9755 /* Apply scroll operation. */
9756 if (scrolltype == MENU_SCROLL_DOWN) {
9757 but = ui_but_next(but);
9758 }
9759 else if (scrolltype == MENU_SCROLL_UP) {
9760 but = ui_but_prev(but);
9761 }
9762 else if (scrolltype == MENU_SCROLL_TOP) {
9763 but = ui_but_first(block);
9764 }
9765 else if (scrolltype == MENU_SCROLL_BOTTOM) {
9766 but = ui_but_last(block);
9767 }
9768 }
9769
9770 if (!but) {
9771 /* wrap button or no active button*/
9772 uiBut *but_wrap = NULL;
9773 if (ELEM(scrolltype, MENU_SCROLL_UP, MENU_SCROLL_BOTTOM)) {
9774 but_wrap = ui_but_last(block);
9775 }
9776 else if (ELEM(scrolltype, MENU_SCROLL_DOWN, MENU_SCROLL_TOP)) {
9777 but_wrap = ui_but_first(block);
9778 }
9779 if (but_wrap) {
9780 but = but_wrap;
9781 }
9782 }
9783
9784 if (but) {
9785 ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE);
9786 ui_menu_scroll_to_but(region, block, but);
9787 }
9788 }
9789
9790 retval = WM_UI_HANDLER_BREAK;
9791 }
9792
9793 break;
9794
9795 case EVT_ONEKEY:
9796 case EVT_PAD1:
9797 act = 1;
9798 ATTR_FALLTHROUGH;
9799 case EVT_TWOKEY:
9800 case EVT_PAD2:
9801 if (act == 0) {
9802 act = 2;
9803 }
9804 ATTR_FALLTHROUGH;
9805 case EVT_THREEKEY:
9806 case EVT_PAD3:
9807 if (act == 0) {
9808 act = 3;
9809 }
9810 ATTR_FALLTHROUGH;
9811 case EVT_FOURKEY:
9812 case EVT_PAD4:
9813 if (act == 0) {
9814 act = 4;
9815 }
9816 ATTR_FALLTHROUGH;
9817 case EVT_FIVEKEY:
9818 case EVT_PAD5:
9819 if (act == 0) {
9820 act = 5;
9821 }
9822 ATTR_FALLTHROUGH;
9823 case EVT_SIXKEY:
9824 case EVT_PAD6:
9825 if (act == 0) {
9826 act = 6;
9827 }
9828 ATTR_FALLTHROUGH;
9829 case EVT_SEVENKEY:
9830 case EVT_PAD7:
9831 if (act == 0) {
9832 act = 7;
9833 }
9834 ATTR_FALLTHROUGH;
9835 case EVT_EIGHTKEY:
9836 case EVT_PAD8:
9837 if (act == 0) {
9838 act = 8;
9839 }
9840 ATTR_FALLTHROUGH;
9841 case EVT_NINEKEY:
9842 case EVT_PAD9:
9843 if (act == 0) {
9844 act = 9;
9845 }
9846 ATTR_FALLTHROUGH;
9847 case EVT_ZEROKEY:
9848 case EVT_PAD0:
9849 if (act == 0) {
9850 act = 10;
9851 }
9852
9853 if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) {
9854 int count;
9855
9856 if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9857 break;
9858 }
9859
9860 if (event->alt) {
9861 act += 10;
9862 }
9863
9864 count = 0;
9865 for (but = block->buttons.first; but; but = but->next) {
9866 bool doit = false;
9867
9868 if (!ELEM(but->type,
9869 UI_BTYPE_LABEL,
9870 UI_BTYPE_SEPR,
9871 UI_BTYPE_SEPR_LINE,
9872 UI_BTYPE_IMAGE)) {
9873 count++;
9874 }
9875
9876 /* exception for rna layer buts */
9877 if (but->rnapoin.data && but->rnaprop &&
9878 ELEM(RNA_property_subtype(but->rnaprop), PROP_LAYER, PROP_LAYER_MEMBER)) {
9879 if (but->rnaindex == act - 1) {
9880 doit = true;
9881 }
9882 }
9883 else if (ELEM(but->type,
9884 UI_BTYPE_BUT,
9885 UI_BTYPE_BUT_MENU,
9886 UI_BTYPE_MENU,
9887 UI_BTYPE_BLOCK,
9888 UI_BTYPE_PULLDOWN) &&
9889 count == act) {
9890 doit = true;
9891 }
9892
9893 if (!(but->flag & UI_BUT_DISABLED) && doit) {
9894 /* activate buttons but open menu's */
9895 uiButtonActivateType activate;
9896 if (but->type == UI_BTYPE_PULLDOWN) {
9897 activate = BUTTON_ACTIVATE_OPEN;
9898 }
9899 else {
9900 activate = BUTTON_ACTIVATE_APPLY;
9901 }
9902
9903 ui_handle_button_activate(C, region, but, activate);
9904 break;
9905 }
9906 }
9907
9908 retval = WM_UI_HANDLER_BREAK;
9909 }
9910 break;
9911
9912 /* Handle keystrokes on menu items */
9913 case EVT_AKEY:
9914 case EVT_BKEY:
9915 case EVT_CKEY:
9916 case EVT_DKEY:
9917 case EVT_EKEY:
9918 case EVT_FKEY:
9919 case EVT_GKEY:
9920 case EVT_HKEY:
9921 case EVT_IKEY:
9922 case EVT_JKEY:
9923 case EVT_KKEY:
9924 case EVT_LKEY:
9925 case EVT_MKEY:
9926 case EVT_NKEY:
9927 case EVT_OKEY:
9928 case EVT_PKEY:
9929 case EVT_QKEY:
9930 case EVT_RKEY:
9931 case EVT_SKEY:
9932 case EVT_TKEY:
9933 case EVT_UKEY:
9934 case EVT_VKEY:
9935 case EVT_WKEY:
9936 case EVT_XKEY:
9937 case EVT_YKEY:
9938 case EVT_ZKEY: {
9939 if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) &&
9940 !IS_EVENT_MOD(event, shift, ctrl, oskey)) {
9941 if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) {
9942 break;
9943 }
9944
9945 for (but = block->buttons.first; but; but = but->next) {
9946 if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) {
9947 if (but->type == UI_BTYPE_BUT) {
9948 UI_but_execute(C, region, but);
9949 }
9950 else {
9951 ui_handle_button_activate_by_type(C, region, but);
9952 }
9953 break;
9954 }
9955 }
9956
9957 retval = WM_UI_HANDLER_BREAK;
9958 }
9959 break;
9960 }
9961 }
9962 }
9963
9964 /* here we check return conditions for menus */
9965 if (block->flag & UI_BLOCK_LOOP) {
9966 /* If we click outside the block, verify if we clicked on the
9967 * button that opened us, otherwise we need to close,
9968 *
9969 * note that there is an exception for root level menus and
9970 * popups which you can click again to close.
9971 *
9972 * Events handled above may have already set the return value,
9973 * don't overwrite them, see: T61015.
9974 */
9975 if ((inside == false) && (menu->menuretval == 0)) {
9976 uiSafetyRct *saferct = block->saferct.first;
9977
9978 if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
9979 if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) {
9980 if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) {
9981 /* for root menus, allow clicking to close */
9982 if (block->flag & UI_BLOCK_OUT_1) {
9983 menu->menuretval = UI_RETURN_OK;
9984 }
9985 else {
9986 menu->menuretval = UI_RETURN_OUT;
9987 }
9988 }
9989 else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) {
9990 if (block->flag & UI_BLOCK_OUT_1) {
9991 menu->menuretval = UI_RETURN_OK;
9992 }
9993 else {
9994 menu->menuretval = UI_RETURN_OUT;
9995 }
9996 }
9997 }
9998 else if (ELEM(event->val, KM_RELEASE, KM_CLICK)) {
9999 /* For buttons that use a hold function,
10000 * exit when mouse-up outside the menu. */
10001 if (block->flag & UI_BLOCK_POPUP_HOLD) {
10002 /* Note, we could check the cursor is over the parent button. */
10003 menu->menuretval = UI_RETURN_CANCEL;
10004 retval = WM_UI_HANDLER_CONTINUE;
10005 }
10006 }
10007 }
10008 }
10009
10010 if (menu->menuretval) {
10011 /* pass */
10012 }
10013 #ifdef USE_KEYNAV_LIMIT
10014 else if ((event->type == MOUSEMOVE) &&
10015 ui_mouse_motion_keynav_test(&menu->keynav_state, event)) {
10016 /* Don't handle the mouse-move if we're using key-navigation. */
10017 retval = WM_UI_HANDLER_BREAK;
10018 }
10019 #endif
10020 else if (event->type == EVT_ESCKEY && event->val == KM_PRESS) {
10021 /* Escape cancels this and all preceding menus. */
10022 menu->menuretval = UI_RETURN_CANCEL;
10023 }
10024 else if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER) && event->val == KM_PRESS) {
10025 uiBut *but_default = ui_region_find_first_but_test_flag(
10026 region, UI_BUT_ACTIVE_DEFAULT, UI_HIDDEN);
10027 if ((but_default != NULL) && (but_default->active == NULL)) {
10028 if (but_default->type == UI_BTYPE_BUT) {
10029 UI_but_execute(C, region, but_default);
10030 }
10031 else {
10032 ui_handle_button_activate_by_type(C, region, but_default);
10033 }
10034 }
10035 else {
10036 uiBut *but_active = ui_region_find_active_but(region);
10037
10038 /* enter will always close this block, we let the event
10039 * get handled by the button if it is activated, otherwise we cancel */
10040 if (but_active == NULL) {
10041 menu->menuretval = UI_RETURN_CANCEL | UI_RETURN_POPUP_OK;
10042 }
10043 }
10044 }
10045 #ifdef USE_DRAG_POPUP
10046 else if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) &&
10047 (inside && is_floating && inside_title)) {
10048 if (!but || !ui_but_contains_point_px(but, region, event->x, event->y)) {
10049 if (but) {
10050 UI_but_tooltip_timer_remove(C, but);
10051 }
10052
10053 menu->is_grab = true;
10054 copy_v2_v2_int(menu->grab_xy_prev, &event->x);
10055 retval = WM_UI_HANDLER_BREAK;
10056 }
10057 }
10058 #endif
10059 else {
10060
10061 /* check mouse moving outside of the menu */
10062 if (inside == false && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) {
10063 uiSafetyRct *saferct;
10064
10065 ui_mouse_motion_towards_check(block, menu, &event->x, is_parent_inside == false);
10066
10067 /* check for all parent rects, enables arrowkeys to be used */
10068 for (saferct = block->saferct.first; saferct; saferct = saferct->next) {
10069 /* for mouse move we only check our own rect, for other
10070 * events we check all preceding block rects too to make
10071 * arrow keys navigation work */
10072 if (event->type != MOUSEMOVE || saferct == block->saferct.first) {
10073 if (BLI_rctf_isect_pt(&saferct->parent, (float)event->x, (float)event->y)) {
10074 break;
10075 }
10076 if (BLI_rctf_isect_pt(&saferct->safety, (float)event->x, (float)event->y)) {
10077 break;
10078 }
10079 }
10080 }
10081
10082 /* strict check, and include the parent rect */
10083 if (!menu->dotowards && !saferct) {
10084 if (block->flag & UI_BLOCK_OUT_1) {
10085 menu->menuretval = UI_RETURN_OK;
10086 }
10087 else {
10088 menu->menuretval = UI_RETURN_OUT;
10089 }
10090 }
10091 else if (menu->dotowards && event->type == MOUSEMOVE) {
10092 retval = WM_UI_HANDLER_BREAK;
10093 }
10094 }
10095 }
10096
10097 /* end switch */
10098 }
10099 }
10100
10101 /* if we are didn't handle the event yet, lets pass it on to
10102 * buttons inside this region. disabled inside check .. not sure
10103 * anymore why it was there? but it meant enter didn't work
10104 * for example when mouse was not over submenu */
10105 if ((event->type == TIMER) ||
10106 (/*inside &&*/ (!menu->menuretval || (menu->menuretval & UI_RETURN_UPDATE)) &&
10107 retval == WM_UI_HANDLER_CONTINUE)) {
10108 retval = ui_handle_menu_button(C, event, menu);
10109 }
10110
10111 #ifdef USE_UI_POPOVER_ONCE
10112 if (block->flag & UI_BLOCK_POPOVER_ONCE) {
10113 if ((event->type == LEFTMOUSE) && (event->val == KM_RELEASE)) {
10114 UI_popover_once_clear(menu->popup_create_vars.arg);
10115 block->flag &= ~UI_BLOCK_POPOVER_ONCE;
10116 }
10117 }
10118 #endif
10119
10120 /* Don't handle double click events, rehandle as regular press/release. */
10121 if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) {
10122 return retval;
10123 }
10124
10125 /* if we set a menu return value, ensure we continue passing this on to
10126 * lower menus and buttons, so always set continue then, and if we are
10127 * inside the region otherwise, ensure we swallow the event */
10128 if (menu->menuretval) {
10129 return WM_UI_HANDLER_CONTINUE;
10130 }
10131 if (inside) {
10132 return WM_UI_HANDLER_BREAK;
10133 }
10134 return retval;
10135 }
10136
ui_handle_menu_return_submenu(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu)10137 static int ui_handle_menu_return_submenu(bContext *C,
10138 const wmEvent *event,
10139 uiPopupBlockHandle *menu)
10140 {
10141 ARegion *region = menu->region;
10142 uiBlock *block = region->uiblocks.first;
10143
10144 uiBut *but = ui_region_find_active_but(region);
10145
10146 BLI_assert(but);
10147
10148 uiHandleButtonData *data = but->active;
10149 uiPopupBlockHandle *submenu = data->menu;
10150
10151 if (submenu->menuretval) {
10152 bool update;
10153
10154 /* first decide if we want to close our own menu cascading, if
10155 * so pass on the sub menu return value to our own menu handle */
10156 if ((submenu->menuretval & UI_RETURN_OK) || (submenu->menuretval & UI_RETURN_CANCEL)) {
10157 if (!(block->flag & UI_BLOCK_KEEP_OPEN)) {
10158 menu->menuretval = submenu->menuretval;
10159 menu->butretval = data->retval;
10160 }
10161 }
10162
10163 update = (submenu->menuretval & UI_RETURN_UPDATE) != 0;
10164
10165 /* now let activated button in this menu exit, which
10166 * will actually close the submenu too */
10167 ui_handle_button_return_submenu(C, event, but);
10168
10169 if (update) {
10170 submenu->menuretval = 0;
10171 }
10172 }
10173
10174 if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
10175 /* for cases where close does not cascade, allow the user to
10176 * move the mouse back towards the menu without closing */
10177 ui_mouse_motion_towards_reinit(menu, &event->x);
10178 }
10179
10180 if (menu->menuretval) {
10181 return WM_UI_HANDLER_CONTINUE;
10182 }
10183 return WM_UI_HANDLER_BREAK;
10184 }
10185
ui_but_pie_menu_supported_apply(uiBut * but)10186 static bool ui_but_pie_menu_supported_apply(uiBut *but)
10187 {
10188 return (!ELEM(but->type, UI_BTYPE_NUM_SLIDER, UI_BTYPE_NUM));
10189 }
10190
ui_but_pie_menu_apply(bContext * C,uiPopupBlockHandle * menu,uiBut * but,bool force_close)10191 static int ui_but_pie_menu_apply(bContext *C,
10192 uiPopupBlockHandle *menu,
10193 uiBut *but,
10194 bool force_close)
10195 {
10196 const int retval = WM_UI_HANDLER_BREAK;
10197
10198 if (but && ui_but_pie_menu_supported_apply(but)) {
10199 if (but->type == UI_BTYPE_MENU) {
10200 /* forcing the pie menu to close will not handle menus */
10201 if (!force_close) {
10202 uiBut *active_but = ui_region_find_active_but(menu->region);
10203
10204 if (active_but) {
10205 button_activate_exit(C, active_but, active_but->active, false, false);
10206 }
10207
10208 button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OPEN);
10209 return retval;
10210 }
10211 menu->menuretval = UI_RETURN_CANCEL;
10212 }
10213 else {
10214 button_activate_exit((bContext *)C, but, but->active, false, false);
10215
10216 menu->menuretval = UI_RETURN_OK;
10217 }
10218 }
10219 else {
10220 menu->menuretval = UI_RETURN_CANCEL;
10221
10222 ED_region_tag_redraw(menu->region);
10223 }
10224
10225 return retval;
10226 }
10227
ui_block_pie_dir_activate(uiBlock * block,const wmEvent * event,RadialDirection dir)10228 static uiBut *ui_block_pie_dir_activate(uiBlock *block, const wmEvent *event, RadialDirection dir)
10229 {
10230 if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) {
10231 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10232 if (but->pie_dir == dir && !ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
10233 return but;
10234 }
10235 }
10236 }
10237
10238 return NULL;
10239 }
10240
ui_but_pie_button_activate(bContext * C,uiBut * but,uiPopupBlockHandle * menu)10241 static int ui_but_pie_button_activate(bContext *C, uiBut *but, uiPopupBlockHandle *menu)
10242 {
10243 if (but == NULL) {
10244 return WM_UI_HANDLER_BREAK;
10245 }
10246
10247 uiBut *active_but = ui_region_find_active_but(menu->region);
10248
10249 if (active_but) {
10250 button_activate_exit(C, active_but, active_but->active, false, false);
10251 }
10252
10253 button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OVER);
10254 return ui_but_pie_menu_apply(C, menu, but, false);
10255 }
10256
ui_pie_handler(bContext * C,const wmEvent * event,uiPopupBlockHandle * menu)10257 static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
10258 {
10259 /* we block all events, this is modal interaction,
10260 * except for drop events which is described below */
10261 int retval = WM_UI_HANDLER_BREAK;
10262
10263 if (event->type == EVT_DROP) {
10264 /* may want to leave this here for later if we support pie ovens */
10265
10266 retval = WM_UI_HANDLER_CONTINUE;
10267 }
10268
10269 ARegion *region = menu->region;
10270 uiBlock *block = region->uiblocks.first;
10271
10272 const bool is_click_style = (block->pie_data.flags & UI_PIE_CLICK_STYLE);
10273
10274 /* if there's an active modal button, don't check events or outside, except for search menu */
10275 uiBut *but_active = ui_region_find_active_but(region);
10276
10277 if (menu->scrolltimer == NULL) {
10278 menu->scrolltimer = WM_event_add_timer(
10279 CTX_wm_manager(C), CTX_wm_window(C), TIMER, PIE_MENU_INTERVAL);
10280 menu->scrolltimer->duration = 0.0;
10281 }
10282
10283 const double duration = menu->scrolltimer->duration;
10284
10285 float event_xy[2] = {event->x, event->y};
10286
10287 ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]);
10288
10289 /* Distance from initial point. */
10290 const float dist = ui_block_calc_pie_segment(block, event_xy);
10291
10292 if (but_active && button_modal_state(but_active->active->state)) {
10293 retval = ui_handle_menu_button(C, event, menu);
10294 }
10295 else {
10296 if (event->type == TIMER) {
10297 if (event->customdata == menu->scrolltimer) {
10298 /* deactivate initial direction after a while */
10299 if (duration > 0.01 * U.pie_initial_timeout) {
10300 block->pie_data.flags &= ~UI_PIE_INITIAL_DIRECTION;
10301 }
10302
10303 /* handle animation */
10304 if (!(block->pie_data.flags & UI_PIE_ANIMATION_FINISHED)) {
10305 const double final_time = 0.01 * U.pie_animation_timeout;
10306 float fac = duration / final_time;
10307 const float pie_radius = U.pie_menu_radius * UI_DPI_FAC;
10308
10309 if (fac > 1.0f) {
10310 fac = 1.0f;
10311 block->pie_data.flags |= UI_PIE_ANIMATION_FINISHED;
10312 }
10313
10314 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10315 if (but->pie_dir != UI_RADIAL_NONE) {
10316 float vec[2];
10317 float center[2];
10318
10319 ui_but_pie_dir(but->pie_dir, vec);
10320
10321 center[0] = (vec[0] > 0.01f) ? 0.5f : ((vec[0] < -0.01f) ? -0.5f : 0.0f);
10322 center[1] = (vec[1] > 0.99f) ? 0.5f : ((vec[1] < -0.99f) ? -0.5f : 0.0f);
10323
10324 center[0] *= BLI_rctf_size_x(&but->rect);
10325 center[1] *= BLI_rctf_size_y(&but->rect);
10326
10327 mul_v2_fl(vec, pie_radius);
10328 add_v2_v2(vec, center);
10329 mul_v2_fl(vec, fac);
10330 add_v2_v2(vec, block->pie_data.pie_center_spawned);
10331
10332 BLI_rctf_recenter(&but->rect, vec[0], vec[1]);
10333 }
10334 }
10335 block->pie_data.alphafac = fac;
10336
10337 ED_region_tag_redraw(region);
10338 }
10339 }
10340
10341 /* check pie velociy here if gesture has ended */
10342 if (block->pie_data.flags & UI_PIE_GESTURE_END_WAIT) {
10343 float len_sq = 10;
10344
10345 /* use a time threshold to ensure we leave time to the mouse to move */
10346 if (duration - block->pie_data.duration_gesture > 0.02) {
10347 len_sq = len_squared_v2v2(event_xy, block->pie_data.last_pos);
10348 copy_v2_v2(block->pie_data.last_pos, event_xy);
10349 block->pie_data.duration_gesture = duration;
10350 }
10351
10352 if (len_sq < 1.0f) {
10353 uiBut *but = ui_region_find_active_but(menu->region);
10354
10355 if (but) {
10356 return ui_but_pie_menu_apply(C, menu, but, true);
10357 }
10358 }
10359 }
10360 }
10361
10362 if (event->type == block->pie_data.event && !is_click_style) {
10363 if (event->val != KM_RELEASE) {
10364 ui_handle_menu_button(C, event, menu);
10365
10366 if (len_squared_v2v2(event_xy, block->pie_data.pie_center_init) > PIE_CLICK_THRESHOLD_SQ) {
10367 block->pie_data.flags |= UI_PIE_DRAG_STYLE;
10368 }
10369 /* why redraw here? It's simple, we are getting many double click events here.
10370 * Those operate like mouse move events almost */
10371 ED_region_tag_redraw(region);
10372 }
10373 else {
10374 if ((duration < 0.01 * U.pie_tap_timeout) &&
10375 !(block->pie_data.flags & UI_PIE_DRAG_STYLE)) {
10376 block->pie_data.flags |= UI_PIE_CLICK_STYLE;
10377 }
10378 else {
10379 uiBut *but = ui_region_find_active_but(menu->region);
10380
10381 if (but && (U.pie_menu_confirm > 0) &&
10382 (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) {
10383 return ui_but_pie_menu_apply(C, menu, but, true);
10384 }
10385
10386 retval = ui_but_pie_menu_apply(C, menu, but, true);
10387 }
10388 }
10389 }
10390 else {
10391 /* direction from numpad */
10392 RadialDirection num_dir = UI_RADIAL_NONE;
10393
10394 switch (event->type) {
10395 case MOUSEMOVE:
10396 if (!is_click_style) {
10397 const float len_sq = len_squared_v2v2(event_xy, block->pie_data.pie_center_init);
10398
10399 /* here we use the initial position explicitly */
10400 if (len_sq > PIE_CLICK_THRESHOLD_SQ) {
10401 block->pie_data.flags |= UI_PIE_DRAG_STYLE;
10402 }
10403
10404 /* here instead, we use the offset location to account for the initial
10405 * direction timeout */
10406 if ((U.pie_menu_confirm > 0) &&
10407 (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) {
10408 block->pie_data.flags |= UI_PIE_GESTURE_END_WAIT;
10409 copy_v2_v2(block->pie_data.last_pos, event_xy);
10410 block->pie_data.duration_gesture = duration;
10411 }
10412 }
10413
10414 ui_handle_menu_button(C, event, menu);
10415
10416 /* mouse move should always refresh the area for pie menus */
10417 ED_region_tag_redraw(region);
10418 break;
10419
10420 case LEFTMOUSE:
10421 if (is_click_style) {
10422 if (block->pie_data.flags & UI_PIE_INVALID_DIR) {
10423 menu->menuretval = UI_RETURN_CANCEL;
10424 }
10425 else {
10426 retval = ui_handle_menu_button(C, event, menu);
10427 }
10428 }
10429 break;
10430
10431 case EVT_ESCKEY:
10432 case RIGHTMOUSE:
10433 menu->menuretval = UI_RETURN_CANCEL;
10434 break;
10435
10436 case EVT_AKEY:
10437 case EVT_BKEY:
10438 case EVT_CKEY:
10439 case EVT_DKEY:
10440 case EVT_EKEY:
10441 case EVT_FKEY:
10442 case EVT_GKEY:
10443 case EVT_HKEY:
10444 case EVT_IKEY:
10445 case EVT_JKEY:
10446 case EVT_KKEY:
10447 case EVT_LKEY:
10448 case EVT_MKEY:
10449 case EVT_NKEY:
10450 case EVT_OKEY:
10451 case EVT_PKEY:
10452 case EVT_QKEY:
10453 case EVT_RKEY:
10454 case EVT_SKEY:
10455 case EVT_TKEY:
10456 case EVT_UKEY:
10457 case EVT_VKEY:
10458 case EVT_WKEY:
10459 case EVT_XKEY:
10460 case EVT_YKEY:
10461 case EVT_ZKEY: {
10462 if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) &&
10463 !IS_EVENT_MOD(event, shift, ctrl, oskey)) {
10464 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10465 if (but->menu_key == event->type) {
10466 ui_but_pie_button_activate(C, but, menu);
10467 }
10468 }
10469 }
10470 break;
10471 }
10472
10473 #define CASE_NUM_TO_DIR(n, d) \
10474 case (EVT_ZEROKEY + n): \
10475 case (EVT_PAD0 + n): { \
10476 if (num_dir == UI_RADIAL_NONE) { \
10477 num_dir = d; \
10478 } \
10479 } \
10480 (void)0
10481
10482 CASE_NUM_TO_DIR(1, UI_RADIAL_SW);
10483 ATTR_FALLTHROUGH;
10484 CASE_NUM_TO_DIR(2, UI_RADIAL_S);
10485 ATTR_FALLTHROUGH;
10486 CASE_NUM_TO_DIR(3, UI_RADIAL_SE);
10487 ATTR_FALLTHROUGH;
10488 CASE_NUM_TO_DIR(4, UI_RADIAL_W);
10489 ATTR_FALLTHROUGH;
10490 CASE_NUM_TO_DIR(6, UI_RADIAL_E);
10491 ATTR_FALLTHROUGH;
10492 CASE_NUM_TO_DIR(7, UI_RADIAL_NW);
10493 ATTR_FALLTHROUGH;
10494 CASE_NUM_TO_DIR(8, UI_RADIAL_N);
10495 ATTR_FALLTHROUGH;
10496 CASE_NUM_TO_DIR(9, UI_RADIAL_NE);
10497 {
10498 uiBut *but = ui_block_pie_dir_activate(block, event, num_dir);
10499 retval = ui_but_pie_button_activate(C, but, menu);
10500 break;
10501 }
10502 #undef CASE_NUM_TO_DIR
10503 default:
10504 retval = ui_handle_menu_button(C, event, menu);
10505 break;
10506 }
10507 }
10508 }
10509
10510 return retval;
10511 }
10512
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)10513 static int ui_handle_menus_recursive(bContext *C,
10514 const wmEvent *event,
10515 uiPopupBlockHandle *menu,
10516 int level,
10517 const bool is_parent_inside,
10518 const bool is_parent_menu,
10519 const bool is_floating)
10520 {
10521 int retval = WM_UI_HANDLER_CONTINUE;
10522 bool do_towards_reinit = false;
10523
10524 /* check if we have a submenu, and handle events for it first */
10525 uiBut *but = ui_region_find_active_but(menu->region);
10526 uiHandleButtonData *data = (but) ? but->active : NULL;
10527 uiPopupBlockHandle *submenu = (data) ? data->menu : NULL;
10528
10529 if (submenu) {
10530 uiBlock *block = menu->region->uiblocks.first;
10531 const bool is_menu = ui_block_is_menu(block);
10532 bool inside = false;
10533 /* root pie menus accept the key that spawned
10534 * them as double click to improve responsiveness */
10535 const bool do_recursion = (!(block->flag & UI_BLOCK_RADIAL) ||
10536 event->type != block->pie_data.event);
10537
10538 if (do_recursion) {
10539 if (is_parent_inside == false) {
10540 int mx = event->x;
10541 int my = event->y;
10542 ui_window_to_block(menu->region, block, &mx, &my);
10543 inside = BLI_rctf_isect_pt(&block->rect, mx, my);
10544 }
10545
10546 retval = ui_handle_menus_recursive(
10547 C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false);
10548 }
10549 }
10550
10551 /* now handle events for our own menu */
10552 if (retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) {
10553 const bool do_but_search = (but && (but->type == UI_BTYPE_SEARCH_MENU));
10554 if (submenu && submenu->menuretval) {
10555 const bool do_ret_out_parent = (submenu->menuretval & UI_RETURN_OUT_PARENT) != 0;
10556 retval = ui_handle_menu_return_submenu(C, event, menu);
10557 submenu = NULL; /* hint not to use this, it may be freed by call above */
10558 (void)submenu;
10559 /* we may want to quit the submenu and handle the even in this menu,
10560 * if its important to use it, check 'data->menu' first */
10561 if (((retval == WM_UI_HANDLER_BREAK) && do_ret_out_parent) == false) {
10562 /* skip applying the event */
10563 return retval;
10564 }
10565 }
10566
10567 if (do_but_search) {
10568 uiBlock *block = menu->region->uiblocks.first;
10569
10570 retval = ui_handle_menu_button(C, event, menu);
10571
10572 if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) {
10573 /* when there is a active search button and we close it,
10574 * we need to reinit the mouse coords T35346. */
10575 if (ui_region_find_active_but(menu->region) != but) {
10576 do_towards_reinit = true;
10577 }
10578 }
10579 }
10580 else {
10581 uiBlock *block = menu->region->uiblocks.first;
10582 uiBut *listbox = ui_list_find_mouse_over(menu->region, event);
10583
10584 if (block->flag & UI_BLOCK_RADIAL) {
10585 retval = ui_pie_handler(C, event, menu);
10586 }
10587 else if (event->type == LEFTMOUSE || event->val != KM_DBL_CLICK) {
10588 bool handled = false;
10589
10590 if (listbox) {
10591 const int retval_test = ui_handle_list_event(C, event, menu->region, listbox);
10592 if (retval_test != WM_UI_HANDLER_CONTINUE) {
10593 retval = retval_test;
10594 handled = true;
10595 }
10596 }
10597
10598 if (handled == false) {
10599 retval = ui_handle_menu_event(
10600 C, event, menu, level, is_parent_inside, is_parent_menu, is_floating);
10601 }
10602 }
10603 }
10604 }
10605
10606 if (do_towards_reinit) {
10607 ui_mouse_motion_towards_reinit(menu, &event->x);
10608 }
10609
10610 return retval;
10611 }
10612
10613 /**
10614 * Allow setting menu return value from externals.
10615 * E.g. WM might need to do this for exiting files correctly.
10616 */
UI_popup_menu_retval_set(const uiBlock * block,const int retval,const bool enable)10617 void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable)
10618 {
10619 uiPopupBlockHandle *menu = block->handle;
10620 if (menu) {
10621 menu->menuretval = enable ? (menu->menuretval | retval) : (menu->menuretval & retval);
10622 }
10623 }
10624
10625 /** \} */
10626
10627 /* -------------------------------------------------------------------- */
10628 /** \name UI Event Handlers
10629 * \{ */
10630
ui_region_handler(bContext * C,const wmEvent * event,void * UNUSED (userdata))10631 static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(userdata))
10632 {
10633 /* here we handle buttons at the region level, non-modal */
10634 ARegion *region = CTX_wm_region(C);
10635 int retval = WM_UI_HANDLER_CONTINUE;
10636
10637 if (region == NULL || BLI_listbase_is_empty(®ion->uiblocks)) {
10638 return retval;
10639 }
10640
10641 /* either handle events for already activated button or try to activate */
10642 uiBut *but = ui_region_find_active_but(region);
10643 uiBut *listbox = ui_list_find_mouse_over(region, event);
10644
10645 retval = ui_handler_panel_region(C, event, region, listbox ? listbox : but);
10646
10647 if (retval == WM_UI_HANDLER_CONTINUE && listbox) {
10648 retval = ui_handle_list_event(C, event, region, listbox);
10649
10650 /* interactions with the listbox should disable tips */
10651 if (retval == WM_UI_HANDLER_BREAK) {
10652 if (but) {
10653 UI_but_tooltip_timer_remove(C, but);
10654 }
10655 }
10656 }
10657
10658 if (retval == WM_UI_HANDLER_CONTINUE) {
10659 if (but) {
10660 retval = ui_handle_button_event(C, event, but);
10661 }
10662 else {
10663 retval = ui_handle_button_over(C, event, region);
10664 }
10665 }
10666
10667 /* re-enable tooltips */
10668 if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
10669 ui_blocks_set_tooltips(region, true);
10670 }
10671
10672 /* delayed apply callbacks */
10673 ui_apply_but_funcs_after(C);
10674
10675 return retval;
10676 }
10677
ui_region_handler_remove(bContext * C,void * UNUSED (userdata))10678 static void ui_region_handler_remove(bContext *C, void *UNUSED(userdata))
10679 {
10680 ARegion *region = CTX_wm_region(C);
10681 if (region == NULL) {
10682 return;
10683 }
10684
10685 UI_blocklist_free(C, ®ion->uiblocks);
10686
10687 bScreen *screen = CTX_wm_screen(C);
10688 if (screen == NULL) {
10689 return;
10690 }
10691
10692 /* delayed apply callbacks, but not for screen level regions, those
10693 * we rather do at the very end after closing them all, which will
10694 * be done in ui_region_handler/window */
10695 if (BLI_findindex(&screen->regionbase, region) == -1) {
10696 ui_apply_but_funcs_after(C);
10697 }
10698 }
10699
10700 /* handle buttons at the window level, modal, for example while
10701 * number sliding, text editing, or when a menu block is open */
ui_handler_region_menu(bContext * C,const wmEvent * event,void * UNUSED (userdata))10702 static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSED(userdata))
10703 {
10704 ARegion *menu_region = CTX_wm_menu(C);
10705 ARegion *region = menu_region ? menu_region : CTX_wm_region(C);
10706 int retval = WM_UI_HANDLER_CONTINUE;
10707
10708 uiBut *but = ui_region_find_active_but(region);
10709
10710 if (but) {
10711 bScreen *screen = CTX_wm_screen(C);
10712 uiBut *but_other;
10713
10714 /* handle activated button events */
10715 uiHandleButtonData *data = but->active;
10716
10717 if ((data->state == BUTTON_STATE_MENU_OPEN) &&
10718 /* Make sure this popup isn't dragging a button.
10719 * can happen with popovers (see T67882). */
10720 (ui_region_find_active_but(data->menu->region) == NULL) &&
10721 /* make sure mouse isn't inside another menu (see T43247) */
10722 (ui_screen_region_find_mouse_over(screen, event) == NULL) &&
10723 (ELEM(but->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) &&
10724 (but_other = ui_but_find_mouse_over(region, event)) && (but != but_other) &&
10725 (ELEM(but_other->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) &&
10726 /* Hover-opening menu's doesn't work well for buttons over one another
10727 * along the same axis the menu is opening on (see T71719). */
10728 (((data->menu->direction & (UI_DIR_LEFT | UI_DIR_RIGHT)) &&
10729 BLI_rctf_isect_rect_x(&but->rect, &but_other->rect, NULL)) ||
10730 ((data->menu->direction & (UI_DIR_DOWN | UI_DIR_UP)) &&
10731 BLI_rctf_isect_rect_y(&but->rect, &but_other->rect, NULL)))) {
10732 /* if mouse moves to a different root-level menu button,
10733 * open it to replace the current menu */
10734 if ((but_other->flag & UI_BUT_DISABLED) == 0) {
10735 ui_handle_button_activate(C, region, but_other, BUTTON_ACTIVATE_OVER);
10736 button_activate_state(C, but_other, BUTTON_STATE_MENU_OPEN);
10737 retval = WM_UI_HANDLER_BREAK;
10738 }
10739 }
10740 else if (data->state == BUTTON_STATE_MENU_OPEN) {
10741 /* handle events for menus and their buttons recursively,
10742 * this will handle events from the top to the bottom menu */
10743 if (data->menu) {
10744 retval = ui_handle_menus_recursive(C, event, data->menu, 0, false, false, false);
10745 }
10746
10747 /* handle events for the activated button */
10748 if ((data->menu && (retval == WM_UI_HANDLER_CONTINUE)) || (event->type == TIMER)) {
10749 if (data->menu && data->menu->menuretval) {
10750 ui_handle_button_return_submenu(C, event, but);
10751 retval = WM_UI_HANDLER_BREAK;
10752 }
10753 else {
10754 retval = ui_handle_button_event(C, event, but);
10755 }
10756 }
10757 }
10758 else {
10759 /* handle events for the activated button */
10760 retval = ui_handle_button_event(C, event, but);
10761 }
10762 }
10763
10764 /* re-enable tooltips */
10765 if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
10766 ui_blocks_set_tooltips(region, true);
10767 }
10768
10769 if (but && but->active && but->active->menu) {
10770 /* Set correct context menu-region. The handling button above breaks if we set the region
10771 * first, so only set it for executing the after-funcs. */
10772 CTX_wm_menu_set(C, but->active->menu->region);
10773 }
10774
10775 /* delayed apply callbacks */
10776 ui_apply_but_funcs_after(C);
10777
10778 /* Reset to previous context region. */
10779 CTX_wm_menu_set(C, menu_region);
10780
10781 /* Don't handle double-click events,
10782 * these will be converted into regular clicks which we handle. */
10783 if (retval == WM_UI_HANDLER_CONTINUE) {
10784 if (event->val == KM_DBL_CLICK) {
10785 return WM_UI_HANDLER_CONTINUE;
10786 }
10787 }
10788
10789 /* we block all events, this is modal interaction */
10790 return WM_UI_HANDLER_BREAK;
10791 }
10792
10793 /* two types of popups, one with operator + enum, other with regular callbacks */
ui_popup_handler(bContext * C,const wmEvent * event,void * userdata)10794 static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata)
10795 {
10796 uiPopupBlockHandle *menu = userdata;
10797 /* we block all events, this is modal interaction,
10798 * except for drop events which is described below */
10799 int retval = WM_UI_HANDLER_BREAK;
10800 bool reset_pie = false;
10801
10802 ARegion *menu_region = CTX_wm_menu(C);
10803 CTX_wm_menu_set(C, menu->region);
10804
10805 if (event->type == EVT_DROP || event->val == KM_DBL_CLICK) {
10806 /* EVT_DROP:
10807 * If we're handling drop event we'll want it to be handled by popup callee as well,
10808 * so it'll be possible to perform such operations as opening .blend files by dropping
10809 * them into blender, even if there's opened popup like splash screen (sergey).
10810 * KM_DBL_CLICK:
10811 * Continue in case of double click so wm_handlers_do calls handler again with KM_PRESS
10812 * event. This is needed to ensure correct button handling for fast clicking (T47532).
10813 */
10814
10815 retval = WM_UI_HANDLER_CONTINUE;
10816 }
10817
10818 ui_handle_menus_recursive(C, event, menu, 0, false, false, true);
10819
10820 /* free if done, does not free handle itself */
10821 if (menu->menuretval) {
10822 wmWindow *win = CTX_wm_window(C);
10823 /* copy values, we have to free first (closes region) */
10824 const uiPopupBlockHandle temp = *menu;
10825 uiBlock *block = menu->region->uiblocks.first;
10826
10827 /* set last pie event to allow chained pie spawning */
10828 if (block->flag & UI_BLOCK_RADIAL) {
10829 win->last_pie_event = block->pie_data.event;
10830 reset_pie = true;
10831 }
10832
10833 ui_popup_block_free(C, menu);
10834 UI_popup_handlers_remove(&win->modalhandlers, menu);
10835 CTX_wm_menu_set(C, NULL);
10836
10837 #ifdef USE_DRAG_TOGGLE
10838 {
10839 WM_event_free_ui_handler_all(C,
10840 &win->modalhandlers,
10841 ui_handler_region_drag_toggle,
10842 ui_handler_region_drag_toggle_remove);
10843 }
10844 #endif
10845
10846 if ((temp.menuretval & UI_RETURN_OK) || (temp.menuretval & UI_RETURN_POPUP_OK)) {
10847 if (temp.popup_func) {
10848 temp.popup_func(C, temp.popup_arg, temp.retvalue);
10849 }
10850 }
10851 else if (temp.cancel_func) {
10852 temp.cancel_func(C, temp.popup_arg);
10853 }
10854
10855 WM_event_add_mousemove(win);
10856 }
10857 else {
10858 /* re-enable tooltips */
10859 if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
10860 ui_blocks_set_tooltips(menu->region, true);
10861 }
10862 }
10863
10864 /* delayed apply callbacks */
10865 ui_apply_but_funcs_after(C);
10866
10867 if (reset_pie) {
10868 /* reaqcuire window in case pie invalidates it somehow */
10869 wmWindow *win = CTX_wm_window(C);
10870
10871 if (win) {
10872 win->last_pie_event = EVENT_NONE;
10873 }
10874 }
10875
10876 CTX_wm_region_set(C, menu_region);
10877
10878 return retval;
10879 }
10880
ui_popup_handler_remove(bContext * C,void * userdata)10881 static void ui_popup_handler_remove(bContext *C, void *userdata)
10882 {
10883 uiPopupBlockHandle *menu = userdata;
10884
10885 /* More correct would be to expect UI_RETURN_CANCEL here, but not wanting to
10886 * cancel when removing handlers because of file exit is a rare exception.
10887 * So instead of setting cancel flag for all menus before removing handlers,
10888 * just explicitly flag menu with UI_RETURN_OK to avoid canceling it. */
10889 if ((menu->menuretval & UI_RETURN_OK) == 0 && menu->cancel_func) {
10890 menu->cancel_func(C, menu->popup_arg);
10891 }
10892
10893 /* free menu block if window is closed for some reason */
10894 ui_popup_block_free(C, menu);
10895
10896 /* delayed apply callbacks */
10897 ui_apply_but_funcs_after(C);
10898 }
10899
UI_region_handlers_add(ListBase * handlers)10900 void UI_region_handlers_add(ListBase *handlers)
10901 {
10902 WM_event_remove_ui_handler(handlers, ui_region_handler, ui_region_handler_remove, NULL, false);
10903 WM_event_add_ui_handler(NULL, handlers, ui_region_handler, ui_region_handler_remove, NULL, 0);
10904 }
10905
UI_popup_handlers_add(bContext * C,ListBase * handlers,uiPopupBlockHandle * popup,const char flag)10906 void UI_popup_handlers_add(bContext *C,
10907 ListBase *handlers,
10908 uiPopupBlockHandle *popup,
10909 const char flag)
10910 {
10911 WM_event_add_ui_handler(C, handlers, ui_popup_handler, ui_popup_handler_remove, popup, flag);
10912 }
10913
UI_popup_handlers_remove(ListBase * handlers,uiPopupBlockHandle * popup)10914 void UI_popup_handlers_remove(ListBase *handlers, uiPopupBlockHandle *popup)
10915 {
10916 LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
10917 if (handler_base->type == WM_HANDLER_TYPE_UI) {
10918 wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
10919
10920 if (handler->handle_fn == ui_popup_handler &&
10921 handler->remove_fn == ui_popup_handler_remove && handler->user_data == popup) {
10922 /* tag refresh parent popup */
10923 wmEventHandler_UI *handler_next = (wmEventHandler_UI *)handler->head.next;
10924 if (handler_next && handler_next->head.type == WM_HANDLER_TYPE_UI &&
10925 handler_next->handle_fn == ui_popup_handler &&
10926 handler_next->remove_fn == ui_popup_handler_remove) {
10927 uiPopupBlockHandle *parent_popup = handler_next->user_data;
10928 ED_region_tag_refresh_ui(parent_popup->region);
10929 }
10930 break;
10931 }
10932 }
10933 }
10934
10935 WM_event_remove_ui_handler(handlers, ui_popup_handler, ui_popup_handler_remove, popup, false);
10936 }
10937
UI_popup_handlers_remove_all(bContext * C,ListBase * handlers)10938 void UI_popup_handlers_remove_all(bContext *C, ListBase *handlers)
10939 {
10940 WM_event_free_ui_handler_all(C, handlers, ui_popup_handler, ui_popup_handler_remove);
10941 }
10942
UI_textbutton_activate_rna(const bContext * C,ARegion * region,const void * rna_poin_data,const char * rna_prop_id)10943 bool UI_textbutton_activate_rna(const bContext *C,
10944 ARegion *region,
10945 const void *rna_poin_data,
10946 const char *rna_prop_id)
10947 {
10948 uiBlock *block_text = NULL;
10949 uiBut *but_text = NULL;
10950
10951 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
10952 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10953 if (but->type == UI_BTYPE_TEXT) {
10954 if (but->rnaprop && but->rnapoin.data == rna_poin_data) {
10955 if (STREQ(RNA_property_identifier(but->rnaprop), rna_prop_id)) {
10956 block_text = block;
10957 but_text = but;
10958 break;
10959 }
10960 }
10961 }
10962 }
10963 if (but_text) {
10964 break;
10965 }
10966 }
10967
10968 if (but_text) {
10969 UI_but_active_only(C, region, block_text, but_text);
10970 return true;
10971 }
10972 return false;
10973 }
10974
UI_textbutton_activate_but(const bContext * C,uiBut * actbut)10975 bool UI_textbutton_activate_but(const bContext *C, uiBut *actbut)
10976 {
10977 ARegion *region = CTX_wm_region(C);
10978 uiBlock *block_text = NULL;
10979 uiBut *but_text = NULL;
10980
10981 LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) {
10982 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
10983 if (but == actbut && but->type == UI_BTYPE_TEXT) {
10984 block_text = block;
10985 but_text = but;
10986 break;
10987 }
10988 }
10989
10990 if (but_text) {
10991 break;
10992 }
10993 }
10994
10995 if (but_text) {
10996 UI_but_active_only(C, region, block_text, but_text);
10997 return true;
10998 }
10999 return false;
11000 }
11001
11002 /** \} */
11003
11004 /* -------------------------------------------------------------------- */
11005 /** \name Public Utilities
11006 * \{ */
11007
11008 /* is called by notifier */
UI_screen_free_active_but(const bContext * C,bScreen * screen)11009 void UI_screen_free_active_but(const bContext *C, bScreen *screen)
11010 {
11011 wmWindow *win = CTX_wm_window(C);
11012
11013 ED_screen_areas_iter (win, screen, area) {
11014 LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
11015 uiBut *but = ui_region_find_active_but(region);
11016 if (but) {
11017 uiHandleButtonData *data = but->active;
11018
11019 if (data->menu == NULL && data->searchbox == NULL) {
11020 if (data->state == BUTTON_STATE_HIGHLIGHT) {
11021 ui_but_active_free(C, but);
11022 }
11023 }
11024 }
11025 }
11026 }
11027 }
11028
11029 /* returns true if highlighted button allows drop of names */
11030 /* called in region context */
UI_but_active_drop_name(bContext * C)11031 bool UI_but_active_drop_name(bContext *C)
11032 {
11033 ARegion *region = CTX_wm_region(C);
11034 uiBut *but = ui_region_find_active_but(region);
11035
11036 if (but) {
11037 if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
11038 return true;
11039 }
11040 }
11041
11042 return false;
11043 }
11044
UI_but_active_drop_color(bContext * C)11045 bool UI_but_active_drop_color(bContext *C)
11046 {
11047 ARegion *region = CTX_wm_region(C);
11048
11049 if (region) {
11050 uiBut *but = ui_region_find_active_but(region);
11051
11052 if (but && but->type == UI_BTYPE_COLOR) {
11053 return true;
11054 }
11055 }
11056
11057 return false;
11058 }
11059
11060 /** \} */
11061