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