Cleanup: style, use braces for editors
[blender.git] / source / blender / editors / gizmo_library / gizmo_types / cage2d_gizmo.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  * The Original Code is Copyright (C) 2014 Blender Foundation.
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup edgizmolib
22  *
23  * \name Cage Gizmo
24  *
25  * 2D Gizmo
26  *
27  * \brief Rectangular gizmo acting as a 'cage' around its content.
28  * Interacting scales or translates the gizmo.
29  */
30
31 #include "MEM_guardedalloc.h"
32
33 #include "BLI_math.h"
34 #include "BLI_dial_2d.h"
35 #include "BLI_rect.h"
36
37 #include "BKE_context.h"
38
39 #include "GPU_matrix.h"
40 #include "GPU_shader.h"
41 #include "GPU_immediate.h"
42 #include "GPU_immediate_util.h"
43 #include "GPU_select.h"
44 #include "GPU_state.h"
45
46 #include "RNA_access.h"
47 #include "RNA_define.h"
48
49 #include "WM_api.h"
50 #include "WM_types.h"
51
52 #include "ED_screen.h"
53 #include "ED_view3d.h"
54 #include "ED_gizmo_library.h"
55
56 /* own includes */
57 #include "../gizmo_library_intern.h"
58
59 #define GIZMO_RESIZER_SIZE 10.0f
60 #define GIZMO_MARGIN_OFFSET_SCALE 1.5f
61
62 static bool gizmo_calc_rect_view_scale(const wmGizmo *gz, const float dims[2], float scale[2])
63 {
64   float matrix_final_no_offset[4][4];
65   float asp[2] = {1.0f, 1.0f};
66   if (dims[0] > dims[1]) {
67     asp[0] = dims[1] / dims[0];
68   }
69   else {
70     asp[1] = dims[0] / dims[1];
71   }
72   float x_axis[3], y_axis[3];
73   WM_gizmo_calc_matrix_final_no_offset(gz, matrix_final_no_offset);
74   mul_v3_mat3_m4v3(x_axis, matrix_final_no_offset, gz->matrix_offset[0]);
75   mul_v3_mat3_m4v3(y_axis, matrix_final_no_offset, gz->matrix_offset[1]);
76
77   mul_v2_v2(x_axis, asp);
78   mul_v2_v2(y_axis, asp);
79
80   float len_x_axis = len_v3(x_axis);
81   float len_y_axis = len_v3(y_axis);
82
83   if (len_x_axis == 0.0f || len_y_axis == 0.0f) {
84     return false;
85   }
86
87   scale[0] = 1.0f / len_x_axis;
88   scale[1] = 1.0f / len_y_axis;
89   return true;
90 }
91
92 static bool gizmo_calc_rect_view_margin(const wmGizmo *gz, const float dims[2], float margin[2])
93 {
94   float handle_size;
95   if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
96     handle_size = 0.15f;
97   }
98   else {
99     handle_size = GIZMO_RESIZER_SIZE;
100   }
101   handle_size *= gz->scale_final;
102   float scale_xy[2];
103   if (!gizmo_calc_rect_view_scale(gz, dims, scale_xy)) {
104     return false;
105   }
106   margin[0] = ((handle_size * scale_xy[0]));
107   margin[1] = ((handle_size * scale_xy[1]));
108   return true;
109 }
110
111 /* -------------------------------------------------------------------- */
112
113 static void gizmo_rect_pivot_from_scale_part(int part, float r_pt[2], bool r_constrain_axis[2])
114 {
115   bool x = true, y = true;
116   switch (part) {
117     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X: {
118       ARRAY_SET_ITEMS(r_pt, 0.5, 0.0);
119       x = false;
120       break;
121     }
122     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X: {
123       ARRAY_SET_ITEMS(r_pt, -0.5, 0.0);
124       x = false;
125       break;
126     }
127     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y: {
128       ARRAY_SET_ITEMS(r_pt, 0.0, 0.5);
129       y = false;
130       break;
131     }
132     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y: {
133       ARRAY_SET_ITEMS(r_pt, 0.0, -0.5);
134       y = false;
135       break;
136     }
137     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y: {
138       ARRAY_SET_ITEMS(r_pt, 0.5, 0.5);
139       x = y = false;
140       break;
141     }
142     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y: {
143       ARRAY_SET_ITEMS(r_pt, 0.5, -0.5);
144       x = y = false;
145       break;
146     }
147     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y: {
148       ARRAY_SET_ITEMS(r_pt, -0.5, 0.5);
149       x = y = false;
150       break;
151     }
152     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y: {
153       ARRAY_SET_ITEMS(r_pt, -0.5, -0.5);
154       x = y = false;
155       break;
156     }
157     default:
158       BLI_assert(0);
159   }
160   r_constrain_axis[0] = x;
161   r_constrain_axis[1] = y;
162 }
163
164 /* -------------------------------------------------------------------- */
165 /** \name Box Draw Style
166  *
167  * Useful for 3D views, see: #ED_GIZMO_CAGE2D_STYLE_BOX
168  * \{ */
169
170 static void cage2d_draw_box_corners(const rctf *r, const float margin[2], const float color[3])
171 {
172   uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
173
174   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
175   immUniformColor3fv(color);
176
177   immBegin(GPU_PRIM_LINES, 16);
178
179   immVertex2f(pos, r->xmin, r->ymin + margin[1]);
180   immVertex2f(pos, r->xmin, r->ymin);
181   immVertex2f(pos, r->xmin, r->ymin);
182   immVertex2f(pos, r->xmin + margin[0], r->ymin);
183
184   immVertex2f(pos, r->xmax, r->ymin + margin[1]);
185   immVertex2f(pos, r->xmax, r->ymin);
186   immVertex2f(pos, r->xmax, r->ymin);
187   immVertex2f(pos, r->xmax - margin[0], r->ymin);
188
189   immVertex2f(pos, r->xmax, r->ymax - margin[1]);
190   immVertex2f(pos, r->xmax, r->ymax);
191   immVertex2f(pos, r->xmax, r->ymax);
192   immVertex2f(pos, r->xmax - margin[0], r->ymax);
193
194   immVertex2f(pos, r->xmin, r->ymax - margin[1]);
195   immVertex2f(pos, r->xmin, r->ymax);
196   immVertex2f(pos, r->xmin, r->ymax);
197   immVertex2f(pos, r->xmin + margin[0], r->ymax);
198
199   immEnd();
200
201   immUnbindProgram();
202 }
203
204 static void cage2d_draw_box_interaction(const float color[4],
205                                         const int highlighted,
206                                         const float size[2],
207                                         const float margin[2],
208                                         const float line_width,
209                                         const bool is_solid,
210                                         const int draw_options)
211 {
212   /* 4 verts for translate, otherwise only 3 are used. */
213   float verts[4][2];
214   uint verts_len = 0;
215   GPUPrimType prim_type = GPU_PRIM_NONE;
216
217   switch (highlighted) {
218     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X: {
219       rctf r = {
220           .xmin = -size[0],
221           .xmax = -size[0] + margin[0],
222           .ymin = -size[1] + margin[1],
223           .ymax = size[1] - margin[1],
224       };
225       ARRAY_SET_ITEMS(verts[0], r.xmin, r.ymin);
226       ARRAY_SET_ITEMS(verts[1], r.xmin, r.ymax);
227       verts_len = 2;
228       if (is_solid) {
229         ARRAY_SET_ITEMS(verts[2], r.xmax, r.ymax);
230         ARRAY_SET_ITEMS(verts[3], r.xmax, r.ymin);
231         verts_len += 2;
232         prim_type = GPU_PRIM_TRI_FAN;
233       }
234       else {
235         prim_type = GPU_PRIM_LINE_STRIP;
236       }
237       break;
238     }
239     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X: {
240       rctf r = {
241           .xmin = size[0] - margin[0],
242           .xmax = size[0],
243           .ymin = -size[1] + margin[1],
244           .ymax = size[1] - margin[1],
245       };
246       ARRAY_SET_ITEMS(verts[0], r.xmax, r.ymin);
247       ARRAY_SET_ITEMS(verts[1], r.xmax, r.ymax);
248       verts_len = 2;
249       if (is_solid) {
250         ARRAY_SET_ITEMS(verts[2], r.xmin, r.ymax);
251         ARRAY_SET_ITEMS(verts[3], r.xmin, r.ymin);
252         verts_len += 2;
253         prim_type = GPU_PRIM_TRI_FAN;
254       }
255       else {
256         prim_type = GPU_PRIM_LINE_STRIP;
257       }
258       break;
259     }
260     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y: {
261       rctf r = {
262           .xmin = -size[0] + margin[0],
263           .xmax = size[0] - margin[0],
264           .ymin = -size[1],
265           .ymax = -size[1] + margin[1],
266       };
267       ARRAY_SET_ITEMS(verts[0], r.xmin, r.ymin);
268       ARRAY_SET_ITEMS(verts[1], r.xmax, r.ymin);
269       verts_len = 2;
270       if (is_solid) {
271         ARRAY_SET_ITEMS(verts[2], r.xmax, r.ymax);
272         ARRAY_SET_ITEMS(verts[3], r.xmin, r.ymax);
273         verts_len += 2;
274         prim_type = GPU_PRIM_TRI_FAN;
275       }
276       else {
277         prim_type = GPU_PRIM_LINE_STRIP;
278       }
279       break;
280     }
281     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y: {
282       rctf r = {
283           .xmin = -size[0] + margin[0],
284           .xmax = size[0] - margin[0],
285           .ymin = size[1] - margin[1],
286           .ymax = size[1],
287       };
288       ARRAY_SET_ITEMS(verts[0], r.xmin, r.ymax);
289       ARRAY_SET_ITEMS(verts[1], r.xmax, r.ymax);
290       verts_len = 2;
291       if (is_solid) {
292         ARRAY_SET_ITEMS(verts[2], r.xmax, r.ymin);
293         ARRAY_SET_ITEMS(verts[3], r.xmin, r.ymin);
294         verts_len += 2;
295         prim_type = GPU_PRIM_TRI_FAN;
296       }
297       else {
298         prim_type = GPU_PRIM_LINE_STRIP;
299       }
300       break;
301     }
302     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y: {
303       rctf r = {
304           .xmin = -size[0],
305           .xmax = -size[0] + margin[0],
306           .ymin = -size[1],
307           .ymax = -size[1] + margin[1],
308       };
309       ARRAY_SET_ITEMS(verts[0], r.xmax, r.ymin);
310       ARRAY_SET_ITEMS(verts[1], r.xmax, r.ymax);
311       ARRAY_SET_ITEMS(verts[2], r.xmin, r.ymax);
312       verts_len = 3;
313       if (is_solid) {
314         ARRAY_SET_ITEMS(verts[3], r.xmin, r.ymin);
315         verts_len += 1;
316         prim_type = GPU_PRIM_TRI_FAN;
317       }
318       else {
319         prim_type = GPU_PRIM_LINE_STRIP;
320       }
321       break;
322     }
323     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y: {
324       rctf r = {
325           .xmin = -size[0],
326           .xmax = -size[0] + margin[0],
327           .ymin = size[1] - margin[1],
328           .ymax = size[1],
329       };
330       ARRAY_SET_ITEMS(verts[0], r.xmax, r.ymax);
331       ARRAY_SET_ITEMS(verts[1], r.xmax, r.ymin);
332       ARRAY_SET_ITEMS(verts[2], r.xmin, r.ymin);
333       verts_len = 3;
334       if (is_solid) {
335         ARRAY_SET_ITEMS(verts[3], r.xmin, r.ymax);
336         verts_len += 1;
337         prim_type = GPU_PRIM_TRI_FAN;
338       }
339       else {
340         prim_type = GPU_PRIM_LINE_STRIP;
341       }
342       break;
343     }
344     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y: {
345       rctf r = {
346           .xmin = size[0] - margin[0],
347           .xmax = size[0],
348           .ymin = -size[1],
349           .ymax = -size[1] + margin[1],
350       };
351       ARRAY_SET_ITEMS(verts[0], r.xmin, r.ymin);
352       ARRAY_SET_ITEMS(verts[1], r.xmin, r.ymax);
353       ARRAY_SET_ITEMS(verts[2], r.xmax, r.ymax);
354       verts_len = 3;
355       if (is_solid) {
356         ARRAY_SET_ITEMS(verts[3], r.xmax, r.ymin);
357         verts_len += 1;
358         prim_type = GPU_PRIM_TRI_FAN;
359       }
360       else {
361         prim_type = GPU_PRIM_LINE_STRIP;
362       }
363       break;
364     }
365     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y: {
366       rctf r = {
367           .xmin = size[0] - margin[0],
368           .xmax = size[0],
369           .ymin = size[1] - margin[1],
370           .ymax = size[1],
371       };
372       ARRAY_SET_ITEMS(verts[0], r.xmin, r.ymax);
373       ARRAY_SET_ITEMS(verts[1], r.xmin, r.ymin);
374       ARRAY_SET_ITEMS(verts[2], r.xmax, r.ymin);
375       verts_len = 3;
376       if (is_solid) {
377         ARRAY_SET_ITEMS(verts[3], r.xmax, r.ymax);
378         verts_len += 1;
379         prim_type = GPU_PRIM_TRI_FAN;
380       }
381       else {
382         prim_type = GPU_PRIM_LINE_STRIP;
383       }
384       break;
385     }
386     case ED_GIZMO_CAGE2D_PART_ROTATE: {
387       const float rotate_pt[2] = {0.0f, size[1] + margin[1]};
388       const rctf r_rotate = {
389           .xmin = rotate_pt[0] - margin[0] / 2.0f,
390           .xmax = rotate_pt[0] + margin[0] / 2.0f,
391           .ymin = rotate_pt[1] - margin[1] / 2.0f,
392           .ymax = rotate_pt[1] + margin[1] / 2.0f,
393       };
394
395       ARRAY_SET_ITEMS(verts[0], r_rotate.xmin, r_rotate.ymin);
396       ARRAY_SET_ITEMS(verts[1], r_rotate.xmin, r_rotate.ymax);
397       ARRAY_SET_ITEMS(verts[2], r_rotate.xmax, r_rotate.ymax);
398       ARRAY_SET_ITEMS(verts[3], r_rotate.xmax, r_rotate.ymin);
399       verts_len = 4;
400       if (is_solid) {
401         prim_type = GPU_PRIM_TRI_FAN;
402       }
403       else {
404         prim_type = GPU_PRIM_LINE_STRIP;
405       }
406       break;
407     }
408
409     case ED_GIZMO_CAGE2D_PART_TRANSLATE:
410       if (draw_options & ED_GIZMO_CAGE2D_DRAW_FLAG_XFORM_CENTER_HANDLE) {
411         ARRAY_SET_ITEMS(verts[0], -margin[0] / 2, -margin[1] / 2);
412         ARRAY_SET_ITEMS(verts[1], margin[0] / 2, margin[1] / 2);
413         ARRAY_SET_ITEMS(verts[2], -margin[0] / 2, margin[1] / 2);
414         ARRAY_SET_ITEMS(verts[3], margin[0] / 2, -margin[1] / 2);
415         verts_len = 4;
416         if (is_solid) {
417           prim_type = GPU_PRIM_TRI_FAN;
418         }
419         else {
420           prim_type = GPU_PRIM_LINES;
421         }
422       }
423       else {
424         /* Only used for 3D view selection, never displayed to the user. */
425         ARRAY_SET_ITEMS(verts[0], -size[0], -size[1]);
426         ARRAY_SET_ITEMS(verts[1], -size[0], size[1]);
427         ARRAY_SET_ITEMS(verts[2], size[0], size[1]);
428         ARRAY_SET_ITEMS(verts[3], size[0], -size[1]);
429         verts_len = 4;
430         if (is_solid) {
431           prim_type = GPU_PRIM_TRI_FAN;
432         }
433         else {
434           /* unreachable */
435           BLI_assert(0);
436           prim_type = GPU_PRIM_LINE_STRIP;
437         }
438       }
439       break;
440     default:
441       return;
442   }
443
444   BLI_assert(prim_type != GPU_PRIM_NONE);
445
446   GPUVertFormat *format = immVertexFormat();
447   struct {
448     uint pos, col;
449   } attr_id = {
450       .pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT),
451       .col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT),
452   };
453   immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
454
455   {
456     if (is_solid) {
457       BLI_assert(ELEM(prim_type, GPU_PRIM_TRI_FAN));
458       immBegin(prim_type, verts_len);
459       immAttr3f(attr_id.col, 0.0f, 0.0f, 0.0f);
460       for (uint i = 0; i < verts_len; i++) {
461         immVertex2fv(attr_id.pos, verts[i]);
462       }
463       immEnd();
464     }
465     else {
466       BLI_assert(ELEM(prim_type, GPU_PRIM_LINE_STRIP, GPU_PRIM_LINES));
467       GPU_line_width(line_width + 3.0f);
468
469       immBegin(prim_type, verts_len);
470       immAttr3f(attr_id.col, 0.0f, 0.0f, 0.0f);
471       for (uint i = 0; i < verts_len; i++) {
472         immVertex2fv(attr_id.pos, verts[i]);
473       }
474       immEnd();
475
476       GPU_line_width(line_width);
477
478       immBegin(prim_type, verts_len);
479       immAttr3fv(attr_id.col, color);
480       for (uint i = 0; i < verts_len; i++) {
481         immVertex2fv(attr_id.pos, verts[i]);
482       }
483       immEnd();
484     }
485   }
486
487   immUnbindProgram();
488 }
489
490 /** \} */
491
492 /* -------------------------------------------------------------------- */
493 /** \name Circle Draw Style
494  *
495  * Useful for 2D views, see: #ED_GIZMO_CAGE2D_STYLE_CIRCLE
496  * \{ */
497
498 static void imm_draw_point_aspect_2d(
499     uint pos, float x, float y, float rad_x, float rad_y, bool solid)
500 {
501   immBegin(solid ? GPU_PRIM_TRI_FAN : GPU_PRIM_LINE_LOOP, 4);
502   immVertex2f(pos, x - rad_x, y - rad_y);
503   immVertex2f(pos, x - rad_x, y + rad_y);
504   immVertex2f(pos, x + rad_x, y + rad_y);
505   immVertex2f(pos, x + rad_x, y - rad_y);
506   immEnd();
507 }
508
509 static void cage2d_draw_circle_wire(const rctf *r,
510                                     const float margin[2],
511                                     const float color[3],
512                                     const int transform_flag,
513                                     const int draw_options)
514 {
515   uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
516
517   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
518   immUniformColor3fv(color);
519
520   immBegin(GPU_PRIM_LINE_LOOP, 4);
521   immVertex2f(pos, r->xmin, r->ymin);
522   immVertex2f(pos, r->xmax, r->ymin);
523   immVertex2f(pos, r->xmax, r->ymax);
524   immVertex2f(pos, r->xmin, r->ymax);
525   immEnd();
526
527   if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE) {
528     immBegin(GPU_PRIM_LINE_LOOP, 2);
529     immVertex2f(pos, BLI_rctf_cent_x(r), r->ymax);
530     immVertex2f(pos, BLI_rctf_cent_x(r), r->ymax + margin[1]);
531     immEnd();
532   }
533
534   if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE) {
535     if (draw_options & ED_GIZMO_CAGE2D_DRAW_FLAG_XFORM_CENTER_HANDLE) {
536       const float rad[2] = {margin[0] / 2, margin[1] / 2};
537       const float center[2] = {BLI_rctf_cent_x(r), BLI_rctf_cent_y(r)};
538
539       immBegin(GPU_PRIM_LINES, 4);
540       immVertex2f(pos, center[0] - rad[0], center[1] - rad[1]);
541       immVertex2f(pos, center[0] + rad[0], center[1] + rad[1]);
542       immVertex2f(pos, center[0] + rad[0], center[1] - rad[1]);
543       immVertex2f(pos, center[0] - rad[0], center[1] + rad[1]);
544       immEnd();
545     }
546   }
547
548   immUnbindProgram();
549 }
550
551 static void cage2d_draw_circle_handles(const rctf *r,
552                                        const float margin[2],
553                                        const float color[3],
554                                        const int transform_flag,
555                                        bool solid)
556 {
557   uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
558   void (*circle_fn)(uint, float, float, float, float, int) = (solid) ?
559                                                                  imm_draw_circle_fill_aspect_2d :
560                                                                  imm_draw_circle_wire_aspect_2d;
561   const int resolu = 12;
562   const float rad[2] = {margin[0] / 3, margin[1] / 3};
563
564   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
565   immUniformColor3fv(color);
566
567   /* should  really divide by two, but looks too bulky. */
568   {
569     imm_draw_point_aspect_2d(pos, r->xmin, r->ymin, rad[0], rad[1], solid);
570     imm_draw_point_aspect_2d(pos, r->xmax, r->ymin, rad[0], rad[1], solid);
571     imm_draw_point_aspect_2d(pos, r->xmax, r->ymax, rad[0], rad[1], solid);
572     imm_draw_point_aspect_2d(pos, r->xmin, r->ymax, rad[0], rad[1], solid);
573   }
574
575   if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE) {
576     const float handle[2] = {
577         BLI_rctf_cent_x(r),
578         r->ymax + (margin[1] * GIZMO_MARGIN_OFFSET_SCALE),
579     };
580     circle_fn(pos, handle[0], handle[1], rad[0], rad[1], resolu);
581   }
582
583   immUnbindProgram();
584 }
585
586 /** \} */
587
588 static void gizmo_cage2d_draw_intern(wmGizmo *gz,
589                                      const bool select,
590                                      const bool highlight,
591                                      const int select_id)
592 {
593   // const bool use_clamp = (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) == 0;
594   float dims[2];
595   RNA_float_get_array(gz->ptr, "dimensions", dims);
596   float matrix_final[4][4];
597
598   const int transform_flag = RNA_enum_get(gz->ptr, "transform");
599   const int draw_style = RNA_enum_get(gz->ptr, "draw_style");
600   const int draw_options = RNA_enum_get(gz->ptr, "draw_options");
601
602   const float size_real[2] = {dims[0] / 2.0f, dims[1] / 2.0f};
603
604   WM_gizmo_calc_matrix_final(gz, matrix_final);
605
606   GPU_matrix_push();
607   GPU_matrix_mul(matrix_final);
608
609   float margin[2];
610   gizmo_calc_rect_view_margin(gz, dims, margin);
611
612   /* Handy for quick testing draw (if it's outside bounds). */
613   if (false) {
614     GPU_blend(true);
615     uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
616     immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
617     immUniformColor4fv((const float[4]){1, 1, 1, 0.5f});
618     float s = 0.5f;
619     immRectf(pos, -s, -s, s, s);
620     immUnbindProgram();
621     GPU_blend(false);
622   }
623
624   if (select) {
625     /* expand for hotspot */
626     const float size[2] = {size_real[0] + margin[0] / 2, size_real[1] + margin[1] / 2};
627
628     if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE) {
629       int scale_parts[] = {
630           ED_GIZMO_CAGE2D_PART_SCALE_MIN_X,
631           ED_GIZMO_CAGE2D_PART_SCALE_MAX_X,
632           ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y,
633           ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y,
634
635           ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y,
636           ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y,
637           ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y,
638           ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y,
639       };
640       for (int i = 0; i < ARRAY_SIZE(scale_parts); i++) {
641         GPU_select_load_id(select_id | scale_parts[i]);
642         cage2d_draw_box_interaction(
643             gz->color, scale_parts[i], size, margin, gz->line_width, true, draw_options);
644       }
645     }
646     if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE) {
647       const int transform_part = ED_GIZMO_CAGE2D_PART_TRANSLATE;
648       GPU_select_load_id(select_id | transform_part);
649       cage2d_draw_box_interaction(
650           gz->color, transform_part, size, margin, gz->line_width, true, draw_options);
651     }
652     if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE) {
653       cage2d_draw_box_interaction(gz->color,
654                                   ED_GIZMO_CAGE2D_PART_ROTATE,
655                                   size_real,
656                                   margin,
657                                   gz->line_width,
658                                   true,
659                                   draw_options);
660     }
661   }
662   else {
663     const rctf r = {
664         .xmin = -size_real[0],
665         .ymin = -size_real[1],
666         .xmax = size_real[0],
667         .ymax = size_real[1],
668     };
669     if (draw_style == ED_GIZMO_CAGE2D_STYLE_BOX) {
670       /* corner gizmos */
671       GPU_line_width(gz->line_width + 3.0f);
672       cage2d_draw_box_corners(&r, margin, (const float[3]){0, 0, 0});
673
674       /* corner gizmos */
675       float color[4];
676       gizmo_color_get(gz, highlight, color);
677       GPU_line_width(gz->line_width);
678       cage2d_draw_box_corners(&r, margin, color);
679
680       bool show = false;
681       if (gz->highlight_part == ED_GIZMO_CAGE2D_PART_TRANSLATE) {
682         /* Only show if we're drawing the center handle
683          * otherwise the entire rectangle is the hotspot. */
684         if (draw_options & ED_GIZMO_CAGE2D_DRAW_FLAG_XFORM_CENTER_HANDLE) {
685           show = true;
686         }
687       }
688       else {
689         show = true;
690       }
691
692       if (show) {
693         cage2d_draw_box_interaction(
694             gz->color, gz->highlight_part, size_real, margin, gz->line_width, false, draw_options);
695       }
696
697       if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE) {
698         cage2d_draw_box_interaction(gz->color,
699                                     ED_GIZMO_CAGE2D_PART_ROTATE,
700                                     size_real,
701                                     margin,
702                                     gz->line_width,
703                                     false,
704                                     draw_options);
705       }
706     }
707     else if (draw_style == ED_GIZMO_CAGE2D_STYLE_CIRCLE) {
708       float color[4];
709       gizmo_color_get(gz, highlight, color);
710
711       GPU_line_smooth(true);
712       GPU_blend(true);
713
714       GPU_line_width(gz->line_width + 3.0f);
715       cage2d_draw_circle_wire(&r, margin, (const float[3]){0, 0, 0}, transform_flag, draw_options);
716       GPU_line_width(gz->line_width);
717       cage2d_draw_circle_wire(&r, margin, color, transform_flag, draw_options);
718
719       /* corner gizmos */
720       cage2d_draw_circle_handles(&r, margin, color, transform_flag, true);
721       cage2d_draw_circle_handles(&r, margin, (const float[3]){0, 0, 0}, transform_flag, false);
722
723       GPU_blend(false);
724       GPU_line_smooth(false);
725     }
726     else {
727       BLI_assert(0);
728     }
729   }
730
731   GPU_line_width(1.0);
732   GPU_matrix_pop();
733 }
734
735 /**
736  * For when we want to draw 2d cage in 3d views.
737  */
738 static void gizmo_cage2d_draw_select(const bContext *UNUSED(C), wmGizmo *gz, int select_id)
739 {
740   gizmo_cage2d_draw_intern(gz, true, false, select_id);
741 }
742
743 static void gizmo_cage2d_draw(const bContext *UNUSED(C), wmGizmo *gz)
744 {
745   const bool is_highlight = (gz->state & WM_GIZMO_STATE_HIGHLIGHT) != 0;
746   gizmo_cage2d_draw_intern(gz, false, is_highlight, -1);
747 }
748
749 static int gizmo_cage2d_get_cursor(wmGizmo *gz)
750 {
751   int highlight_part = gz->highlight_part;
752
753   if (gz->parent_gzgroup->type->flag & WM_GIZMOGROUPTYPE_3D) {
754     return BC_NSEW_SCROLLCURSOR;
755   }
756
757   switch (highlight_part) {
758     case ED_GIZMO_CAGE2D_PART_TRANSLATE:
759       return BC_NSEW_SCROLLCURSOR;
760     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X:
761     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X:
762       return CURSOR_X_MOVE;
763     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y:
764     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y:
765       return CURSOR_Y_MOVE;
766
767       /* TODO diagonal cursor */
768     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y:
769     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y:
770       return BC_NSEW_SCROLLCURSOR;
771     case ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y:
772     case ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y:
773       return BC_NSEW_SCROLLCURSOR;
774     case ED_GIZMO_CAGE2D_PART_ROTATE:
775       return BC_CROSSCURSOR;
776     default:
777       return CURSOR_STD;
778   }
779 }
780
781 static int gizmo_cage2d_test_select(bContext *C, wmGizmo *gz, const int mval[2])
782 {
783   float point_local[2];
784   float dims[2];
785   RNA_float_get_array(gz->ptr, "dimensions", dims);
786   const float size_real[2] = {dims[0] / 2.0f, dims[1] / 2.0f};
787
788   if (gizmo_window_project_2d(C, gz, (const float[2]){UNPACK2(mval)}, 2, true, point_local) ==
789       false) {
790     return -1;
791   }
792
793   float margin[2];
794   if (!gizmo_calc_rect_view_margin(gz, dims, margin)) {
795     return -1;
796   }
797
798   /* expand for hotspot */
799   const float size[2] = {size_real[0] + margin[0] / 2, size_real[1] + margin[1] / 2};
800
801   const int transform_flag = RNA_enum_get(gz->ptr, "transform");
802   const int draw_options = RNA_enum_get(gz->ptr, "draw_options");
803
804   if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE) {
805     rctf r;
806     if (draw_options & ED_GIZMO_CAGE2D_DRAW_FLAG_XFORM_CENTER_HANDLE) {
807       r.xmin = -margin[0] / 2;
808       r.ymin = -margin[1] / 2;
809       r.xmax = margin[0] / 2;
810       r.ymax = margin[1] / 2;
811     }
812     else {
813       r.xmin = -size[0] + margin[0];
814       r.ymin = -size[1] + margin[1];
815       r.xmax = size[0] - margin[0];
816       r.ymax = size[1] - margin[1];
817     }
818     bool isect = BLI_rctf_isect_pt_v(&r, point_local);
819     if (isect) {
820       return ED_GIZMO_CAGE2D_PART_TRANSLATE;
821     }
822   }
823
824   /* if gizmo does not have a scale intersection, don't do it */
825   if (transform_flag &
826       (ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE | ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE_UNIFORM)) {
827     const rctf r_xmin = {
828         .xmin = -size[0],
829         .ymin = -size[1],
830         .xmax = -size[0] + margin[0],
831         .ymax = size[1],
832     };
833     const rctf r_xmax = {
834         .xmin = size[0] - margin[0],
835         .ymin = -size[1],
836         .xmax = size[0],
837         .ymax = size[1],
838     };
839     const rctf r_ymin = {
840         .xmin = -size[0],
841         .ymin = -size[1],
842         .xmax = size[0],
843         .ymax = -size[1] + margin[1],
844     };
845     const rctf r_ymax = {
846         .xmin = -size[0],
847         .ymin = size[1] - margin[1],
848         .xmax = size[0],
849         .ymax = size[1],
850     };
851
852     if (BLI_rctf_isect_pt_v(&r_xmin, point_local)) {
853       if (BLI_rctf_isect_pt_v(&r_ymin, point_local)) {
854         return ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y;
855       }
856       if (BLI_rctf_isect_pt_v(&r_ymax, point_local)) {
857         return ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y;
858       }
859       return ED_GIZMO_CAGE2D_PART_SCALE_MIN_X;
860     }
861     if (BLI_rctf_isect_pt_v(&r_xmax, point_local)) {
862       if (BLI_rctf_isect_pt_v(&r_ymin, point_local)) {
863         return ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y;
864       }
865       if (BLI_rctf_isect_pt_v(&r_ymax, point_local)) {
866         return ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y;
867       }
868       return ED_GIZMO_CAGE2D_PART_SCALE_MAX_X;
869     }
870     if (BLI_rctf_isect_pt_v(&r_ymin, point_local)) {
871       return ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y;
872     }
873     if (BLI_rctf_isect_pt_v(&r_ymax, point_local)) {
874       return ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y;
875     }
876   }
877
878   if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE) {
879     /* Rotate:
880      *  (*) <-- hot spot is here!
881      * +---+
882      * |   |
883      * +---+ */
884     const float r_rotate_pt[2] = {0.0f, size_real[1] + (margin[1] * GIZMO_MARGIN_OFFSET_SCALE)};
885     const rctf r_rotate = {
886         .xmin = r_rotate_pt[0] - margin[0] / 2.0f,
887         .xmax = r_rotate_pt[0] + margin[0] / 2.0f,
888         .ymin = r_rotate_pt[1] - margin[1] / 2.0f,
889         .ymax = r_rotate_pt[1] + margin[1] / 2.0f,
890     };
891
892     if (BLI_rctf_isect_pt_v(&r_rotate, point_local)) {
893       return ED_GIZMO_CAGE2D_PART_ROTATE;
894     }
895   }
896
897   return -1;
898 }
899
900 typedef struct RectTransformInteraction {
901   float orig_mouse[2];
902   float orig_matrix_offset[4][4];
903   float orig_matrix_final_no_offset[4][4];
904   Dial *dial;
905 } RectTransformInteraction;
906
907 static void gizmo_cage2d_setup(wmGizmo *gz)
908 {
909   gz->flag |= WM_GIZMO_DRAW_MODAL | WM_GIZMO_DRAW_NO_SCALE;
910 }
911
912 static int gizmo_cage2d_invoke(bContext *C, wmGizmo *gz, const wmEvent *event)
913 {
914   RectTransformInteraction *data = MEM_callocN(sizeof(RectTransformInteraction),
915                                                "cage_interaction");
916
917   copy_m4_m4(data->orig_matrix_offset, gz->matrix_offset);
918   WM_gizmo_calc_matrix_final_no_offset(gz, data->orig_matrix_final_no_offset);
919
920   if (gizmo_window_project_2d(
921           C, gz, (const float[2]){UNPACK2(event->mval)}, 2, false, data->orig_mouse) == 0) {
922     zero_v2(data->orig_mouse);
923   }
924
925   gz->interaction_data = data;
926
927   return OPERATOR_RUNNING_MODAL;
928 }
929
930 static int gizmo_cage2d_modal(bContext *C,
931                               wmGizmo *gz,
932                               const wmEvent *event,
933                               eWM_GizmoFlagTweak UNUSED(tweak_flag))
934 {
935   if (event->type != MOUSEMOVE) {
936     return OPERATOR_RUNNING_MODAL;
937   }
938   /* For transform logic to be manageable we operate in -0.5..0.5 2D space,
939    * no matter the size of the rectangle, mouse coords are scaled to unit space.
940    * The mouse coords have been projected into the matrix
941    * so we don't need to worry about axis alignment.
942    *
943    * - The cursor offset are multiplied by 'dims'.
944    * - Matrix translation is also multiplied by 'dims'.
945    */
946   RectTransformInteraction *data = gz->interaction_data;
947   float point_local[2];
948
949   float dims[2];
950   RNA_float_get_array(gz->ptr, "dimensions", dims);
951
952   {
953     float matrix_back[4][4];
954     copy_m4_m4(matrix_back, gz->matrix_offset);
955     copy_m4_m4(gz->matrix_offset, data->orig_matrix_offset);
956
957     bool ok = gizmo_window_project_2d(
958         C, gz, (const float[2]){UNPACK2(event->mval)}, 2, false, point_local);
959     copy_m4_m4(gz->matrix_offset, matrix_back);
960     if (!ok) {
961       return OPERATOR_RUNNING_MODAL;
962     }
963   }
964
965   const int transform_flag = RNA_enum_get(gz->ptr, "transform");
966   wmGizmoProperty *gz_prop;
967
968   gz_prop = WM_gizmo_target_property_find(gz, "matrix");
969   if (gz_prop->type != NULL) {
970     WM_gizmo_target_property_float_get_array(gz, gz_prop, &gz->matrix_offset[0][0]);
971   }
972
973   if (gz->highlight_part == ED_GIZMO_CAGE2D_PART_TRANSLATE) {
974     /* do this to prevent clamping from changing size */
975     copy_m4_m4(gz->matrix_offset, data->orig_matrix_offset);
976     gz->matrix_offset[3][0] = data->orig_matrix_offset[3][0] +
977                               (point_local[0] - data->orig_mouse[0]);
978     gz->matrix_offset[3][1] = data->orig_matrix_offset[3][1] +
979                               (point_local[1] - data->orig_mouse[1]);
980   }
981   else if (gz->highlight_part == ED_GIZMO_CAGE2D_PART_ROTATE) {
982
983 #define MUL_V2_V3_M4_FINAL(test_co, mouse_co) \
984   mul_v3_m4v3( \
985       test_co, data->orig_matrix_final_no_offset, ((const float[3]){UNPACK2(mouse_co), 0.0}))
986
987     float test_co[3];
988
989     if (data->dial == NULL) {
990       MUL_V2_V3_M4_FINAL(test_co, data->orig_matrix_offset[3]);
991
992       data->dial = BLI_dial_initialize(test_co, FLT_EPSILON);
993
994       MUL_V2_V3_M4_FINAL(test_co, data->orig_mouse);
995       BLI_dial_angle(data->dial, test_co);
996     }
997
998     /* rotate */
999     MUL_V2_V3_M4_FINAL(test_co, point_local);
1000     const float angle = BLI_dial_angle(data->dial, test_co);
1001
1002     float matrix_space_inv[4][4];
1003     float matrix_rotate[4][4];
1004     float pivot[3];
1005
1006     copy_v3_v3(pivot, data->orig_matrix_offset[3]);
1007
1008     invert_m4_m4(matrix_space_inv, gz->matrix_space);
1009
1010     unit_m4(matrix_rotate);
1011     mul_m4_m4m4(matrix_rotate, matrix_rotate, matrix_space_inv);
1012     rotate_m4(matrix_rotate, 'Z', -angle);
1013     mul_m4_m4m4(matrix_rotate, matrix_rotate, gz->matrix_space);
1014
1015     zero_v3(matrix_rotate[3]);
1016     transform_pivot_set_m4(matrix_rotate, pivot);
1017
1018     mul_m4_m4m4(gz->matrix_offset, matrix_rotate, data->orig_matrix_offset);
1019
1020 #undef MUL_V2_V3_M4_FINAL
1021   }
1022   else {
1023     /* scale */
1024     copy_m4_m4(gz->matrix_offset, data->orig_matrix_offset);
1025     float pivot[2];
1026     bool constrain_axis[2] = {false};
1027
1028     if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE) {
1029       gizmo_rect_pivot_from_scale_part(gz->highlight_part, pivot, constrain_axis);
1030     }
1031     else {
1032       zero_v2(pivot);
1033     }
1034
1035     /* Cursor deltas scaled to (-0.5..0.5). */
1036     float delta_orig[2], delta_curr[2];
1037     for (int i = 0; i < 2; i++) {
1038       delta_orig[i] = ((data->orig_mouse[i] - data->orig_matrix_offset[3][i]) / dims[i]) -
1039                       pivot[i];
1040       delta_curr[i] = ((point_local[i] - data->orig_matrix_offset[3][i]) / dims[i]) - pivot[i];
1041     }
1042
1043     float scale[2] = {1.0f, 1.0f};
1044     for (int i = 0; i < 2; i++) {
1045       if (constrain_axis[i] == false) {
1046         if (delta_orig[i] < 0.0f) {
1047           delta_orig[i] *= -1.0f;
1048           delta_curr[i] *= -1.0f;
1049         }
1050         const int sign = signum_i(scale[i]);
1051
1052         scale[i] = 1.0f + ((delta_curr[i] - delta_orig[i]) / len_v3(data->orig_matrix_offset[i]));
1053
1054         if ((transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE_SIGNED) == 0) {
1055           if (sign != signum_i(scale[i])) {
1056             scale[i] = 0.0f;
1057           }
1058         }
1059       }
1060     }
1061
1062     if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE_UNIFORM) {
1063       if (constrain_axis[0] == false && constrain_axis[1] == false) {
1064         scale[1] = scale[0] = (scale[1] + scale[0]) / 2.0f;
1065       }
1066       else if (constrain_axis[0] == false) {
1067         scale[1] = scale[0];
1068       }
1069       else if (constrain_axis[1] == false) {
1070         scale[0] = scale[1];
1071       }
1072       else {
1073         BLI_assert(0);
1074       }
1075     }
1076
1077     /* scale around pivot */
1078     float matrix_scale[4][4];
1079     unit_m4(matrix_scale);
1080
1081     mul_v3_fl(matrix_scale[0], scale[0]);
1082     mul_v3_fl(matrix_scale[1], scale[1]);
1083
1084     transform_pivot_set_m4(matrix_scale,
1085                            (const float[3]){pivot[0] * dims[0], pivot[1] * dims[1], 0.0f});
1086     mul_m4_m4m4(gz->matrix_offset, data->orig_matrix_offset, matrix_scale);
1087   }
1088
1089   if (gz_prop->type != NULL) {
1090     WM_gizmo_target_property_float_set_array(C, gz, gz_prop, &gz->matrix_offset[0][0]);
1091   }
1092
1093   /* tag the region for redraw */
1094   ED_region_tag_redraw(CTX_wm_region(C));
1095   WM_event_add_mousemove(C);
1096
1097   return OPERATOR_RUNNING_MODAL;
1098 }
1099
1100 static void gizmo_cage2d_property_update(wmGizmo *gz, wmGizmoProperty *gz_prop)
1101 {
1102   if (STREQ(gz_prop->type->idname, "matrix")) {
1103     if (WM_gizmo_target_property_array_length(gz, gz_prop) == 16) {
1104       WM_gizmo_target_property_float_get_array(gz, gz_prop, &gz->matrix_offset[0][0]);
1105     }
1106     else {
1107       BLI_assert(0);
1108     }
1109   }
1110   else {
1111     BLI_assert(0);
1112   }
1113 }
1114
1115 static void gizmo_cage2d_exit(bContext *C, wmGizmo *gz, const bool cancel)
1116 {
1117   RectTransformInteraction *data = gz->interaction_data;
1118
1119   MEM_SAFE_FREE(data->dial);
1120
1121   if (!cancel) {
1122     return;
1123   }
1124
1125   wmGizmoProperty *gz_prop;
1126
1127   /* reset properties */
1128   gz_prop = WM_gizmo_target_property_find(gz, "matrix");
1129   if (gz_prop->type != NULL) {
1130     WM_gizmo_target_property_float_set_array(C, gz, gz_prop, &data->orig_matrix_offset[0][0]);
1131   }
1132
1133   copy_m4_m4(gz->matrix_offset, data->orig_matrix_offset);
1134 }
1135
1136 /* -------------------------------------------------------------------- */
1137 /** \name Cage Gizmo API
1138  *
1139  * \{ */
1140
1141 static void GIZMO_GT_cage_2d(wmGizmoType *gzt)
1142 {
1143   /* identifiers */
1144   gzt->idname = "GIZMO_GT_cage_2d";
1145
1146   /* api callbacks */
1147   gzt->draw = gizmo_cage2d_draw;
1148   gzt->draw_select = gizmo_cage2d_draw_select;
1149   gzt->test_select = gizmo_cage2d_test_select;
1150   gzt->setup = gizmo_cage2d_setup;
1151   gzt->invoke = gizmo_cage2d_invoke;
1152   gzt->property_update = gizmo_cage2d_property_update;
1153   gzt->modal = gizmo_cage2d_modal;
1154   gzt->exit = gizmo_cage2d_exit;
1155   gzt->cursor_get = gizmo_cage2d_get_cursor;
1156
1157   gzt->struct_size = sizeof(wmGizmo);
1158
1159   /* rna */
1160   static EnumPropertyItem rna_enum_draw_style[] = {
1161       {ED_GIZMO_CAGE2D_STYLE_BOX, "BOX", 0, "Box", ""},
1162       {ED_GIZMO_CAGE2D_STYLE_CIRCLE, "CIRCLE", 0, "Circle", ""},
1163       {0, NULL, 0, NULL, NULL},
1164   };
1165   static EnumPropertyItem rna_enum_transform[] = {
1166       {ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE, "TRANSLATE", 0, "Move", ""},
1167       {ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE, "ROTATE", 0, "Rotate", ""},
1168       {ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE, "SCALE", 0, "Scale", ""},
1169       {ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE_UNIFORM, "SCALE_UNIFORM", 0, "Scale Uniform", ""},
1170       {0, NULL, 0, NULL, NULL},
1171   };
1172   static EnumPropertyItem rna_enum_draw_options[] = {
1173       {ED_GIZMO_CAGE2D_DRAW_FLAG_XFORM_CENTER_HANDLE,
1174        "XFORM_CENTER_HANDLE",
1175        0,
1176        "Center Handle",
1177        ""},
1178       {0, NULL, 0, NULL, NULL},
1179   };
1180   static float unit_v2[2] = {1.0f, 1.0f};
1181   RNA_def_float_vector(
1182       gzt->srna, "dimensions", 2, unit_v2, 0, FLT_MAX, "Dimensions", "", 0.0f, FLT_MAX);
1183   RNA_def_enum_flag(gzt->srna, "transform", rna_enum_transform, 0, "Transform Options", "");
1184   RNA_def_enum(gzt->srna,
1185                "draw_style",
1186                rna_enum_draw_style,
1187                ED_GIZMO_CAGE2D_STYLE_CIRCLE,
1188                "Draw Style",
1189                "");
1190   RNA_def_enum_flag(gzt->srna,
1191                     "draw_options",
1192                     rna_enum_draw_options,
1193                     ED_GIZMO_CAGE2D_DRAW_FLAG_XFORM_CENTER_HANDLE,
1194                     "Draw Options",
1195                     "");
1196
1197   WM_gizmotype_target_property_def(gzt, "matrix", PROP_FLOAT, 16);
1198 }
1199
1200 void ED_gizmotypes_cage_2d(void)
1201 {
1202   WM_gizmotype_append(GIZMO_GT_cage_2d);
1203 }
1204
1205 /** \} */