Cleanup: style, use braces for editors
[blender.git] / source / blender / editors / transform / transform_gizmo_2d.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16
17 /** \file
18  * \ingroup edtransform
19  *
20  * \name 2D Transform Gizmo
21  *
22  * Used for UV/Image Editor
23  */
24
25 #include "MEM_guardedalloc.h"
26
27 #include "BLI_math.h"
28
29 #include "DNA_meshdata_types.h"
30 #include "DNA_object_types.h"
31 #include "DNA_screen_types.h"
32 #include "DNA_space_types.h"
33 #include "DNA_view3d_types.h"
34
35 #include "BKE_context.h"
36 #include "BKE_editmesh.h"
37 #include "BKE_layer.h"
38
39 #include "RNA_access.h"
40
41 #include "UI_resources.h"
42 #include "UI_view2d.h"
43
44 #include "WM_api.h"
45 #include "WM_types.h"
46 #include "wm.h" /* XXX */
47
48 #include "ED_image.h"
49 #include "ED_screen.h"
50 #include "ED_uvedit.h"
51 #include "ED_gizmo_library.h"
52
53 #include "transform.h" /* own include */
54
55 /* axes as index */
56 enum {
57   MAN2D_AXIS_TRANS_X = 0,
58   MAN2D_AXIS_TRANS_Y,
59
60   MAN2D_AXIS_LAST,
61 };
62
63 typedef struct GizmoGroup2D {
64   wmGizmo *translate_x, *translate_y;
65
66   wmGizmo *cage;
67
68   /* Current origin in view space, used to update widget origin for possible view changes */
69   float origin[2];
70   float min[2];
71   float max[2];
72
73 } GizmoGroup2D;
74
75 /* **************** Utilities **************** */
76
77 /* loop over axes */
78 #define MAN2D_ITER_AXES_BEGIN(axis, axis_idx) \
79   { \
80     wmGizmo *axis; \
81     int axis_idx; \
82     for (axis_idx = 0; axis_idx < MAN2D_AXIS_LAST; axis_idx++) { \
83       axis = gizmo2d_get_axis_from_index(ggd, axis_idx);
84
85 #define MAN2D_ITER_AXES_END \
86   } \
87   } \
88   ((void)0)
89
90 static wmGizmo *gizmo2d_get_axis_from_index(const GizmoGroup2D *ggd, const short axis_idx)
91 {
92   BLI_assert(IN_RANGE_INCL(axis_idx, (float)MAN2D_AXIS_TRANS_X, (float)MAN2D_AXIS_TRANS_Y));
93
94   switch (axis_idx) {
95     case MAN2D_AXIS_TRANS_X:
96       return ggd->translate_x;
97     case MAN2D_AXIS_TRANS_Y:
98       return ggd->translate_y;
99   }
100
101   return NULL;
102 }
103
104 static void gizmo2d_get_axis_color(const int axis_idx, float *r_col, float *r_col_hi)
105 {
106   const float alpha = 0.6f;
107   const float alpha_hi = 1.0f;
108   int col_id;
109
110   switch (axis_idx) {
111     case MAN2D_AXIS_TRANS_X:
112       col_id = TH_AXIS_X;
113       break;
114     case MAN2D_AXIS_TRANS_Y:
115       col_id = TH_AXIS_Y;
116       break;
117   }
118
119   UI_GetThemeColor4fv(col_id, r_col);
120
121   copy_v4_v4(r_col_hi, r_col);
122   r_col[3] *= alpha;
123   r_col_hi[3] *= alpha_hi;
124 }
125
126 static GizmoGroup2D *gizmogroup2d_init(wmGizmoGroup *gzgroup)
127 {
128   const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_2d", true);
129   const wmGizmoType *gzt_cage = WM_gizmotype_find("GIZMO_GT_cage_2d", true);
130
131   GizmoGroup2D *ggd = MEM_callocN(sizeof(GizmoGroup2D), __func__);
132
133   ggd->translate_x = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
134   ggd->translate_y = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
135   ggd->cage = WM_gizmo_new_ptr(gzt_cage, gzgroup, NULL);
136
137   RNA_enum_set(ggd->cage->ptr,
138                "transform",
139                ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE |
140                    ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE);
141
142   return ggd;
143 }
144
145 /**
146  * Calculates origin in view space, use with #gizmo2d_origin_to_region.
147  */
148 static void gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min, float *r_max)
149 {
150   float min_buf[2], max_buf[2];
151   if (r_min == NULL) {
152     r_min = min_buf;
153   }
154   if (r_max == NULL) {
155     r_max = max_buf;
156   }
157
158   ScrArea *sa = CTX_wm_area(C);
159   if (sa->spacetype == SPACE_IMAGE) {
160     SpaceImage *sima = sa->spacedata.first;
161     ViewLayer *view_layer = CTX_data_view_layer(C);
162     Image *ima = ED_space_image(sima);
163     uint objects_len = 0;
164     Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
165         view_layer, NULL, &objects_len);
166     if (!ED_uvedit_minmax_multi(CTX_data_scene(C), ima, objects, objects_len, r_min, r_max)) {
167       zero_v2(r_min);
168       zero_v2(r_max);
169     }
170     MEM_freeN(objects);
171   }
172   else {
173     zero_v2(r_min);
174     zero_v2(r_max);
175   }
176   mid_v2_v2v2(r_center, r_min, r_max);
177 }
178
179 /**
180  * Convert origin (or any other point) from view to region space.
181  */
182 BLI_INLINE void gizmo2d_origin_to_region(ARegion *ar, float *r_origin)
183 {
184   UI_view2d_view_to_region_fl(&ar->v2d, r_origin[0], r_origin[1], &r_origin[0], &r_origin[1]);
185 }
186
187 /**
188  * Custom handler for gizmo widgets
189  */
190 static int gizmo2d_modal(bContext *C,
191                          wmGizmo *widget,
192                          const wmEvent *UNUSED(event),
193                          eWM_GizmoFlagTweak UNUSED(tweak_flag))
194 {
195   ARegion *ar = CTX_wm_region(C);
196   float origin[3];
197
198   gizmo2d_calc_bounds(C, origin, NULL, NULL);
199   gizmo2d_origin_to_region(ar, origin);
200   WM_gizmo_set_matrix_location(widget, origin);
201
202   ED_region_tag_redraw(ar);
203
204   return OPERATOR_RUNNING_MODAL;
205 }
206
207 void ED_widgetgroup_gizmo2d_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup)
208 {
209   wmOperatorType *ot_translate = WM_operatortype_find("TRANSFORM_OT_translate", true);
210   GizmoGroup2D *ggd = gizmogroup2d_init(gzgroup);
211   gzgroup->customdata = ggd;
212
213   MAN2D_ITER_AXES_BEGIN (axis, axis_idx) {
214     const float offset[3] = {0.0f, 0.2f};
215
216     float color[4], color_hi[4];
217     gizmo2d_get_axis_color(axis_idx, color, color_hi);
218
219     /* custom handler! */
220     WM_gizmo_set_fn_custom_modal(axis, gizmo2d_modal);
221     /* set up widget data */
222     RNA_float_set(axis->ptr, "angle", -M_PI_2 * axis_idx);
223     RNA_float_set(axis->ptr, "length", 0.8f);
224     WM_gizmo_set_matrix_offset_location(axis, offset);
225     WM_gizmo_set_line_width(axis, GIZMO_AXIS_LINE_WIDTH);
226     WM_gizmo_set_scale(axis, U.gizmo_size);
227     WM_gizmo_set_color(axis, color);
228     WM_gizmo_set_color_highlight(axis, color_hi);
229
230     /* assign operator */
231     PointerRNA *ptr = WM_gizmo_operator_set(axis, 0, ot_translate, NULL);
232     bool constraint[3] = {0};
233     constraint[(axis_idx + 1) % 2] = 1;
234     if (RNA_struct_find_property(ptr, "constraint_axis")) {
235       RNA_boolean_set_array(ptr, "constraint_axis", constraint);
236     }
237     RNA_boolean_set(ptr, "release_confirm", 1);
238   }
239   MAN2D_ITER_AXES_END;
240
241   {
242     wmOperatorType *ot_resize = WM_operatortype_find("TRANSFORM_OT_resize", true);
243     wmOperatorType *ot_rotate = WM_operatortype_find("TRANSFORM_OT_rotate", true);
244     PointerRNA *ptr;
245
246     /* assign operator */
247     ptr = WM_gizmo_operator_set(ggd->cage, 0, ot_translate, NULL);
248     RNA_boolean_set(ptr, "release_confirm", 1);
249
250     bool constraint_x[3] = {1, 0, 0};
251     bool constraint_y[3] = {0, 1, 0};
252
253     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X, ot_resize, NULL);
254     PropertyRNA *prop_release_confirm = RNA_struct_find_property(ptr, "release_confirm");
255     PropertyRNA *prop_constraint_axis = RNA_struct_find_property(ptr, "constraint_axis");
256     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x);
257     RNA_property_boolean_set(ptr, prop_release_confirm, true);
258     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X, ot_resize, NULL);
259     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x);
260     RNA_property_boolean_set(ptr, prop_release_confirm, true);
261     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y, ot_resize, NULL);
262     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y);
263     RNA_property_boolean_set(ptr, prop_release_confirm, true);
264     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y, ot_resize, NULL);
265     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y);
266     RNA_property_boolean_set(ptr, prop_release_confirm, true);
267
268     ptr = WM_gizmo_operator_set(
269         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y, ot_resize, NULL);
270     RNA_property_boolean_set(ptr, prop_release_confirm, true);
271     ptr = WM_gizmo_operator_set(
272         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y, ot_resize, NULL);
273     RNA_property_boolean_set(ptr, prop_release_confirm, true);
274     ptr = WM_gizmo_operator_set(
275         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y, ot_resize, NULL);
276     RNA_property_boolean_set(ptr, prop_release_confirm, true);
277     ptr = WM_gizmo_operator_set(
278         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y, ot_resize, NULL);
279     RNA_property_boolean_set(ptr, prop_release_confirm, true);
280     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE, ot_rotate, NULL);
281     RNA_property_boolean_set(ptr, prop_release_confirm, true);
282   }
283 }
284
285 void ED_widgetgroup_gizmo2d_refresh(const bContext *C, wmGizmoGroup *gzgroup)
286 {
287   GizmoGroup2D *ggd = gzgroup->customdata;
288   float origin[3];
289   gizmo2d_calc_bounds(C, origin, ggd->min, ggd->max);
290   copy_v2_v2(ggd->origin, origin);
291   bool show_cage = !equals_v2v2(ggd->min, ggd->max);
292
293   if (show_cage) {
294     ggd->cage->flag &= ~WM_GIZMO_HIDDEN;
295     ggd->translate_x->flag |= WM_GIZMO_HIDDEN;
296     ggd->translate_y->flag |= WM_GIZMO_HIDDEN;
297   }
298   else {
299     ggd->cage->flag |= WM_GIZMO_HIDDEN;
300     ggd->translate_x->flag &= ~WM_GIZMO_HIDDEN;
301     ggd->translate_y->flag &= ~WM_GIZMO_HIDDEN;
302   }
303
304   if (show_cage) {
305     wmGizmoOpElem *gzop;
306     float mid[2];
307     const float *min = ggd->min;
308     const float *max = ggd->max;
309     mid_v2_v2v2(mid, min, max);
310
311     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X);
312     PropertyRNA *prop_center_override = RNA_struct_find_property(&gzop->ptr, "center_override");
313     RNA_property_float_set_array(
314         &gzop->ptr, prop_center_override, (float[3]){max[0], mid[1], 0.0f});
315     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X);
316     RNA_property_float_set_array(
317         &gzop->ptr, prop_center_override, (float[3]){min[0], mid[1], 0.0f});
318     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y);
319     RNA_property_float_set_array(
320         &gzop->ptr, prop_center_override, (float[3]){mid[0], max[1], 0.0f});
321     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y);
322     RNA_property_float_set_array(
323         &gzop->ptr, prop_center_override, (float[3]){mid[0], min[1], 0.0f});
324
325     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y);
326     RNA_property_float_set_array(
327         &gzop->ptr, prop_center_override, (float[3]){max[0], max[1], 0.0f});
328     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y);
329     RNA_property_float_set_array(
330         &gzop->ptr, prop_center_override, (float[3]){max[0], min[1], 0.0f});
331     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y);
332     RNA_property_float_set_array(
333         &gzop->ptr, prop_center_override, (float[3]){min[0], max[1], 0.0f});
334     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y);
335     RNA_property_float_set_array(
336         &gzop->ptr, prop_center_override, (float[3]){min[0], min[1], 0.0f});
337
338     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE);
339     RNA_property_float_set_array(
340         &gzop->ptr, prop_center_override, (float[3]){mid[0], mid[1], 0.0f});
341   }
342 }
343
344 void ED_widgetgroup_gizmo2d_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
345 {
346   ARegion *ar = CTX_wm_region(C);
347   GizmoGroup2D *ggd = gzgroup->customdata;
348   float origin[3] = {UNPACK2(ggd->origin), 0.0f};
349   float origin_aa[3] = {UNPACK2(ggd->origin), 0.0f};
350
351   gizmo2d_origin_to_region(ar, origin);
352
353   MAN2D_ITER_AXES_BEGIN (axis, axis_idx) {
354     WM_gizmo_set_matrix_location(axis, origin);
355   }
356   MAN2D_ITER_AXES_END;
357
358   UI_view2d_view_to_region_m4(&ar->v2d, ggd->cage->matrix_space);
359   WM_gizmo_set_matrix_offset_location(ggd->cage, origin_aa);
360   ggd->cage->matrix_offset[0][0] = (ggd->max[0] - ggd->min[0]);
361   ggd->cage->matrix_offset[1][1] = (ggd->max[1] - ggd->min[1]);
362 }
363
364 /* TODO (Julian)
365  * - Called on every redraw, better to do a more simple poll and check for selection in _refresh
366  * - UV editing only, could be expanded for other things.
367  */
368 bool ED_widgetgroup_gizmo2d_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt))
369 {
370   if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) {
371     return false;
372   }
373
374   SpaceImage *sima = CTX_wm_space_image(C);
375   Object *obedit = CTX_data_edit_object(C);
376
377   if (ED_space_image_show_uvedit(sima, obedit)) {
378     Image *ima = ED_space_image(sima);
379     Scene *scene = CTX_data_scene(C);
380     BMEditMesh *em = BKE_editmesh_from_object(obedit);
381     BMFace *efa;
382     BMLoop *l;
383     BMIter iter, liter;
384
385     const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
386
387     /* check if there's a selected poly */
388     BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
389       if (!uvedit_face_visible_test(scene, obedit, ima, efa)) {
390         continue;
391       }
392
393       BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
394         if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
395           return true;
396         }
397       }
398     }
399   }
400
401   return false;
402 }