Fix T59210: Measure tool crash w/o overlays/gizmos
[blender.git] / source / blender / editors / space_view3d / view3d_gizmo_ruler.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  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file blender/editors/space_view3d/view3d_gizmo_ruler.c
22  *  \ingroup spview3d
23  */
24
25 #include "BLI_listbase.h"
26 #include "BLI_string.h"
27 #include "BLI_rect.h"
28 #include "BLI_math.h"
29 #include "BLI_utildefines.h"
30
31 #include "BLT_translation.h"
32
33 #include "BKE_context.h"
34 #include "BKE_gpencil.h"
35 #include "BKE_main.h"
36 #include "BKE_report.h"
37
38 #include "BKE_object.h"
39 #include "BKE_unit.h"
40 #include "BKE_material.h"
41
42 #include "DNA_meshdata_types.h"
43 #include "DNA_object_types.h"
44 #include "DNA_gpencil_types.h"
45 #include "DNA_view3d_types.h"
46
47 #include "BIF_gl.h"
48
49 #include "ED_gizmo_utils.h"
50 #include "ED_gpencil.h"
51 #include "ED_screen.h"
52 #include "ED_transform_snap_object_context.h"
53 #include "ED_view3d.h"
54
55 #include "UI_resources.h"
56 #include "UI_interface.h"
57
58 #include "MEM_guardedalloc.h"
59
60 #include "RNA_access.h"
61
62 #include "WM_api.h"
63 #include "WM_types.h"
64 #include "WM_toolsystem.h"
65
66 #include "view3d_intern.h"  /* own include */
67
68 #include "GPU_immediate.h"
69 #include "GPU_immediate_util.h"
70 #include "GPU_select.h"
71 #include "GPU_state.h"
72
73 #include "BLF_api.h"
74
75
76 static const char *view3d_gzgt_ruler_id = "VIEW3D_GGT_ruler";
77
78
79 #define MVAL_MAX_PX_DIST 12.0f
80
81 /* -------------------------------------------------------------------- */
82 /* Ruler Item (we can have many) */
83 enum {
84         RULERITEM_USE_ANGLE = (1 << 0),  /* use protractor */
85         RULERITEM_USE_RAYCAST = (1 << 1)
86 };
87
88 enum {
89         RULERITEM_DIRECTION_IN = 0,
90         RULERITEM_DIRECTION_OUT
91 };
92
93 /* keep smaller then selection, since we may want click elsewhere without selecting a ruler */
94 #define RULER_PICK_DIST 12.0f
95 #define RULER_PICK_DIST_SQ (RULER_PICK_DIST * RULER_PICK_DIST)
96
97 /* not clicking on a point */
98 #define PART_LINE 0xff
99
100 /* -------------------------------------------------------------------- */
101 /* Ruler Info (wmGizmoGroup customdata) */
102
103 enum {
104         RULER_STATE_NORMAL = 0,
105         RULER_STATE_DRAG
106 };
107
108 enum {
109         RULER_SNAP_OK = (1 << 0),
110 };
111
112 typedef struct RulerInfo {
113         // ListBase items;
114         int      item_active;
115         int flag;
116         int snap_flag;
117         int state;
118
119         struct SnapObjectContext *snap_context;
120
121         /* wm state */
122         wmWindow *win;
123         ScrArea *sa;
124         ARegion *ar;  /* re-assigned every modal update */
125 } RulerInfo;
126
127 /* -------------------------------------------------------------------- */
128 /* Ruler Item (two or three points) */
129
130 typedef struct RulerItem {
131         wmGizmo gz;
132
133         /* worldspace coords, middle being optional */
134         float co[3][3];
135
136         int   flag;
137         int   raycast_dir;  /* RULER_DIRECTION_* */
138 } RulerItem;
139
140 typedef struct RulerInteraction {
141         /* selected coord */
142         char  co_index; /* 0 -> 2 */
143         float drag_start_co[3];
144         uint inside_region : 1;
145 } RulerInteraction;
146
147 /* -------------------------------------------------------------------- */
148 /** \name Internal Ruler Utilities
149  * \{ */
150
151 static RulerItem *ruler_item_add(wmGizmoGroup *gzgroup)
152 {
153         /* could pass this as an arg */
154         const wmGizmoType *gzt_ruler = WM_gizmotype_find("VIEW3D_GT_ruler_item", true);
155         RulerItem *ruler_item = (RulerItem *)WM_gizmo_new_ptr(gzt_ruler, gzgroup, NULL);
156         WM_gizmo_set_flag(&ruler_item->gz, WM_GIZMO_DRAW_MODAL, true);
157         return ruler_item;
158 }
159
160 static void ruler_item_remove(bContext *C, wmGizmoGroup *gzgroup, RulerItem *ruler_item)
161 {
162         WM_gizmo_unlink(&gzgroup->gizmos, gzgroup->parent_gzmap, &ruler_item->gz, C);
163 }
164
165 static void ruler_item_as_string(RulerItem *ruler_item, UnitSettings *unit,
166                                  char *numstr, size_t numstr_size, int prec)
167 {
168         if (ruler_item->flag & RULERITEM_USE_ANGLE) {
169                 const float ruler_angle = angle_v3v3v3(ruler_item->co[0],
170                                                        ruler_item->co[1],
171                                                        ruler_item->co[2]);
172
173                 if (unit->system == USER_UNIT_NONE) {
174                         BLI_snprintf(numstr, numstr_size, "%.*f°", prec, RAD2DEGF(ruler_angle));
175                 }
176                 else {
177                         bUnit_AsString2(
178                                 numstr, numstr_size, (double)ruler_angle,
179                                 prec, B_UNIT_ROTATION, unit, false);
180                 }
181         }
182         else {
183                 const float ruler_len = len_v3v3(ruler_item->co[0],
184                                                  ruler_item->co[2]);
185
186                 if (unit->system == USER_UNIT_NONE) {
187                         BLI_snprintf(numstr, numstr_size, "%.*f", prec, ruler_len);
188                 }
189                 else {
190                         bUnit_AsString2(
191                                 numstr, numstr_size, (double)(ruler_len * unit->scale_length),
192                                 prec, B_UNIT_LENGTH, unit, false);
193                 }
194         }
195 }
196
197 static bool view3d_ruler_pick(
198         wmGizmoGroup *gzgroup, RulerItem *ruler_item, const float mval[2],
199         int *r_co_index)
200 {
201         RulerInfo *ruler_info = gzgroup->customdata;
202         ARegion *ar = ruler_info->ar;
203         bool found = false;
204
205         float dist_best = RULER_PICK_DIST_SQ;
206         int co_index_best = -1;
207
208         {
209                 float co_ss[3][2];
210                 float dist;
211                 int j;
212
213                 /* should these be checked? - ok for now not to */
214                 for (j = 0; j < 3; j++) {
215                         ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
216                 }
217
218                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
219                         dist = min_ff(dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[1]),
220                                       dist_squared_to_line_segment_v2(mval, co_ss[1], co_ss[2]));
221                         if (dist < dist_best) {
222                                 dist_best = dist;
223                                 found = true;
224
225                                 {
226                                         const float dist_points[3] = {
227                                             len_squared_v2v2(co_ss[0], mval),
228                                             len_squared_v2v2(co_ss[1], mval),
229                                             len_squared_v2v2(co_ss[2], mval),
230                                         };
231                                         if (min_fff(UNPACK3(dist_points)) < RULER_PICK_DIST_SQ) {
232                                                 co_index_best = min_axis_v3(dist_points);
233                                         }
234                                         else {
235                                                 co_index_best = -1;
236                                         }
237                                 }
238                         }
239                 }
240                 else {
241                         dist = dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[2]);
242                         if (dist < dist_best) {
243                                 dist_best = dist;
244                                 found = true;
245
246                                 {
247                                         const float dist_points[2] = {
248                                             len_squared_v2v2(co_ss[0], mval),
249                                             len_squared_v2v2(co_ss[2], mval),
250                                         };
251                                         if (min_ff(UNPACK2(dist_points)) < RULER_PICK_DIST_SQ) {
252                                                 co_index_best = (dist_points[0] < dist_points[1]) ? 0 : 2;
253                                         }
254                                         else {
255                                                 co_index_best = -1;
256                                         }
257                                 }
258                         }
259                 }
260         }
261
262         *r_co_index = co_index_best;
263         return found;
264 }
265
266 /**
267  * Ensure the 'snap_context' is only cached while dragging,
268  * needed since the user may toggle modes between tool use.
269  */
270 static void ruler_state_set(bContext *C, RulerInfo *ruler_info, int state)
271 {
272         Main *bmain = CTX_data_main(C);
273         if (state == ruler_info->state) {
274                 return;
275         }
276
277         /* always remove */
278         if (ruler_info->snap_context) {
279                 ED_transform_snap_object_context_destroy(ruler_info->snap_context);
280                 ruler_info->snap_context = NULL;
281         }
282
283         if (state == RULER_STATE_NORMAL) {
284                 /* pass */
285         }
286         else if (state == RULER_STATE_DRAG) {
287                 ruler_info->snap_context = ED_transform_snap_object_context_create_view3d(
288                         bmain, CTX_data_scene(C), CTX_data_depsgraph(C), 0,
289                         ruler_info->ar, CTX_wm_view3d(C));
290         }
291         else {
292                 BLI_assert(0);
293         }
294
295         ruler_info->state = state;
296 }
297
298 static void view3d_ruler_item_project(
299         RulerInfo *ruler_info, float r_co[3],
300         const int xy[2])
301 {
302         ED_view3d_win_to_3d_int(ruler_info->sa->spacedata.first, ruler_info->ar, r_co, xy, r_co);
303 }
304
305 /* use for mousemove events */
306 static bool view3d_ruler_item_mousemove(
307         RulerInfo *ruler_info, RulerItem *ruler_item, const int mval[2],
308         const bool do_thickness, const bool do_snap)
309 {
310         const float eps_bias = 0.0002f;
311         float dist_px = MVAL_MAX_PX_DIST * U.pixelsize;  /* snap dist */
312
313         ruler_info->snap_flag &= ~RULER_SNAP_OK;
314
315         if (ruler_item) {
316                 RulerInteraction *inter = ruler_item->gz.interaction_data;
317                 float *co = ruler_item->co[inter->co_index];
318                 /* restore the initial depth */
319                 copy_v3_v3(co, inter->drag_start_co);
320                 view3d_ruler_item_project(ruler_info, co, mval);
321                 if (do_thickness && inter->co_index != 1) {
322                         // Scene *scene = CTX_data_scene(C);
323                         // View3D *v3d = ruler_info->sa->spacedata.first;
324                         const float mval_fl[2] = {UNPACK2(mval)};
325                         float ray_normal[3];
326                         float ray_start[3];
327                         float *co_other;
328
329                         co_other = ruler_item->co[inter->co_index == 0 ? 2 : 0];
330
331                         if (ED_transform_snap_object_project_view3d(
332                                 ruler_info->snap_context,
333                                 SCE_SNAP_MODE_FACE,
334                                 &(const struct SnapObjectParams){
335                                     .snap_select = SNAP_ALL,
336                                     .use_object_edit_cage = true,
337                                 },
338                                 mval_fl, &dist_px,
339                                 co, ray_normal))
340                         {
341                                 negate_v3(ray_normal);
342                                 /* add some bias */
343                                 madd_v3_v3v3fl(ray_start, co, ray_normal, eps_bias);
344                                 ED_transform_snap_object_project_ray(
345                                         ruler_info->snap_context,
346                                         &(const struct SnapObjectParams){
347                                             .snap_select = SNAP_ALL,
348                                             .use_object_edit_cage = true,
349                                         },
350                                         ray_start, ray_normal, NULL,
351                                         co_other, NULL);
352                         }
353                 }
354                 else if (do_snap) {
355                         const float mval_fl[2] = {UNPACK2(mval)};
356
357                         if (ED_transform_snap_object_project_view3d(
358                                 ruler_info->snap_context,
359                                 (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE),
360                                 &(const struct SnapObjectParams){
361                                     .snap_select = SNAP_ALL,
362                                     .use_object_edit_cage = true,
363                                     .use_occlusion_test = true,
364                                 },
365                                 mval_fl, &dist_px,
366                                 co, NULL))
367                         {
368                                 ruler_info->snap_flag |= RULER_SNAP_OK;
369                         }
370                 }
371                 return true;
372         }
373         else {
374                 return false;
375         }
376 }
377
378
379 /** \} */
380
381 /* -------------------------------------------------------------------- */
382 /** \name Ruler/Grease Pencil Conversion
383  * \{ */
384
385 #define RULER_ID "RulerData3D"
386 static bool view3d_ruler_to_gpencil(bContext *C, wmGizmoGroup *gzgroup)
387 {
388         // RulerInfo *ruler_info = gzgroup->customdata;
389         Main *bmain = CTX_data_main(C);
390         Scene *scene = CTX_data_scene(C);
391
392         bGPdata *gpd;
393         bGPDlayer *gpl;
394         bGPDframe *gpf;
395         bGPDstroke *gps;
396         RulerItem *ruler_item;
397         const char *ruler_name = RULER_ID;
398         bool changed = false;
399
400         if (scene->gpd == NULL) {
401                 scene->gpd = BKE_gpencil_data_addnew(bmain, "Annotations");
402         }
403         gpd = scene->gpd;
404
405         gpl = BLI_findstring(&gpd->layers, ruler_name, offsetof(bGPDlayer, info));
406         if (gpl == NULL) {
407                 gpl = BKE_gpencil_layer_addnew(gpd, ruler_name, false);
408                 copy_v4_v4(gpl->color, U.gpencil_new_layer_col);
409                 gpl->thickness = 1;
410                 gpl->flag |= GP_LAYER_HIDE;
411         }
412
413         gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_ADD_NEW);
414         BKE_gpencil_free_strokes(gpf);
415
416         for (ruler_item = gzgroup->gizmos.first; ruler_item; ruler_item = (RulerItem *)ruler_item->gz.next) {
417                 bGPDspoint *pt;
418                 int j;
419
420                 /* allocate memory for a new stroke */
421                 gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
422                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
423                         gps->totpoints = 3;
424                         pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
425                         for (j = 0; j < 3; j++) {
426                                 copy_v3_v3(&pt->x, ruler_item->co[j]);
427                                 pt->pressure = 1.0f;
428                                 pt->strength = 1.0f;
429                                 pt++;
430                         }
431                 }
432                 else {
433                         gps->totpoints = 2;
434                         pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
435                         for (j = 0; j < 3; j += 2) {
436                                 copy_v3_v3(&pt->x, ruler_item->co[j]);
437                                 pt->pressure = 1.0f;
438                                 pt->strength = 1.0f;
439                                 pt++;
440                         }
441                 }
442                 gps->flag = GP_STROKE_3DSPACE;
443                 gps->thickness = 3;
444
445                 BLI_addtail(&gpf->strokes, gps);
446                 changed = true;
447         }
448
449         return changed;
450 }
451
452 static bool view3d_ruler_from_gpencil(const bContext *C, wmGizmoGroup *gzgroup)
453 {
454         Scene *scene = CTX_data_scene(C);
455         bool changed = false;
456
457         if (scene->gpd) {
458                 bGPDlayer *gpl;
459                 const char *ruler_name = RULER_ID;
460                 gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
461                 if (gpl) {
462                         bGPDframe *gpf;
463                         gpf = BKE_gpencil_layer_getframe(gpl, CFRA, GP_GETFRAME_USE_PREV);
464                         if (gpf) {
465                                 bGPDstroke *gps;
466                                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
467                                         bGPDspoint *pt = gps->points;
468                                         int j;
469                                         RulerItem *ruler_item = NULL;
470                                         if (gps->totpoints == 3) {
471                                                 ruler_item = ruler_item_add(gzgroup);
472                                                 for (j = 0; j < 3; j++) {
473                                                         copy_v3_v3(ruler_item->co[j], &pt->x);
474                                                         pt++;
475                                                 }
476                                                 ruler_item->flag |= RULERITEM_USE_ANGLE;
477                                                 changed = true;
478                                         }
479                                         else if (gps->totpoints == 2) {
480                                                 ruler_item = ruler_item_add(gzgroup);
481                                                 for (j = 0; j < 3; j += 2) {
482                                                         copy_v3_v3(ruler_item->co[j], &pt->x);
483                                                         pt++;
484                                                 }
485                                                 changed = true;
486                                         }
487                                 }
488                         }
489                 }
490         }
491
492         return changed;
493 }
494
495 /** \} */
496
497 /* -------------------------------------------------------------------- */
498 /** \name Ruler Item Gizmo Type
499  * \{ */
500
501 static void gizmo_ruler_draw(const bContext *C, wmGizmo *gz)
502 {
503         Scene *scene = CTX_data_scene(C);
504         UnitSettings *unit = &scene->unit;
505         RulerInfo *ruler_info = gz->parent_gzgroup->customdata;
506         RulerItem *ruler_item = (RulerItem *)gz;
507         ARegion *ar = ruler_info->ar;
508         RegionView3D *rv3d = ar->regiondata;
509         const float cap_size = 4.0f;
510         const float bg_margin = 4.0f * U.pixelsize;
511         const float arc_size = 64.0f * U.pixelsize;
512 #define ARC_STEPS 24
513         const int arc_steps = ARC_STEPS;
514         const float color_act[4] = {1.0f, 1.0f, 1.0f, 1.0f};
515         const float color_base[4] = {0.0f, 0.0f, 0.0f, 1.0f};
516         unsigned char color_text[3];
517         unsigned char color_wire[3];
518         float color_back[4] = {1.0f, 1.0f, 1.0f, 0.5f};
519
520         /* anti-aliased lines for more consistent appearance */
521         GPU_line_smooth(true);
522         GPU_line_width(1.0f);
523
524         BLF_enable(blf_mono_font, BLF_ROTATION);
525         BLF_size(blf_mono_font, 14 * U.pixelsize, U.dpi);
526         BLF_rotation(blf_mono_font, 0.0f);
527
528         UI_GetThemeColor3ubv(TH_TEXT, color_text);
529         UI_GetThemeColor3ubv(TH_WIRE, color_wire);
530
531         /* Avoid white on white text. (TODO Fix by using theme) */
532         if ((int)color_text[0] + (int)color_text[1] + (int)color_text[2] > 127 * 3 * 0.6f) {
533                 copy_v3_fl(color_back, 0.0f);
534         }
535
536         const bool is_act = (gz->flag & WM_GIZMO_DRAW_HOVER);
537         float dir_ruler[2];
538         float co_ss[3][2];
539         int j;
540
541         /* should these be checked? - ok for now not to */
542         for (j = 0; j < 3; j++) {
543                 ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
544         }
545
546         GPU_blend(true);
547
548         const uint shdr_pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
549
550         if (ruler_item->flag & RULERITEM_USE_ANGLE) {
551                 immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
552
553                 float viewport_size[4];
554                 GPU_viewport_size_get_f(viewport_size);
555                 immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
556
557                 immUniform1i("colors_len", 2);  /* "advanced" mode */
558                 const float *col = is_act ? color_act : color_base;
559                 immUniformArray4fv("colors", (float *)(float[][4]){{0.67f, 0.67f, 0.67f, 1.0f}, {col[0], col[1], col[2], col[3]}}, 2);
560                 immUniform1f("dash_width", 6.0f);
561
562                 immBegin(GPU_PRIM_LINE_STRIP, 3);
563
564                 immVertex2fv(shdr_pos, co_ss[0]);
565                 immVertex2fv(shdr_pos, co_ss[1]);
566                 immVertex2fv(shdr_pos, co_ss[2]);
567
568                 immEnd();
569
570                 immUnbindProgram();
571
572                 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
573
574                 /* arc */
575                 {
576                         float dir_tmp[3];
577                         float co_tmp[3];
578                         float arc_ss_coord[2];
579
580                         float dir_a[3];
581                         float dir_b[3];
582                         float quat[4];
583                         float axis[3];
584                         float angle;
585                         const float px_scale = (ED_view3d_pixel_size_no_ui_scale(rv3d, ruler_item->co[1]) *
586                                                 min_fff(arc_size,
587                                                         len_v2v2(co_ss[0], co_ss[1]) / 2.0f,
588                                                         len_v2v2(co_ss[2], co_ss[1]) / 2.0f));
589
590                         sub_v3_v3v3(dir_a, ruler_item->co[0], ruler_item->co[1]);
591                         sub_v3_v3v3(dir_b, ruler_item->co[2], ruler_item->co[1]);
592                         normalize_v3(dir_a);
593                         normalize_v3(dir_b);
594
595                         cross_v3_v3v3(axis, dir_a, dir_b);
596                         angle = angle_normalized_v3v3(dir_a, dir_b);
597
598                         axis_angle_to_quat(quat, axis, angle / arc_steps);
599
600                         copy_v3_v3(dir_tmp, dir_a);
601
602                         immUniformColor3ubv(color_wire);
603
604                         immBegin(GPU_PRIM_LINE_STRIP, arc_steps + 1);
605
606                         for (j = 0; j <= arc_steps; j++) {
607                                 madd_v3_v3v3fl(co_tmp, ruler_item->co[1], dir_tmp, px_scale);
608                                 ED_view3d_project_float_global(ar, co_tmp, arc_ss_coord, V3D_PROJ_TEST_NOP);
609                                 mul_qt_v3(quat, dir_tmp);
610
611                                 immVertex2fv(shdr_pos, arc_ss_coord);
612                         }
613
614                         immEnd();
615                 }
616
617                 /* capping */
618                 {
619                         float rot_90_vec_a[2];
620                         float rot_90_vec_b[2];
621                         float cap[2];
622
623                         sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[1]);
624                         rot_90_vec_a[0] = -dir_ruler[1];
625                         rot_90_vec_a[1] =  dir_ruler[0];
626                         normalize_v2(rot_90_vec_a);
627
628                         sub_v2_v2v2(dir_ruler, co_ss[1], co_ss[2]);
629                         rot_90_vec_b[0] = -dir_ruler[1];
630                         rot_90_vec_b[1] =  dir_ruler[0];
631                         normalize_v2(rot_90_vec_b);
632
633                         GPU_blend(true);
634
635                         immUniformColor3ubv(color_wire);
636
637                         immBegin(GPU_PRIM_LINES, 8);
638
639                         madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, cap_size);
640                         immVertex2fv(shdr_pos, cap);
641                         madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, -cap_size);
642                         immVertex2fv(shdr_pos, cap);
643
644                         madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, cap_size);
645                         immVertex2fv(shdr_pos, cap);
646                         madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, -cap_size);
647                         immVertex2fv(shdr_pos, cap);
648
649                         /* angle vertex */
650                         immVertex2f(shdr_pos, co_ss[1][0] - cap_size, co_ss[1][1] - cap_size);
651                         immVertex2f(shdr_pos, co_ss[1][0] + cap_size, co_ss[1][1] + cap_size);
652                         immVertex2f(shdr_pos, co_ss[1][0] - cap_size, co_ss[1][1] + cap_size);
653                         immVertex2f(shdr_pos, co_ss[1][0] + cap_size, co_ss[1][1] - cap_size);
654
655                         immEnd();
656
657                         GPU_blend(false);
658                 }
659
660                 /* text */
661                 char numstr[256];
662                 float numstr_size[2];
663                 float posit[2];
664                 const int prec = 2;  /* XXX, todo, make optional */
665
666                 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
667
668                 BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
669
670                 posit[0] = co_ss[1][0] + (cap_size * 2.0f);
671                 posit[1] = co_ss[1][1] - (numstr_size[1] / 2.0f);
672
673                 /* draw text (bg) */
674                 {
675                         immUniformColor4fv(color_back);
676                         GPU_blend(true);
677                         immRectf(shdr_pos,
678                                  posit[0] - bg_margin,                  posit[1] - bg_margin,
679                                  posit[0] + bg_margin + numstr_size[0], posit[1] + bg_margin + numstr_size[1]);
680                         GPU_blend(false);
681                 }
682
683                 immUnbindProgram();
684
685                 /* draw text */
686                 {
687                         BLF_color3ubv(blf_mono_font, color_text);
688                         BLF_position(blf_mono_font, posit[0], posit[1], 0.0f);
689                         BLF_rotation(blf_mono_font, 0.0f);
690                         BLF_draw(blf_mono_font, numstr, sizeof(numstr));
691                 }
692         }
693         else {
694                 immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR);
695
696                 float viewport_size[4];
697                 GPU_viewport_size_get_f(viewport_size);
698                 immUniform2f("viewport_size", viewport_size[2], viewport_size[3]);
699
700                 immUniform1i("colors_len", 2);  /* "advanced" mode */
701                 const float *col = is_act ? color_act : color_base;
702                 immUniformArray4fv("colors", (float *)(float[][4]){{0.67f, 0.67f, 0.67f, 1.0f}, {col[0], col[1], col[2], col[3]}}, 2);
703                 immUniform1f("dash_width", 6.0f);
704
705                 immBegin(GPU_PRIM_LINES, 2);
706
707                 immVertex2fv(shdr_pos, co_ss[0]);
708                 immVertex2fv(shdr_pos, co_ss[2]);
709
710                 immEnd();
711
712                 immUnbindProgram();
713
714                 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
715
716                 sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]);
717
718                 /* capping */
719                 {
720                         float rot_90_vec[2] = {-dir_ruler[1], dir_ruler[0]};
721                         float cap[2];
722
723                         normalize_v2(rot_90_vec);
724
725                         GPU_blend(true);
726
727                         immUniformColor3ubv(color_wire);
728
729                         immBegin(GPU_PRIM_LINES, 4);
730
731                         madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, cap_size);
732                         immVertex2fv(shdr_pos, cap);
733                         madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, -cap_size);
734                         immVertex2fv(shdr_pos, cap);
735
736                         madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, cap_size);
737                         immVertex2fv(shdr_pos, cap);
738                         madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, -cap_size);
739                         immVertex2fv(shdr_pos, cap);
740
741                         immEnd();
742
743                         GPU_blend(false);
744                 }
745
746                 /* text */
747                 char numstr[256];
748                 float numstr_size[2];
749                 const int prec = 6;  /* XXX, todo, make optional */
750                 float posit[2];
751
752                 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
753
754                 BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
755
756                 mid_v2_v2v2(posit, co_ss[0], co_ss[2]);
757
758                 /* center text */
759                 posit[0] -= numstr_size[0] / 2.0f;
760                 posit[1] -= numstr_size[1] / 2.0f;
761
762                 /* draw text (bg) */
763                 {
764                         immUniformColor4fv(color_back);
765                         GPU_blend(true);
766                         immRectf(shdr_pos,
767                                  posit[0] - bg_margin,                  posit[1] - bg_margin,
768                                  posit[0] + bg_margin + numstr_size[0], posit[1] + bg_margin + numstr_size[1]);
769                         GPU_blend(false);
770                 }
771
772                 immUnbindProgram();
773
774                 /* draw text */
775                 {
776                         BLF_color3ubv(blf_mono_font, color_text);
777                         BLF_position(blf_mono_font, posit[0], posit[1], 0.0f);
778                         BLF_draw(blf_mono_font, numstr, sizeof(numstr));
779                 }
780         }
781
782         GPU_line_smooth(false);
783
784         BLF_disable(blf_mono_font, BLF_ROTATION);
785
786 #undef ARC_STEPS
787
788         /* draw snap */
789         if ((ruler_info->snap_flag & RULER_SNAP_OK) &&
790             (ruler_info->state == RULER_STATE_DRAG) &&
791             (ruler_item->gz.interaction_data != NULL))
792         {
793                 RulerInteraction *inter = ruler_item->gz.interaction_data;
794                 /* size from drawSnapping */
795                 const float size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
796                 float co_ss_snap[3];
797                 ED_view3d_project_float_global(ar, ruler_item->co[inter->co_index], co_ss_snap, V3D_PROJ_TEST_NOP);
798
799                 uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
800
801                 immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
802                 immUniformColor4fv(color_act);
803
804                 imm_draw_circle_wire_2d(pos, co_ss_snap[0], co_ss_snap[1], size * U.pixelsize, 32);
805
806                 immUnbindProgram();
807         }
808 }
809
810 static int gizmo_ruler_test_select(
811         bContext *UNUSED(C), wmGizmo *gz, const int mval[2])
812 {
813         RulerItem *ruler_item_pick = (RulerItem *)gz;
814         float mval_fl[2] = {UNPACK2(mval)};
815         int co_index;
816
817         /* select and drag */
818         if (view3d_ruler_pick(gz->parent_gzgroup, ruler_item_pick, mval_fl, &co_index)) {
819                 if (co_index == -1) {
820                         if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
821                                 return PART_LINE;
822                         }
823                 }
824                 else {
825                         return co_index;
826                 }
827         }
828         return -1;
829 }
830
831 static int gizmo_ruler_modal(
832         bContext *C, wmGizmo *gz, const wmEvent *event,
833         eWM_GizmoFlagTweak UNUSED(tweak_flag))
834 {
835         bool do_draw = false;
836         int exit_code = OPERATOR_RUNNING_MODAL;
837         RulerInfo *ruler_info = gz->parent_gzgroup->customdata;
838         RulerItem *ruler_item = (RulerItem *)gz;
839         RulerInteraction *inter = ruler_item->gz.interaction_data;
840         ARegion *ar = CTX_wm_region(C);
841
842         ruler_info->ar = ar;
843
844         switch (event->type) {
845                 case MOUSEMOVE:
846                 {
847                         if (ruler_info->state == RULER_STATE_DRAG) {
848                                 if (view3d_ruler_item_mousemove(
849                                         ruler_info, ruler_item, event->mval,
850                                         event->shift != 0, event->ctrl != 0))
851                                 {
852                                         do_draw = true;
853                                 }
854                                 inter->inside_region = BLI_rcti_isect_pt_v(&ar->winrct, &event->x);
855                         }
856                         break;
857                 }
858         }
859         if (do_draw) {
860                 ED_region_tag_redraw(ar);
861         }
862         return exit_code;
863 }
864
865 static int gizmo_ruler_invoke(
866         bContext *C, wmGizmo *gz, const wmEvent *event)
867 {
868         wmGizmoGroup *gzgroup = gz->parent_gzgroup;
869         RulerInfo *ruler_info = gzgroup->customdata;
870         RulerItem *ruler_item_pick = (RulerItem *)gz;
871         RulerInteraction *inter = MEM_callocN(sizeof(RulerInteraction), __func__);
872         gz->interaction_data = inter;
873
874         ARegion *ar = ruler_info->ar;
875
876         const float mval_fl[2] = {UNPACK2(event->mval)};
877
878         /* select and drag */
879         if (gz->highlight_part == PART_LINE) {
880                 if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
881                         /* Add Center Point */
882                         ruler_item_pick->flag |= RULERITEM_USE_ANGLE;
883                         inter->co_index = 1;
884                         ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
885
886                         /* find the factor */
887                         {
888                                 float co_ss[2][2];
889                                 float fac;
890
891                                 ED_view3d_project_float_global(ar, ruler_item_pick->co[0], co_ss[0], V3D_PROJ_TEST_NOP);
892                                 ED_view3d_project_float_global(ar, ruler_item_pick->co[2], co_ss[1], V3D_PROJ_TEST_NOP);
893
894                                 fac = line_point_factor_v2(mval_fl, co_ss[0], co_ss[1]);
895                                 CLAMP(fac, 0.0f, 1.0f);
896
897                                 interp_v3_v3v3(ruler_item_pick->co[1],
898                                                ruler_item_pick->co[0],
899                                                ruler_item_pick->co[2], fac);
900                         }
901
902                         /* update the new location */
903                         view3d_ruler_item_mousemove(
904                                 ruler_info, ruler_item_pick, event->mval,
905                                 event->shift != 0, event->ctrl != 0);
906                 }
907         }
908         else {
909                 inter->co_index = gz->highlight_part;
910                 ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
911
912                 /* store the initial depth */
913                 copy_v3_v3(inter->drag_start_co, ruler_item_pick->co[inter->co_index]);
914         }
915
916         /* Should always be true. */
917         inter->inside_region = BLI_rcti_isect_pt_v(&ar->winrct, &event->x);
918
919         return OPERATOR_RUNNING_MODAL;
920 }
921
922 static void gizmo_ruler_exit(bContext *C, wmGizmo *gz, const bool cancel)
923 {
924         wmGizmoGroup *gzgroup = gz->parent_gzgroup;
925         RulerInfo *ruler_info = gzgroup->customdata;
926
927         if (!cancel) {
928                 if (ruler_info->state == RULER_STATE_DRAG) {
929                         RulerItem *ruler_item = (RulerItem *)gz;
930                         RulerInteraction *inter = gz->interaction_data;
931                         /* rubber-band angle removal */
932                         if (!inter->inside_region) {
933                                 if ((inter->co_index == 1) && (ruler_item->flag & RULERITEM_USE_ANGLE)) {
934                                         ruler_item->flag &= ~RULERITEM_USE_ANGLE;
935                                 }
936                                 else {
937                                         /* Not ideal, since the ruler isn't a mode and we don't want to override delete key
938                                          * use dragging out of the view for removal. */
939                                         ruler_item_remove(C, gzgroup, ruler_item);
940                                         ruler_item = NULL;
941                                         gz = NULL;
942                                         inter = NULL;
943                                 }
944                         }
945                         if (ruler_info->snap_flag & RULER_SNAP_OK) {
946                                 ruler_info->snap_flag &= ~RULER_SNAP_OK;
947                         }
948                         ruler_state_set(C, ruler_info, RULER_STATE_NORMAL);
949                 }
950                 /* We could convert only the current gizmo, for now just re-generate. */
951                 view3d_ruler_to_gpencil(C, gzgroup);
952         }
953
954         if (gz) {
955                 MEM_SAFE_FREE(gz->interaction_data);
956         }
957
958         ruler_state_set(C, ruler_info, RULER_STATE_NORMAL);
959 }
960
961 static int gizmo_ruler_cursor_get(wmGizmo *gz)
962 {
963         if (gz->highlight_part == PART_LINE) {
964                 return BC_CROSSCURSOR;
965         }
966         return BC_NSEW_SCROLLCURSOR;
967 }
968
969 void VIEW3D_GT_ruler_item(wmGizmoType *gzt)
970 {
971         /* identifiers */
972         gzt->idname = "VIEW3D_GT_ruler_item";
973
974         /* api callbacks */
975         gzt->draw = gizmo_ruler_draw;
976         gzt->test_select = gizmo_ruler_test_select;
977         gzt->modal = gizmo_ruler_modal;
978         gzt->invoke = gizmo_ruler_invoke;
979         gzt->exit = gizmo_ruler_exit;
980         gzt->cursor_get = gizmo_ruler_cursor_get;
981
982         gzt->struct_size = sizeof(RulerItem);
983 }
984
985 /** \} */
986
987 /* -------------------------------------------------------------------- */
988 /** \name Ruler Gizmo Group
989  * \{ */
990
991 static void WIDGETGROUP_ruler_setup(const bContext *C, wmGizmoGroup *gzgroup)
992 {
993         RulerInfo *ruler_info = MEM_callocN(sizeof(RulerInfo), __func__);
994
995         if (view3d_ruler_from_gpencil(C, gzgroup)) {
996                 /* nop */
997         }
998
999         wmWindow *win = CTX_wm_window(C);
1000         ScrArea *sa = CTX_wm_area(C);
1001         ARegion *ar = CTX_wm_region(C);
1002         ruler_info->win = win;
1003         ruler_info->sa = sa;
1004         ruler_info->ar = ar;
1005
1006         gzgroup->customdata = ruler_info;
1007 }
1008
1009 void VIEW3D_GGT_ruler(wmGizmoGroupType *gzgt)
1010 {
1011         gzgt->name = "Ruler Widgets";
1012         gzgt->idname = view3d_gzgt_ruler_id;
1013
1014         gzgt->flag |= WM_GIZMOGROUPTYPE_SCALE | WM_GIZMOGROUPTYPE_DRAW_MODAL_ALL;
1015
1016         gzgt->gzmap_params.spaceid = SPACE_VIEW3D;
1017         gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW;
1018
1019         gzgt->poll = ED_gizmo_poll_or_unlink_delayed_from_tool;
1020         gzgt->setup = WIDGETGROUP_ruler_setup;
1021 }
1022
1023 /** \} */
1024
1025 /* -------------------------------------------------------------------- */
1026 /** \name Add Ruler Operator
1027  * \{ */
1028
1029 static bool view3d_ruler_poll(bContext *C)
1030 {
1031         bToolRef_Runtime *tref_rt = WM_toolsystem_runtime_from_context((bContext *)C);
1032         if ((tref_rt == NULL) ||
1033             !STREQ(view3d_gzgt_ruler_id, tref_rt->gizmo_group) ||
1034             CTX_wm_region_view3d(C) == NULL)
1035         {
1036                 return false;
1037         }
1038         return true;
1039 }
1040
1041 static int view3d_ruler_add_invoke(bContext *C, wmOperator *op, const wmEvent *event)
1042 {
1043         ARegion *ar = CTX_wm_region(C);
1044         View3D *v3d = CTX_wm_view3d(C);
1045         RegionView3D *rv3d = ar->regiondata;
1046
1047         if ((v3d->flag2 & V3D_RENDER_OVERRIDE) ||
1048             (v3d->gizmo_flag & (V3D_GIZMO_HIDE | V3D_GIZMO_HIDE_TOOL)))
1049         {
1050                 BKE_report(op->reports, RPT_WARNING, "Gizmos hidden in this view");
1051                 return OPERATOR_CANCELLED;
1052         }
1053
1054         wmGizmoMap *gzmap = ar->gizmo_map;
1055         wmGizmoGroup *gzgroup = WM_gizmomap_group_find(gzmap, view3d_gzgt_ruler_id);
1056         const bool use_depth = (v3d->shading.type >= OB_SOLID);
1057
1058         /* Create new line */
1059         RulerItem *ruler_item;
1060         ruler_item = ruler_item_add(gzgroup);
1061
1062         /* This is a little weak, but there is no real good way to tweak directly. */
1063         WM_gizmo_highlight_set(gzmap, &ruler_item->gz);
1064         if (WM_operator_name_call(
1065                 C, "GIZMOGROUP_OT_gizmo_tweak",
1066                 WM_OP_INVOKE_REGION_WIN, NULL) == OPERATOR_RUNNING_MODAL)
1067         {
1068                 RulerInfo *ruler_info = gzgroup->customdata;
1069                 RulerInteraction *inter = ruler_item->gz.interaction_data;
1070                 if (use_depth) {
1071                         /* snap the first point added, not essential but handy */
1072                         inter->co_index = 0;
1073                         view3d_ruler_item_mousemove(ruler_info, ruler_item, event->mval, false, true);
1074                         copy_v3_v3(inter->drag_start_co, ruler_item->co[inter->co_index]);
1075                 }
1076                 else {
1077                         negate_v3_v3(inter->drag_start_co, rv3d->ofs);
1078                         copy_v3_v3(ruler_item->co[0], inter->drag_start_co);
1079                         view3d_ruler_item_project(ruler_info, ruler_item->co[0], event->mval);
1080                 }
1081
1082                 copy_v3_v3(ruler_item->co[2], ruler_item->co[0]);
1083                 ruler_item->gz.highlight_part = inter->co_index = 2;
1084         }
1085         return OPERATOR_FINISHED;
1086 }
1087
1088 void VIEW3D_OT_ruler_add(wmOperatorType *ot)
1089 {
1090         /* identifiers */
1091         ot->name = "Ruler Add";
1092         ot->idname = "VIEW3D_OT_ruler_add";
1093
1094         ot->invoke = view3d_ruler_add_invoke;
1095         ot->poll = view3d_ruler_poll;
1096
1097         /* flags */
1098         ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
1099 }
1100
1101 /** \} */