UI: ignore events in empty region overlap areas
[blender.git] / source / blender / editors / interface / interface_query.c
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
17 /** \file
18  * \ingroup edinterface
19  *
20  * Utilities to inspect the interface, extract information.
21  */
22
23 #include "BLI_utildefines.h"
24 #include "BLI_math.h"
25 #include "BLI_rect.h"
26
27 #include "DNA_screen_types.h"
28
29 #include "UI_interface.h"
30 #include "UI_view2d.h"
31
32 #include "RNA_access.h"
33
34 #include "interface_intern.h"
35
36 #include "WM_api.h"
37 #include "WM_types.h"
38
39 /* -------------------------------------------------------------------- */
40 /** \name Button (#uiBut) State
41  * \{ */
42
43 bool ui_but_is_editable(const uiBut *but)
44 {
45   return !ELEM(but->type,
46                UI_BTYPE_LABEL,
47                UI_BTYPE_SEPR,
48                UI_BTYPE_SEPR_LINE,
49                UI_BTYPE_ROUNDBOX,
50                UI_BTYPE_LISTBOX,
51                UI_BTYPE_PROGRESS_BAR);
52 }
53
54 bool ui_but_is_editable_as_text(const uiBut *but)
55 {
56   return ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_SEARCH_MENU);
57 }
58
59 bool ui_but_is_toggle(const uiBut *but)
60 {
61   return ELEM(but->type,
62               UI_BTYPE_BUT_TOGGLE,
63               UI_BTYPE_TOGGLE,
64               UI_BTYPE_ICON_TOGGLE,
65               UI_BTYPE_ICON_TOGGLE_N,
66               UI_BTYPE_TOGGLE_N,
67               UI_BTYPE_CHECKBOX,
68               UI_BTYPE_CHECKBOX_N,
69               UI_BTYPE_ROW);
70 }
71
72 /**
73  * Can we mouse over the button or is it hidden/disabled/layout.
74  * \note ctrl is kind of a hack currently,
75  * so that non-embossed UI_BTYPE_TEXT button behaves as a label when ctrl is not pressed.
76  */
77 bool ui_but_is_interactive(const uiBut *but, const bool labeledit)
78 {
79   /* note, UI_BTYPE_LABEL is included for highlights, this allows drags */
80   if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == NULL) {
81     return false;
82   }
83   if (ELEM(but->type, UI_BTYPE_ROUNDBOX, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_LISTBOX)) {
84     return false;
85   }
86   if (but->flag & UI_HIDDEN) {
87     return false;
88   }
89   if (but->flag & UI_SCROLLED) {
90     return false;
91   }
92   if ((but->type == UI_BTYPE_TEXT) && (but->dt == UI_EMBOSS_NONE) && !labeledit) {
93     return false;
94   }
95   if ((but->type == UI_BTYPE_LISTROW) && labeledit) {
96     return false;
97   }
98
99   return true;
100 }
101
102 /* file selectors are exempt from utf-8 checks */
103 bool ui_but_is_utf8(const uiBut *but)
104 {
105   if (but->rnaprop) {
106     const int subtype = RNA_property_subtype(but->rnaprop);
107     return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING));
108   }
109   else {
110     return !(but->flag & UI_BUT_NO_UTF8);
111   }
112 }
113
114 #ifdef USE_UI_POPOVER_ONCE
115 bool ui_but_is_popover_once_compat(const uiBut *but)
116 {
117   return ((but->type == UI_BTYPE_BUT) || ui_but_is_toggle(but));
118 }
119 #endif
120
121 bool ui_but_has_array_value(const uiBut *but)
122 {
123   return (but->rnapoin.data && but->rnaprop &&
124           ELEM(RNA_property_subtype(but->rnaprop),
125                PROP_COLOR,
126                PROP_TRANSLATION,
127                PROP_DIRECTION,
128                PROP_VELOCITY,
129                PROP_ACCELERATION,
130                PROP_MATRIX,
131                PROP_EULER,
132                PROP_QUATERNION,
133                PROP_AXISANGLE,
134                PROP_XYZ,
135                PROP_XYZ_LENGTH,
136                PROP_COLOR_GAMMA,
137                PROP_COORDS));
138 }
139
140 bool UI_but_is_tool(const uiBut *but)
141 {
142   /* very evil! */
143   if (but->optype != NULL) {
144     static wmOperatorType *ot = NULL;
145     if (ot == NULL) {
146       ot = WM_operatortype_find("WM_OT_tool_set_by_id", false);
147     }
148     if (but->optype == ot) {
149       return true;
150     }
151   }
152   return false;
153 }
154
155 bool UI_but_has_tooltip_label(const uiBut *but)
156 {
157   if ((but->drawstr[0] == '\0') && !ui_block_is_popover(but->block)) {
158     return UI_but_is_tool(but);
159   }
160   return false;
161 }
162
163 /** \} */
164
165 /* -------------------------------------------------------------------- */
166 /** \name Button (#uiBut) Spatial
167  * \{ */
168
169 void ui_but_pie_dir(RadialDirection dir, float vec[2])
170 {
171   float angle;
172
173   BLI_assert(dir != UI_RADIAL_NONE);
174
175   angle = DEG2RADF((float)ui_radial_dir_to_angle[dir]);
176   vec[0] = cosf(angle);
177   vec[1] = sinf(angle);
178 }
179
180 static bool ui_but_isect_pie_seg(const uiBlock *block, const uiBut *but)
181 {
182   const float angle_range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? M_PI_4 :
183                                                                                    M_PI_4 / 2.0;
184   float vec[2];
185
186   if (block->pie_data.flags & UI_PIE_INVALID_DIR) {
187     return false;
188   }
189
190   ui_but_pie_dir(but->pie_dir, vec);
191
192   if (saacos(dot_v2v2(vec, block->pie_data.pie_dir)) < angle_range) {
193     return true;
194   }
195
196   return false;
197 }
198
199 bool ui_but_contains_pt(const uiBut *but, float mx, float my)
200 {
201   return BLI_rctf_isect_pt(&but->rect, mx, my);
202 }
203
204 bool ui_but_contains_rect(const uiBut *but, const rctf *rect)
205 {
206   return BLI_rctf_isect(&but->rect, rect, NULL);
207 }
208
209 bool ui_but_contains_point_px(const uiBut *but, const ARegion *ar, int x, int y)
210 {
211   uiBlock *block = but->block;
212   float mx, my;
213   if (!ui_region_contains_point_px(ar, x, y)) {
214     return false;
215   }
216
217   mx = x;
218   my = y;
219
220   ui_window_to_block_fl(ar, block, &mx, &my);
221
222   if (but->pie_dir != UI_RADIAL_NONE) {
223     if (!ui_but_isect_pie_seg(block, but)) {
224       return false;
225     }
226   }
227   else if (!ui_but_contains_pt(but, mx, my)) {
228     return false;
229   }
230
231   return true;
232 }
233
234 bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *ar, const wmEvent *event)
235 {
236   rcti rect;
237   int x = event->x, y = event->y;
238
239   ui_window_to_block(ar, but->block, &x, &y);
240
241   BLI_rcti_rctf_copy(&rect, &but->rect);
242
243   if (but->imb || but->type == UI_BTYPE_COLOR) {
244     /* use button size itself */
245   }
246   else if (but->drawflag & UI_BUT_ICON_LEFT) {
247     rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect));
248   }
249   else {
250     int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect);
251     rect.xmin += delta / 2;
252     rect.xmax -= delta / 2;
253   }
254
255   return BLI_rcti_isect_pt(&rect, x, y);
256 }
257
258 /* x and y are only used in case event is NULL... */
259 uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, const bool labeledit)
260 {
261   uiBlock *block;
262   uiBut *but, *butover = NULL;
263   float mx, my;
264
265   //  if (!win->active) {
266   //      return NULL;
267   //  }
268   if (!ui_region_contains_point_px(ar, x, y)) {
269     return NULL;
270   }
271
272   for (block = ar->uiblocks.first; block; block = block->next) {
273     mx = x;
274     my = y;
275     ui_window_to_block_fl(ar, block, &mx, &my);
276
277     for (but = block->buttons.last; but; but = but->prev) {
278       if (ui_but_is_interactive(but, labeledit)) {
279         if (but->pie_dir != UI_RADIAL_NONE) {
280           if (ui_but_isect_pie_seg(block, but)) {
281             butover = but;
282             break;
283           }
284         }
285         else if (ui_but_contains_pt(but, mx, my)) {
286           butover = but;
287           break;
288         }
289       }
290     }
291
292     /* CLIP_EVENTS prevents the event from reaching other blocks */
293     if (block->flag & UI_BLOCK_CLIP_EVENTS) {
294       /* check if mouse is inside block */
295       if (BLI_rctf_isect_pt(&block->rect, mx, my)) {
296         break;
297       }
298     }
299   }
300
301   return butover;
302 }
303
304 uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event)
305 {
306   return ui_but_find_mouse_over_ex(ar, event->x, event->y, event->ctrl != 0);
307 }
308
309 uiBut *ui_but_find_rect_over(const struct ARegion *ar, const rcti *rect_px)
310 {
311   if (!ui_region_contains_rect_px(ar, rect_px)) {
312     return NULL;
313   }
314
315   /* Currently no need to expose this at the moment. */
316   bool labeledit = true;
317   rctf rect_px_fl;
318   BLI_rctf_rcti_copy(&rect_px_fl, rect_px);
319   uiBut *butover = NULL;
320
321   for (uiBlock *block = ar->uiblocks.first; block; block = block->next) {
322     rctf rect_block;
323     ui_window_to_block_rctf(ar, block, &rect_block, &rect_px_fl);
324
325     for (uiBut *but = block->buttons.last; but; but = but->prev) {
326       if (ui_but_is_interactive(but, labeledit)) {
327         /* No pie menu support. */
328         BLI_assert(but->pie_dir == UI_RADIAL_NONE);
329         if (ui_but_contains_rect(but, &rect_block)) {
330           butover = but;
331           break;
332         }
333       }
334     }
335
336     /* CLIP_EVENTS prevents the event from reaching other blocks */
337     if (block->flag & UI_BLOCK_CLIP_EVENTS) {
338       /* check if mouse is inside block */
339       if (BLI_rctf_isect(&block->rect, &rect_block, NULL)) {
340         break;
341       }
342     }
343   }
344   return butover;
345 }
346
347 uiBut *ui_list_find_mouse_over_ex(ARegion *ar, int x, int y)
348 {
349   uiBlock *block;
350   uiBut *but;
351   float mx, my;
352
353   if (!ui_region_contains_point_px(ar, x, y)) {
354     return NULL;
355   }
356
357   for (block = ar->uiblocks.first; block; block = block->next) {
358     mx = x;
359     my = y;
360     ui_window_to_block_fl(ar, block, &mx, &my);
361
362     for (but = block->buttons.last; but; but = but->prev) {
363       if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) {
364         return but;
365       }
366     }
367   }
368
369   return NULL;
370 }
371
372 uiBut *ui_list_find_mouse_over(ARegion *ar, const wmEvent *event)
373 {
374   return ui_list_find_mouse_over_ex(ar, event->x, event->y);
375 }
376
377 /** \} */
378
379 /* -------------------------------------------------------------------- */
380 /** \name Button (#uiBut) Relations
381  * \{ */
382
383 uiBut *ui_but_prev(uiBut *but)
384 {
385   while (but->prev) {
386     but = but->prev;
387     if (ui_but_is_editable(but)) {
388       return but;
389     }
390   }
391   return NULL;
392 }
393
394 uiBut *ui_but_next(uiBut *but)
395 {
396   while (but->next) {
397     but = but->next;
398     if (ui_but_is_editable(but)) {
399       return but;
400     }
401   }
402   return NULL;
403 }
404
405 uiBut *ui_but_first(uiBlock *block)
406 {
407   uiBut *but;
408
409   but = block->buttons.first;
410   while (but) {
411     if (ui_but_is_editable(but)) {
412       return but;
413     }
414     but = but->next;
415   }
416   return NULL;
417 }
418
419 uiBut *ui_but_last(uiBlock *block)
420 {
421   uiBut *but;
422
423   but = block->buttons.last;
424   while (but) {
425     if (ui_but_is_editable(but)) {
426       return but;
427     }
428     but = but->prev;
429   }
430   return NULL;
431 }
432
433 bool ui_but_is_cursor_warp(const uiBut *but)
434 {
435   if (U.uiflag & USER_CONTINUOUS_MOUSE) {
436     if (ELEM(but->type,
437              UI_BTYPE_NUM,
438              UI_BTYPE_NUM_SLIDER,
439              UI_BTYPE_HSVCIRCLE,
440              UI_BTYPE_TRACK_PREVIEW,
441              UI_BTYPE_HSVCUBE,
442              UI_BTYPE_CURVE)) {
443       return true;
444     }
445   }
446
447   return false;
448 }
449
450 bool ui_but_contains_password(const uiBut *but)
451 {
452   return but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD);
453 }
454
455 /** \} */
456
457 /* -------------------------------------------------------------------- */
458 /** \name Block (#uiBlock) State
459  * \{ */
460
461 bool ui_block_is_menu(const uiBlock *block)
462 {
463   return (((block->flag & UI_BLOCK_LOOP) != 0) &&
464           /* non-menu popups use keep-open, so check this is off */
465           ((block->flag & UI_BLOCK_KEEP_OPEN) == 0));
466 }
467
468 bool ui_block_is_popover(const uiBlock *block)
469 {
470   return (block->flag & UI_BLOCK_POPOVER) != 0;
471 }
472
473 bool ui_block_is_pie_menu(const uiBlock *block)
474 {
475   return ((block->flag & UI_BLOCK_RADIAL) != 0);
476 }
477
478 bool ui_block_is_popup_any(const uiBlock *block)
479 {
480   return (ui_block_is_menu(block) || ui_block_is_popover(block) || ui_block_is_pie_menu(block));
481 }
482
483 bool UI_block_is_empty(const uiBlock *block)
484 {
485   for (const uiBut *but = block->buttons.first; but; but = but->next) {
486     if (!ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) {
487       return false;
488     }
489   }
490   return true;
491 }
492
493 /** \} */
494
495 /* -------------------------------------------------------------------- */
496 /** \name Region (#ARegion) State
497  * \{ */
498
499 uiBut *ui_region_find_active_but(ARegion *ar)
500 {
501   uiBlock *block;
502   uiBut *but;
503
504   for (block = ar->uiblocks.first; block; block = block->next) {
505     for (but = block->buttons.first; but; but = but->next) {
506       if (but->active) {
507         return but;
508       }
509     }
510   }
511
512   return NULL;
513 }
514
515 uiBut *ui_region_find_first_but_test_flag(ARegion *ar, int flag_include, int flag_exclude)
516 {
517   for (uiBlock *block = ar->uiblocks.first; block; block = block->next) {
518     for (uiBut *but = block->buttons.first; but; but = but->next) {
519       if (((but->flag & flag_include) == flag_include) && ((but->flag & flag_exclude) == 0)) {
520         return but;
521       }
522     }
523   }
524
525   return NULL;
526 }
527
528 /** \} */
529
530 /* -------------------------------------------------------------------- */
531 /** \name Region (#ARegion) Spatial
532  * \{ */
533
534 bool ui_region_contains_point_px(const ARegion *ar, int x, int y)
535 {
536   rcti winrct;
537   ui_region_winrct_get_no_margin(ar, &winrct);
538   if (!BLI_rcti_isect_pt(&winrct, x, y)) {
539     return false;
540   }
541
542   /* also, check that with view2d, that the mouse is not over the scrollbars
543    * NOTE: care is needed here, since the mask rect may include the scrollbars
544    * even when they are not visible, so we need to make a copy of the mask to
545    * use to check
546    */
547   if (ar->v2d.mask.xmin != ar->v2d.mask.xmax) {
548     const View2D *v2d = &ar->v2d;
549     int mx = x, my = y;
550
551     ui_window_to_region(ar, &mx, &my);
552     if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) ||
553         UI_view2d_mouse_in_scrollers(ar, &ar->v2d, x, y)) {
554       return false;
555     }
556   }
557
558   return true;
559 }
560
561 bool ui_region_contains_rect_px(const ARegion *ar, const rcti *rect_px)
562 {
563   rcti winrct;
564   ui_region_winrct_get_no_margin(ar, &winrct);
565   if (!BLI_rcti_isect(&winrct, rect_px, NULL)) {
566     return false;
567   }
568
569   /* See comment in 'ui_region_contains_point_px' */
570   if (ar->v2d.mask.xmin != ar->v2d.mask.xmax) {
571     const View2D *v2d = &ar->v2d;
572     rcti rect_region;
573     ui_window_to_region_rcti(ar, &rect_region, rect_px);
574     if (!BLI_rcti_isect(&v2d->mask, &rect_region, NULL) ||
575         UI_view2d_rect_in_scrollers(ar, &ar->v2d, rect_px)) {
576       return false;
577     }
578   }
579
580   return true;
581 }
582
583 /** \} */