WM: refactor gestures for use as tools
[blender.git] / source / blender / editors / space_view3d / view3d_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  * Contributor(s): Campbell Barton
19  *
20  * ***** END GPL LICENSE BLOCK *****
21  */
22
23 /** \file blender/editors/space_view3d/view3d_ruler.c
24  *  \ingroup spview3d
25  */
26
27 /* defines VIEW3D_OT_ruler modal operator */
28
29 #include "DNA_scene_types.h"
30 #include "DNA_object_types.h"
31 #include "DNA_gpencil_types.h"
32
33 #include "MEM_guardedalloc.h"
34
35 #include "BLI_math.h"
36 #include "BLI_blenlib.h"
37
38 #include "BLT_translation.h"
39
40 #include "BKE_context.h"
41 #include "BKE_unit.h"
42 #include "BKE_gpencil.h"
43
44 #include "BIF_gl.h"
45
46 #include "WM_api.h"
47 #include "WM_types.h"
48
49 #include "ED_screen.h"
50 #include "ED_view3d.h"
51 #include "ED_transform_snap_object_context.h"
52 #include "ED_space_api.h"
53
54 #include "BLF_api.h"
55 #include "BIF_glutil.h"
56
57 #include "UI_resources.h"
58 #include "UI_interface.h"
59
60 #include "view3d_intern.h"  /* own include */
61
62 #define MVAL_MAX_PX_DIST 12.0f
63
64
65 /* -------------------------------------------------------------------- */
66 /* Ruler Item (we can have many) */
67 enum {
68         RULERITEM_USE_ANGLE = (1 << 0),  /* use protractor */
69         RULERITEM_USE_RAYCAST = (1 << 1)
70 };
71
72 enum {
73         RULERITEM_DIRECTION_IN = 0,
74         RULERITEM_DIRECTION_OUT
75 };
76
77 /* keep smaller then selection, since we may want click elsewhere without selecting a ruler */
78 #define RULER_PICK_DIST 12.0f
79 #define RULER_PICK_DIST_SQ (RULER_PICK_DIST * RULER_PICK_DIST)
80
81 typedef struct RulerItem {
82         struct RulerItem *next, *prev;
83
84         /* worldspace coords, middle being optional */
85         float co[3][3];
86
87         /* selected coord */
88         char  co_index; /* 0 -> 2*/
89
90         int   flag;
91         int   raycast_dir;  /* RULER_DIRECTION_* */
92 } RulerItem;
93
94
95 /* -------------------------------------------------------------------- */
96 /* Ruler Info (one per session) */
97
98 enum {
99         RULER_STATE_NORMAL = 0,
100         RULER_STATE_DRAG
101 };
102
103 enum {
104         RULER_SNAP_OK = (1 << 0),
105 };
106
107 typedef struct RulerInfo {
108         ListBase items;
109         int      item_active;
110         int flag;
111         int snap_flag;
112         int state;
113         float drag_start_co[3];
114
115         struct SnapObjectContext *snap_context;
116
117         /* wm state */
118         wmWindow *win;
119         ScrArea *sa;
120         void *draw_handle_pixel;
121         ARegion *ar;  /* re-assigned every modal update */
122 } RulerInfo;
123
124 /* -------------------------------------------------------------------- */
125 /* local functions */
126 static RulerItem *ruler_item_add(RulerInfo *ruler_info)
127 {
128         RulerItem *ruler_item = MEM_callocN(sizeof(RulerItem), "RulerItem");
129         BLI_addtail(&ruler_info->items, ruler_item);
130         return ruler_item;
131 }
132
133 static void ruler_item_remove(RulerInfo *ruler_info, RulerItem *ruler_item)
134 {
135         BLI_remlink(&ruler_info->items, ruler_item);
136
137         MEM_freeN(ruler_item);
138 }
139
140 static RulerItem *ruler_item_active_get(RulerInfo *ruler_info)
141 {
142         return BLI_findlink(&ruler_info->items, ruler_info->item_active);
143 }
144
145 static void ruler_item_active_set(RulerInfo *ruler_info, RulerItem *ruler_item)
146 {
147         ruler_info->item_active = BLI_findindex(&ruler_info->items, ruler_item);
148 }
149
150 static void ruler_item_as_string(RulerItem *ruler_item, UnitSettings *unit,
151                                  char *numstr, size_t numstr_size, int prec)
152 {
153         const bool do_split = (unit->flag & USER_UNIT_OPT_SPLIT) != 0;
154
155         if (ruler_item->flag & RULERITEM_USE_ANGLE) {
156                 const float ruler_angle = angle_v3v3v3(ruler_item->co[0],
157                                                        ruler_item->co[1],
158                                                        ruler_item->co[2]);
159
160                 if (unit->system == USER_UNIT_NONE) {
161                         BLI_snprintf(numstr, numstr_size, "%.*f°", prec, RAD2DEGF(ruler_angle));
162                 }
163                 else {
164                         bUnit_AsString(numstr, numstr_size,
165                                        (double)ruler_angle,
166                                        prec, unit->system, B_UNIT_ROTATION, do_split, false);
167                 }
168         }
169         else {
170                 const float ruler_len = len_v3v3(ruler_item->co[0],
171                                                  ruler_item->co[2]);
172
173                 if (unit->system == USER_UNIT_NONE) {
174                         BLI_snprintf(numstr, numstr_size, "%.*f", prec, ruler_len);
175                 }
176                 else {
177                         bUnit_AsString(numstr, numstr_size,
178                                        (double)(ruler_len * unit->scale_length),
179                                        prec, unit->system, B_UNIT_LENGTH, do_split, false);
180                 }
181         }
182
183 }
184
185 static bool view3d_ruler_pick(RulerInfo *ruler_info, const float mval[2],
186                               RulerItem **r_ruler_item, int *r_co_index)
187 {
188         ARegion *ar = ruler_info->ar;
189         RulerItem *ruler_item;
190
191         float dist_best = RULER_PICK_DIST_SQ;
192         RulerItem *ruler_item_best = NULL;
193         int co_index_best = -1;
194
195         for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
196                 float co_ss[3][2];
197                 float dist;
198                 int j;
199
200                 /* should these be checked? - ok for now not to */
201                 for (j = 0; j < 3; j++) {
202                         ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
203                 }
204
205                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
206                         dist = min_ff(dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[1]),
207                                       dist_squared_to_line_segment_v2(mval, co_ss[1], co_ss[2]));
208                         if (dist < dist_best) {
209                                 dist_best = dist;
210                                 ruler_item_best = ruler_item;
211
212                                 {
213                                         const float dist_points[3] = {
214                                             len_squared_v2v2(co_ss[0], mval),
215                                             len_squared_v2v2(co_ss[1], mval),
216                                             len_squared_v2v2(co_ss[2], mval),
217                                         };
218                                         if (min_fff(UNPACK3(dist_points)) < RULER_PICK_DIST_SQ) {
219                                                 co_index_best = min_axis_v3(dist_points);
220                                         }
221                                         else {
222                                                 co_index_best = -1;
223                                         }
224                                 }
225                         }
226                 }
227                 else {
228                         dist = dist_squared_to_line_segment_v2(mval, co_ss[0], co_ss[2]);
229                         if (dist < dist_best) {
230                                 dist_best = dist;
231                                 ruler_item_best = ruler_item;
232
233                                 {
234                                         const float dist_points[2] = {
235                                             len_squared_v2v2(co_ss[0], mval),
236                                             len_squared_v2v2(co_ss[2], mval),
237                                         };
238                                         if (min_ff(UNPACK2(dist_points)) < RULER_PICK_DIST_SQ) {
239                                                 co_index_best = (dist_points[0] < dist_points[1]) ? 0 : 2;
240                                         }
241                                         else {
242                                                 co_index_best = -1;
243                                         }
244                                 }
245                         }
246                 }
247         }
248
249         if (ruler_item_best) {
250                 *r_ruler_item = ruler_item_best;
251                 *r_co_index = co_index_best;
252                 return true;
253         }
254         else {
255                 *r_ruler_item = NULL;
256                 *r_co_index = -1;
257                 return false;
258         }
259 }
260
261 /**
262  * Ensure the 'snap_context' is only cached while dragging,
263  * needed since the user may toggle modes between tool use.
264  */
265 static void ruler_state_set(bContext *C, RulerInfo *ruler_info, int state)
266 {
267         if (state == ruler_info->state) {
268                 return;
269         }
270
271         /* always remove */
272         if (ruler_info->snap_context) {
273                 ED_transform_snap_object_context_destroy(ruler_info->snap_context);
274                 ruler_info->snap_context = NULL;
275         }
276
277         if (state == RULER_STATE_NORMAL) {
278                 /* pass */
279         }
280         else if (state == RULER_STATE_DRAG) {
281                 ruler_info->snap_context = ED_transform_snap_object_context_create_view3d(
282                         CTX_data_main(C), CTX_data_scene(C), 0,
283                         ruler_info->ar, CTX_wm_view3d(C));
284         }
285         else {
286                 BLI_assert(0);
287         }
288
289         ruler_info->state = state;
290 }
291
292 #define RULER_ID "RulerData3D"
293 static bool view3d_ruler_to_gpencil(bContext *C, RulerInfo *ruler_info)
294 {
295         Scene *scene = CTX_data_scene(C);
296         bGPDlayer *gpl;
297         bGPDframe *gpf;
298         bGPDstroke *gps;
299         bGPDpalette *palette;
300         bGPDpalettecolor *palcolor;
301         RulerItem *ruler_item;
302         const char *ruler_name = RULER_ID;
303         bool changed = false;
304
305         if (scene->gpd == NULL) {
306                 scene->gpd = BKE_gpencil_data_addnew("GPencil");
307         }
308
309         gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
310         if (gpl == NULL) {
311                 gpl = BKE_gpencil_layer_addnew(scene->gpd, ruler_name, false);
312                 gpl->thickness = 1;
313                 gpl->flag |= GP_LAYER_HIDE;
314         }
315
316         /* try to get active palette or create a new one */
317         palette = BKE_gpencil_palette_getactive(scene->gpd);
318         if (palette == NULL) {
319                 palette = BKE_gpencil_palette_addnew(scene->gpd, DATA_("GP_Palette"), true);
320         }
321         /* try to get color with the ruler name or create a new one */
322         palcolor = BKE_gpencil_palettecolor_getbyname(palette, (char *)ruler_name);
323         if (palcolor == NULL) {
324                 palcolor = BKE_gpencil_palettecolor_addnew(palette, (char *)ruler_name, true);
325         }
326         
327         gpf = BKE_gpencil_layer_getframe(gpl, CFRA, true);
328         BKE_gpencil_free_strokes(gpf);
329
330         for (ruler_item = ruler_info->items.first; ruler_item; ruler_item = ruler_item->next) {
331                 bGPDspoint *pt;
332                 int j;
333
334                 /* allocate memory for a new stroke */
335                 gps = MEM_callocN(sizeof(bGPDstroke), "gp_stroke");
336                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
337                         gps->totpoints = 3;
338                         pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
339                         for (j = 0; j < 3; j++) {
340                                 copy_v3_v3(&pt->x, ruler_item->co[j]);
341                                 pt->pressure = 1.0f;
342                                 pt->strength = 1.0f;
343                                 pt++;
344                         }
345                 }
346                 else {
347                         gps->totpoints = 2;
348                         pt = gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
349                         for (j = 0; j < 3; j += 2) {
350                                 copy_v3_v3(&pt->x, ruler_item->co[j]);
351                                 pt->pressure = 1.0f;
352                                 pt->strength = 1.0f;
353                                 pt++;
354                         }
355                 }
356                 gps->flag = GP_STROKE_3DSPACE;
357                 gps->thickness = 3;
358                 /* assign color to stroke */
359                 BLI_strncpy(gps->colorname, palcolor->info, sizeof(gps->colorname));
360                 gps->palcolor = palcolor;
361                 BLI_addtail(&gpf->strokes, gps);
362                 changed = true;
363         }
364
365         return changed;
366 }
367
368 static bool view3d_ruler_from_gpencil(bContext *C, RulerInfo *ruler_info)
369 {
370         Scene *scene = CTX_data_scene(C);
371         bool changed = false;
372
373         if (scene->gpd) {
374                 bGPDlayer *gpl;
375                 const char *ruler_name = RULER_ID;
376                 gpl = BLI_findstring(&scene->gpd->layers, ruler_name, offsetof(bGPDlayer, info));
377                 if (gpl) {
378                         bGPDframe *gpf;
379                         gpf = BKE_gpencil_layer_getframe(gpl, CFRA, false);
380                         if (gpf) {
381                                 bGPDstroke *gps;
382                                 for (gps = gpf->strokes.first; gps; gps = gps->next) {
383                                         bGPDspoint *pt = gps->points;
384                                         int j;
385                                         if (gps->totpoints == 3) {
386                                                 RulerItem *ruler_item = ruler_item_add(ruler_info);
387                                                 for (j = 0; j < 3; j++) {
388                                                         copy_v3_v3(ruler_item->co[j], &pt->x);
389                                                         pt++;
390                                                 }
391                                                 ruler_item->flag |= RULERITEM_USE_ANGLE;
392                                                 changed = true;
393                                         }
394                                         else if (gps->totpoints == 2) {
395                                                 RulerItem *ruler_item = ruler_item_add(ruler_info);
396                                                 for (j = 0; j < 3; j += 2) {
397                                                         copy_v3_v3(ruler_item->co[j], &pt->x);
398                                                         pt++;
399                                                 }
400                                                 changed = true;
401                                         }
402                                 }
403                         }
404                 }
405         }
406
407         return changed;
408 }
409
410 /* -------------------------------------------------------------------- */
411 /* local callbacks */
412
413 static void ruler_info_draw_pixel(const struct bContext *C, ARegion *ar, void *arg)
414 {
415         Scene *scene = CTX_data_scene(C);
416         UnitSettings *unit = &scene->unit;
417         RulerItem *ruler_item;
418         RulerInfo *ruler_info = arg;
419         RegionView3D *rv3d = ar->regiondata;
420 //      ARegion *ar = ruler_info->ar;
421         const float cap_size = 4.0f;
422         const float bg_margin = 4.0f * U.pixelsize;
423         const float bg_radius = 4.0f * U.pixelsize;
424         const float arc_size = 64.0f * U.pixelsize;
425 #define ARC_STEPS 24
426         const int arc_steps = ARC_STEPS;
427         int i;
428         //unsigned int color_act = 0x666600;
429         unsigned int color_act = 0xffffff;
430         unsigned int color_base = 0x0;
431         unsigned char color_back[4] = {0xff, 0xff, 0xff, 0x80};
432         unsigned char color_text[3];
433         unsigned char color_wire[3];
434
435         /* anti-aliased lines for more consistent appearance */
436         glEnable(GL_LINE_SMOOTH);
437
438         BLF_enable(blf_mono_font, BLF_ROTATION);
439         BLF_size(blf_mono_font, 14 * U.pixelsize, U.dpi);
440         BLF_rotation(blf_mono_font, 0.0f);
441
442         UI_GetThemeColor3ubv(TH_TEXT, color_text);
443         UI_GetThemeColor3ubv(TH_WIRE, color_wire);
444
445         for (ruler_item = ruler_info->items.first, i = 0; ruler_item; ruler_item = ruler_item->next, i++) {
446                 const bool is_act = (i == ruler_info->item_active);
447                 float dir_ruler[2];
448                 float co_ss[3][2];
449                 int j;
450
451                 /* should these be checked? - ok for now not to */
452                 for (j = 0; j < 3; j++) {
453                         ED_view3d_project_float_global(ar, ruler_item->co[j], co_ss[j], V3D_PROJ_TEST_NOP);
454                 }
455
456                 glEnable(GL_BLEND);
457
458                 cpack(is_act ? color_act : color_base);
459
460                 if (ruler_item->flag & RULERITEM_USE_ANGLE) {
461                         glBegin(GL_LINE_STRIP);
462                         for (j = 0; j < 3; j++) {
463                                 glVertex2fv(co_ss[j]);
464                         }
465                         glEnd();
466                         cpack(0xaaaaaa);
467                         setlinestyle(3);
468                         glBegin(GL_LINE_STRIP);
469                         for (j = 0; j < 3; j++) {
470                                 glVertex2fv(co_ss[j]);
471                         }
472                         glEnd();
473                         setlinestyle(0);
474
475                         /* arc */
476                         {
477                                 float dir_tmp[3];
478                                 float co_tmp[3];
479                                 float arc_ss_coords[ARC_STEPS + 1][2];
480
481                                 float dir_a[3];
482                                 float dir_b[3];
483                                 float quat[4];
484                                 float axis[3];
485                                 float angle;
486                                 const float px_scale = (ED_view3d_pixel_size(rv3d, ruler_item->co[1]) *
487                                                         min_fff(arc_size,
488                                                                 len_v2v2(co_ss[0], co_ss[1]) / 2.0f,
489                                                                 len_v2v2(co_ss[2], co_ss[1]) / 2.0f));
490
491                                 sub_v3_v3v3(dir_a, ruler_item->co[0], ruler_item->co[1]);
492                                 sub_v3_v3v3(dir_b, ruler_item->co[2], ruler_item->co[1]);
493                                 normalize_v3(dir_a);
494                                 normalize_v3(dir_b);
495
496                                 cross_v3_v3v3(axis, dir_a, dir_b);
497                                 angle = angle_normalized_v3v3(dir_a, dir_b);
498
499                                 axis_angle_to_quat(quat, axis, angle / arc_steps);
500
501                                 copy_v3_v3(dir_tmp, dir_a);
502
503                                 glColor3ubv(color_wire);
504
505                                 for (j = 0; j <= arc_steps; j++) {
506                                         madd_v3_v3v3fl(co_tmp, ruler_item->co[1], dir_tmp, px_scale);
507                                         ED_view3d_project_float_global(ar, co_tmp, arc_ss_coords[j], V3D_PROJ_TEST_NOP);
508                                         mul_qt_v3(quat, dir_tmp);
509                                 }
510
511                                 glEnableClientState(GL_VERTEX_ARRAY);
512                                 glVertexPointer(2, GL_FLOAT, 0, arc_ss_coords);
513                                 glDrawArrays(GL_LINE_STRIP, 0, arc_steps + 1);
514                                 glDisableClientState(GL_VERTEX_ARRAY);
515                         }
516
517                         /* text */
518                         {
519                                 char numstr[256];
520                                 float numstr_size[2];
521                                 float pos[2];
522                                 const int prec = 2;  /* XXX, todo, make optional */
523
524                                 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
525
526                                 BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
527
528                                 pos[0] = co_ss[1][0] + (cap_size * 2.0f);
529                                 pos[1] = co_ss[1][1] - (numstr_size[1] / 2.0f);
530
531                                 /* draw text (bg) */
532                                 glColor4ubv(color_back);
533                                 UI_draw_roundbox_corner_set(UI_CNR_ALL);
534                                 UI_draw_roundbox(
535                                         pos[0] - bg_margin,                  pos[1] - bg_margin,
536                                         pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
537                                         bg_radius);
538                                 /* draw text */
539                                 glColor3ubv(color_text);
540                                 BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
541                                 BLF_rotation(blf_mono_font, 0.0f);
542                                 BLF_draw(blf_mono_font, numstr, sizeof(numstr));
543                         }
544
545                         /* capping */
546                         {
547                                 float rot_90_vec_a[2];
548                                 float rot_90_vec_b[2];
549                                 float cap[2];
550
551                                 sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[1]);
552                                 rot_90_vec_a[0] = -dir_ruler[1];
553                                 rot_90_vec_a[1] =  dir_ruler[0];
554                                 normalize_v2(rot_90_vec_a);
555
556                                 sub_v2_v2v2(dir_ruler, co_ss[1], co_ss[2]);
557                                 rot_90_vec_b[0] = -dir_ruler[1];
558                                 rot_90_vec_b[1] =  dir_ruler[0];
559                                 normalize_v2(rot_90_vec_b);
560
561                                 glEnable(GL_BLEND);
562
563                                 glColor3ubv(color_wire);
564
565                                 glBegin(GL_LINES);
566
567                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, cap_size);
568                                 glVertex2fv(cap);
569                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec_a, -cap_size);
570                                 glVertex2fv(cap);
571
572                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, cap_size);
573                                 glVertex2fv(cap);
574                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec_b, -cap_size);
575                                 glVertex2fv(cap);
576
577                                 /* angle vertex */
578                                 glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] - cap_size);
579                                 glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] + cap_size);
580                                 glVertex2f(co_ss[1][0] - cap_size, co_ss[1][1] + cap_size);
581                                 glVertex2f(co_ss[1][0] + cap_size, co_ss[1][1] - cap_size);
582                                 glEnd();
583
584                                 glDisable(GL_BLEND);
585                         }
586                 }
587                 else {
588                         glBegin(GL_LINE_STRIP);
589                         for (j = 0; j < 3; j += 2) {
590                                 glVertex2fv(co_ss[j]);
591                         }
592                         glEnd();
593                         cpack(0xaaaaaa);
594                         setlinestyle(3);
595                         glBegin(GL_LINE_STRIP);
596                         for (j = 0; j < 3; j += 2) {
597                                 glVertex2fv(co_ss[j]);
598                         }
599                         glEnd();
600                         setlinestyle(0);
601
602                         sub_v2_v2v2(dir_ruler, co_ss[0], co_ss[2]);
603
604                         /* text */
605                         {
606                                 char numstr[256];
607                                 float numstr_size[2];
608                                 const int prec = 6;  /* XXX, todo, make optional */
609                                 float pos[2];
610
611                                 ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
612
613                                 BLF_width_and_height(blf_mono_font, numstr, sizeof(numstr), &numstr_size[0], &numstr_size[1]);
614
615                                 mid_v2_v2v2(pos, co_ss[0], co_ss[2]);
616
617                                 /* center text */
618                                 pos[0] -= numstr_size[0] / 2.0f;
619                                 pos[1] -= numstr_size[1] / 2.0f;
620
621                                 /* draw text (bg) */
622                                 glColor4ubv(color_back);
623                                 UI_draw_roundbox_corner_set(UI_CNR_ALL);
624                                 UI_draw_roundbox(pos[0] - bg_margin,                  pos[1] - bg_margin,
625                                            pos[0] + bg_margin + numstr_size[0], pos[1] + bg_margin + numstr_size[1],
626                                            bg_radius);
627                                 /* draw text */
628                                 glColor3ubv(color_text);
629                                 BLF_position(blf_mono_font, pos[0], pos[1], 0.0f);
630                                 BLF_draw(blf_mono_font, numstr, sizeof(numstr));
631                         }
632
633                         /* capping */
634                         {
635                                 float rot_90_vec[2] = {-dir_ruler[1], dir_ruler[0]};
636                                 float cap[2];
637
638                                 normalize_v2(rot_90_vec);
639
640                                 glEnable(GL_BLEND);
641                                 glColor3ubv(color_wire);
642
643                                 glBegin(GL_LINES);
644                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, cap_size);
645                                 glVertex2fv(cap);
646                                 madd_v2_v2v2fl(cap, co_ss[0], rot_90_vec, -cap_size);
647                                 glVertex2fv(cap);
648
649                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, cap_size);
650                                 glVertex2fv(cap);
651                                 madd_v2_v2v2fl(cap, co_ss[2], rot_90_vec, -cap_size);
652                                 glVertex2fv(cap);
653                                 glEnd();
654
655                                 glDisable(GL_BLEND);
656                         }
657                 }
658         }
659
660         glDisable(GL_LINE_SMOOTH);
661
662         BLF_disable(blf_mono_font, BLF_ROTATION);
663
664 #undef ARC_STEPS
665
666         /* draw snap */
667         if ((ruler_info->snap_flag & RULER_SNAP_OK) && (ruler_info->state == RULER_STATE_DRAG)) {
668                 ruler_item = ruler_item_active_get(ruler_info);
669                 if (ruler_item) {
670                         /* size from drawSnapping */
671                         const float size = 2.5f * UI_GetThemeValuef(TH_VERTEX_SIZE);
672                         float co_ss[3];
673                         ED_view3d_project_float_global(ar, ruler_item->co[ruler_item->co_index], co_ss, V3D_PROJ_TEST_NOP);
674
675                         cpack(color_act);
676                         circ(co_ss[0], co_ss[1], size * U.pixelsize);
677                 }
678         }
679
680 }
681
682 /* free, use for both cancel and finish */
683 static void view3d_ruler_end(const struct bContext *UNUSED(C), RulerInfo *ruler_info)
684 {
685         ED_region_draw_cb_exit(ruler_info->ar->type, ruler_info->draw_handle_pixel);
686 }
687
688 static void view3d_ruler_free(RulerInfo *ruler_info)
689 {
690         BLI_freelistN(&ruler_info->items);
691
692         if (ruler_info->snap_context) {
693                 ED_transform_snap_object_context_destroy(ruler_info->snap_context);
694         }
695
696         MEM_freeN(ruler_info);
697 }
698
699 static void view3d_ruler_item_project(RulerInfo *ruler_info, float r_co[3],
700                                       const int xy[2])
701 {
702         ED_view3d_win_to_3d_int(ruler_info->sa->spacedata.first, ruler_info->ar, r_co, xy, r_co);
703 }
704
705 /* use for mousemove events */
706 static bool view3d_ruler_item_mousemove(
707         RulerInfo *ruler_info, const int mval[2],
708         const bool do_thickness, const bool do_snap)
709 {
710         const float eps_bias = 0.0002f;
711         float dist_px = MVAL_MAX_PX_DIST * U.pixelsize;  /* snap dist */
712         RulerItem *ruler_item = ruler_item_active_get(ruler_info);
713
714         ruler_info->snap_flag &= ~RULER_SNAP_OK;
715
716         if (ruler_item) {
717                 float *co = ruler_item->co[ruler_item->co_index];
718                 /* restore the initial depth */
719                 copy_v3_v3(co, ruler_info->drag_start_co);
720                 view3d_ruler_item_project(ruler_info, co, mval);
721                 if (do_thickness && ruler_item->co_index != 1) {
722                         // Scene *scene = CTX_data_scene(C);
723                         // View3D *v3d = ruler_info->sa->spacedata.first;
724                         const float mval_fl[2] = {UNPACK2(mval)};
725                         float ray_normal[3];
726                         float ray_start[3];
727                         float *co_other;
728
729                         co_other = ruler_item->co[ruler_item->co_index == 0 ? 2 : 0];
730
731                         if (ED_transform_snap_object_project_view3d_mixed(
732                                 ruler_info->snap_context,
733                                 SCE_SELECT_FACE,
734                                 &(const struct SnapObjectParams){
735                                     .snap_select = SNAP_ALL,
736                                     .use_object_edit_cage = true,
737                                 },
738                                 mval_fl, &dist_px, true,
739                                 co, ray_normal))
740                         {
741                                 negate_v3(ray_normal);
742                                 /* add some bias */
743                                 madd_v3_v3v3fl(ray_start, co, ray_normal, eps_bias);
744                                 ED_transform_snap_object_project_ray(
745                                         ruler_info->snap_context,
746                                         &(const struct SnapObjectParams){
747                                             .snap_select = SNAP_ALL,
748                                             .use_object_edit_cage = true,
749                                         },
750                                         ray_start, ray_normal, NULL,
751                                         co_other, NULL);
752                         }
753                 }
754                 else if (do_snap) {
755                         // Scene *scene = CTX_data_scene(C);
756                         View3D *v3d = ruler_info->sa->spacedata.first;
757                         const float mval_fl[2] = {UNPACK2(mval)};
758                         bool use_depth = (v3d->drawtype >= OB_SOLID);
759
760                         if (ED_transform_snap_object_project_view3d_mixed(
761                                 ruler_info->snap_context,
762                                 (SCE_SELECT_VERTEX | SCE_SELECT_EDGE) | (use_depth ? SCE_SELECT_FACE : 0),
763                                 &(const struct SnapObjectParams){
764                                     .snap_select = SNAP_ALL,
765                                     .use_object_edit_cage = true,
766                                 },
767                                 mval_fl, &dist_px, use_depth,
768                                 co, NULL))
769                         {
770                                 ruler_info->snap_flag |= RULER_SNAP_OK;
771                         }
772                 }
773                 return true;
774         }
775         else {
776                 return false;
777         }
778 }
779
780 static void view3d_ruler_header_update(ScrArea *sa)
781 {
782         const char *text = IFACE_("Ctrl+LMB: Add, "
783                                   "Del: Remove, "
784                                   "Ctrl+Drag: Snap, "
785                                   "Shift+Drag: Thickness, "
786                                   "Ctrl+C: Copy Value, "
787                                   "Enter: Store,  "
788                                   "Esc: Cancel");
789
790         ED_area_headerprint(sa, text);
791 }
792
793 /* -------------------------------------------------------------------- */
794 /* Operator callbacks */
795
796 static int view3d_ruler_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
797 {
798         wmWindow *win = CTX_wm_window(C);
799         ScrArea *sa = CTX_wm_area(C);
800         ARegion *ar = CTX_wm_region(C);
801         RulerInfo *ruler_info;
802
803         ruler_info = MEM_callocN(sizeof(RulerInfo), "RulerInfo");
804
805         if (view3d_ruler_from_gpencil(C, ruler_info)) {
806                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
807         }
808
809         op->customdata = ruler_info;
810
811         ruler_info->win = win;
812         ruler_info->sa = sa;
813         ruler_info->draw_handle_pixel = ED_region_draw_cb_activate(ar->type, ruler_info_draw_pixel,
814                                                                    ruler_info, REGION_DRAW_POST_PIXEL);
815
816         view3d_ruler_header_update(sa);
817
818         op->flag |= OP_IS_MODAL_CURSOR_REGION;
819
820         WM_cursor_modal_set(win, BC_CROSSCURSOR);
821         WM_event_add_modal_handler(C, op);
822
823         return OPERATOR_RUNNING_MODAL;
824 }
825
826 static void view3d_ruler_cancel(bContext *C, wmOperator *op)
827 {
828         RulerInfo *ruler_info = op->customdata;
829
830         view3d_ruler_end(C, ruler_info);
831         view3d_ruler_free(ruler_info);
832         op->customdata = NULL;
833 }
834
835 static int view3d_ruler_modal(bContext *C, wmOperator *op, const wmEvent *event)
836 {
837         bool do_draw = false;
838         int exit_code = OPERATOR_RUNNING_MODAL;
839         RulerInfo *ruler_info = op->customdata;
840         ScrArea *sa = CTX_wm_area(C);
841         ARegion *ar = CTX_wm_region(C);
842         RegionView3D *rv3d = ar->regiondata;
843
844         /* its possible to change spaces while running the operator [#34894] */
845         if (UNLIKELY(sa != ruler_info->sa)) {
846                 exit_code = OPERATOR_FINISHED;
847                 goto exit;
848         }
849
850         ruler_info->ar = ar;
851
852         switch (event->type) {
853                 case LEFTMOUSE:
854                         if (event->val == KM_RELEASE) {
855                                 if (ruler_info->state == RULER_STATE_DRAG) {
856                                         /* rubber-band angle removal */
857                                         RulerItem *ruler_item = ruler_item_active_get(ruler_info);
858                                         if (ruler_item && (ruler_item->co_index == 1) && (ruler_item->flag & RULERITEM_USE_ANGLE)) {
859                                                 if (!BLI_rcti_isect_pt_v(&ar->winrct, &event->x)) {
860                                                         ruler_item->flag &= ~RULERITEM_USE_ANGLE;
861                                                         do_draw = true;
862                                                 }
863                                         }
864                                         if (ruler_info->snap_flag & RULER_SNAP_OK) {
865                                                 ruler_info->snap_flag &= ~RULER_SNAP_OK;
866                                                 do_draw = true;
867                                         }
868                                         ruler_state_set(C, ruler_info, RULER_STATE_NORMAL);
869                                 }
870                         }
871                         else {
872                                 if (ruler_info->state == RULER_STATE_NORMAL) {
873
874                                         if (event->ctrl ||
875                                             /* weak - but user friendly */
876                                             BLI_listbase_is_empty(&ruler_info->items))
877                                         {
878                                                 View3D *v3d = CTX_wm_view3d(C);
879                                                 const bool use_depth = (v3d->drawtype >= OB_SOLID);
880
881                                                 /* Create new line */
882                                                 RulerItem *ruler_item_prev = ruler_item_active_get(ruler_info);
883                                                 RulerItem *ruler_item;
884                                                 /* check if we want to drag an existing point or add a new one */
885                                                 ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
886
887                                                 ruler_item = ruler_item_add(ruler_info);
888                                                 ruler_item_active_set(ruler_info, ruler_item);
889
890                                                 if (use_depth) {
891                                                         /* snap the first point added, not essential but handy */
892                                                         ruler_item->co_index = 0;
893                                                         view3d_ruler_item_mousemove(ruler_info, event->mval, false, true);
894                                                         copy_v3_v3(ruler_info->drag_start_co, ruler_item->co[ruler_item->co_index]);
895                                                 }
896                                                 else {
897                                                         /* initial depth either previous ruler, view offset */
898                                                         if (ruler_item_prev) {
899                                                                 copy_v3_v3(ruler_info->drag_start_co, ruler_item_prev->co[ruler_item_prev->co_index]);
900                                                         }
901                                                         else {
902                                                                 negate_v3_v3(ruler_info->drag_start_co, rv3d->ofs);
903                                                         }
904
905                                                         copy_v3_v3(ruler_item->co[0], ruler_info->drag_start_co);
906                                                         view3d_ruler_item_project(ruler_info, ruler_item->co[0], event->mval);
907                                                 }
908
909                                                 copy_v3_v3(ruler_item->co[2], ruler_item->co[0]);
910                                                 ruler_item->co_index = 2;
911
912                                                 do_draw = true;
913                                         }
914                                         else {
915                                                 float mval_fl[2] = {UNPACK2(event->mval)};
916                                                 RulerItem *ruler_item_pick;
917                                                 int co_index;
918
919                                                 /* select and drag */
920                                                 if (view3d_ruler_pick(ruler_info, mval_fl, &ruler_item_pick, &co_index)) {
921                                                         if (co_index == -1) {
922                                                                 if ((ruler_item_pick->flag & RULERITEM_USE_ANGLE) == 0) {
923                                                                         /* Add Center Point */
924                                                                         ruler_item_active_set(ruler_info, ruler_item_pick);
925                                                                         ruler_item_pick->flag |= RULERITEM_USE_ANGLE;
926                                                                         ruler_item_pick->co_index = 1;
927                                                                         ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
928
929                                                                         /* find the factor */
930                                                                         {
931                                                                                 float co_ss[2][2];
932                                                                                 float fac;
933
934                                                                                 ED_view3d_project_float_global(ar, ruler_item_pick->co[0], co_ss[0], V3D_PROJ_TEST_NOP);
935                                                                                 ED_view3d_project_float_global(ar, ruler_item_pick->co[2], co_ss[1], V3D_PROJ_TEST_NOP);
936
937                                                                                 fac = line_point_factor_v2(mval_fl, co_ss[0], co_ss[1]);
938                                                                                 CLAMP(fac, 0.0f, 1.0f);
939
940                                                                                 interp_v3_v3v3(ruler_item_pick->co[1],
941                                                                                                ruler_item_pick->co[0],
942                                                                                                ruler_item_pick->co[2], fac);
943                                                                         }
944
945                                                                         /* update the new location */
946                                                                         view3d_ruler_item_mousemove(ruler_info, event->mval,
947                                                                                                     event->shift != 0, event->ctrl != 0);
948                                                                         do_draw = true;
949                                                                 }
950                                                         }
951                                                         else {
952                                                                 ruler_item_active_set(ruler_info, ruler_item_pick);
953                                                                 ruler_item_pick->co_index = co_index;
954                                                                 ruler_state_set(C, ruler_info, RULER_STATE_DRAG);
955
956                                                                 /* store the initial depth */
957                                                                 copy_v3_v3(ruler_info->drag_start_co, ruler_item_pick->co[ruler_item_pick->co_index]);
958
959                                                                 do_draw = true;
960                                                         }
961                                                 }
962                                                 else {
963                                                         exit_code = OPERATOR_PASS_THROUGH;
964                                                 }
965
966                                         }
967                                 }
968                         }
969                         break;
970                 case CKEY:
971                 {
972                         if (event->ctrl) {
973                                 RulerItem *ruler_item = ruler_item_active_get(ruler_info);
974                                 if (ruler_item) {
975                                         const int prec = 8;
976                                         char numstr[256];
977                                         Scene *scene = CTX_data_scene(C);
978                                         UnitSettings *unit = &scene->unit;
979
980                                         ruler_item_as_string(ruler_item, unit, numstr, sizeof(numstr), prec);
981                                         WM_clipboard_text_set((void *) numstr, false);
982                                 }
983                         }
984                         break;
985                 }
986                 case RIGHTCTRLKEY:
987                 case LEFTCTRLKEY:
988                 {
989                         WM_event_add_mousemove(C);
990                         break;
991                 }
992                 case MOUSEMOVE:
993                 {
994                         if (ruler_info->state == RULER_STATE_DRAG) {
995                                 if (view3d_ruler_item_mousemove(ruler_info, event->mval,
996                                                                 event->shift != 0, event->ctrl != 0))
997                                 {
998                                         do_draw = true;
999                                 }
1000                         }
1001                         break;
1002                 }
1003                 case ESCKEY:
1004                 {
1005                         do_draw = true;
1006                         exit_code = OPERATOR_CANCELLED;
1007                         break;
1008                 }
1009                 case RETKEY:
1010                 {
1011                         /* Enter may be used to invoke from search. */
1012                         if (event->val == KM_PRESS) {
1013                                 view3d_ruler_to_gpencil(C, ruler_info);
1014                                 do_draw = true;
1015                                 exit_code = OPERATOR_FINISHED;
1016                         }
1017                         break;
1018                 }
1019                 case DELKEY:
1020                 {
1021                         if (event->val == KM_PRESS) {
1022                                 if (ruler_info->state == RULER_STATE_NORMAL) {
1023                                         RulerItem *ruler_item = ruler_item_active_get(ruler_info);
1024                                         if (ruler_item) {
1025                                                 RulerItem *ruler_item_other = ruler_item->prev ? ruler_item->prev : ruler_item->next;
1026                                                 ruler_item_remove(ruler_info, ruler_item);
1027                                                 ruler_item_active_set(ruler_info, ruler_item_other);
1028                                                 do_draw = true;
1029                                         }
1030                                 }
1031                         }
1032                         break;
1033                 }
1034                 default:
1035                         exit_code = OPERATOR_PASS_THROUGH;
1036                         break;
1037
1038         }
1039
1040         if (ruler_info->state == RULER_STATE_DRAG) {
1041                 op->flag &= ~OP_IS_MODAL_CURSOR_REGION;
1042         }
1043         else {
1044                 op->flag |= OP_IS_MODAL_CURSOR_REGION;
1045         }
1046
1047         if (do_draw) {
1048                 view3d_ruler_header_update(sa);
1049
1050                 /* all 3d views draw rulers */
1051                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL);
1052         }
1053
1054 exit:
1055         if (ELEM(exit_code, OPERATOR_FINISHED, OPERATOR_CANCELLED)) {
1056                 WM_cursor_modal_restore(ruler_info->win);
1057
1058                 view3d_ruler_end(C, ruler_info);
1059                 view3d_ruler_free(ruler_info);
1060                 op->customdata = NULL;
1061
1062                 ED_area_headerprint(sa, NULL);
1063         }
1064
1065         return exit_code;
1066 }
1067
1068 void VIEW3D_OT_ruler(wmOperatorType *ot)
1069 {
1070         /* identifiers */
1071         ot->name = "Ruler/Protractor";
1072         ot->description = "Interactive ruler";
1073         ot->idname = "VIEW3D_OT_ruler";
1074
1075         /* api callbacks */
1076         ot->invoke = view3d_ruler_invoke;
1077         ot->cancel = view3d_ruler_cancel;
1078         ot->modal = view3d_ruler_modal;
1079         ot->poll = ED_operator_view3d_active;
1080
1081         /* flags */
1082         ot->flag = 0;
1083 }