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