1eec37372150d42a3171ffbff5af784a7a1032c8
[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
49 #include "BLI_math.h"
50 #include "BLI_string.h"
51 #include "BLI_string_utf8.h"
52 #include "BLI_rect.h"
53 #include "BLI_utildefines.h"
54
55 #include "BKE_context.h"
56 #include "BKE_screen.h"
57
58 #include "WM_api.h"
59 #include "WM_types.h"
60
61 #include "RNA_access.h"
62
63 #include "BIF_gl.h"
64
65 #include "UI_interface.h"
66
67 #include "BLF_api.h"
68 #include "BLT_translation.h"
69
70 #include "ED_screen.h"
71
72 #include "interface_intern.h"
73 #include "interface_regions_intern.h"
74
75 #define UI_TIP_PAD_FAC      1.3f
76 #define UI_TIP_PADDING      (int)(UI_TIP_PAD_FAC * UI_UNIT_Y)
77 #define UI_TIP_MAXWIDTH     600
78
79
80 typedef struct uiTooltipFormat {
81         enum {
82                 UI_TIP_STYLE_NORMAL = 0,
83                 UI_TIP_STYLE_HEADER,
84                 UI_TIP_STYLE_MONO,
85         } style : 3;
86         enum {
87                 UI_TIP_LC_MAIN = 0,     /* primary text */
88                 UI_TIP_LC_VALUE,        /* the value of buttons (also shortcuts) */
89                 UI_TIP_LC_ACTIVE,       /* titles of active enum values */
90                 UI_TIP_LC_NORMAL,       /* regular text */
91                 UI_TIP_LC_PYTHON,       /* Python snippet */
92                 UI_TIP_LC_ALERT,        /* description of why operator can't run */
93         } color_id : 4;
94         int is_pad : 1;
95 } uiTooltipFormat;
96
97 typedef struct uiTooltipField {
98         char *text;
99         char *text_suffix;
100         struct {
101                 uint x_pos;     /* x cursor position at the end of the last line */
102                 uint lines;     /* number of lines, 1 or more with word-wrap */
103         } geom;
104         uiTooltipFormat format;
105
106 } uiTooltipField;
107
108 typedef struct uiTooltipData {
109         rcti bbox;
110         uiTooltipField *fields;
111         uint            fields_len;
112         uiFontStyle fstyle;
113         int wrap_width;
114         int toth, lineh;
115 } uiTooltipData;
116
117 #define UI_TIP_LC_MAX 6
118
119 BLI_STATIC_ASSERT(UI_TIP_LC_MAX == UI_TIP_LC_ALERT + 1, "invalid lc-max");
120 BLI_STATIC_ASSERT(sizeof(uiTooltipFormat) <= sizeof(int), "oversize");
121
122 static uiTooltipField *text_field_add_only(
123         uiTooltipData *data)
124 {
125         data->fields_len += 1;
126         data->fields = MEM_recallocN(data->fields, sizeof(*data->fields) * data->fields_len);
127         return &data->fields[data->fields_len - 1];
128 }
129
130 static uiTooltipField *text_field_add(
131         uiTooltipData *data,
132         const uiTooltipFormat *format)
133 {
134         uiTooltipField *field = text_field_add_only(data);
135         field->format = *format;
136         return field;
137 }
138
139 /* -------------------------------------------------------------------- */
140 /** \name ToolTip Callbacks (Draw & Free)
141  * \{ */
142
143 static void rgb_tint(
144         float col[3],
145         float h, float h_strength,
146         float v, float v_strength)
147 {
148         float col_hsv_from[3];
149         float col_hsv_to[3];
150
151         rgb_to_hsv_v(col, col_hsv_from);
152
153         col_hsv_to[0] = h;
154         col_hsv_to[1] = h_strength;
155         col_hsv_to[2] = (col_hsv_from[2] * (1.0f - v_strength)) + (v * v_strength);
156
157         hsv_to_rgb_v(col_hsv_to, col);
158 }
159
160 static void ui_tooltip_region_draw_cb(const bContext *UNUSED(C), ARegion *ar)
161 {
162         const float pad_px = UI_TIP_PADDING;
163         uiTooltipData *data = ar->regiondata;
164         uiWidgetColors *theme = ui_tooltip_get_theme();
165         rcti bbox = data->bbox;
166         float tip_colors[UI_TIP_LC_MAX][3];
167
168         float *main_color    = tip_colors[UI_TIP_LC_MAIN]; /* the color from the theme */
169         float *value_color   = tip_colors[UI_TIP_LC_VALUE];
170         float *active_color  = tip_colors[UI_TIP_LC_ACTIVE];
171         float *normal_color  = tip_colors[UI_TIP_LC_NORMAL];
172         float *python_color  = tip_colors[UI_TIP_LC_PYTHON];
173         float *alert_color   = tip_colors[UI_TIP_LC_ALERT];
174
175         float background_color[3];
176         float tone_bg;
177         int i, multisample_enabled;
178
179         /* disable AA, makes widgets too blurry */
180         multisample_enabled = glIsEnabled(GL_MULTISAMPLE);
181         if (multisample_enabled)
182                 glDisable(GL_MULTISAMPLE);
183
184         wmOrtho2_region_pixelspace(ar);
185
186         /* draw background */
187         ui_draw_tooltip_background(UI_style_get(), NULL, &bbox);
188
189         /* set background_color */
190         rgb_uchar_to_float(background_color, (const unsigned char *)theme->inner);
191
192         /* calculate normal_color */
193         rgb_uchar_to_float(main_color, (const unsigned char *)theme->text);
194         copy_v3_v3(active_color, main_color);
195         copy_v3_v3(normal_color, main_color);
196         copy_v3_v3(python_color, main_color);
197         copy_v3_v3(alert_color, main_color);
198         copy_v3_v3(value_color, main_color);
199
200         /* find the brightness difference between background and text colors */
201
202         tone_bg = rgb_to_grayscale(background_color);
203         /* tone_fg = rgb_to_grayscale(main_color); */
204
205         /* mix the colors */
206         rgb_tint(value_color,  0.0f, 0.0f, tone_bg, 0.2f);  /* light gray */
207         rgb_tint(active_color, 0.6f, 0.2f, tone_bg, 0.2f);  /* light blue */
208         rgb_tint(normal_color, 0.0f, 0.0f, tone_bg, 0.4f);  /* gray       */
209         rgb_tint(python_color, 0.0f, 0.0f, tone_bg, 0.5f);  /* dark gray  */
210         rgb_tint(alert_color,  0.0f, 0.8f, tone_bg, 0.1f);  /* red        */
211
212         /* draw text */
213         BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
214         BLF_wordwrap(blf_mono_font, data->wrap_width);
215
216         bbox.xmin += 0.5f * pad_px;  /* add padding to the text */
217         bbox.ymax -= 0.25f * pad_px;
218
219         for (i = 0; i < data->fields_len; i++) {
220                 const uiTooltipField *field = &data->fields[i];
221                 const uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL;
222
223                 bbox.ymin = bbox.ymax - (data->lineh * field->geom.lines);
224                 if (field->format.style == UI_TIP_STYLE_HEADER) {
225                         /* draw header and active data (is done here to be able to change color) */
226                         uiFontStyle fstyle_header = data->fstyle;
227
228                         /* override text-style */
229                         fstyle_header.shadow = 1;
230                         fstyle_header.shadowcolor = rgb_to_grayscale(tip_colors[UI_TIP_LC_MAIN]);
231                         fstyle_header.shadx = fstyle_header.shady = 0;
232                         fstyle_header.shadowalpha = 1.0f;
233                         fstyle_header.word_wrap = true;
234
235                         UI_fontstyle_set(&fstyle_header);
236                         glColor3fv(tip_colors[UI_TIP_LC_MAIN]);
237                         UI_fontstyle_draw(&fstyle_header, &bbox, field->text);
238
239                         fstyle_header.shadow = 0;
240
241                         /* offset to the end of the last line */
242                         if (field->text_suffix) {
243                                 float xofs = field->geom.x_pos;
244                                 float yofs = data->lineh * (field->geom.lines - 1);
245                                 bbox.xmin += xofs;
246                                 bbox.ymax -= yofs;
247
248                                 glColor3fv(tip_colors[UI_TIP_LC_ACTIVE]);
249                                 UI_fontstyle_draw(&fstyle_header, &bbox, field->text_suffix);
250
251                                 /* undo offset */
252                                 bbox.xmin -= xofs;
253                                 bbox.ymax += yofs;
254                         }
255                 }
256                 else if (field->format.style == UI_TIP_STYLE_MONO) {
257                         uiFontStyle fstyle_mono = data->fstyle;
258                         fstyle_mono.uifont_id = blf_mono_font;
259                         fstyle_mono.word_wrap = true;
260
261                         UI_fontstyle_set(&fstyle_mono);
262                         /* XXX, needed because we dont have mono in 'U.uifonts' */
263                         BLF_size(fstyle_mono.uifont_id, fstyle_mono.points * U.pixelsize, U.dpi);
264                         glColor3fv(tip_colors[field->format.color_id]);
265                         UI_fontstyle_draw(&fstyle_mono, &bbox, field->text);
266                 }
267                 else {
268                         uiFontStyle fstyle_normal = data->fstyle;
269                         BLI_assert(field->format.style == UI_TIP_STYLE_NORMAL);
270                         fstyle_normal.word_wrap = true;
271
272                         /* draw remaining data */
273                         UI_fontstyle_set(&fstyle_normal);
274                         glColor3fv(tip_colors[field->format.color_id]);
275                         UI_fontstyle_draw(&fstyle_normal, &bbox, field->text);
276                 }
277
278                 bbox.ymax -= data->lineh * field->geom.lines;
279
280                 if (field_next && field_next->format.is_pad) {
281                         bbox.ymax -= data->lineh * (UI_TIP_PAD_FAC - 1);
282                 }
283         }
284
285         BLF_disable(data->fstyle.uifont_id, BLF_WORD_WRAP);
286         BLF_disable(blf_mono_font, BLF_WORD_WRAP);
287
288         if (multisample_enabled)
289                 glEnable(GL_MULTISAMPLE);
290 }
291
292 static void ui_tooltip_region_free_cb(ARegion *ar)
293 {
294         uiTooltipData *data;
295
296         data = ar->regiondata;
297
298         for (int  i = 0; i < data->fields_len; i++) {
299                 const uiTooltipField *field = &data->fields[i];
300                 MEM_freeN(field->text);
301                 if (field->text_suffix) {
302                         MEM_freeN(field->text_suffix);
303                 }
304         }
305         MEM_freeN(data->fields);
306         MEM_freeN(data);
307         ar->regiondata = NULL;
308 }
309
310 /** \} */
311
312 /* -------------------------------------------------------------------- */
313 /** \name ToolTip Creation
314  * \{ */
315
316 static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
317 {
318         uiStringInfo but_tip = {BUT_GET_TIP, NULL};
319         uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL};
320         uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL};
321         uiStringInfo op_keymap = {BUT_GET_OP_KEYMAP, NULL};
322         uiStringInfo prop_keymap = {BUT_GET_PROP_KEYMAP, NULL};
323         uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL};
324         uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL};
325
326         char buf[512];
327
328         /* create tooltip data */
329         uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
330
331         UI_but_string_info_get(C, but, &but_tip, &enum_label, &enum_tip, &op_keymap, &prop_keymap, &rna_struct, &rna_prop, NULL);
332
333         /* Tip */
334         if (but_tip.strinfo) {
335                 {
336                         uiTooltipField *field = text_field_add(
337                                 data, &(uiTooltipFormat){
338                                     .style = UI_TIP_STYLE_HEADER,
339                                     .color_id = UI_TIP_LC_NORMAL,
340                                 });
341                         if (enum_label.strinfo) {
342                                 field->text = BLI_sprintfN("%s:  ", but_tip.strinfo);
343                                 field->text_suffix = BLI_strdup(enum_label.strinfo);
344                         }
345                         else {
346                                 field->text = BLI_sprintfN("%s.", but_tip.strinfo);
347                         }
348                 }
349
350                 /* special case enum rna buttons */
351                 if ((but->type & UI_BTYPE_ROW) && but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) {
352                         uiTooltipField *field = text_field_add(
353                                 data, &(uiTooltipFormat){
354                                     .style = UI_TIP_STYLE_NORMAL,
355                                     .color_id = UI_TIP_LC_NORMAL,
356                                 });
357                         field->text = BLI_strdup(IFACE_("(Shift-Click/Drag to select multiple)"));
358                 }
359
360         }
361         /* Enum field label & tip */
362         if (enum_tip.strinfo) {
363                 uiTooltipField *field = text_field_add(
364                         data, &(uiTooltipFormat){
365                             .style = UI_TIP_STYLE_NORMAL,
366                             .color_id = UI_TIP_LC_VALUE,
367                             .is_pad = true,
368                         });
369                 field->text = BLI_strdup(enum_tip.strinfo);
370         }
371
372         /* Op shortcut */
373         if (op_keymap.strinfo) {
374                 uiTooltipField *field = text_field_add(
375                         data, &(uiTooltipFormat){
376                             .style = UI_TIP_STYLE_NORMAL,
377                             .color_id = UI_TIP_LC_VALUE,
378                             .is_pad = true,
379                         });
380                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), op_keymap.strinfo);
381         }
382
383         /* Property context-toggle shortcut */
384         if (prop_keymap.strinfo) {
385                 uiTooltipField *field = text_field_add(
386                         data, &(uiTooltipFormat){
387                             .style = UI_TIP_STYLE_NORMAL,
388                             .color_id = UI_TIP_LC_VALUE,
389                             .is_pad = true,
390                         });
391                 field->text = BLI_sprintfN(TIP_("Shortcut: %s"), prop_keymap.strinfo);
392         }
393
394         if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
395                 /* better not show the value of a password */
396                 if ((but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) == 0) {
397                         /* full string */
398                         ui_but_string_get(but, buf, sizeof(buf));
399                         if (buf[0]) {
400                                 uiTooltipField *field = text_field_add(
401                                         data, &(uiTooltipFormat){
402                                             .style = UI_TIP_STYLE_NORMAL,
403                                             .color_id = UI_TIP_LC_VALUE,
404                                             .is_pad = true,
405                                         });
406                                 field->text = BLI_sprintfN(TIP_("Value: %s"), buf);
407                         }
408                 }
409         }
410
411         if (but->rnaprop) {
412                 int unit_type = UI_but_unit_type_get(but);
413
414                 if (unit_type == PROP_UNIT_ROTATION) {
415                         if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
416                                 float value = RNA_property_array_check(but->rnaprop) ?
417                                                   RNA_property_float_get_index(&but->rnapoin, but->rnaprop, but->rnaindex) :
418                                                   RNA_property_float_get(&but->rnapoin, but->rnaprop);
419
420                                 uiTooltipField *field = text_field_add(
421                                         data, &(uiTooltipFormat){
422                                             .style = UI_TIP_STYLE_NORMAL,
423                                             .color_id = UI_TIP_LC_VALUE,
424                                         });
425                                 field->text = BLI_sprintfN(TIP_("Radians: %f"), value);
426                         }
427                 }
428
429                 if (but->flag & UI_BUT_DRIVEN) {
430                         if (ui_but_anim_expression_get(but, buf, sizeof(buf))) {
431                                 uiTooltipField *field = text_field_add(
432                                         data, &(uiTooltipFormat){
433                                             .style = UI_TIP_STYLE_NORMAL,
434                                             .color_id = UI_TIP_LC_NORMAL,
435                                         });
436                                 field->text = BLI_sprintfN(TIP_("Expression: %s"), buf);
437                         }
438                 }
439
440                 if (but->rnapoin.id.data) {
441                         const ID *id = but->rnapoin.id.data;
442                         if (ID_IS_LINKED(id)) {
443                                 uiTooltipField *field = text_field_add(
444                                         data, &(uiTooltipFormat){
445                                             .style = UI_TIP_STYLE_NORMAL,
446                                             .color_id = UI_TIP_LC_NORMAL,
447                                         });
448                                 field->text = BLI_sprintfN(TIP_("Library: %s"), id->lib->name);
449                         }
450                 }
451         }
452         else if (but->optype) {
453                 PointerRNA *opptr;
454                 char *str;
455                 opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
456
457                 /* so the context is passed to fieldf functions (some py fieldf functions use it) */
458                 WM_operator_properties_sanitize(opptr, false);
459
460                 str = WM_operator_pystring_ex(C, NULL, false, false, but->optype, opptr);
461
462                 /* avoid overly verbose tips (eg, arrays of 20 layers), exact limit is arbitrary */
463                 WM_operator_pystring_abbreviate(str, 32);
464
465                 /* operator info */
466                 if ((U.flag & USER_TOOLTIPS_PYTHON) == 0) {
467                         uiTooltipField *field = text_field_add(
468                                 data, &(uiTooltipFormat){
469                                     .style = UI_TIP_STYLE_MONO,
470                                     .color_id = UI_TIP_LC_PYTHON,
471                                     .is_pad = true,
472                                 });
473                         field->text = BLI_sprintfN(TIP_("Python: %s"), str);
474                 }
475
476                 MEM_freeN(str);
477         }
478
479         /* button is disabled, we may be able to tell user why */
480         if (but->flag & UI_BUT_DISABLED) {
481                 const char *disabled_msg = NULL;
482
483                 /* if operator poll check failed, it can give pretty precise info why */
484                 if (but->optype) {
485                         CTX_wm_operator_poll_msg_set(C, NULL);
486                         WM_operator_poll_context(C, but->optype, but->opcontext);
487                         disabled_msg = CTX_wm_operator_poll_msg_get(C);
488                 }
489                 /* alternatively, buttons can store some reasoning too */
490                 else if (but->disabled_info) {
491                         disabled_msg = TIP_(but->disabled_info);
492                 }
493
494                 if (disabled_msg && disabled_msg[0]) {
495                         uiTooltipField *field = text_field_add(
496                                 data, &(uiTooltipFormat){
497                                     .style = UI_TIP_STYLE_NORMAL,
498                                     .color_id = UI_TIP_LC_ALERT,
499                                 });
500                         field->text = BLI_sprintfN(TIP_("Disabled: %s"), disabled_msg);
501                 }
502         }
503
504         if ((U.flag & USER_TOOLTIPS_PYTHON) == 0 && !but->optype && rna_struct.strinfo) {
505                 {
506                         uiTooltipField *field = text_field_add(
507                                 data, &(uiTooltipFormat){
508                                     .style = UI_TIP_STYLE_MONO,
509                                     .color_id = UI_TIP_LC_PYTHON,
510                                     .is_pad = true,
511                                 });
512                         if (rna_prop.strinfo) {
513                                 /* Struct and prop */
514                                 field->text = BLI_sprintfN(TIP_("Python: %s.%s"), rna_struct.strinfo, rna_prop.strinfo);
515                         }
516                         else {
517                                 /* Only struct (e.g. menus) */
518                                 field->text = BLI_sprintfN(TIP_("Python: %s"), rna_struct.strinfo);
519                         }
520                 }
521
522                 if (but->rnapoin.id.data) {
523                         uiTooltipField *field = text_field_add(
524                                 data, &(uiTooltipFormat){
525                                     .style = UI_TIP_STYLE_MONO,
526                                     .color_id = UI_TIP_LC_PYTHON,
527                                 });
528
529                         /* this could get its own 'BUT_GET_...' type */
530
531                         /* never fails */
532                         /* move ownership (no need for re-alloc) */
533                         if (but->rnaprop) {
534                                 field->text = RNA_path_full_property_py_ex(&but->rnapoin, but->rnaprop, but->rnaindex, true);
535                         }
536                         else {
537                                 field->text = RNA_path_full_struct_py(&but->rnapoin);
538                         }
539
540                 }
541         }
542
543         /* Free strinfo's... */
544         if (but_tip.strinfo)
545                 MEM_freeN(but_tip.strinfo);
546         if (enum_label.strinfo)
547                 MEM_freeN(enum_label.strinfo);
548         if (enum_tip.strinfo)
549                 MEM_freeN(enum_tip.strinfo);
550         if (op_keymap.strinfo)
551                 MEM_freeN(op_keymap.strinfo);
552         if (prop_keymap.strinfo)
553                 MEM_freeN(prop_keymap.strinfo);
554         if (rna_struct.strinfo)
555                 MEM_freeN(rna_struct.strinfo);
556         if (rna_prop.strinfo)
557                 MEM_freeN(rna_prop.strinfo);
558
559         if (data->fields_len == 0) {
560                 MEM_freeN(data);
561                 return NULL;
562         }
563         else {
564                 return data;
565         }
566 }
567
568 static ARegion *ui_tooltip_create_with_data(
569         bContext *C, uiTooltipData *data,
570         const float init_position[2],
571         const float aspect)
572 {
573         const float pad_px = UI_TIP_PADDING;
574         wmWindow *win = CTX_wm_window(C);
575         const int winx = WM_window_pixels_x(win);
576         uiStyle *style = UI_style_get();
577         static ARegionType type;
578         ARegion *ar;
579         int fonth, fontw;
580         int h, i;
581         rctf rect_fl;
582         rcti rect_i;
583         int font_flag = 0;
584
585         /* create area region */
586         ar = ui_region_temp_add(CTX_wm_screen(C));
587
588         memset(&type, 0, sizeof(ARegionType));
589         type.draw = ui_tooltip_region_draw_cb;
590         type.free = ui_tooltip_region_free_cb;
591         type.regionid = RGN_TYPE_TEMPORARY;
592         ar->type = &type;
593
594         /* set font, get bb */
595         data->fstyle = style->widget; /* copy struct */
596         ui_fontscale(&data->fstyle.points, aspect);
597
598         UI_fontstyle_set(&data->fstyle);
599
600         data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2));
601
602         font_flag |= BLF_WORD_WRAP;
603         if (data->fstyle.kerning == 1) {
604                 font_flag |= BLF_KERNING_DEFAULT;
605         }
606         BLF_enable(data->fstyle.uifont_id, font_flag);
607         BLF_enable(blf_mono_font, font_flag);
608         BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
609         BLF_wordwrap(blf_mono_font, data->wrap_width);
610
611         /* these defines tweaked depending on font */
612 #define TIP_BORDER_X (16.0f / aspect)
613 #define TIP_BORDER_Y (6.0f / aspect)
614
615         h = BLF_height_max(data->fstyle.uifont_id);
616
617         for (i = 0, fontw = 0, fonth = 0; i < data->fields_len; i++) {
618                 uiTooltipField *field = &data->fields[i];
619                 uiTooltipField *field_next = (i + 1) != data->fields_len ? &data->fields[i + 1] : NULL;
620
621                 struct ResultBLF info;
622                 int w, x_pos = 0;
623                 int font_id;
624
625                 if (field->format.style == UI_TIP_STYLE_MONO) {
626                         BLF_size(blf_mono_font, data->fstyle.points * U.pixelsize, U.dpi);
627                         font_id = blf_mono_font;
628                 }
629                 else {
630                         BLI_assert(ELEM(field->format.style, UI_TIP_STYLE_NORMAL, UI_TIP_STYLE_HEADER));
631                         font_id = data->fstyle.uifont_id;
632                 }
633                 w = BLF_width_ex(font_id, field->text, BLF_DRAW_STR_DUMMY_MAX, &info);
634
635                 /* check for suffix (enum label) */
636                 if (field->text_suffix && field->text_suffix[0]) {
637                         x_pos = info.width;
638                         w = max_ii(w, x_pos + BLF_width(font_id, field->text_suffix, BLF_DRAW_STR_DUMMY_MAX));
639                 }
640                 fontw = max_ii(fontw, w);
641
642                 fonth += h * info.lines;
643                 if (field_next && field_next->format.is_pad) {
644                         fonth += h * (UI_TIP_PAD_FAC - 1);
645                 }
646
647                 field->geom.lines = info.lines;
648                 field->geom.x_pos = x_pos;
649         }
650
651         //fontw *= aspect;
652
653         BLF_disable(data->fstyle.uifont_id, font_flag);
654         BLF_disable(blf_mono_font, font_flag);
655
656         ar->regiondata = data;
657
658         data->toth = fonth;
659         data->lineh = h;
660
661         /* compute position */
662
663         rect_fl.xmin = init_position[0] - TIP_BORDER_X;
664         rect_fl.xmax = rect_fl.xmin + fontw + pad_px;
665         rect_fl.ymax = init_position[1] - TIP_BORDER_Y;
666         rect_fl.ymin = rect_fl.ymax - fonth  - TIP_BORDER_Y;
667
668         BLI_rcti_rctf_copy(&rect_i, &rect_fl);
669
670 #undef TIP_BORDER_X
671 #undef TIP_BORDER_Y
672
673         /* clip with window boundaries */
674         if (rect_i.xmax > winx) {
675                 /* super size */
676                 if (rect_i.xmax > winx + rect_i.xmin) {
677                         rect_i.xmax = winx;
678                         rect_i.xmin = 0;
679                 }
680                 else {
681                         rect_i.xmin -= rect_i.xmax - winx;
682                         rect_i.xmax = winx;
683                 }
684         }
685         /* ensure at least 5 px above screen bounds
686          * 25 is just a guess to be above the menu item */
687         if (rect_i.ymin < 5) {
688                 rect_i.ymax += (-rect_i.ymin) + 30;
689                 rect_i.ymin = 30;
690         }
691
692         /* add padding */
693         BLI_rcti_resize(&rect_i,
694                         BLI_rcti_size_x(&rect_i) + pad_px,
695                         BLI_rcti_size_y(&rect_i) + pad_px);
696
697         /* widget rect, in region coords */
698         {
699                 const int margin = UI_POPUP_MARGIN;
700
701                 data->bbox.xmin = margin;
702                 data->bbox.xmax = BLI_rcti_size_x(&rect_i) - margin;
703                 data->bbox.ymin = margin;
704                 data->bbox.ymax = BLI_rcti_size_y(&rect_i);
705
706                 /* region bigger for shadow */
707                 ar->winrct.xmin = rect_i.xmin - margin;
708                 ar->winrct.xmax = rect_i.xmax + margin;
709                 ar->winrct.ymin = rect_i.ymin - margin;
710                 ar->winrct.ymax = rect_i.ymax + margin;
711         }
712
713         /* adds subwindow */
714         ED_region_init(C, ar);
715
716         /* notify change and redraw */
717         ED_region_tag_redraw(ar);
718
719         return ar;
720 }
721
722 /** \} */
723
724 /* -------------------------------------------------------------------- */
725 /** \name ToolTip Public API
726  * \{ */
727
728
729 ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but)
730 {
731         wmWindow *win = CTX_wm_window(C);
732         /* aspect values that shrink text are likely unreadable */
733         const float aspect = min_ff(1.0f, but->block->aspect);
734         float init_position[2];
735
736         if (but->drawflag & UI_BUT_NO_TOOLTIP) {
737                 return NULL;
738         }
739         uiTooltipData *data = NULL;
740
741         if (data == NULL) {
742                 data = ui_tooltip_data_from_button(C, but);
743         }
744         if (data == NULL) {
745                 return NULL;
746         }
747
748         init_position[0] = BLI_rctf_cent_x(&but->rect);
749         init_position[1] = but->rect.ymin;
750
751         if (butregion) {
752                 ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]);
753                 init_position[0] = win->eventstate->x;
754         }
755
756         return ui_tooltip_create_with_data(C, data, init_position, aspect);
757 }
758
759 void UI_tooltip_free(bContext *C, bScreen *sc, ARegion *ar)
760 {
761         ui_region_temp_remove(C, sc, ar);
762 }
763
764 /** \} */