UI: tweak tool bar tooltips
[blender.git] / source / blender / editors / interface / interface_region_tooltip.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2008 Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Blender Foundation
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/editors/interface/interface_region_tooltip.c
27  *  \ingroup edinterface
28  *
29  * ToolTip Region and Construction
30  */
31
32 /* TODO(campbell):
33  * We may want to have a higher level API that initializes a timer,
34  * checks for mouse motion and clears the tool-tip afterwards.
35  * We never want multiple tool-tips at once so this could be handled on the window / window-manager level.
36  *
37  * For now it's not a priority, so leave as-is.
38  */
39
40 #include <stdarg.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <assert.h>
44
45 #include "MEM_guardedalloc.h"
46
47 #include "DNA_userdef_types.h"
48 #include "DNA_brush_types.h"
49
50 #include "BLI_math.h"
51 #include "BLI_string.h"
52 #include "BLI_string_utf8.h"
53 #include "BLI_rect.h"
54 #include "BLI_utildefines.h"
55
56 #include "BKE_context.h"
57 #include "BKE_screen.h"
58 #include "BKE_library.h"
59
60 #include "WM_api.h"
61 #include "WM_types.h"
62
63 #include "RNA_access.h"
64
65 #include "BIF_gl.h"
66
67 #include "UI_interface.h"
68
69 #include "BLF_api.h"
70 #include "BLT_translation.h"
71
72 #ifdef WITH_PYTHON
73 #  include "BPY_extern.h"
74 #endif
75
76 #include "ED_screen.h"
77
78 #include "interface_intern.h"
79 #include "interface_regions_intern.h"
80
81 #define UI_TIP_PAD_FAC      1.3f
82 #define UI_TIP_PADDING      (int)(UI_TIP_PAD_FAC * UI_UNIT_Y)
83 #define UI_TIP_MAXWIDTH     600
84
85
86 typedef struct uiTooltipFormat {
87         enum {
88                 UI_TIP_STYLE_NORMAL = 0,
89                 UI_TIP_STYLE_HEADER,
90                 UI_TIP_STYLE_MONO,
91         } style : 3;
92         enum {
93                 UI_TIP_LC_MAIN = 0,     /* primary text */
94                 UI_TIP_LC_VALUE,        /* the value of buttons (also shortcuts) */
95                 UI_TIP_LC_ACTIVE,       /* titles of active enum values */
96                 UI_TIP_LC_NORMAL,       /* regular text */
97                 UI_TIP_LC_PYTHON,       /* Python snippet */
98                 UI_TIP_LC_ALERT,        /* description of why operator can't run */
99         } color_id : 4;
100         int is_pad : 1;
101 } uiTooltipFormat;
102
103 typedef struct uiTooltipField {
104         char *text;
105         char *text_suffix;
106         struct {
107                 uint x_pos;     /* x cursor position at the end of the last line */
108                 uint lines;     /* number of lines, 1 or more with word-wrap */
109         } geom;
110         uiTooltipFormat format;
111
112 } uiTooltipField;
113
114 typedef struct uiTooltipData {
115         rcti bbox;
116         uiTooltipField *fields;
117         uint            fields_len;
118         uiFontStyle fstyle;
119         int wrap_width;
120         int toth, lineh;
121 } uiTooltipData;
122
123 #define UI_TIP_LC_MAX 6
124
125 BLI_STATIC_ASSERT(UI_TIP_LC_MAX == UI_TIP_LC_ALERT + 1, "invalid lc-max");
126 BLI_STATIC_ASSERT(sizeof(uiTooltipFormat) <= sizeof(int), "oversize");
127
128 static uiTooltipField *text_field_add_only(
129         uiTooltipData *data)
130 {
131         data->fields_len += 1;
132         data->fields = MEM_recallocN(data->fields, sizeof(*data->fields) * data->fields_len);
133         return &data->fields[data->fields_len - 1];
134 }
135
136 static uiTooltipField *text_field_add(
137         uiTooltipData *data,
138         const uiTooltipFormat *format)
139 {
140         uiTooltipField *field = text_field_add_only(data);
141         field->format = *format;
142         return field;
143 }
144
145 /* -------------------------------------------------------------------- */
146 /** \name ToolTip Callbacks (Draw & Free)
147  * \{ */
148
149 static void rgb_tint(
150         float col[3],
151         float h, float h_strength,
152         float v, float v_strength)
153 {
154         float col_hsv_from[3];
155         float col_hsv_to[3];
156
157         rgb_to_hsv_v(col, col_hsv_from);
158
159         col_hsv_to[0] = h;
160         col_hsv_to[1] = h_strength;
161         col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength);
162
163         hsv_to_rgb_v(col_hsv_to, col);
164 }
165
166 static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
167 {
168         const float pad_px = UI_TIP_PADDING;
169         uiTooltipData *data = ar->regiondata;
170         uiWidgetColors *theme = ui_tooltip_get_theme();
171         rcti bbox = data->bbox;
172         float tip_colors[UI_TIP_LC_MAX][3];
173         unsigned char drawcol[4] = {0, 0, 0, 255}; /* to store color in while drawing (alpha is always 255) */
174
175         float *main_color    = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */
176         float *value_color   = tip_colors[UI_TIP_LC_VALUE];
177         float *active_color  = tip_colors[UI_TIP_LC_ACTIVE];
178         float *normal_color  = tip_colors[UI_TIP_LC_NORMAL];
179         float *python_color  = tip_colors[UI_TIP_LC_PYTHON];
180         float *alert_color   = tip_colors[UI_TIP_LC_ALERT];
181
182         float background_color[3];
183         float tone_bg;
184         int i;
185
186         wmOrtho2_region_pixelspace(ar);
187
188         /* draw background */
189         ui_draw_tooltip_background(UI_style_get(), NULL, &bbox);
190
191         /* set background_color */
192         rgb_uchar_to_float(background_color, (const unsigned char *)theme->inner);
193
194         /* calculate normal_color */
195         rgb_uchar_to_float(main_color, (const unsigned char *)theme->text);
196         copy_v3_v3(active_color, main_color);
197         copy_v3_v3(normal_color, main_color);
198         copy_v3_v3(python_color, main_color);
199         copy_v3_v3(alert_color, main_color);
200         copy_v3_v3(value_color, main_color);
201
202         /* find the brightness difference between background and text colors */
203
204         tone_bg = rgb_to_grayscale(background_color);
205         /* tone_fg = rgb_to_grayscale(main_color); */
206
207         /* mix the colors */
208         rgb_tint(value_color,  0.0f, 0.0f, tone_bg, 0.2f);  /* light gray */
209         rgb_tint(active_color, 0.6f, 0.2f, tone_bg, 0.2f);  /* light blue */
210         rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.4f);  /* gray       */
211         rgb_tint(python_color, 0.0f, 0.0f, tone_bg, 0.5f);  /* dark gray  */
212         rgb_tint(alert_color,  0.0f, 0.8f, tone_bg, 0.1f);  /* red        */
213
214         /* draw text */
215         BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
216         BLF_wordwrap(blf_mono_font, data->wrap_width);
217
218         bbox.xmin += 0.5f * pad_px;  /* add padding to the text */
219         bbox.ymax -= 0.25f * pad_px;
220
221         for (i = 0; i < data->fields_len; i++) {
222                 const uiTooltipField *field = &data->fields[i];
223                 const uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL;
224
225                 bbox.ymin = bbox.ymax - (data->lineh * field->geom.lines);
226                 if (field->format.style == UI_TIP_STYLE_HEADER) {
227                         /* draw header and active data (is done here to be able to change color) */
228                         uiFontStyle fstyle_header = data->fstyle;
229
230                         /* override text-style */
231                         fstyle_header.shadow = 1;
232                         fstyle_header.shadowcolor = rgb_to_grayscale(tip_colors[UI_TIP_LC_MAIN]);
233                         fstyle_header.shadx = fstyle_header.shady = 0;
234                         fstyle_header.shadowalpha = 1.0f;
235                         fstyle_header.word_wrap = true;
236
237                         rgb_float_to_uchar(drawcol, tip_colors[UI_TIP_LC_MAIN]);
238                         UI_fontstyle_set(&fstyle_header);
239                         UI_fontstyle_draw(&fstyle_header, &bbox, field->text, drawcol);
240
241                         fstyle_header.shadow = 0;
242
243                         /* offset to the end of the last line */
244                         if (field->text_suffix) {
245                                 float xofs = field->geom.x_pos;
246                                 float yofs = data->lineh * (field->geom.lines - 1);
247                                 bbox.xmin += xofs;
248                                 bbox.ymax -= yofs;
249
250                                 rgb_float_to_uchar(drawcol, tip_colors[UI_TIP_LC_ACTIVE]);
251                                 UI_fontstyle_draw(&fstyle_header, &bbox, field->text_suffix, drawcol);
252
253                                 /* undo offset */
254                                 bbox.xmin -= xofs;
255                                 bbox.ymax += yofs;
256                         }
257                 }
258                 else if (field->format.style == UI_TIP_STYLE_MONO) {
259                         uiFontStyle fstyle_mono = data->fstyle;
260                         fstyle_mono.uifont_id = blf_mono_font;
261                         fstyle_mono.word_wrap = true;
262
263                         UI_fontstyle_set(&fstyle_mono);
264                         /* XXX, needed because we dont have mono in 'U.uifonts' */
265                         BLF_size(fstyle_mono.uifont_id, fstyle_mono.points * U.pixelsize, U.dpi);
266                         rgb_float_to_uchar(drawcol, tip_colors[field->format.color_id]);
267                         UI_fontstyle_draw(&fstyle_mono, &bbox, field->text, drawcol);
268                 }
269                 else {
270                         uiFontStyle fstyle_normal = data->fstyle;
271                         BLI_assert(field->format.style == UI_TIP_STYLE_NORMAL);
272                         fstyle_normal.word_wrap = true;
273
274                         /* draw remaining data */
275                         rgb_float_to_uchar(drawcol, tip_colors[field->format.color_id]);
276                         UI_fontstyle_set(&fstyle_normal);
277                         UI_fontstyle_draw(&fstyle_normal, &bbox, field->text, drawcol);
278                 }
279
280                 bbox.ymax -= data->lineh * field->geom.lines;
281
282                 if (field_next && field_next->format.is_pad) {
283                         bbox.ymax -= data->lineh * (UI_TIP_PAD_FAC - 1);
284                 }
285         }
286
287         BLF_disable(data->fstyle.uifont_id, BLF_WORD_WRAP);
288         BLF_disable(blf_mono_font, BLF_WORD_WRAP);
289 }
290
291 static void ui_tooltip_region_free_cb(ARegion *ar)
292 {
293         uiTooltipData *data;
294
295         data = ar->regiondata;
296
297         for (int  i = 0; i < data->fields_len; i++) {
298                 const uiTooltipField *field = &data->fields[i];
299                 MEM_freeN(field->text);
300                 if (field->text_suffix) {
301                         MEM_freeN(field->text_suffix);
302                 }
303         }
304         MEM_freeN(data->fields);
305         MEM_freeN(data);
306         ar->regiondata = NULL;
307 }
308
309 /** \} */
310
311 /* -------------------------------------------------------------------- */
312 /** \name ToolTip Creation
313  * \{ */
314
315 static bool ui_tooltip_data_append_from_keymap(
316         bContext *C, uiTooltipData *data,
317         wmKeyMap *keymap)
318 {
319         const int fields_len_init = data->fields_len;
320         char buf[512];
321
322         for (wmKeyMapItem *kmi = keymap->items.first; kmi; kmi = kmi->next) {
323                 wmOperatorType *ot = WM_operatortype_find(kmi->idname, true);
324                 if (ot != NULL) {
325                         /* Tip */
326                         {
327                                 uiTooltipField *field = text_field_add(
328                                         data, &(uiTooltipFormat){
329                                             .style = UI_TIP_STYLE_NORMAL,
330                                             .color_id = UI_TIP_LC_MAIN,
331                                             .is_pad = true,
332                                         });
333                                 field->text = BLI_strdup(ot->description[0] ? ot->description : ot->name);
334                         }
335                         /* Shortcut */
336                         {
337                                 uiTooltipField *field = text_field_add(
338                                         data, &(uiTooltipFormat){
339                                             .style = UI_TIP_STYLE_NORMAL,
340                                             .color_id = UI_TIP_LC_NORMAL,
341                                         });
342                                 bool found = false;
343                                 if (WM_keymap_item_to_string(kmi, false, buf, sizeof(buf))) {
344                                         found = true;
345                                 }
346                                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None");
347                         }
348
349                         /* Python */
350                         if (U.flag & USER_TOOLTIPS_PYTHON) {
351                                 uiTooltipField *field = text_field_add(
352                                         data, &(uiTooltipFormat){
353                                             .style = UI_TIP_STYLE_NORMAL,
354                                             .color_id = UI_TIP_LC_PYTHON,
355                                         });
356                                 char *str = WM_operator_pystring_ex(C, NULL, false, false, ot, kmi->ptr);
357                                 WM_operator_pystring_abbreviate(str, 32);
358                                 field->text = BLI_sprintfN(TIP_("Python: %s"), str);
359                                 MEM_freeN(str);
360                         }
361                 }
362         }
363
364         return (fields_len_init != data->fields_len);
365 }
366
367
368 /**
369  * Special tool-system exception.
370  */
371 static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is_label)
372 {
373         if (but->optype == NULL) {
374                 return NULL;
375         }
376
377         if (!STREQ(but->optype->idname, "WM_OT_tool_set_by_name")) {
378                 return NULL;
379         }
380
381         /* Needed to get the space-data's type (below). */
382         if (CTX_wm_space_data(C) == NULL) {
383                 return NULL;
384         }
385
386         char tool_name[MAX_NAME];
387         RNA_string_get(but->opptr, "name", tool_name);
388         BLI_assert(tool_name[0] != '\0');
389
390         /* We have a tool, now extract the info. */
391         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
392
393 #ifdef WITH_PYTHON
394         /* it turns out to be most simple to do this via Python since C
395          * doesn't have access to information about non-active tools.
396          */
397
398         /* Title (when icon-only). */
399         if (but->drawstr[0] == '\0') {
400                 uiTooltipField *field = text_field_add(
401                         data, &(uiTooltipFormat){
402                             .style = UI_TIP_STYLE_NORMAL,
403                             .color_id = UI_TIP_LC_MAIN,
404                             .is_pad = true,
405                         });
406                 field->text = BLI_strdup(tool_name);
407         }
408
409         /* Tip. */
410         if (is_label == false) {
411                 const char *expr_imports[] = {"bpy", "bl_ui", NULL};
412                 char expr[256];
413                 SNPRINTF(
414                         expr,
415                         "bl_ui.space_toolsystem_common.description_from_name("
416                         "bpy.context, "
417                         "bpy.context.space_data.type, "
418                         "'%s') + '.'",
419                         tool_name);
420
421                 char *expr_result = NULL;
422                 if (BPY_execute_string_as_string(C, expr_imports, expr, true, &expr_result)) {
423                         if (!STREQ(expr_result, ".")) {
424                                 uiTooltipField *field = text_field_add(
425                                         data, &(uiTooltipFormat){
426                                             .style = UI_TIP_STYLE_NORMAL,
427                                             .color_id = UI_TIP_LC_MAIN,
428                                             .is_pad = true,
429                                         });
430                                 field->text = expr_result;
431                         }
432                         else {
433                                 MEM_freeN(expr_result);
434                         }
435                 }
436                 else {
437                         BLI_assert(0);
438                 }
439         }
440
441         /* Shortcut. */
442         if (is_label == false) {
443                 /* There are different kinds of shortcuts:
444                  *
445                  * - Direct access to the tool (as if the toolbar button is pressed).
446                  * - The key is bound to a brush type (not the exact brush name).
447                  * - The key is assigned to the operator it's self (bypassing the tool, executing the operator).
448                  *
449                  * Either way case it's useful to show the shortcut.
450                  */
451                 char *shortcut = NULL;
452
453                 {
454                         uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
455                         UI_but_string_info_get(C, but, &op_keymap, NULL);
456                         shortcut = op_keymap.strinfo;
457                 }
458
459                 if (shortcut == NULL) {
460                         int mode = CTX_data_mode_enum(C);
461                         const char *tool_attr = NULL;
462                         uint tool_offset = 0;
463
464                         switch (mode) {
465                                 case CTX_MODE_SCULPT:
466                                         tool_attr = "sculpt_tool";
467                                         tool_offset = offsetof(Brush, sculpt_tool);
468                                         break;
469                                 case CTX_MODE_PAINT_VERTEX:
470                                         tool_attr = "vertex_paint_tool";
471                                         tool_offset = offsetof(Brush, vertexpaint_tool);
472                                         break;
473                                 case CTX_MODE_PAINT_WEIGHT:
474                                         tool_attr = "weight_paint_tool";
475                                         tool_offset = offsetof(Brush, vertexpaint_tool);
476                                         break;
477                                 case CTX_MODE_PAINT_TEXTURE:
478                                         tool_attr = "texture_paint_tool";
479                                         tool_offset = offsetof(Brush, imagepaint_tool);
480                                         break;
481                                 default:
482                                         break;
483                         }
484
485                         if (tool_attr != NULL) {
486                                 struct Main *bmain = CTX_data_main(C);
487                                 Brush *brush = (Brush *)BKE_libblock_find_name(bmain, ID_BR, tool_name);
488                                 if (brush) {
489                                         Object *ob = CTX_data_active_object(C);
490                                         wmOperatorType *ot = WM_operatortype_find("paint.brush_select", true);
491
492                                         PointerRNA op_props;
493                                         WM_operator_properties_create_ptr(&op_props, ot);
494                                         RNA_enum_set(&op_props, "paint_mode", ob->mode);
495                                         RNA_enum_set(&op_props, tool_attr, *(((char *)brush) + tool_offset));
496
497                                         /* Check for direct access to the tool. */
498                                         char shortcut_brush[128] = "";
499                                         if (WM_key_event_operator_string(
500                                                     C, ot->idname, WM_OP_INVOKE_REGION_WIN, op_props.data, true,
501                                                     shortcut_brush, ARRAY_SIZE(shortcut_brush)))
502                                         {
503                                                 shortcut = BLI_strdup(shortcut_brush);
504                                         }
505                                         WM_operator_properties_free(&op_props);
506                                 }
507                         }
508                 }
509
510                 if (shortcut == NULL) {
511                         /* Check for direct access to the tool. */
512                         char shortcut_toolbar[128] = "";
513                         if (WM_key_event_operator_string(
514                                     C, "WM_OT_toolbar", WM_OP_INVOKE_REGION_WIN, NULL, true,
515                                     shortcut_toolbar, ARRAY_SIZE(shortcut_toolbar)))
516                         {
517                                 /* Generate keymap in order to inspect it.
518                                  * Note, we could make a utility to avoid the keymap generation part of this. */
519                                 const char *expr_imports[] = {"bpy", "bl_ui", NULL};
520                                 const char *expr = (
521                                         "getattr("
522                                         "bl_ui.space_toolsystem_common.keymap_from_context("
523                                         "bpy.context, "
524                                         "bpy.context.space_data.type), "
525                                         "'as_pointer', lambda: 0)()");
526
527                                 intptr_t expr_result = 0;
528                                 if (BPY_execute_string_as_intptr(C, expr_imports, expr, true, &expr_result)) {
529                                         if (expr_result != 0) {
530                                                 wmKeyMap *keymap = (wmKeyMap *)expr_result;
531                                                 for (wmKeyMapItem *kmi = keymap->items.first; kmi; kmi = kmi->next) {
532                                                         if (STREQ(kmi->idname, but->optype->idname)) {
533                                                                 char tool_name_test[MAX_NAME];
534                                                                 RNA_string_get(kmi->ptr, "name", tool_name_test);
535                                                                 if (STREQ(tool_name, tool_name_test)) {
536                                                                         char buf[128];
537                                                                         WM_keymap_item_to_string(kmi, false, buf, sizeof(buf));
538                                                                         shortcut = BLI_sprintfN("%s, %s", shortcut_toolbar, buf);
539                                                                         break;
540                                                                 }
541                                                         }
542                                                 }
543                                         }
544                                 }
545                                 else {
546                                         BLI_assert(0);
547                                 }
548                         }
549                 }
550
551                 if (shortcut != NULL) {
552                         uiTooltipField *field = text_field_add(
553                                 data, &(uiTooltipFormat){
554                                     .style = UI_TIP_STYLE_NORMAL,
555                                     .color_id = UI_TIP_LC_VALUE,
556                                     .is_pad = true,
557                                 });
558                         field->text = BLI_sprintfN(TIP_("Shortcut: %s"), shortcut);
559                         MEM_freeN(shortcut);
560                 }
561         }
562
563         /* Keymap */
564
565         /* This is too handy not to expose somehow, let's be sneaky for now. */
566         if ((is_label == false) && CTX_wm_window(C)->eventstate->shift) {
567                 const char *expr_imports[] = {"bpy", "bl_ui", NULL};
568                 char expr[256];
569                 SNPRINTF(
570                         expr,
571                         "getattr("
572                         "bl_ui.space_toolsystem_common.keymap_from_name("
573                         "bpy.context, "
574                         "bpy.context.space_data.type, "
575                         "'%s'), "
576                         "'as_pointer', lambda: 0)()",
577                         tool_name);
578
579                 intptr_t expr_result = 0;
580                 if (BPY_execute_string_as_intptr(C, expr_imports, expr, true, &expr_result)) {
581                         if (expr_result != 0) {
582                                 {
583                                         uiTooltipField *field = text_field_add(
584                                                 data, &(uiTooltipFormat){
585                                                     .style = UI_TIP_STYLE_NORMAL,
586                                                     .color_id = UI_TIP_LC_NORMAL,
587                                                     .is_pad = true,
588                                                 });
589                                         field->text = BLI_strdup("Tool Keymap:");
590                                 }
591                                 wmKeyMap *keymap = (wmKeyMap *)expr_result;
592                                 ui_tooltip_data_append_from_keymap(C, data, keymap);
593                         }
594                 }
595                 else {
596                         BLI_assert(0);
597                 }
598         }
599 #endif  /* WITH_PYTHON */
600
601         if (data->fields_len == 0) {
602                 MEM_freeN(data);
603                 return NULL;
604         }
605         else {
606                 return data;
607         }
608 }
609
610 static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
611 {
612         uiStringInfo but_tip = {BUT_GET_TIP, NULL};
613         uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL};
614         uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL};
615         uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
616         uiStringInfo prop_keymap = {BUT_GET_PROP_KEYMAP, NULL};
617         uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL};
618         uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL};
619
620         char buf[512];
621
622         /* create tooltip data */
623         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
624
625         UI_but_string_info_get(C, but, &but_tip, &enum_label, &enum_tip, &op_keymap, &prop_keymap, &rna_struct, &rna_prop, NULL);
626
627         /* Tip */
628         if (but_tip.strinfo) {
629                 {
630                         uiTooltipField *field = text_field_add(
631                                 data, &(uiTooltipFormat){
632                                     .style = UI_TIP_STYLE_HEADER,
633                                     .color_id = UI_TIP_LC_NORMAL,
634                                 });
635                         if (enum_label.strinfo) {
636                                 field->text = BLI_sprintfN("%s:  ", but_tip.strinfo);
637                                 field->text_suffix = BLI_strdup(enum_label.strinfo);
638                         }
639                         else {
640                                 field->text = BLI_sprintfN("%s.", but_tip.strinfo);
641                         }
642                 }
643
644                 /* special case enum rna buttons */
645                 if ((but->type & UI_BTYPE_ROW) && but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) {
646                         uiTooltipField *field = text_field_add(
647                                 data, &(uiTooltipFormat){
648                                     .style = UI_TIP_STYLE_NORMAL,
649                                     .color_id = UI_TIP_LC_NORMAL,
650                                 });
651                         field->text = BLI_strdup(IFACE_("(Shift-Click/Drag to select multiple)"));
652                 }
653
654         }
655         /* Enum field label & tip */
656         if (enum_tip.strinfo) {
657                 uiTooltipField *field = text_field_add(
658                         data, &(uiTooltipFormat){
659                             .style = UI_TIP_STYLE_NORMAL,
660                             .color_id = UI_TIP_LC_VALUE,
661                             .is_pad = true,
662                         });
663                 field->text = BLI_strdup(enum_tip.strinfo);
664         }
665
666         /* Op shortcut */
667         if (op_keymap.strinfo) {
668                 uiTooltipField *field = text_field_add(
669                         data, &(uiTooltipFormat){
670                             .style = UI_TIP_STYLE_NORMAL,
671                             .color_id = UI_TIP_LC_VALUE,
672                             .is_pad = true,
673                         });
674                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), op_keymap.strinfo);
675         }
676
677         /* Property context-toggle shortcut */
678         if (prop_keymap.strinfo) {
679                 uiTooltipField *field = text_field_add(
680                         data, &(uiTooltipFormat){
681                             .style = UI_TIP_STYLE_NORMAL,
682                             .color_id = UI_TIP_LC_VALUE,
683                             .is_pad = true,
684                         });
685                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), prop_keymap.strinfo);
686         }
687
688         if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
689                 /* better not show the value of a password */
690                 if ((but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) == 0) {
691                         /* full string */
692                         ui_but_string_get(but, buf, sizeof(buf));
693                         if (buf[0]) {
694                                 uiTooltipField *field = text_field_add(
695                                         data, &(uiTooltipFormat){
696                                             .style = UI_TIP_STYLE_NORMAL,
697                                             .color_id = UI_TIP_LC_VALUE,
698                                             .is_pad = true,
699                                         });
700                                 field->text = BLI_sprintfN(TIP_("Value: %s"), buf);
701                         }
702                 }
703         }
704
705         if (but->rnaprop) {
706                 int unit_type = UI_but_unit_type_get(but);
707
708                 if (unit_type == PROP_UNIT_ROTATION) {
709                         if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
710                                 float value = RNA_property_array_check(but->rnaprop) ?
711                                                   RNA_property_float_get_index(&but->rnapoin, but->rnaprop, but->rnaindex) :
712                                                   RNA_property_float_get(&but->rnapoin, but->rnaprop);
713
714                                 uiTooltipField *field = text_field_add(
715                                         data, &(uiTooltipFormat){
716                                             .style = UI_TIP_STYLE_NORMAL,
717                                             .color_id = UI_TIP_LC_VALUE,
718                                         });
719                                 field->text = BLI_sprintfN(TIP_("Radians: %f"), value);
720                         }
721                 }
722
723                 if (but->flag & UI_BUT_DRIVEN) {
724                         if (ui_but_anim_expression_get(but, buf, sizeof(buf))) {
725                                 uiTooltipField *field = text_field_add(
726                                         data, &(uiTooltipFormat){
727                                             .style = UI_TIP_STYLE_NORMAL,
728                                             .color_id = UI_TIP_LC_NORMAL,
729                                         });
730                                 field->text = BLI_sprintfN(TIP_("Expression: %s"), buf);
731                         }
732                 }
733
734                 if (but->rnapoin.id.data) {
735                         const ID *id = but->rnapoin.id.data;
736                         if (ID_IS_LINKED(id)) {
737                                 uiTooltipField *field = text_field_add(
738                                         data, &(uiTooltipFormat){
739                                             .style = UI_TIP_STYLE_NORMAL,
740                                             .color_id = UI_TIP_LC_NORMAL,
741                                         });
742                                 field->text = BLI_sprintfN(TIP_("Library: %s"), id->lib->name);
743                         }
744                 }
745         }
746         else if (but->optype) {
747                 PointerRNA *opptr;
748                 char *str;
749                 opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
750
751                 /* so the context is passed to fieldf functions (some py fieldf functions use it) */
752                 WM_operator_properties_sanitize(opptr, false);
753
754                 str = WM_operator_pystring_ex(C, NULL, false, false, but->optype, opptr);
755
756                 /* avoid overly verbose tips (eg, arrays of 20 layers), exact limit is arbitrary */
757                 WM_operator_pystring_abbreviate(str, 32);
758
759                 /* operator info */
760                 if (U.flag & USER_TOOLTIPS_PYTHON) {
761                         uiTooltipField *field = text_field_add(
762                                 data, &(uiTooltipFormat){
763                                     .style = UI_TIP_STYLE_MONO,
764                                     .color_id = UI_TIP_LC_PYTHON,
765                                     .is_pad = true,
766                                 });
767                         field->text = BLI_sprintfN(TIP_("Python: %s"), str);
768                 }
769
770                 MEM_freeN(str);
771         }
772
773         /* button is disabled, we may be able to tell user why */
774         if (but->flag & UI_BUT_DISABLED) {
775                 const char *disabled_msg = NULL;
776
777                 /* if operator poll check failed, it can give pretty precise info why */
778                 if (but->optype) {
779                         CTX_wm_operator_poll_msg_set(C, NULL);
780                         WM_operator_poll_context(C, but->optype, but->opcontext);
781                         disabled_msg = CTX_wm_operator_poll_msg_get(C);
782                 }
783                 /* alternatively, buttons can store some reasoning too */
784                 else if (but->disabled_info) {
785                         disabled_msg = TIP_(but->disabled_info);
786                 }
787
788                 if (disabled_msg && disabled_msg[0]) {
789                         uiTooltipField *field = text_field_add(
790                                 data, &(uiTooltipFormat){
791                                     .style = UI_TIP_STYLE_NORMAL,
792                                     .color_id = UI_TIP_LC_ALERT,
793                                 });
794                         field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg);
795                 }
796         }
797
798         if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) {
799                 {
800                         uiTooltipField *field = text_field_add(
801                                 data, &(uiTooltipFormat){
802                                     .style = UI_TIP_STYLE_MONO,
803                                     .color_id = UI_TIP_LC_PYTHON,
804                                     .is_pad = true,
805                                 });
806                         if (rna_prop.strinfo) {
807                                 /* Struct and prop */
808                                 field->text = BLI_sprintfN(TIP_("Python: %s.%s"), rna_struct.strinfo, rna_prop.strinfo);
809                         }
810                         else {
811                                 /* Only struct (e.g. menus) */
812                                 field->text = BLI_sprintfN(TIP_("Python: %s"), rna_struct.strinfo);
813                         }
814                 }
815
816                 if (but->rnapoin.id.data) {
817                         uiTooltipField *field = text_field_add(
818                                 data, &(uiTooltipFormat){
819                                     .style = UI_TIP_STYLE_MONO,
820                                     .color_id = UI_TIP_LC_PYTHON,
821                                 });
822
823                         /* this could get its own 'BUT_GET_...' type */
824
825                         /* never fails */
826                         /* move ownership (no need for re-alloc) */
827                         if (but->rnaprop) {
828                                 field->text = RNA_path_full_property_py_ex(&but->rnapoin, but->rnaprop, but->rnaindex, true);
829                         }
830                         else {
831                                 field->text = RNA_path_full_struct_py(&but->rnapoin);
832                         }
833
834                 }
835         }
836
837         /* Free strinfo's... */
838         if (but_tip.strinfo)
839                 MEM_freeN(but_tip.strinfo);
840         if (enum_label.strinfo)
841                 MEM_freeN(enum_label.strinfo);
842         if (enum_tip.strinfo)
843                 MEM_freeN(enum_tip.strinfo);
844         if (op_keymap.strinfo)
845                 MEM_freeN(op_keymap.strinfo);
846         if (prop_keymap.strinfo)
847                 MEM_freeN(prop_keymap.strinfo);
848         if (rna_struct.strinfo)
849                 MEM_freeN(rna_struct.strinfo);
850         if (rna_prop.strinfo)
851                 MEM_freeN(rna_prop.strinfo);
852
853         if (data->fields_len == 0) {
854                 MEM_freeN(data);
855                 return NULL;
856         }
857         else {
858                 return data;
859         }
860 }
861
862 static uiTooltipData *ui_tooltip_data_from_gizmo(bContext *C, wmGizmo *gz)
863 {
864         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
865
866         /* TODO(campbell): a way for gizmos to have their own descriptions (low priority). */
867
868         /* Operator Actions */
869         {
870                 bool use_drag = gz->drag_part != -1 && gz->highlight_part != gz->drag_part;
871
872                 const struct {
873                         int part;
874                         const char *prefix;
875                 } mpop_actions[] = {
876                         {
877                                 .part = gz->highlight_part,
878                                 .prefix = use_drag ? TIP_("Click") : NULL,
879                         }, {
880                                 .part = use_drag ? gz->drag_part : -1,
881                                 .prefix = use_drag ? TIP_("Drag") : NULL,
882                         },
883                 };
884
885                 for (int i = 0; i < ARRAY_SIZE(mpop_actions); i++) {
886                         wmGizmoOpElem *mpop = (mpop_actions[i].part != -1) ? WM_gizmo_operator_get(gz, mpop_actions[i].part) : NULL;
887                         if (mpop != NULL) {
888                                 /* Description */
889                                 const char *info = RNA_struct_ui_description(mpop->type->srna);
890                                 if (!(info && info[0])) {
891                                         info  = RNA_struct_ui_name(mpop->type->srna);
892                                 }
893
894                                 if (info && info[0]) {
895                                         char *text = NULL;
896                                         if (mpop_actions[i].prefix != NULL) {
897                                                 text = BLI_sprintfN("%s: %s", mpop_actions[i].prefix, info);
898                                         }
899                                         else {
900                                                 text = BLI_strdup(info);
901                                         }
902
903                                         if (text != NULL) {
904                                                 uiTooltipField *field = text_field_add(
905                                                         data, &(uiTooltipFormat){
906                                                             .style = UI_TIP_STYLE_HEADER,
907                                                             .color_id = UI_TIP_LC_VALUE,
908                                                             .is_pad = true,
909                                                         });
910                                                 field->text = text;
911                                         }
912                                 }
913
914                                 /* Shortcut */
915                                 {
916                                         bool found = false;
917                                         IDProperty *prop = mpop->ptr.data;
918                                         char buf[128];
919                                         if (WM_key_event_operator_string(
920                                                     C, mpop->type->idname, WM_OP_INVOKE_DEFAULT, prop, true,
921                                                     buf, ARRAY_SIZE(buf)))
922                                         {
923                                                 found = true;
924                                         }
925                                         uiTooltipField *field = text_field_add(
926                                                 data, &(uiTooltipFormat){
927                                                     .style = UI_TIP_STYLE_NORMAL,
928                                                     .color_id = UI_TIP_LC_VALUE,
929                                                     .is_pad = true,
930                                                 });
931                                         field->text = BLI_sprintfN(TIP_("Shortcut: %s"), found ? buf : "None");
932                                 }
933                         }
934                 }
935         }
936
937         /* Property Actions */
938         if (gz->type->target_property_defs_len) {
939                 wmGizmoProperty *gz_prop_array = WM_gizmo_target_property_array(gz);
940                 for (int i = 0; i < gz->type->target_property_defs_len; i++) {
941                         /* TODO(campbell): function callback descriptions. */
942                         wmGizmoProperty *gz_prop = &gz_prop_array[i];
943                         if (gz_prop->prop != NULL) {
944                                 const char *info = RNA_property_ui_description(gz_prop->prop);
945                                 if (info && info[0]) {
946                                         uiTooltipField *field = text_field_add(
947                                                 data, &(uiTooltipFormat){
948                                                     .style = UI_TIP_STYLE_NORMAL,
949                                                     .color_id = UI_TIP_LC_VALUE,
950                                                     .is_pad = true,
951                                                 });
952                                         field->text = BLI_strdup(info);
953                                 }
954                         }
955                 }
956         }
957
958         if (data->fields_len == 0) {
959                 MEM_freeN(data);
960                 return NULL;
961         }
962         else {
963                 return data;
964         }
965 }
966
967
968 static ARegion *ui_tooltip_create_with_data(
969         bContext *C, uiTooltipData *data,
970         const float init_position[2], const rcti *init_rect_overlap,
971         const float aspect)
972 {
973         const float pad_px = UI_TIP_PADDING;
974         wmWindow *win = CTX_wm_window(C);
975         const int winx = WM_window_pixels_x(win);
976         const int winy = WM_window_pixels_y(win);
977         uiStyle *style = UI_style_get();
978         static ARegionType type;
979         ARegion *ar;
980         int fonth, fontw;
981         int h, i;
982         rcti rect_i;
983         int font_flag = 0;
984
985         /* create area region */
986         ar = ui_region_temp_add(CTX_wm_screen(C));
987
988         memset(&type, 0, sizeof(ARegionType));
989         type.draw = ui_tooltip_region_draw_cb;
990         type.free = ui_tooltip_region_free_cb;
991         type.regionid = RGN_TYPE_TEMPORARY;
992         ar->type = &type;
993
994         /* set font, get bb */
995         data->fstyle = style->widget; /* copy struct */
996         ui_fontscale(&data->fstyle.points, aspect);
997
998         UI_fontstyle_set(&data->fstyle);
999
1000         data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2));
1001
1002         font_flag |= BLF_WORD_WRAP;
1003         if (data->fstyle.kerning == 1) {
1004                 font_flag |= BLF_KERNING_DEFAULT;
1005         }
1006         BLF_enable(data->fstyle.uifont_id, font_flag);
1007         BLF_enable(blf_mono_font, font_flag);
1008         BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
1009         BLF_wordwrap(blf_mono_font, data->wrap_width);
1010
1011         /* these defines tweaked depending on font */
1012 #define TIP_BORDER_X (16.0f / aspect)
1013 #define TIP_BORDER_Y (6.0f / aspect)
1014
1015         h = BLF_height_max(data->fstyle.uifont_id);
1016
1017         for (i = 0, fontw = 0, fonth = 0; i < data->fields_len; i++) {
1018                 uiTooltipField *field = &data->fields[i];
1019                 uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL;
1020
1021                 struct ResultBLF info;
1022                 int w, x_pos = 0;
1023                 int font_id;
1024
1025                 if (field->format.style == UI_TIP_STYLE_MONO) {
1026                         BLF_size(blf_mono_font, data->fstyle.points * U.pixelsize, U.dpi);
1027                         font_id = blf_mono_font;
1028                 }
1029                 else {
1030                         BLI_assert(ELEM(field->format.style, UI_TIP_STYLE_NORMAL, UI_TIP_STYLE_HEADER));
1031                         font_id = data->fstyle.uifont_id;
1032                 }
1033                 w = BLF_width_ex(font_id, field->text, BLF_DRAW_STR_DUMMY_MAX, &info);
1034
1035                 /* check for suffix (enum label) */
1036                 if (field->text_suffix && field->text_suffix[0]) {
1037                         x_pos = info.width;
1038                         w = max_ii(w, x_pos + BLF_width(font_id, field->text_suffix, BLF_DRAW_STR_DUMMY_MAX));
1039                 }
1040                 fontw = max_ii(fontw, w);
1041
1042                 fonth += h * info.lines;
1043                 if (field_next && field_next->format.is_pad) {
1044                         fonth += h * (UI_TIP_PAD_FAC - 1);
1045                 }
1046
1047                 field->geom.lines = info.lines;
1048                 field->geom.x_pos = x_pos;
1049         }
1050
1051         //fontw *= aspect;
1052
1053         BLF_disable(data->fstyle.uifont_id, font_flag);
1054         BLF_disable(blf_mono_font, font_flag);
1055
1056         ar->regiondata = data;
1057
1058         data->toth = fonth;
1059         data->lineh = h;
1060
1061         /* Compute position. */
1062         {
1063                 rctf rect_fl;
1064                 rect_fl.xmin = init_position[0] - TIP_BORDER_X;
1065                 rect_fl.xmax = rect_fl.xmin + fontw + pad_px;
1066                 rect_fl.ymax = init_position[1] - TIP_BORDER_Y;
1067                 rect_fl.ymin = rect_fl.ymax - fonth - TIP_BORDER_Y;
1068                 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
1069         }
1070
1071 #undef TIP_BORDER_X
1072 #undef TIP_BORDER_Y
1073
1074 // #define USE_ALIGN_Y_CENTER
1075
1076         /* Clamp to window bounds. */
1077         {
1078                 /* Ensure at least 5 px above screen bounds
1079                  * UI_UNIT_Y is just a guess to be above the menu item */
1080                 if (init_rect_overlap != NULL) {
1081                         const int pad = max_ff(1.0f, U.pixelsize) * 5;
1082                         const rcti init_rect = {
1083                                 .xmin = init_rect_overlap->xmin - pad,
1084                                 .xmax = init_rect_overlap->xmax + pad,
1085                                 .ymin = init_rect_overlap->ymin - pad,
1086                                 .ymax = init_rect_overlap->ymax + pad,
1087                         };
1088                         const rcti rect_clamp = {
1089                                 .xmin = 0,
1090                                 .xmax = winx,
1091                                 .ymin = 0,
1092                                 .ymax = winy,
1093                         };
1094                         /* try right. */
1095                         const int size_x = BLI_rcti_size_x(&rect_i);
1096                         const int size_y = BLI_rcti_size_y(&rect_i);
1097                         const int cent_overlap_x = BLI_rcti_cent_x(&init_rect);
1098 #ifdef USE_ALIGN_Y_CENTER
1099                         const int cent_overlap_y = BLI_rcti_cent_y(&init_rect);
1100 #endif
1101                         struct {
1102                                 rcti xpos;
1103                                 rcti xneg;
1104                                 rcti ypos;
1105                                 rcti yneg;
1106                         } rect;
1107
1108                         {       /* xpos */
1109                                 rcti r = rect_i;
1110                                 r.xmin = init_rect.xmax;
1111                                 r.xmax = r.xmin + size_x;
1112 #ifdef USE_ALIGN_Y_CENTER
1113                                 r.ymin = cent_overlap_y - (size_y / 2);
1114                                 r.ymax = r.ymin + size_y;
1115 #else
1116                                 r.ymin = init_rect.ymax - BLI_rcti_size_y(&rect_i);
1117                                 r.ymax = init_rect.ymax;
1118                                 r.ymin -= UI_POPUP_MARGIN;
1119                                 r.ymax -= UI_POPUP_MARGIN;
1120 #endif
1121                                 rect.xpos = r;
1122                         }
1123                         {       /* xneg */
1124                                 rcti r = rect_i;
1125                                 r.xmin = init_rect.xmin - size_x;
1126                                 r.xmax = r.xmin + size_x;
1127 #ifdef USE_ALIGN_Y_CENTER
1128                                 r.ymin = cent_overlap_y - (size_y / 2);
1129                                 r.ymax = r.ymin + size_y;
1130 #else
1131                                 r.ymin = init_rect.ymax - BLI_rcti_size_y(&rect_i);
1132                                 r.ymax = init_rect.ymax;
1133                                 r.ymin -= UI_POPUP_MARGIN;
1134                                 r.ymax -= UI_POPUP_MARGIN;
1135 #endif
1136                                 rect.xneg = r;
1137                         }
1138                         {       /* ypos */
1139                                 rcti r = rect_i;
1140                                 r.xmin = cent_overlap_x - (size_x / 2);
1141                                 r.xmax = r.xmin + size_x;
1142                                 r.ymin = init_rect.ymax;
1143                                 r.ymax = r.ymin + size_y;
1144                                 rect.ypos = r;
1145                         }
1146                         {       /* yneg */
1147                                 rcti r = rect_i;
1148                                 r.xmin = cent_overlap_x - (size_x / 2);
1149                                 r.xmax = r.xmin + size_x;
1150                                 r.ymin = init_rect.ymin - size_y;
1151                                 r.ymax = r.ymin + size_y;
1152                                 rect.yneg = r;
1153                         }
1154
1155                         bool found = false;
1156                         for (int j = 0; j < 4; j++) {
1157                                 const rcti *r = (&rect.xpos) + j;
1158                                 if (BLI_rcti_inside_rcti(&rect_clamp, r)) {
1159                                         rect_i = *r;
1160                                         found = true;
1161                                         break;
1162                                 }
1163                         }
1164                         if (!found) {
1165                                 /* Fallback, we could pick the best fallback, for now just use xpos. */
1166                                 int offset_dummy[2];
1167                                 rect_i = rect.xpos;
1168                                 BLI_rcti_clamp(&rect_i, &rect_clamp, offset_dummy);
1169                         }
1170
1171                 }
1172                 else {
1173                         const int pad = max_ff(1.0f, U.pixelsize) * 5;
1174                         const rcti rect_clamp = {
1175                                 .xmin = pad,
1176                                 .xmax = winx - pad,
1177                                 .ymin = pad + (UI_UNIT_Y * 2),
1178                                 .ymax = winy - pad,
1179                         };
1180                         int offset_dummy[2];
1181                         BLI_rcti_clamp(&rect_i, &rect_clamp, offset_dummy);
1182                 }
1183         }
1184
1185 #undef USE_ALIGN_Y_CENTER
1186
1187         /* add padding */
1188         BLI_rcti_resize(&rect_i,
1189                         BLI_rcti_size_x(&rect_i) + pad_px,
1190                         BLI_rcti_size_y(&rect_i) + pad_px);
1191
1192         /* widget rect, in region coords */
1193         {
1194                 /* Compensate for margin offset, visually this corrects the position. */
1195                 const int margin = UI_POPUP_MARGIN;
1196                 if (init_rect_overlap != NULL) {
1197                         BLI_rcti_translate(&rect_i, margin, margin / 2);
1198                 }
1199
1200                 data->bbox.xmin = margin;
1201                 data->bbox.xmax = BLI_rcti_size_x(&rect_i) - margin;
1202                 data->bbox.ymin = margin;
1203                 data->bbox.ymax = BLI_rcti_size_y(&rect_i);
1204
1205                 /* region bigger for shadow */
1206                 ar->winrct.xmin = rect_i.xmin - margin;
1207                 ar->winrct.xmax = rect_i.xmax + margin;
1208                 ar->winrct.ymin = rect_i.ymin - margin;
1209                 ar->winrct.ymax = rect_i.ymax + margin;
1210         }
1211
1212         /* adds subwindow */
1213         ED_region_init(ar);
1214
1215         /* notify change and redraw */
1216         ED_region_tag_redraw(ar);
1217
1218         return ar;
1219 }
1220
1221 /** \} */
1222
1223 /* -------------------------------------------------------------------- */
1224 /** \name ToolTip Public API
1225  * \{ */
1226
1227
1228 ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label)
1229 {
1230         wmWindow *win = CTX_wm_window(C);
1231         /* aspect values that shrink text are likely unreadable */
1232         const float aspect = min_ff(1.0f, but->block->aspect);
1233         float init_position[2];
1234
1235         if (but->drawflag & UI_BUT_NO_TOOLTIP) {
1236                 return NULL;
1237         }
1238         uiTooltipData *data = NULL;
1239
1240         if (data == NULL) {
1241                 data = ui_tooltip_data_from_tool(C, but, is_label);
1242         }
1243
1244         if (data == NULL) {
1245                 data = ui_tooltip_data_from_button(C, but);
1246         }
1247
1248         if (data == NULL) {
1249                 return NULL;
1250         }
1251
1252         const bool is_no_overlap = UI_but_has_tooltip_label(but) || UI_but_is_tool(but);
1253         rcti init_rect;
1254         if (is_no_overlap) {
1255                 rctf overlap_rect_fl;
1256                 init_position[0] = BLI_rctf_cent_x(&but->rect);
1257                 init_position[1] = BLI_rctf_cent_y(&but->rect);
1258                 if (butregion) {
1259                         ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]);
1260                         ui_block_to_window_rctf(butregion, but->block, &overlap_rect_fl, &but->rect);
1261                 }
1262                 else {
1263                         overlap_rect_fl = but->rect;
1264                 }
1265                 BLI_rcti_rctf_copy_round(&init_rect, &overlap_rect_fl);
1266         }
1267         else {
1268                 init_position[0] = BLI_rctf_cent_x(&but->rect);
1269                 init_position[1] = but->rect.ymin - (UI_POPUP_MARGIN / 2);
1270                 if (butregion) {
1271                         ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]);
1272                         init_position[0] = win->eventstate->x;
1273                 }
1274         }
1275
1276         ARegion *ar = ui_tooltip_create_with_data(C, data, init_position, is_no_overlap ? &init_rect : NULL, aspect);
1277
1278         return ar;
1279 }
1280
1281 ARegion *UI_tooltip_create_from_gizmo(bContext *C, wmGizmo *gz)
1282 {
1283         wmWindow *win = CTX_wm_window(C);
1284         const float aspect = 1.0f;
1285         float init_position[2];
1286
1287         uiTooltipData *data = ui_tooltip_data_from_gizmo(C, gz);
1288         if (data == NULL) {
1289                 return NULL;
1290         }
1291
1292         init_position[0] = win->eventstate->x;
1293         init_position[1] = win->eventstate->y;
1294
1295         return ui_tooltip_create_with_data(C, data, init_position, NULL, aspect);
1296 }
1297
1298 void UI_tooltip_free(bContext *C, bScreen *sc, ARegion *ar)
1299 {
1300         ui_region_temp_remove(C, sc, ar);
1301 }
1302
1303 /** \} */