Merge branch 'blender-v2.81-release'
[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     default:
118       BLI_assert(0);
119       col_id = TH_AXIS_Y;
120       break;
121   }
122
123   UI_GetThemeColor4fv(col_id, r_col);
124
125   copy_v4_v4(r_col_hi, r_col);
126   r_col[3] *= alpha;
127   r_col_hi[3] *= alpha_hi;
128 }
129
130 static GizmoGroup2D *gizmogroup2d_init(wmGizmoGroup *gzgroup)
131 {
132   const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_2d", true);
133   const wmGizmoType *gzt_cage = WM_gizmotype_find("GIZMO_GT_cage_2d", true);
134
135   GizmoGroup2D *ggd = MEM_callocN(sizeof(GizmoGroup2D), __func__);
136
137   ggd->translate_x = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
138   ggd->translate_y = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
139   ggd->cage = WM_gizmo_new_ptr(gzt_cage, gzgroup, NULL);
140
141   RNA_enum_set(ggd->cage->ptr,
142                "transform",
143                ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE |
144                    ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE);
145
146   return ggd;
147 }
148
149 /**
150  * Calculates origin in view space, use with #gizmo2d_origin_to_region.
151  */
152 static void gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min, float *r_max)
153 {
154   float min_buf[2], max_buf[2];
155   if (r_min == NULL) {
156     r_min = min_buf;
157   }
158   if (r_max == NULL) {
159     r_max = max_buf;
160   }
161
162   ScrArea *sa = CTX_wm_area(C);
163   if (sa->spacetype == SPACE_IMAGE) {
164     SpaceImage *sima = sa->spacedata.first;
165     ViewLayer *view_layer = CTX_data_view_layer(C);
166     Image *ima = ED_space_image(sima);
167     uint objects_len = 0;
168     Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
169         view_layer, NULL, &objects_len);
170     if (!ED_uvedit_minmax_multi(CTX_data_scene(C), ima, objects, objects_len, r_min, r_max)) {
171       zero_v2(r_min);
172       zero_v2(r_max);
173     }
174     MEM_freeN(objects);
175   }
176   else {
177     zero_v2(r_min);
178     zero_v2(r_max);
179   }
180   mid_v2_v2v2(r_center, r_min, r_max);
181 }
182
183 /**
184  * Convert origin (or any other point) from view to region space.
185  */
186 BLI_INLINE void gizmo2d_origin_to_region(ARegion *ar, float *r_origin)
187 {
188   UI_view2d_view_to_region_fl(&ar->v2d, r_origin[0], r_origin[1], &r_origin[0], &r_origin[1]);
189 }
190
191 /**
192  * Custom handler for gizmo widgets
193  */
194 static int gizmo2d_modal(bContext *C,
195                          wmGizmo *widget,
196                          const wmEvent *UNUSED(event),
197                          eWM_GizmoFlagTweak UNUSED(tweak_flag))
198 {
199   ARegion *ar = CTX_wm_region(C);
200   float origin[3];
201
202   gizmo2d_calc_bounds(C, origin, NULL, NULL);
203   gizmo2d_origin_to_region(ar, origin);
204   WM_gizmo_set_matrix_location(widget, origin);
205
206   ED_region_tag_redraw(ar);
207
208   return OPERATOR_RUNNING_MODAL;
209 }
210
211 void ED_widgetgroup_gizmo2d_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup)
212 {
213   wmOperatorType *ot_translate = WM_operatortype_find("TRANSFORM_OT_translate", true);
214   GizmoGroup2D *ggd = gizmogroup2d_init(gzgroup);
215   gzgroup->customdata = ggd;
216
217   MAN2D_ITER_AXES_BEGIN (axis, axis_idx) {
218     const float offset[3] = {0.0f, 0.2f};
219
220     float color[4], color_hi[4];
221     gizmo2d_get_axis_color(axis_idx, color, color_hi);
222
223     /* custom handler! */
224     WM_gizmo_set_fn_custom_modal(axis, gizmo2d_modal);
225     /* set up widget data */
226     RNA_float_set(axis->ptr, "angle", -M_PI_2 * axis_idx);
227     RNA_float_set(axis->ptr, "length", 0.8f);
228     WM_gizmo_set_matrix_offset_location(axis, offset);
229     WM_gizmo_set_line_width(axis, GIZMO_AXIS_LINE_WIDTH);
230     WM_gizmo_set_scale(axis, U.gizmo_size);
231     WM_gizmo_set_color(axis, color);
232     WM_gizmo_set_color_highlight(axis, color_hi);
233
234     /* assign operator */
235     PointerRNA *ptr = WM_gizmo_operator_set(axis, 0, ot_translate, NULL);
236     bool constraint[3] = {0};
237     constraint[(axis_idx + 1) % 2] = 1;
238     if (RNA_struct_find_property(ptr, "constraint_axis")) {
239       RNA_boolean_set_array(ptr, "constraint_axis", constraint);
240     }
241     RNA_boolean_set(ptr, "release_confirm", 1);
242   }
243   MAN2D_ITER_AXES_END;
244
245   {
246     wmOperatorType *ot_resize = WM_operatortype_find("TRANSFORM_OT_resize", true);
247     wmOperatorType *ot_rotate = WM_operatortype_find("TRANSFORM_OT_rotate", true);
248     PointerRNA *ptr;
249
250     /* assign operator */
251     ptr = WM_gizmo_operator_set(ggd->cage, 0, ot_translate, NULL);
252     RNA_boolean_set(ptr, "release_confirm", 1);
253
254     bool constraint_x[3] = {1, 0, 0};
255     bool constraint_y[3] = {0, 1, 0};
256
257     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X, ot_resize, NULL);
258     PropertyRNA *prop_release_confirm = RNA_struct_find_property(ptr, "release_confirm");
259     PropertyRNA *prop_constraint_axis = RNA_struct_find_property(ptr, "constraint_axis");
260     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x);
261     RNA_property_boolean_set(ptr, prop_release_confirm, true);
262     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X, ot_resize, NULL);
263     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x);
264     RNA_property_boolean_set(ptr, prop_release_confirm, true);
265     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y, ot_resize, NULL);
266     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y);
267     RNA_property_boolean_set(ptr, prop_release_confirm, true);
268     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y, ot_resize, NULL);
269     RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y);
270     RNA_property_boolean_set(ptr, prop_release_confirm, true);
271
272     ptr = WM_gizmo_operator_set(
273         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y, ot_resize, NULL);
274     RNA_property_boolean_set(ptr, prop_release_confirm, true);
275     ptr = WM_gizmo_operator_set(
276         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y, ot_resize, NULL);
277     RNA_property_boolean_set(ptr, prop_release_confirm, true);
278     ptr = WM_gizmo_operator_set(
279         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y, ot_resize, NULL);
280     RNA_property_boolean_set(ptr, prop_release_confirm, true);
281     ptr = WM_gizmo_operator_set(
282         ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y, ot_resize, NULL);
283     RNA_property_boolean_set(ptr, prop_release_confirm, true);
284     ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE, ot_rotate, NULL);
285     RNA_property_boolean_set(ptr, prop_release_confirm, true);
286   }
287 }
288
289 void ED_widgetgroup_gizmo2d_refresh(const bContext *C, wmGizmoGroup *gzgroup)
290 {
291   GizmoGroup2D *ggd = gzgroup->customdata;
292   float origin[3];
293   gizmo2d_calc_bounds(C, origin, ggd->min, ggd->max);
294   copy_v2_v2(ggd->origin, origin);
295   bool show_cage = !equals_v2v2(ggd->min, ggd->max);
296
297   if (show_cage) {
298     ggd->cage->flag &= ~WM_GIZMO_HIDDEN;
299     ggd->translate_x->flag |= WM_GIZMO_HIDDEN;
300     ggd->translate_y->flag |= WM_GIZMO_HIDDEN;
301   }
302   else {
303     ggd->cage->flag |= WM_GIZMO_HIDDEN;
304     ggd->translate_x->flag &= ~WM_GIZMO_HIDDEN;
305     ggd->translate_y->flag &= ~WM_GIZMO_HIDDEN;
306   }
307
308   if (show_cage) {
309     wmGizmoOpElem *gzop;
310     float mid[2];
311     const float *min = ggd->min;
312     const float *max = ggd->max;
313     mid_v2_v2v2(mid, min, max);
314
315     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X);
316     PropertyRNA *prop_center_override = RNA_struct_find_property(&gzop->ptr, "center_override");
317     RNA_property_float_set_array(
318         &gzop->ptr, prop_center_override, (float[3]){max[0], mid[1], 0.0f});
319     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X);
320     RNA_property_float_set_array(
321         &gzop->ptr, prop_center_override, (float[3]){min[0], mid[1], 0.0f});
322     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y);
323     RNA_property_float_set_array(
324         &gzop->ptr, prop_center_override, (float[3]){mid[0], max[1], 0.0f});
325     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y);
326     RNA_property_float_set_array(
327         &gzop->ptr, prop_center_override, (float[3]){mid[0], min[1], 0.0f});
328
329     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y);
330     RNA_property_float_set_array(
331         &gzop->ptr, prop_center_override, (float[3]){max[0], max[1], 0.0f});
332     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y);
333     RNA_property_float_set_array(
334         &gzop->ptr, prop_center_override, (float[3]){max[0], min[1], 0.0f});
335     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y);
336     RNA_property_float_set_array(
337         &gzop->ptr, prop_center_override, (float[3]){min[0], max[1], 0.0f});
338     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y);
339     RNA_property_float_set_array(
340         &gzop->ptr, prop_center_override, (float[3]){min[0], min[1], 0.0f});
341
342     gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE);
343     RNA_property_float_set_array(
344         &gzop->ptr, prop_center_override, (float[3]){mid[0], mid[1], 0.0f});
345   }
346 }
347
348 void ED_widgetgroup_gizmo2d_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
349 {
350   ARegion *ar = CTX_wm_region(C);
351   GizmoGroup2D *ggd = gzgroup->customdata;
352   float origin[3] = {UNPACK2(ggd->origin), 0.0f};
353   float origin_aa[3] = {UNPACK2(ggd->origin), 0.0f};
354
355   gizmo2d_origin_to_region(ar, origin);
356
357   MAN2D_ITER_AXES_BEGIN (axis, axis_idx) {
358     WM_gizmo_set_matrix_location(axis, origin);
359   }
360   MAN2D_ITER_AXES_END;
361
362   UI_view2d_view_to_region_m4(&ar->v2d, ggd->cage->matrix_space);
363   WM_gizmo_set_matrix_offset_location(ggd->cage, origin_aa);
364   ggd->cage->matrix_offset[0][0] = (ggd->max[0] - ggd->min[0]);
365   ggd->cage->matrix_offset[1][1] = (ggd->max[1] - ggd->min[1]);
366 }
367
368 /* TODO (Julian)
369  * - Called on every redraw, better to do a more simple poll and check for selection in _refresh
370  * - UV editing only, could be expanded for other things.
371  */
372 bool ED_widgetgroup_gizmo2d_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt))
373 {
374   if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) {
375     return false;
376   }
377
378   SpaceImage *sima = CTX_wm_space_image(C);
379   Object *obedit = CTX_data_edit_object(C);
380
381   if (ED_space_image_show_uvedit(sima, obedit)) {
382     Image *ima = ED_space_image(sima);
383     Scene *scene = CTX_data_scene(C);
384     BMEditMesh *em = BKE_editmesh_from_object(obedit);
385     BMFace *efa;
386     BMLoop *l;
387     BMIter iter, liter;
388
389     const int cd_loop_uv_offset = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
390
391     /* check if there's a selected poly */
392     BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
393       if (!uvedit_face_visible_test(scene, obedit, ima, efa)) {
394         continue;
395       }
396
397       BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
398         if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
399           return true;
400         }
401       }
402     }
403   }
404
405   return false;
406 }