Fix T61536: can't snap vertex to another vertex in edit mode using curves
[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 \ingroup edtransform
18  *
19  * \name 2D Transform Gizmo
20  *
21  * Used for UV/Image Editor
22  */
23
24 #include "MEM_guardedalloc.h"
25
26 #include "BLI_math.h"
27
28 #include "DNA_meshdata_types.h"
29 #include "DNA_object_types.h"
30 #include "DNA_screen_types.h"
31 #include "DNA_space_types.h"
32 #include "DNA_view3d_types.h"
33
34 #include "BKE_context.h"
35 #include "BKE_editmesh.h"
36 #include "BKE_layer.h"
37
38 #include "RNA_access.h"
39
40 #include "UI_resources.h"
41 #include "UI_view2d.h"
42
43 #include "WM_api.h"
44 #include "WM_types.h"
45 #include "wm.h" /* XXX */
46
47 #include "ED_image.h"
48 #include "ED_screen.h"
49 #include "ED_uvedit.h"
50 #include "ED_gizmo_library.h"
51
52 #include "transform.h" /* own include */
53
54 /* axes as index */
55 enum {
56         MAN2D_AXIS_TRANS_X = 0,
57         MAN2D_AXIS_TRANS_Y,
58
59         MAN2D_AXIS_LAST,
60 };
61
62 typedef struct GizmoGroup2D {
63         wmGizmo *translate_x,
64                  *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
76 /* **************** Utilities **************** */
77
78 /* loop over axes */
79 #define MAN2D_ITER_AXES_BEGIN(axis, axis_idx) \
80         { \
81                 wmGizmo *axis; \
82                 int axis_idx; \
83                 for (axis_idx = 0; axis_idx < MAN2D_AXIS_LAST; axis_idx++) { \
84                         axis = gizmo2d_get_axis_from_index(ggd, axis_idx);
85
86 #define MAN2D_ITER_AXES_END \
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, "transform",
138                      ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE |
139                      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(
191         bContext *C, wmGizmo *widget, const wmEvent *UNUSED(event),
192         eWM_GizmoFlagTweak UNUSED(tweak_flag))
193 {
194         ARegion *ar = CTX_wm_region(C);
195         float origin[3];
196
197         gizmo2d_calc_bounds(C, origin, NULL, NULL);
198         gizmo2d_origin_to_region(ar, origin);
199         WM_gizmo_set_matrix_location(widget, origin);
200
201         ED_region_tag_redraw(ar);
202
203         return OPERATOR_RUNNING_MODAL;
204 }
205
206 void ED_widgetgroup_gizmo2d_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup)
207 {
208         wmOperatorType *ot_translate = WM_operatortype_find("TRANSFORM_OT_translate", true);
209         GizmoGroup2D *ggd = gizmogroup2d_init(gzgroup);
210         gzgroup->customdata = ggd;
211
212         MAN2D_ITER_AXES_BEGIN(axis, axis_idx)
213         {
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                 RNA_boolean_set(ptr, "release_confirm", 1);
237         }
238         MAN2D_ITER_AXES_END;
239
240         {
241                 wmOperatorType *ot_resize = WM_operatortype_find("TRANSFORM_OT_resize", true);
242                 wmOperatorType *ot_rotate = WM_operatortype_find("TRANSFORM_OT_rotate", true);
243                 PointerRNA *ptr;
244
245                 /* assign operator */
246                 ptr = WM_gizmo_operator_set(ggd->cage, 0, ot_translate, NULL);
247                 RNA_boolean_set(ptr, "release_confirm", 1);
248
249                 bool constraint_x[3] = {1, 0, 0};
250                 bool constraint_y[3] = {0, 1, 0};
251
252                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X, ot_resize, NULL);
253                 PropertyRNA *prop_release_confirm = RNA_struct_find_property(ptr, "release_confirm");
254                 PropertyRNA *prop_constraint_axis = RNA_struct_find_property(ptr, "constraint_axis");
255                 RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x);
256                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
257                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X, ot_resize, NULL);
258                 RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x);
259                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
260                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y, ot_resize, NULL);
261                 RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y);
262                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
263                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y, ot_resize, NULL);
264                 RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y);
265                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
266
267                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y, ot_resize, NULL);
268                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
269                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y, ot_resize, NULL);
270                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
271                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y, ot_resize, NULL);
272                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
273                 ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_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_ROTATE, ot_rotate, NULL);
276                 RNA_property_boolean_set(ptr, prop_release_confirm, true);
277         }
278 }
279
280 void ED_widgetgroup_gizmo2d_refresh(const bContext *C, wmGizmoGroup *gzgroup)
281 {
282         GizmoGroup2D *ggd = gzgroup->customdata;
283         float origin[3];
284         gizmo2d_calc_bounds(C, origin, ggd->min, ggd->max);
285         copy_v2_v2(ggd->origin, origin);
286         bool show_cage = !equals_v2v2(ggd->min, ggd->max);
287
288         if (show_cage) {
289                 ggd->cage->flag &= ~WM_GIZMO_HIDDEN;
290                 ggd->translate_x->flag |= WM_GIZMO_HIDDEN;
291                 ggd->translate_y->flag |= WM_GIZMO_HIDDEN;
292         }
293         else {
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
299         if (show_cage) {
300                 wmGizmoOpElem *gzop;
301                 float mid[2];
302                 const float *min = ggd->min;
303                 const float *max = ggd->max;
304                 mid_v2_v2v2(mid, min, max);
305
306                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X);
307                 PropertyRNA *prop_center_override = RNA_struct_find_property(&gzop->ptr, "center_override");
308                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){max[0], mid[1], 0.0f});
309                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X);
310                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){min[0], mid[1], 0.0f});
311                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y);
312                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){mid[0], max[1], 0.0f});
313                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y);
314                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){mid[0], min[1], 0.0f});
315
316                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y);
317                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){max[0], max[1], 0.0f});
318                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y);
319                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){max[0], min[1], 0.0f});
320                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y);
321                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){min[0], max[1], 0.0f});
322                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y);
323                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){min[0], min[1], 0.0f});
324
325                 gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE);
326                 RNA_property_float_set_array(&gzop->ptr, prop_center_override, (float[3]){mid[0], mid[1], 0.0f});
327         }
328 }
329
330 void ED_widgetgroup_gizmo2d_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup)
331 {
332         ARegion *ar = CTX_wm_region(C);
333         GizmoGroup2D *ggd = gzgroup->customdata;
334         float origin[3] = {UNPACK2(ggd->origin), 0.0f};
335         float origin_aa[3] = {UNPACK2(ggd->origin), 0.0f};
336
337         gizmo2d_origin_to_region(ar, origin);
338
339         MAN2D_ITER_AXES_BEGIN(axis, axis_idx)
340         {
341                 WM_gizmo_set_matrix_location(axis, origin);
342         }
343         MAN2D_ITER_AXES_END;
344
345         UI_view2d_view_to_region_m4(&ar->v2d, ggd->cage->matrix_space);
346         WM_gizmo_set_matrix_offset_location(ggd->cage, origin_aa);
347         ggd->cage->matrix_offset[0][0] = (ggd->max[0] - ggd->min[0]);
348         ggd->cage->matrix_offset[1][1] = (ggd->max[1] - ggd->min[1]);
349 }
350
351 /* TODO (Julian)
352  * - Called on every redraw, better to do a more simple poll and check for selection in _refresh
353  * - UV editing only, could be expanded for other things.
354  */
355 bool ED_widgetgroup_gizmo2d_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt))
356 {
357         if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) {
358                 return false;
359         }
360
361         SpaceImage *sima = CTX_wm_space_image(C);
362         Object *obedit = CTX_data_edit_object(C);
363
364         if (ED_space_image_show_uvedit(sima, obedit)) {
365                 Image *ima = ED_space_image(sima);
366                 Scene *scene = CTX_data_scene(C);
367                 BMEditMesh *em = BKE_editmesh_from_object(obedit);
368                 BMFace *efa;
369                 BMLoop *l;
370                 BMIter iter, liter;
371
372                 const int cd_loop_uv_offset  = CustomData_get_offset(&em->bm->ldata, CD_MLOOPUV);
373
374                 /* check if there's a selected poly */
375                 BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) {
376                         if (!uvedit_face_visible_test(scene, obedit, ima, efa))
377                                 continue;
378
379                         BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) {
380                                 if (uvedit_uv_select_test(scene, l, cd_loop_uv_offset)) {
381                                         return true;
382                                 }
383                         }
384                 }
385         }
386
387         return false;
388 }