Merge branch 'blender2.7'
[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,
65                  *translate_y;
66
67         wmGizmo *cage;
68
69         /* Current origin in view space, used to update widget origin for possible view changes */
70         float origin[2];
71         float min[2];
72         float max[2];
73
74 } GizmoGroup2D;
75
76
77 /* **************** Utilities **************** */
78
79 /* loop over axes */
80 #define MAN2D_ITER_AXES_BEGIN(axis, axis_idx) \
81         { \
82                 wmGizmo *axis; \
83                 int axis_idx; \
84                 for (axis_idx = 0; axis_idx < MAN2D_AXIS_LAST; axis_idx++) { \
85                         axis = gizmo2d_get_axis_from_index(ggd, axis_idx);
86
87 #define MAN2D_ITER_AXES_END \
88                 } \
89         } ((void)0)
90
91 static wmGizmo *gizmo2d_get_axis_from_index(const GizmoGroup2D *ggd, const short axis_idx)
92 {
93         BLI_assert(IN_RANGE_INCL(axis_idx, (float)MAN2D_AXIS_TRANS_X, (float)MAN2D_AXIS_TRANS_Y));
94
95         switch (axis_idx) {
96                 case MAN2D_AXIS_TRANS_X:
97                         return ggd->translate_x;
98                 case MAN2D_AXIS_TRANS_Y:
99                         return ggd->translate_y;
100         }
101
102         return NULL;
103 }
104
105 static void gizmo2d_get_axis_color(const int axis_idx, float *r_col, float *r_col_hi)
106 {
107         const float alpha = 0.6f;
108         const float alpha_hi = 1.0f;
109         int col_id;
110
111         switch (axis_idx) {
112                 case MAN2D_AXIS_TRANS_X:
113                         col_id = TH_AXIS_X;
114                         break;
115                 case MAN2D_AXIS_TRANS_Y:
116                         col_id = TH_AXIS_Y;
117                         break;
118         }
119
120         UI_GetThemeColor4fv(col_id, r_col);
121
122         copy_v4_v4(r_col_hi, r_col);
123         r_col[3] *= alpha;
124         r_col_hi[3] *= alpha_hi;
125 }
126
127 static GizmoGroup2D *gizmogroup2d_init(wmGizmoGroup *gzgroup)
128 {
129         const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_2d", true);
130         const wmGizmoType *gzt_cage = WM_gizmotype_find("GIZMO_GT_cage_2d", true);
131
132         GizmoGroup2D *ggd = MEM_callocN(sizeof(GizmoGroup2D), __func__);
133
134         ggd->translate_x = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
135         ggd->translate_y = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL);
136         ggd->cage = WM_gizmo_new_ptr(gzt_cage, gzgroup, NULL);
137
138         RNA_enum_set(ggd->cage->ptr, "transform",
139                      ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE |
140                      ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE |
141                      ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE);
142
143         return ggd;
144 }
145
146 /**
147  * Calculates origin in view space, use with #gizmo2d_origin_to_region.
148  */
149 static void gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min, float *r_max)
150 {
151         float min_buf[2], max_buf[2];
152         if (r_min == NULL) {
153                 r_min = min_buf;
154         }
155         if (r_max == NULL) {
156                 r_max = max_buf;
157         }
158
159         ScrArea *sa = CTX_wm_area(C);
160         if (sa->spacetype == SPACE_IMAGE) {
161                 SpaceImage *sima = sa->spacedata.first;
162                 ViewLayer *view_layer = CTX_data_view_layer(C);
163                 Image *ima = ED_space_image(sima);
164                 uint objects_len = 0;
165                 Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
166                         view_layer, NULL, &objects_len);
167                 if (!ED_uvedit_minmax_multi(CTX_data_scene(C), ima, objects, objects_len, r_min, r_max)) {
168                         zero_v2(r_min);
169                         zero_v2(r_max);
170                 }
171                 MEM_freeN(objects);
172         }
173         else {
174                 zero_v2(r_min);
175                 zero_v2(r_max);
176         }
177         mid_v2_v2v2(r_center, r_min, r_max);
178 }
179
180 /**
181  * Convert origin (or any other point) from view to region space.
182  */
183 BLI_INLINE void gizmo2d_origin_to_region(ARegion *ar, float *r_origin)
184 {
185         UI_view2d_view_to_region_fl(&ar->v2d, r_origin[0], r_origin[1], &r_origin[0], &r_origin[1]);
186 }
187
188 /**
189  * Custom handler for gizmo widgets
190  */
191 static int gizmo2d_modal(
192         bContext *C, wmGizmo *widget, 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         {
215                 const float offset[3] = {0.0f, 0.2f};
216
217                 float color[4], color_hi[4];
218                 gizmo2d_get_axis_color(axis_idx, color, color_hi);
219
220                 /* custom handler! */
221                 WM_gizmo_set_fn_custom_modal(axis, gizmo2d_modal);
222                 /* set up widget data */
223                 RNA_float_set(axis->ptr, "angle", -M_PI_2 * axis_idx);
224                 RNA_float_set(axis->ptr, "length", 0.8f);
225                 WM_gizmo_set_matrix_offset_location(axis, offset);
226                 WM_gizmo_set_line_width(axis, GIZMO_AXIS_LINE_WIDTH);
227                 WM_gizmo_set_scale(axis, U.gizmo_size);
228                 WM_gizmo_set_color(axis, color);
229                 WM_gizmo_set_color_highlight(axis, color_hi);
230
231                 /* assign operator */
232                 PointerRNA *ptr = WM_gizmo_operator_set(axis, 0, ot_translate, NULL);
233                 bool constraint[3] = {0};
234                 constraint[(axis_idx + 1) % 2] = 1;
235                 if (RNA_struct_find_property(ptr, "constraint_axis"))
236                         RNA_boolean_set_array(ptr, "constraint_axis", constraint);
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(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y, ot_resize, NULL);
269                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
270                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y, ot_resize, NULL);
271                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
272                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y, ot_resize, NULL);
273                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
274                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y, ot_resize, NULL);
275                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
276                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE, ot_rotate, NULL);
277                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
278         }
279 }
280
281 void ED_widgetgroup_gizmo2d_refresh(const bContext *C, wmGizmoGroup *gzgroup)
282 {
283         GizmoGroup2D *ggd = gzgroup->customdata;
284         float origin[3];
285         gizmo2d_calc_bounds(C, origin, ggd->min, ggd->max);
286         copy_v2_v2(ggd->origin, origin);
287         bool show_cage = !equals_v2v2(ggd->min, ggd->max);
288
289         if (show_cage) {
290                 ggd->cage->flag &= ~WM_GIZMO_HIDDEN;
291                 ggd->translate_x->flag |= WM_GIZMO_HIDDEN;
292                 ggd->translate_y->flag |= WM_GIZMO_HIDDEN;
293         }
294         else {
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
300         if (show_cage) {
301                 wmGizmoOpElem *gzop;
302                 float mid[2];
303                 const float *min = ggd->min;
304                 const float *max = ggd->max;
305                 mid_v2_v2v2(mid, min, max);
306
307                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X);
308                 PropertyRNA *prop_center_override = RNA_struct_find_property(&gzop->ptr, "center_override");
309                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){max[0], mid[1], 0.0f});
310                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X);
311                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){min[0], mid[1], 0.0f});
312                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y);
313                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){mid[0], max[1], 0.0f});
314                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y);
315                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){mid[0], min[1], 0.0f});
316
317                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y);
318                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){max[0], max[1], 0.0f});
319                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y);
320                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){max[0], min[1], 0.0f});
321                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y);
322                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){min[0], max[1], 0.0f});
323                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y);
324                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){min[0], min[1], 0.0f});
325
326                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE);
327                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){mid[0], mid[1], 0.0f});
328         }
329 }
330
331 void ED_widgetgroup_gizmo2d_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
332 {
333         ARegion *ar = CTX_wm_region(C);
334         GizmoGroup2D *ggd = gzgroup->customdata;
335         float origin[3] = {UNPACK2(ggd->origin), 0.0f};
336         float origin_aa[3] = {UNPACK2(ggd->origin), 0.0f};
337
338         gizmo2d_origin_to_region(ar, origin);
339
340         MAN2D_ITER_AXES_BEGIN(axis, axis_idx)
341         {
342                 WM_gizmo_set_matrix_location(axis, origin);
343         }
344         MAN2D_ITER_AXES_END;
345
346         UI_view2d_view_to_region_m4(&ar->v2d, ggd->cage->matrix_space);
347         WM_gizmo_set_matrix_offset_location(ggd->cage, origin_aa);
348         ggd->cage->matrix_offset[0][0] = (ggd->max[0] - ggd->min[0]);
349         ggd->cage->matrix_offset[1][1] = (ggd->max[1] - ggd->min[1]);
350 }
351
352 /* TODO (Julian)
353  * - Called on every redraw, better to do a more simple poll and check for selection in _refresh
354  * - UV editing only, could be expanded for other things.
355  */
356 bool ED_widgetgroup_gizmo2d_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt))
357 {
358         if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) {
359                 return false;
360         }
361
362         SpaceImage *sima = CTX_wm_space_image(C);
363         Object *obedit = CTX_data_edit_object(C);
364
365         if (ED_space_image_show_uvedit(sima, obedit)) {
366                 Image *ima = ED_space_image(sima);
367                 Scene *scene = CTX_data_scene(C);
368                 BMEditMesh *em = BKE_editmesh_from_object(obedit);
369                 BMFace *efa;
370                 BMLoop *l;
371                 BMIter iter, liter;
372
373                 const int cd_loop_uv_offset  = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
374
375                 /* check if there's a selected poly */
376                 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
377                         if (!uvedit_face_visible_test(scene, obedit, ima, efa))
378                                 continue;
379
380                         BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
381                                 if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
382                                         return true;
383                                 }
384                         }
385                 }
386         }
387
388         return false;
389 }