diff options
10 files changed, 302 insertions, 81 deletions
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index f8793e2a747..25eb80672e3 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -2054,3 +2054,17 @@ bool checkUseAxisMatrix(TransInfo *t) return false; } + +bool transform_apply_matrix(TransInfo *t, float mat[4][4]) +{ + if (t->transform_matrix != NULL) { + t->transform_matrix(t, mat); + return true; + } + return false; +} + +void transform_final_value_get(const TransInfo *t, float *value, const int value_num) +{ + memcpy(value, t->values_final, sizeof(float) * value_num); +} diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index a3df6a44682..d1c2676169d 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -533,6 +533,13 @@ typedef struct TransInfo { /* Event handler function that determines whether the viewport needs to be redrawn. */ eRedrawFlag (*handleEvent)(struct TransInfo *, const struct wmEvent *); + /** + * Optional callback to transform a single matrix. + * + * \note used by the gizmo to transform the matrix used to position it. + */ + void (*transform_matrix)(struct TransInfo *t, float mat_xform[4][4]); + /** Constraint Data. */ TransCon con; @@ -713,6 +720,12 @@ void removeAspectRatio(TransInfo *t, float vec[2]); */ struct wmKeyMap *transform_modal_keymap(struct wmKeyConfig *keyconf); +/** + * Transform a single matrix using the current `t->final_values`. + */ +bool transform_apply_matrix(TransInfo *t, float mat[4][4]); +void transform_final_value_get(const TransInfo *t, float *value, int value_num); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index aa8dad2b95f..4499be66d5a 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -71,6 +71,10 @@ #include "GPU_state.h" +static void gizmo_refresh_from_matrix(wmGizmoGroup *gzgroup, + const float twmat[4][4], + const float scale[3]); + /* return codes for select, and drawing flags */ #define MAN_TRANS_X (1 << 0) @@ -1461,18 +1465,98 @@ static int gizmo_modal(bContext *C, ARegion *region = CTX_wm_region(C); RegionView3D *rv3d = region->regiondata; - struct TransformBounds tbounds; - - if (ED_transform_calc_gizmo_stats(C, - &(struct TransformCalcParams){ - .use_only_center = true, - }, - &tbounds)) { - gizmo_prepare_mat(C, rv3d, &tbounds); - WM_gizmo_set_matrix_location(widget, rv3d->twmat[3]); + wmGizmoGroup *gzgroup = widget->parent_gzgroup; + + /* Recalculating the orientation has two problems. + * - The matrix calculated based on the transformed selection may not match the matrix + * that was set when transform started. + * - Inspecting the selection for every update is expensive (for *every* redraw). + * + * Instead, use #transform_apply_matrix to transform `rv3d->twmat` or the final scale value + * when scaling. + */ + if (false) { + struct TransformBounds tbounds; + + if (ED_transform_calc_gizmo_stats(C, + &(struct TransformCalcParams){ + .use_only_center = true, + }, + &tbounds)) { + gizmo_prepare_mat(C, rv3d, &tbounds); + for (wmGizmo *gz = gzgroup->gizmos.first; gz; gz = gz->next) { + WM_gizmo_set_matrix_location(gz, rv3d->twmat[3]); + } + } } + else { + GizmoGroup *ggd = gzgroup->customdata; + + short axis_type = 0; + MAN_ITER_AXES_BEGIN (axis, axis_idx) { + if (axis == widget) { + axis_type = gizmo_get_axis_type(axis_idx); + break; + } + } + MAN_ITER_AXES_END; + + /* Showing axes which aren't being manipulated doesn't always work so well. + * + * - For rotate: global axis will reset after finish. + * Also, gimbal axis isn't properly recalculated while transforming. + * - For scale: showing the other axes isn't so useful and can be distracting + * since the handles can get very big-small. + */ + if (ELEM(axis_type, MAN_AXES_ROTATE, MAN_AXES_SCALE)) { + MAN_ITER_AXES_BEGIN (axis, axis_idx) { + if (axis == widget) { + continue; + } + + bool is_plane_dummy; + const uint aidx_norm = gizmo_orientation_axis(axis_idx, &is_plane_dummy); + /* Always show the axis-aligned handle as it's distracting when it's disabled. */ + if (aidx_norm == 3) { + continue; + } + WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true); + } + MAN_ITER_AXES_END; + } + + wmWindow *win = CTX_wm_window(C); + wmOperator *op = NULL; + for (int i = 0; i < widget->op_data_len; i++) { + wmGizmoOpElem *gzop = WM_gizmo_operator_get(widget, i); + op = WM_operator_find_modal_by_type(win, gzop->type); + if (op != NULL) { + break; + } + } + + if (op != NULL) { + float twmat[4][4]; + float scale_buf[3]; + float *scale = NULL; + bool update = false; + copy_m4_m4(twmat, rv3d->twmat); + + if (axis_type == MAN_AXES_SCALE) { + scale = scale_buf; + transform_final_value_get(op->customdata, scale, 3); + update = true; + } + else if (transform_apply_matrix(op->customdata, twmat)) { + update = true; + } - ED_region_tag_redraw_editor_overlays(region); + if (update) { + gizmo_refresh_from_matrix(gzgroup, twmat, scale); + ED_region_tag_redraw_editor_overlays(region); + } + } + } return OPERATOR_RUNNING_MODAL; } @@ -1638,46 +1722,23 @@ static void WIDGETGROUP_gizmo_setup(const bContext *C, wmGizmoGroup *gzgroup) gizmogroup_init_properties_from_twtype(gzgroup); } -static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup) +/** + * Set properties for axes. + * + * \param twmat: The transform matrix (typically #RegionView3D.twmat). + * \param scale: Optional scale, to show scale while modally dragging the scale handles. + */ +static void gizmo_refresh_from_matrix(wmGizmoGroup *gzgroup, + const float twmat[4][4], + const float scale[3]) { GizmoGroup *ggd = gzgroup->customdata; - Scene *scene = CTX_data_scene(C); - ScrArea *area = CTX_wm_area(C); - View3D *v3d = area->spacedata.first; - ARegion *region = CTX_wm_region(C); - RegionView3D *rv3d = region->regiondata; - struct TransformBounds tbounds; - - if (ggd->use_twtype_refresh) { - ggd->twtype = v3d->gizmo_show_object & ggd->twtype_init; - if (ggd->twtype != ggd->twtype_prev) { - ggd->twtype_prev = ggd->twtype; - gizmogroup_init_properties_from_twtype(gzgroup); - } - } - - const int orient_index = BKE_scene_orientation_get_index_from_flag(scene, ggd->twtype_init); - - /* skip, we don't draw anything anyway */ - if ((ggd->all_hidden = (ED_transform_calc_gizmo_stats(C, - &(struct TransformCalcParams){ - .use_only_center = true, - .orientation_index = orient_index + 1, - }, - &tbounds) == 0))) { - return; - } - - gizmo_prepare_mat(C, rv3d, &tbounds); - - /* *** set properties for axes *** */ MAN_ITER_AXES_BEGIN (axis, axis_idx) { const short axis_type = gizmo_get_axis_type(axis_idx); const int aidx_norm = gizmo_orientation_axis(axis_idx, NULL); - WM_gizmo_set_matrix_location(axis, rv3d->twmat[3]); - + WM_gizmo_set_matrix_location(axis, twmat[3]); switch (axis_idx) { case MAN_AXIS_TRANS_X: case MAN_AXIS_TRANS_Y: @@ -1690,7 +1751,7 @@ static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup) gizmo_line_range(ggd->twtype, axis_type, &start_co[2], &len); - WM_gizmo_set_matrix_rotation_from_z_axis(axis, rv3d->twmat[aidx_norm]); + WM_gizmo_set_matrix_rotation_from_z_axis(axis, twmat[aidx_norm]); RNA_float_set(axis->ptr, "length", len); if (axis_idx >= MAN_AXIS_RANGE_TRANS_START && axis_idx < MAN_AXIS_RANGE_TRANS_END) { @@ -1700,13 +1761,20 @@ static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup) } } WM_gizmo_set_matrix_offset_location(axis, start_co); + WM_gizmo_set_flag(axis, WM_GIZMO_DRAW_OFFSET_SCALE, true); + + if (scale) { + if (axis_type == MAN_AXES_SCALE) { + mul_v3_fl(axis->matrix_basis[2], scale[aidx_norm]); + } + } break; } case MAN_AXIS_ROT_X: case MAN_AXIS_ROT_Y: case MAN_AXIS_ROT_Z: - WM_gizmo_set_matrix_rotation_from_z_axis(axis, rv3d->twmat[aidx_norm]); + WM_gizmo_set_matrix_rotation_from_z_axis(axis, twmat[aidx_norm]); break; case MAN_AXIS_TRANS_XY: case MAN_AXIS_TRANS_YZ: @@ -1714,9 +1782,18 @@ static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup) case MAN_AXIS_SCALE_XY: case MAN_AXIS_SCALE_YZ: case MAN_AXIS_SCALE_ZX: { - const float *y_axis = rv3d->twmat[aidx_norm - 1 < 0 ? 2 : aidx_norm - 1]; - const float *z_axis = rv3d->twmat[aidx_norm]; + const int aidx_norm_x = (aidx_norm + 1) % 3; + const int aidx_norm_y = (aidx_norm + 2) % 3; + const float *y_axis = twmat[aidx_norm_y]; + const float *z_axis = twmat[aidx_norm]; WM_gizmo_set_matrix_rotation_from_yz_axis(axis, y_axis, z_axis); + + if (scale) { + if (axis_type == MAN_AXES_SCALE) { + mul_v3_fl(axis->matrix_basis[0], scale[aidx_norm_x]); + mul_v3_fl(axis->matrix_basis[1], scale[aidx_norm_y]); + } + } break; } } @@ -1733,6 +1810,49 @@ static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup) } } +static void WIDGETGROUP_gizmo_refresh(const bContext *C, wmGizmoGroup *gzgroup) +{ + ARegion *region = CTX_wm_region(C); + + { + wmGizmo *gz = WM_gizmomap_get_modal(region->gizmo_map); + if (gz && gz->parent_gzgroup == gzgroup) { + return; + } + } + + GizmoGroup *ggd = gzgroup->customdata; + Scene *scene = CTX_data_scene(C); + ScrArea *area = CTX_wm_area(C); + View3D *v3d = area->spacedata.first; + RegionView3D *rv3d = region->regiondata; + struct TransformBounds tbounds; + + if (ggd->use_twtype_refresh) { + ggd->twtype = v3d->gizmo_show_object & ggd->twtype_init; + if (ggd->twtype != ggd->twtype_prev) { + ggd->twtype_prev = ggd->twtype; + gizmogroup_init_properties_from_twtype(gzgroup); + } + } + + const int orient_index = BKE_scene_orientation_get_index_from_flag(scene, ggd->twtype_init); + + /* skip, we don't draw anything anyway */ + if ((ggd->all_hidden = (ED_transform_calc_gizmo_stats(C, + &(struct TransformCalcParams){ + .use_only_center = true, + .orientation_index = orient_index + 1, + }, + &tbounds) == 0))) { + return; + } + + gizmo_prepare_mat(C, rv3d, &tbounds); + + gizmo_refresh_from_matrix(gzgroup, rv3d->twmat, NULL); +} + static void WIDGETGROUP_gizmo_message_subscribe(const bContext *C, wmGizmoGroup *gzgroup, struct wmMsgBus *mbus) @@ -1756,11 +1876,22 @@ static void WIDGETGROUP_gizmo_draw_prepare(const bContext *C, wmGizmoGroup *gzgr copy_m3_m4(viewinv_m3, rv3d->viewinv); float idot[3]; + /* Re-calculate hidden unless modal. */ + bool calc_hidden = true; + { + wmGizmo *gz = WM_gizmomap_get_modal(region->gizmo_map); + if (gz && gz->parent_gzgroup == gzgroup) { + calc_hidden = false; + } + } + /* when looking through a selected camera, the gizmo can be at the * exact same position as the view, skip so we don't break selection */ if (ggd->all_hidden || fabsf(ED_view3d_pixel_size(rv3d, rv3d->twmat[3])) < 5e-7f) { MAN_ITER_AXES_BEGIN (axis, axis_idx) { - WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true); + if (calc_hidden) { + WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true); + } } MAN_ITER_AXES_END; return; @@ -1771,12 +1902,15 @@ static void WIDGETGROUP_gizmo_draw_prepare(const bContext *C, wmGizmoGroup *gzgr MAN_ITER_AXES_BEGIN (axis, axis_idx) { const short axis_type = gizmo_get_axis_type(axis_idx); /* XXX maybe unset _HIDDEN flag on redraw? */ - if (gizmo_is_axis_visible(rv3d, ggd->twtype, idot, axis_type, axis_idx)) { - WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, false); + if (calc_hidden) { + WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, false); + } } else { - WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true); + if (calc_hidden) { + WM_gizmo_set_flag(axis, WM_GIZMO_HIDDEN, true); + } continue; } @@ -1944,8 +2078,8 @@ void VIEW3D_GGT_xform_gizmo(wmGizmoGroupType *gzgt) gzgt->name = "3D View: Transform Gizmo"; gzgt->idname = "VIEW3D_GGT_xform_gizmo"; - gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_DRAW_MODAL_EXCLUDE | - WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; + gzgt->flag = WM_GIZMOGROUPTYPE_3D | WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP | + WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK; gzgt->gzmap_params.spaceid = SPACE_VIEW3D; gzgt->gzmap_params.regionid = RGN_TYPE_WINDOW; diff --git a/source/blender/editors/transform/transform_mode_rotate.c b/source/blender/editors/transform/transform_mode_rotate.c index 4b86adc4f46..94caaa288e5 100644 --- a/source/blender/editors/transform/transform_mode_rotate.c +++ b/source/blender/editors/transform/transform_mode_rotate.c @@ -322,10 +322,30 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) ED_area_status_text(t->area, str); } +static void applyRotationMatrix(TransInfo *t, float mat_xform[4][4]) +{ + float axis_final[3]; + const float angle_final = t->values_final[0]; + if ((t->con.mode & CON_APPLY) && t->con.applyRot) { + t->con.applyRot(t, NULL, NULL, axis_final, NULL); + } + else { + negate_v3_v3(axis_final, t->spacemtx[t->orient_axis]); + } + + float mat3[3][3]; + float mat4[4][4]; + axis_angle_normalized_to_mat3(mat3, axis_final, angle_final); + copy_m4_m3(mat4, mat3); + transform_pivot_set_m4(mat4, t->center_global); + mul_m4_m4m4(mat_xform, mat4, mat_xform); +} + void initRotation(TransInfo *t) { t->mode = TFM_ROTATION; t->transform = applyRotation; + t->transform_matrix = applyRotationMatrix; t->tsnap.applySnap = ApplySnapRotation; t->tsnap.distance = RotationBetween; diff --git a/source/blender/editors/transform/transform_mode_trackball.c b/source/blender/editors/transform/transform_mode_trackball.c index 2e405449bc3..3716826800b 100644 --- a/source/blender/editors/transform/transform_mode_trackball.c +++ b/source/blender/editors/transform/transform_mode_trackball.c @@ -75,19 +75,25 @@ static void transdata_elem_trackball_fn(void *__restrict iter_data_v, /** \name Transform (Rotation - Trackball) * \{ */ -static void applyTrackballValue(TransInfo *t, - const float axis1[3], - const float axis2[3], - const float angles[2]) +static void applyTrackballValue_calc_axis_angle(const TransInfo *t, + const float phi[2], + float r_axis[3], + float *r_angle) +{ + float axis1[3], axis2[3]; + normalize_v3_v3(axis1, t->persinv[0]); + normalize_v3_v3(axis2, t->persinv[1]); + + mul_v3_v3fl(r_axis, axis1, phi[0]); + madd_v3_v3fl(r_axis, axis2, phi[1]); + *r_angle = normalize_v3(r_axis); +} + +static void applyTrackballValue(TransInfo *t, const float axis[3], const float angle) { float mat_final[3][3]; - float axis[3]; - float angle; int i; - mul_v3_v3fl(axis, axis1, angles[0]); - madd_v3_v3fl(axis, axis2, angles[1]); - angle = normalize_v3(axis); axis_angle_normalized_to_mat3(mat_final, axis, angle); FOREACH_TRANS_DATA_CONTAINER (t, tc) { @@ -120,17 +126,8 @@ static void applyTrackball(TransInfo *t, const int UNUSED(mval[2])) { char str[UI_MAX_DRAW_STR]; size_t ofs = 0; - float axis1[3], axis2[3]; -#if 0 /* UNUSED */ - float mat[3][3], totmat[3][3], smat[3][3]; -#endif float phi[2]; - copy_v3_v3(axis1, t->persinv[0]); - copy_v3_v3(axis2, t->persinv[1]); - normalize_v3(axis1); - normalize_v3(axis2); - copy_v2_v2(phi, t->values); transform_snap_increment(t, phi); @@ -165,27 +162,35 @@ static void applyTrackball(TransInfo *t, const int UNUSED(mval[2])) str + ofs, sizeof(str) - ofs, TIP_(" Proportional size: %.2f"), t->prop_size); } -#if 0 /* UNUSED */ - axis_angle_normalized_to_mat3(smat, axis1, phi[0]); - axis_angle_normalized_to_mat3(totmat, axis2, phi[1]); + float axis_final[3], angle_final; + applyTrackballValue_calc_axis_angle(t, phi, axis_final, &angle_final); + applyTrackballValue(t, axis_final, angle_final); - mul_m3_m3m3(mat, smat, totmat); + recalcData(t); - /* TRANSFORM_FIX_ME */ - // copy_m3_m3(t->mat, mat); /* used in gizmo. */ -#endif + ED_area_status_text(t->area, str); +} - applyTrackballValue(t, axis1, axis2, phi); +static void applyTrackballMatrix(TransInfo *t, float mat_xform[4][4]) +{ + const float phi[2] = {UNPACK2(t->values_final)}; - recalcData(t); + float axis_final[3], angle_final; + applyTrackballValue_calc_axis_angle(t, phi, axis_final, &angle_final); - ED_area_status_text(t->area, str); + float mat3[3][3], mat4[4][4]; + axis_angle_normalized_to_mat3(mat3, axis_final, angle_final); + + copy_m4_m3(mat4, mat3); + transform_pivot_set_m4(mat4, t->center_global); + mul_m4_m4m4(mat_xform, mat4, mat_xform); } void initTrackball(TransInfo *t) { t->mode = TFM_TRACKBALL; t->transform = applyTrackball; + t->transform_matrix = applyTrackballMatrix; initMouseInputMode(t, &t->mouse, INPUT_TRACKBALL); diff --git a/source/blender/editors/transform/transform_mode_translate.c b/source/blender/editors/transform/transform_mode_translate.c index c881cde351e..e9982b79747 100644 --- a/source/blender/editors/transform/transform_mode_translate.c +++ b/source/blender/editors/transform/transform_mode_translate.c @@ -518,6 +518,13 @@ static void applyTranslation(TransInfo *t, const int UNUSED(mval[2])) ED_area_status_text(t->area, str); } +static void applyTranslationMatrix(TransInfo *t, float mat_xform[4][4]) +{ + float delta[3]; + mul_v3_m3v3(delta, t->spacemtx, t->values_final); + add_v3_v3(mat_xform[3], delta); +} + void initTranslation(TransInfo *t) { if (t->spacetype == SPACE_ACTION) { @@ -530,6 +537,7 @@ void initTranslation(TransInfo *t) } t->transform = applyTranslation; + t->transform_matrix = applyTranslationMatrix; t->tsnap.applySnap = ApplySnapTranslation; t->tsnap.distance = transform_snap_distance_len_squared_fn; diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index dec1260ad1d..ac06ababfd4 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -388,6 +388,12 @@ struct wmEventHandler_UI *WM_event_add_ui_handler(const struct bContext *C, wmUIHandlerRemoveFunc remove_fn, void *user_data, char flag); + +/** + * Return the first modal operator of type \a ot or NULL. + */ +wmOperator *WM_operator_find_modal_by_type(wmWindow *win, const wmOperatorType *ot); + /** * \param postpone: Enable for `win->modalhandlers`, * this is in a running for () loop in wm_handlers_do(). diff --git a/source/blender/windowmanager/gizmo/WM_gizmo_api.h b/source/blender/windowmanager/gizmo/WM_gizmo_api.h index 80d29bb8c40..663c8a0baed 100644 --- a/source/blender/windowmanager/gizmo/WM_gizmo_api.h +++ b/source/blender/windowmanager/gizmo/WM_gizmo_api.h @@ -388,6 +388,8 @@ void WM_gizmomap_message_subscribe(const struct bContext *C, struct ARegion *region, struct wmMsgBus *mbus); bool WM_gizmomap_is_any_selected(const struct wmGizmoMap *gzmap); +struct wmGizmo *WM_gizmomap_get_modal(const struct wmGizmoMap *gzmap); + /** * \note We could use a callback to define bounds, for now just use matrix location. */ diff --git a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c index f481f19045d..36faeab161f 100644 --- a/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c +++ b/source/blender/windowmanager/gizmo/intern/wm_gizmo_map.c @@ -229,6 +229,11 @@ bool WM_gizmomap_is_any_selected(const wmGizmoMap *gzmap) return gzmap->gzmap_context.select.len != 0; } +wmGizmo *WM_gizmomap_get_modal(const wmGizmoMap *gzmap) +{ + return gzmap->gzmap_context.modal; +} + bool WM_gizmomap_minmax(const wmGizmoMap *gzmap, bool UNUSED(use_hidden), bool use_select, diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 3ff3117dafe..b28ae16c5a1 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -4583,6 +4583,20 @@ void WM_event_remove_area_handler(ListBase *handlers, void *area) } } +wmOperator *WM_operator_find_modal_by_type(wmWindow *win, const wmOperatorType *ot) +{ + LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) { + if (handler_base->type != WM_HANDLER_TYPE_OP) { + continue; + } + wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base; + if (handler->op && handler->op->type == ot) { + return handler->op; + } + } + return NULL; +} + #if 0 static void WM_event_remove_handler(ListBase *handlers, wmEventHandler *handler) { |