/* SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edtransform * * \name 2D Transform Gizmo * * Used for UV/Image Editor */ #include "MEM_guardedalloc.h" #include "BLI_math.h" #include "DNA_object_types.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" #include "DNA_view3d_types.h" #include "BKE_context.h" #include "BKE_global.h" #include "BKE_layer.h" #include "RNA_access.h" #include "UI_resources.h" #include "UI_view2d.h" #include "WM_api.h" #include "WM_message.h" #include "WM_types.h" #include "ED_gizmo_library.h" #include "ED_gizmo_utils.h" #include "ED_image.h" #include "ED_screen.h" #include "ED_uvedit.h" #include "SEQ_channels.h" #include "SEQ_iterator.h" #include "SEQ_sequencer.h" #include "SEQ_time.h" #include "SEQ_transform.h" #include "transform.h" /* own include */ /* -------------------------------------------------------------------- */ /** \name Shared Callback's * \{ */ static bool gizmo2d_generic_poll(const bContext *C, wmGizmoGroupType *gzgt) { if (!ED_gizmo_poll_or_unlink_delayed_from_tool(C, gzgt)) { return false; } if ((U.gizmo_flag & USER_GIZMO_DRAW) == 0) { return false; } if (G.moving) { return false; } ScrArea *area = CTX_wm_area(C); if (area == NULL) { return false; } /* NOTE: below this is assumed to be a tool gizmo. * If there are cases that need to check other flags - this function could be split. */ switch (area->spacetype) { case SPACE_IMAGE: { const SpaceImage *sima = area->spacedata.first; Object *obedit = CTX_data_edit_object(C); if (!ED_space_image_show_uvedit(sima, obedit)) { return false; } break; } case SPACE_SEQ: { const SpaceSeq *sseq = area->spacedata.first; if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_TOOL)) { return false; } if (sseq->mainb != SEQ_DRAW_IMG_IMBUF) { return false; } Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); if (ed == NULL) { return false; } break; } } return true; } static void gizmo2d_pivot_point_message_subscribe(struct wmGizmoGroup *gzgroup, struct wmMsgBus *mbus, /* Additional args. */ bScreen *screen, ScrArea *area, ARegion *region) { wmMsgSubscribeValue msg_sub_value_gz_tag_refresh = { .owner = region, .user_data = gzgroup->parent_gzmap, .notify = WM_gizmo_do_msg_notify_tag_refresh, }; switch (area->spacetype) { case SPACE_IMAGE: { SpaceImage *sima = area->spacedata.first; PointerRNA ptr; RNA_pointer_create(&screen->id, &RNA_SpaceImageEditor, sima, &ptr); { const PropertyRNA *props[] = { &rna_SpaceImageEditor_pivot_point, (sima->around == V3D_AROUND_CURSOR) ? &rna_SpaceImageEditor_cursor_location : NULL, }; for (int i = 0; i < ARRAY_SIZE(props); i++) { if (props[i] == NULL) { continue; } WM_msg_subscribe_rna(mbus, &ptr, props[i], &msg_sub_value_gz_tag_refresh, __func__); } } break; } } } /** \} */ /* -------------------------------------------------------------------- */ /** \name Arrow / Cage Gizmo Group * * Defines public functions, not the gizmo itself: * * - #ED_widgetgroup_gizmo2d_xform_callbacks_set * - #ED_widgetgroup_gizmo2d_xform_no_cage_callbacks_set * * \{ */ /* axes as index */ enum { MAN2D_AXIS_TRANS_X = 0, MAN2D_AXIS_TRANS_Y, MAN2D_AXIS_LAST, }; typedef struct GizmoGroup2D { wmGizmo *translate_xy[3]; wmGizmo *cage; /* Current origin in view space, used to update widget origin for possible view changes */ float origin[2]; float min[2]; float max[2]; float rotation; bool no_cage; } GizmoGroup2D; /* **************** Utilities **************** */ static void gizmo2d_get_axis_color(const int axis_idx, float *r_col, float *r_col_hi) { const float alpha = 0.6f; const float alpha_hi = 1.0f; int col_id; switch (axis_idx) { case MAN2D_AXIS_TRANS_X: col_id = TH_AXIS_X; break; case MAN2D_AXIS_TRANS_Y: col_id = TH_AXIS_Y; break; default: BLI_assert(0); col_id = TH_AXIS_Y; break; } UI_GetThemeColor4fv(col_id, r_col); copy_v4_v4(r_col_hi, r_col); r_col[3] *= alpha; r_col_hi[3] *= alpha_hi; } static GizmoGroup2D *gizmogroup2d_init(wmGizmoGroup *gzgroup) { const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true); const wmGizmoType *gzt_cage = WM_gizmotype_find("GIZMO_GT_cage_2d", true); const wmGizmoType *gzt_button = WM_gizmotype_find("GIZMO_GT_button_2d", true); GizmoGroup2D *ggd = MEM_callocN(sizeof(GizmoGroup2D), __func__); ggd->translate_xy[0] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL); ggd->translate_xy[1] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL); ggd->translate_xy[2] = WM_gizmo_new_ptr(gzt_button, gzgroup, NULL); ggd->cage = WM_gizmo_new_ptr(gzt_cage, gzgroup, NULL); RNA_enum_set(ggd->cage->ptr, "transform", ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE | ED_GIZMO_CAGE2D_XFORM_FLAG_SCALE | ED_GIZMO_CAGE2D_XFORM_FLAG_ROTATE); return ggd; } /** * Calculates origin in view space, use with #gizmo2d_origin_to_region. */ static bool gizmo2d_calc_bounds(const bContext *C, float *r_center, float *r_min, float *r_max) { float min_buf[2], max_buf[2]; if (r_min == NULL) { r_min = min_buf; } if (r_max == NULL) { r_max = max_buf; } ScrArea *area = CTX_wm_area(C); bool has_select = false; if (area->spacetype == SPACE_IMAGE) { Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs( scene, view_layer, NULL, &objects_len); if (ED_uvedit_minmax_multi(scene, objects, objects_len, r_min, r_max)) { has_select = true; } MEM_freeN(objects); } else if (area->spacetype == SPACE_SEQ) { Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed); SeqCollection *strips = SEQ_query_rendered_strips(scene, channels, seqbase, scene->r.cfra, 0); SEQ_filter_selected_strips(strips); int selected_strips = SEQ_collection_len(strips); if (selected_strips > 0) { has_select = true; SEQ_image_transform_bounding_box_from_collection( scene, strips, selected_strips != 1, r_min, r_max); } SEQ_collection_free(strips); if (selected_strips > 1) { /* Don't draw the cage as transforming multiple strips isn't currently very useful as it * doesn't behave as one would expect. * * This is because our current transform system doesn't support shearing which would make the * scaling transforms of the bounding box behave weirdly. * In addition to this, the rotation of the bounding box can not currently be hooked up * properly to read the result from the transform system (when transforming multiple strips). */ const int pivot_point = scene->toolsettings->sequencer_tool_settings->pivot_point; if (pivot_point == V3D_AROUND_CURSOR) { SpaceSeq *sseq = area->spacedata.first; SEQ_image_preview_unit_to_px(scene, sseq->cursor, r_center); } else { mid_v2_v2v2(r_center, r_min, r_max); } zero_v2(r_min); zero_v2(r_max); return has_select; } } if (has_select == false) { zero_v2(r_min); zero_v2(r_max); } mid_v2_v2v2(r_center, r_min, r_max); return has_select; } static int gizmo2d_calc_transform_orientation(const bContext *C) { ScrArea *area = CTX_wm_area(C); if (area->spacetype != SPACE_SEQ) { return V3D_ORIENT_GLOBAL; } Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed); SeqCollection *strips = SEQ_query_rendered_strips(scene, channels, seqbase, scene->r.cfra, 0); SEQ_filter_selected_strips(strips); bool use_local_orient = SEQ_collection_len(strips) == 1; SEQ_collection_free(strips); if (use_local_orient) { return V3D_ORIENT_LOCAL; } return V3D_ORIENT_GLOBAL; } static float gizmo2d_calc_rotation(const bContext *C) { ScrArea *area = CTX_wm_area(C); if (area->spacetype != SPACE_SEQ) { return 0.0f; } Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed); SeqCollection *strips = SEQ_query_rendered_strips(scene, channels, seqbase, scene->r.cfra, 0); SEQ_filter_selected_strips(strips); if (SEQ_collection_len(strips) == 1) { /* Only return the strip rotation if only one is selected. */ Sequence *seq; SEQ_ITERATOR_FOREACH (seq, strips) { StripTransform *transform = seq->strip->transform; float mirror[2]; SEQ_image_transform_mirror_factor_get(seq, mirror); SEQ_collection_free(strips); return transform->rotation * mirror[0] * mirror[1]; } } SEQ_collection_free(strips); return 0.0f; } static bool seq_get_strip_pivot_median(const Scene *scene, float r_pivot[2]) { zero_v2(r_pivot); Editing *ed = SEQ_editing_get(scene); ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed); SeqCollection *strips = SEQ_query_rendered_strips(scene, channels, seqbase, scene->r.cfra, 0); SEQ_filter_selected_strips(strips); bool has_select = SEQ_collection_len(strips) != 0; if (has_select) { Sequence *seq; SEQ_ITERATOR_FOREACH (seq, strips) { float origin[2]; SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, origin); add_v2_v2(r_pivot, origin); } mul_v2_fl(r_pivot, 1.0f / SEQ_collection_len(strips)); } SEQ_collection_free(strips); return has_select; } static bool gizmo2d_calc_transform_pivot(const bContext *C, float r_pivot[2]) { ScrArea *area = CTX_wm_area(C); Scene *scene = CTX_data_scene(C); bool has_select = false; if (area->spacetype == SPACE_IMAGE) { SpaceImage *sima = area->spacedata.first; ViewLayer *view_layer = CTX_data_view_layer(C); ED_uvedit_center_from_pivot_ex(sima, scene, view_layer, r_pivot, sima->around, &has_select); } else if (area->spacetype == SPACE_SEQ) { SpaceSeq *sseq = area->spacedata.first; const int pivot_point = scene->toolsettings->sequencer_tool_settings->pivot_point; if (pivot_point == V3D_AROUND_CURSOR) { SEQ_image_preview_unit_to_px(scene, sseq->cursor, r_pivot); Editing *ed = SEQ_editing_get(scene); ListBase *seqbase = SEQ_active_seqbase_get(ed); ListBase *channels = SEQ_channels_displayed_get(ed); SeqCollection *strips = SEQ_query_rendered_strips( scene, channels, seqbase, scene->r.cfra, 0); SEQ_filter_selected_strips(strips); has_select = SEQ_collection_len(strips) != 0; SEQ_collection_free(strips); } else { has_select = seq_get_strip_pivot_median(scene, r_pivot); } } else { BLI_assert_msg(0, "Unhandled space type!"); } return has_select; } /** * Convert origin (or any other point) from view to region space. */ BLI_INLINE void gizmo2d_origin_to_region(ARegion *region, float *r_origin) { UI_view2d_view_to_region_fl(®ion->v2d, r_origin[0], r_origin[1], &r_origin[0], &r_origin[1]); } /** * Custom handler for gizmo widgets */ static int gizmo2d_modal(bContext *C, wmGizmo *widget, const wmEvent *UNUSED(event), eWM_GizmoFlagTweak UNUSED(tweak_flag)) { ARegion *region = CTX_wm_region(C); float origin[3]; gizmo2d_calc_transform_pivot(C, origin); gizmo2d_origin_to_region(region, origin); WM_gizmo_set_matrix_location(widget, origin); ED_region_tag_redraw_editor_overlays(region); return OPERATOR_RUNNING_MODAL; } static void gizmo2d_xform_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { wmOperatorType *ot_translate = WM_operatortype_find("TRANSFORM_OT_translate", true); GizmoGroup2D *ggd = gizmogroup2d_init(gzgroup); gzgroup->customdata = ggd; for (int i = 0; i < ARRAY_SIZE(ggd->translate_xy); i++) { wmGizmo *gz = ggd->translate_xy[i]; /* custom handler! */ WM_gizmo_set_fn_custom_modal(gz, gizmo2d_modal); if (i < 2) { float color[4], color_hi[4]; gizmo2d_get_axis_color(i, color, color_hi); /* set up widget data */ RNA_float_set(gz->ptr, "length", 0.8f); float axis[3] = {0.0f}; axis[i] = 1.0f; WM_gizmo_set_matrix_rotation_from_z_axis(gz, axis); float offset[3] = {0, 0, 0}; offset[2] = 0.18f; WM_gizmo_set_matrix_offset_location(gz, offset); gz->flag |= WM_GIZMO_DRAW_OFFSET_SCALE; WM_gizmo_set_line_width(gz, GIZMO_AXIS_LINE_WIDTH); WM_gizmo_set_color(gz, color); WM_gizmo_set_color_highlight(gz, color_hi); WM_gizmo_set_scale(gz, 1.0f); } else { float color[4], color_hi[4]; UI_GetThemeColor4fv(TH_GIZMO_VIEW_ALIGN, color); copy_v4_v4(color_hi, color); color[3] *= 0.6f; PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon"); RNA_property_enum_set(gz->ptr, prop, ICON_NONE); RNA_enum_set(gz->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_BACKDROP); /* Make the center low alpha. */ WM_gizmo_set_line_width(gz, 2.0f); RNA_float_set(gz->ptr, "backdrop_fill_alpha", 0.0); WM_gizmo_set_color(gz, color); WM_gizmo_set_color_highlight(gz, color_hi); WM_gizmo_set_scale(gz, 0.2f); } /* Assign operator. */ PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ot_translate, NULL); if (i < 2) { bool constraint[3] = {false}; constraint[i] = true; if (RNA_struct_find_property(ptr, "constraint_axis")) { RNA_boolean_set_array(ptr, "constraint_axis", constraint); } } RNA_boolean_set(ptr, "release_confirm", true); } { wmOperatorType *ot_resize = WM_operatortype_find("TRANSFORM_OT_resize", true); wmOperatorType *ot_rotate = WM_operatortype_find("TRANSFORM_OT_rotate", true); PointerRNA *ptr; /* assign operator */ ptr = WM_gizmo_operator_set(ggd->cage, 0, ot_translate, NULL); RNA_boolean_set(ptr, "release_confirm", 1); const bool constraint_x[3] = {1, 0, 0}; const bool constraint_y[3] = {0, 1, 0}; ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X, ot_resize, NULL); PropertyRNA *prop_release_confirm = RNA_struct_find_property(ptr, "release_confirm"); PropertyRNA *prop_constraint_axis = RNA_struct_find_property(ptr, "constraint_axis"); RNA_property_boolean_set(ptr, prop_release_confirm, true); RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x); ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_x); ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y); ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); RNA_property_boolean_set_array(ptr, prop_constraint_axis, constraint_y); ptr = WM_gizmo_operator_set( ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); ptr = WM_gizmo_operator_set( ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); ptr = WM_gizmo_operator_set( ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); ptr = WM_gizmo_operator_set( ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y, ot_resize, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); ptr = WM_gizmo_operator_set(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE, ot_rotate, NULL); RNA_property_boolean_set(ptr, prop_release_confirm, true); } } static void rotate_around_center_v2(float point[2], const float center[2], const float angle) { float tmp[2]; sub_v2_v2v2(tmp, point, center); rotate_v2_v2fl(point, tmp, angle); add_v2_v2(point, center); } static void gizmo2d_xform_refresh(const bContext *C, wmGizmoGroup *gzgroup) { GizmoGroup2D *ggd = gzgroup->customdata; bool has_select; if (ggd->no_cage) { has_select = gizmo2d_calc_transform_pivot(C, ggd->origin); } else { has_select = gizmo2d_calc_bounds(C, ggd->origin, ggd->min, ggd->max); ggd->rotation = gizmo2d_calc_rotation(C); } bool show_cage = !ggd->no_cage && !equals_v2v2(ggd->min, ggd->max); if (has_select == false) { /* Nothing selected. Disable gizmo drawing and return. */ ggd->cage->flag |= WM_GIZMO_HIDDEN; for (int i = 0; i < ARRAY_SIZE(ggd->translate_xy); i++) { ggd->translate_xy[i]->flag |= WM_GIZMO_HIDDEN; } return; } if (!show_cage) { /* Disable cage gizmo drawing and return. */ ggd->cage->flag |= WM_GIZMO_HIDDEN; for (int i = 0; i < ARRAY_SIZE(ggd->translate_xy); i++) { ggd->translate_xy[i]->flag &= ~WM_GIZMO_HIDDEN; } return; } /* We will show the cage gizmo! Setup all necessary data. */ ggd->cage->flag &= ~WM_GIZMO_HIDDEN; for (int i = 0; i < ARRAY_SIZE(ggd->translate_xy); i++) { ggd->translate_xy[i]->flag |= WM_GIZMO_HIDDEN; } } static void gizmo2d_xform_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup) { ARegion *region = CTX_wm_region(C); GizmoGroup2D *ggd = gzgroup->customdata; float origin[3] = {UNPACK2(ggd->origin), 0.0f}; gizmo2d_origin_to_region(region, origin); for (int i = 0; i < ARRAY_SIZE(ggd->translate_xy); i++) { wmGizmo *gz = ggd->translate_xy[i]; WM_gizmo_set_matrix_location(gz, origin); } UI_view2d_view_to_region_m4(®ion->v2d, ggd->cage->matrix_space); /* Define the bounding box of the gizmo in the offset transform matrix. */ unit_m4(ggd->cage->matrix_offset); ggd->cage->matrix_offset[0][0] = (ggd->max[0] - ggd->min[0]); ggd->cage->matrix_offset[1][1] = (ggd->max[1] - ggd->min[1]); ScrArea *area = CTX_wm_area(C); if (area->spacetype == SPACE_SEQ) { Scene *scene = CTX_data_scene(C); seq_get_strip_pivot_median(scene, origin); float matrix_rotate[4][4]; unit_m4(matrix_rotate); copy_v3_v3(matrix_rotate[3], origin); rotate_m4(matrix_rotate, 'Z', ggd->rotation); unit_m4(ggd->cage->matrix_basis); mul_m4_m4m4(ggd->cage->matrix_basis, matrix_rotate, ggd->cage->matrix_basis); float mid[2]; sub_v2_v2v2(mid, origin, ggd->origin); mul_v2_fl(mid, -1.0f); copy_v2_v2(ggd->cage->matrix_offset[3], mid); } else { const float origin_aa[3] = {UNPACK2(ggd->origin), 0.0f}; WM_gizmo_set_matrix_offset_location(ggd->cage, origin_aa); } } static void gizmo2d_xform_invoke_prepare(const bContext *C, wmGizmoGroup *gzgroup, wmGizmo *UNUSED(gz), const wmEvent *UNUSED(event)) { GizmoGroup2D *ggd = gzgroup->customdata; wmGizmoOpElem *gzop; const float *mid = ggd->origin; const float *min = ggd->min; const float *max = ggd->max; /* Define the different transform center points that will be used when grabbing the corners or * rotating with the gizmo. * * The coordinates are referred to as their cardinal directions: * N * o *NW | NE * x-----------x * | | *W| C |E * | | * x-----------x *SW S SE */ float n[3] = {mid[0], max[1], 0.0f}; float w[3] = {min[0], mid[1], 0.0f}; float e[3] = {max[0], mid[1], 0.0f}; float s[3] = {mid[0], min[1], 0.0f}; float nw[3] = {min[0], max[1], 0.0f}; float ne[3] = {max[0], max[1], 0.0f}; float sw[3] = {min[0], min[1], 0.0f}; float se[3] = {max[0], min[1], 0.0f}; float c[3] = {mid[0], mid[1], 0.0f}; float orient_matrix[3][3]; unit_m3(orient_matrix); ScrArea *area = CTX_wm_area(C); if (ggd->rotation != 0.0f && area->spacetype == SPACE_SEQ) { float origin[3]; Scene *scene = CTX_data_scene(C); seq_get_strip_pivot_median(scene, origin); /* We need to rotate the cardinal points so they align with the rotated bounding box. */ rotate_around_center_v2(n, origin, ggd->rotation); rotate_around_center_v2(w, origin, ggd->rotation); rotate_around_center_v2(e, origin, ggd->rotation); rotate_around_center_v2(s, origin, ggd->rotation); rotate_around_center_v2(nw, origin, ggd->rotation); rotate_around_center_v2(ne, origin, ggd->rotation); rotate_around_center_v2(sw, origin, ggd->rotation); rotate_around_center_v2(se, origin, ggd->rotation); rotate_around_center_v2(c, origin, ggd->rotation); axis_angle_to_mat3_single(orient_matrix, 'Z', ggd->rotation); } int orient_type = gizmo2d_calc_transform_orientation(C); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X); PropertyRNA *prop_center_override = RNA_struct_find_property(&gzop->ptr, "center_override"); PropertyRNA *prop_mouse_dir = RNA_struct_find_property(&gzop->ptr, "mouse_dir_constraint"); RNA_property_float_set_array(&gzop->ptr, prop_center_override, e); RNA_property_float_set_array(&gzop->ptr, prop_mouse_dir, orient_matrix[0]); RNA_enum_set(&gzop->ptr, "orient_type", orient_type); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X); RNA_property_float_set_array(&gzop->ptr, prop_center_override, w); RNA_property_float_set_array(&gzop->ptr, prop_mouse_dir, orient_matrix[0]); RNA_enum_set(&gzop->ptr, "orient_type", orient_type); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_Y); RNA_property_float_set_array(&gzop->ptr, prop_center_override, n); RNA_property_float_set_array(&gzop->ptr, prop_mouse_dir, orient_matrix[1]); RNA_enum_set(&gzop->ptr, "orient_type", orient_type); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_Y); RNA_property_float_set_array(&gzop->ptr, prop_center_override, s); RNA_property_float_set_array(&gzop->ptr, prop_mouse_dir, orient_matrix[1]); RNA_enum_set(&gzop->ptr, "orient_type", orient_type); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MIN_Y); RNA_property_float_set_array(&gzop->ptr, prop_center_override, ne); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MIN_X_MAX_Y); RNA_property_float_set_array(&gzop->ptr, prop_center_override, se); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MIN_Y); RNA_property_float_set_array(&gzop->ptr, prop_center_override, nw); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_SCALE_MAX_X_MAX_Y); RNA_property_float_set_array(&gzop->ptr, prop_center_override, sw); gzop = WM_gizmo_operator_get(ggd->cage, ED_GIZMO_CAGE2D_PART_ROTATE); RNA_property_float_set_array(&gzop->ptr, prop_center_override, c); } void ED_widgetgroup_gizmo2d_xform_callbacks_set(wmGizmoGroupType *gzgt) { gzgt->poll = gizmo2d_generic_poll; gzgt->setup = gizmo2d_xform_setup; gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag; gzgt->refresh = gizmo2d_xform_refresh; gzgt->draw_prepare = gizmo2d_xform_draw_prepare; gzgt->invoke_prepare = gizmo2d_xform_invoke_prepare; } static void gizmo2d_xform_setup_no_cage(const bContext *C, wmGizmoGroup *gzgroup) { gizmo2d_xform_setup(C, gzgroup); GizmoGroup2D *ggd = gzgroup->customdata; ggd->no_cage = true; } static void gizmo2d_xform_no_cage_message_subscribe(const struct bContext *C, struct wmGizmoGroup *gzgroup, struct wmMsgBus *mbus) { bScreen *screen = CTX_wm_screen(C); ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); gizmo2d_pivot_point_message_subscribe(gzgroup, mbus, screen, area, region); } void ED_widgetgroup_gizmo2d_xform_no_cage_callbacks_set(wmGizmoGroupType *gzgt) { ED_widgetgroup_gizmo2d_xform_callbacks_set(gzgt); gzgt->setup = gizmo2d_xform_setup_no_cage; gzgt->message_subscribe = gizmo2d_xform_no_cage_message_subscribe; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Scale Handles * * Defines public functions, not the gizmo itself: * * - #ED_widgetgroup_gizmo2d_resize_callbacks_set * * \{ */ typedef struct GizmoGroup_Resize2D { wmGizmo *gizmo_xy[3]; float origin[2]; float rotation; } GizmoGroup_Resize2D; static GizmoGroup_Resize2D *gizmogroup2d_resize_init(wmGizmoGroup *gzgroup) { const wmGizmoType *gzt_arrow = WM_gizmotype_find("GIZMO_GT_arrow_3d", true); const wmGizmoType *gzt_button = WM_gizmotype_find("GIZMO_GT_button_2d", true); GizmoGroup_Resize2D *ggd = MEM_callocN(sizeof(GizmoGroup_Resize2D), __func__); ggd->gizmo_xy[0] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL); ggd->gizmo_xy[1] = WM_gizmo_new_ptr(gzt_arrow, gzgroup, NULL); ggd->gizmo_xy[2] = WM_gizmo_new_ptr(gzt_button, gzgroup, NULL); return ggd; } static void gizmo2d_resize_refresh(const bContext *C, wmGizmoGroup *gzgroup) { GizmoGroup_Resize2D *ggd = gzgroup->customdata; float origin[3]; const bool has_select = gizmo2d_calc_transform_pivot(C, origin); if (has_select == false) { for (int i = 0; i < ARRAY_SIZE(ggd->gizmo_xy); i++) { ggd->gizmo_xy[i]->flag |= WM_GIZMO_HIDDEN; } } else { for (int i = 0; i < ARRAY_SIZE(ggd->gizmo_xy); i++) { ggd->gizmo_xy[i]->flag &= ~WM_GIZMO_HIDDEN; } copy_v2_v2(ggd->origin, origin); ggd->rotation = gizmo2d_calc_rotation(C); } } static void gizmo2d_resize_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup) { ARegion *region = CTX_wm_region(C); GizmoGroup_Resize2D *ggd = gzgroup->customdata; float origin[3] = {UNPACK2(ggd->origin), 0.0f}; gizmo2d_origin_to_region(region, origin); for (int i = 0; i < ARRAY_SIZE(ggd->gizmo_xy); i++) { wmGizmo *gz = ggd->gizmo_xy[i]; WM_gizmo_set_matrix_location(gz, origin); if (i < 2) { float axis[3] = {0.0f}, rotated_axis[3]; axis[i] = 1.0f; rotate_v3_v3v3fl(rotated_axis, axis, (float[3]){0, 0, 1}, ggd->rotation); WM_gizmo_set_matrix_rotation_from_z_axis(gz, rotated_axis); } } } static void gizmo2d_resize_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { wmOperatorType *ot_resize = WM_operatortype_find("TRANSFORM_OT_resize", true); GizmoGroup_Resize2D *ggd = gizmogroup2d_resize_init(gzgroup); gzgroup->customdata = ggd; for (int i = 0; i < ARRAY_SIZE(ggd->gizmo_xy); i++) { wmGizmo *gz = ggd->gizmo_xy[i]; /* custom handler! */ WM_gizmo_set_fn_custom_modal(gz, gizmo2d_modal); if (i < 2) { float color[4], color_hi[4]; gizmo2d_get_axis_color(i, color, color_hi); /* set up widget data */ RNA_float_set(gz->ptr, "length", 1.0f); RNA_enum_set(gz->ptr, "draw_style", ED_GIZMO_ARROW_STYLE_BOX); WM_gizmo_set_line_width(gz, GIZMO_AXIS_LINE_WIDTH); WM_gizmo_set_color(gz, color); WM_gizmo_set_color_highlight(gz, color_hi); WM_gizmo_set_scale(gz, 1.0f); } else { float color[4], color_hi[4]; UI_GetThemeColor4fv(TH_GIZMO_VIEW_ALIGN, color); copy_v4_v4(color_hi, color); color[3] *= 0.6f; PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon"); RNA_property_enum_set(gz->ptr, prop, ICON_NONE); RNA_enum_set(gz->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_BACKDROP); /* Make the center low alpha. */ WM_gizmo_set_line_width(gz, 2.0f); RNA_float_set(gz->ptr, "backdrop_fill_alpha", 0.0); WM_gizmo_set_color(gz, color); WM_gizmo_set_color_highlight(gz, color_hi); WM_gizmo_set_scale(gz, 1.2f); } /* Assign operator. */ PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ot_resize, NULL); if (i < 2) { bool constraint[3] = {false}; constraint[i] = true; if (RNA_struct_find_property(ptr, "constraint_axis")) { RNA_boolean_set_array(ptr, "constraint_axis", constraint); } } RNA_boolean_set(ptr, "release_confirm", true); } } static void gizmo2d_resize_invoke_prepare(const bContext *C, wmGizmoGroup *UNUSED(gzgroup), wmGizmo *gz, const wmEvent *UNUSED(event)) { wmGizmoOpElem *gzop; int orient_type = gizmo2d_calc_transform_orientation(C); gzop = WM_gizmo_operator_get(gz, 0); RNA_enum_set(&gzop->ptr, "orient_type", orient_type); } static void gizmo2d_resize_message_subscribe(const struct bContext *C, struct wmGizmoGroup *gzgroup, struct wmMsgBus *mbus) { bScreen *screen = CTX_wm_screen(C); ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); gizmo2d_pivot_point_message_subscribe(gzgroup, mbus, screen, area, region); } void ED_widgetgroup_gizmo2d_resize_callbacks_set(wmGizmoGroupType *gzgt) { gzgt->poll = gizmo2d_generic_poll; gzgt->setup = gizmo2d_resize_setup; gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag; gzgt->refresh = gizmo2d_resize_refresh; gzgt->draw_prepare = gizmo2d_resize_draw_prepare; gzgt->invoke_prepare = gizmo2d_resize_invoke_prepare; gzgt->message_subscribe = gizmo2d_resize_message_subscribe; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Rotate Handles * * Defines public functions, not the gizmo itself: * * - #ED_widgetgroup_gizmo2d_rotate_setup * * \{ */ typedef struct GizmoGroup_Rotate2D { wmGizmo *gizmo; float origin[2]; } GizmoGroup_Rotate2D; static GizmoGroup_Rotate2D *gizmogroup2d_rotate_init(wmGizmoGroup *gzgroup) { const wmGizmoType *gzt_button = WM_gizmotype_find("GIZMO_GT_button_2d", true); GizmoGroup_Rotate2D *ggd = MEM_callocN(sizeof(GizmoGroup_Rotate2D), __func__); ggd->gizmo = WM_gizmo_new_ptr(gzt_button, gzgroup, NULL); return ggd; } static void gizmo2d_rotate_refresh(const bContext *C, wmGizmoGroup *gzgroup) { GizmoGroup_Rotate2D *ggd = gzgroup->customdata; float origin[3]; const bool has_select = gizmo2d_calc_transform_pivot(C, origin); if (has_select == false) { ggd->gizmo->flag |= WM_GIZMO_HIDDEN; } else { ggd->gizmo->flag &= ~WM_GIZMO_HIDDEN; copy_v2_v2(ggd->origin, origin); } } static void gizmo2d_rotate_draw_prepare(const bContext *C, wmGizmoGroup *gzgroup) { ARegion *region = CTX_wm_region(C); GizmoGroup_Rotate2D *ggd = gzgroup->customdata; float origin[3] = {UNPACK2(ggd->origin), 0.0f}; gizmo2d_origin_to_region(region, origin); wmGizmo *gz = ggd->gizmo; WM_gizmo_set_matrix_location(gz, origin); } static void gizmo2d_rotate_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { wmOperatorType *ot_resize = WM_operatortype_find("TRANSFORM_OT_rotate", true); GizmoGroup_Rotate2D *ggd = gizmogroup2d_rotate_init(gzgroup); gzgroup->customdata = ggd; /* Other setup functions iterate over axis. */ { wmGizmo *gz = ggd->gizmo; /* custom handler! */ WM_gizmo_set_fn_custom_modal(gz, gizmo2d_modal); WM_gizmo_set_scale(gz, 1.2f); { float color[4]; UI_GetThemeColor4fv(TH_GIZMO_VIEW_ALIGN, color); PropertyRNA *prop = RNA_struct_find_property(gz->ptr, "icon"); RNA_property_enum_set(gz->ptr, prop, ICON_NONE); RNA_enum_set(gz->ptr, "draw_options", ED_GIZMO_BUTTON_SHOW_BACKDROP); /* Make the center low alpha. */ WM_gizmo_set_line_width(gz, 2.0f); RNA_float_set(gz->ptr, "backdrop_fill_alpha", 0.0); WM_gizmo_set_color(gz, color); WM_gizmo_set_color_highlight(gz, color); } /* Assign operator. */ PointerRNA *ptr = WM_gizmo_operator_set(gz, 0, ot_resize, NULL); RNA_boolean_set(ptr, "release_confirm", true); } } static void gizmo2d_rotate_message_subscribe(const struct bContext *C, struct wmGizmoGroup *gzgroup, struct wmMsgBus *mbus) { bScreen *screen = CTX_wm_screen(C); ScrArea *area = CTX_wm_area(C); ARegion *region = CTX_wm_region(C); gizmo2d_pivot_point_message_subscribe(gzgroup, mbus, screen, area, region); } void ED_widgetgroup_gizmo2d_rotate_callbacks_set(wmGizmoGroupType *gzgt) { gzgt->poll = gizmo2d_generic_poll; gzgt->setup = gizmo2d_rotate_setup; gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_maybe_drag; gzgt->refresh = gizmo2d_rotate_refresh; gzgt->draw_prepare = gizmo2d_rotate_draw_prepare; gzgt->message_subscribe = gizmo2d_rotate_message_subscribe; } /** \} */