Some RNA structs, like operators or keymaps, are not allowed to have ID pointer properties. now this check will ignore those, and report an error message in the console. Related to T82597. Notes: While a bit more involved than rBf39fbb3e6046, this commit remains fairly localized and non-intrusive. It relies on some rather obscure and weird behaviors of our RNA code though, a cleaner solution could be e.g. to add a tye to `StructOrFunctionRNA`, so that we could properly 'rebuild' (re-cast) the pointer to either `StructRNA` or `FunctionRNA` when needed in internal code...
1122 lines
31 KiB
C
1122 lines
31 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2007 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup wm
|
|
*
|
|
* Default operator callbacks for use with gestures (border/circle/lasso/straightline).
|
|
* Operators themselves are defined elsewhere.
|
|
*
|
|
* - Keymaps are in ``wm_operators.c``.
|
|
* - Property definitions are in ``wm_operator_props.c``.
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "BLI_math.h"
|
|
#include "BLI_rect.h"
|
|
|
|
#include "BKE_context.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "wm.h"
|
|
#include "wm_event_system.h"
|
|
#include "wm_event_types.h"
|
|
|
|
#include "ED_screen.h"
|
|
#include "ED_select_utils.h"
|
|
|
|
#include "UI_interface.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Internal Gesture Utilities
|
|
*
|
|
* Border gesture has two types:
|
|
* -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border.
|
|
* -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends.
|
|
*
|
|
* It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type).
|
|
*
|
|
* \{ */
|
|
|
|
static void gesture_modal_end(bContext *C, wmOperator *op)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
wmGesture *gesture = op->customdata;
|
|
|
|
WM_gesture_end(win, gesture); /* frees gesture itself, and unregisters from window */
|
|
op->customdata = NULL;
|
|
|
|
ED_area_tag_redraw(CTX_wm_area(C));
|
|
|
|
if (RNA_struct_find_property(op->ptr, "cursor")) {
|
|
WM_cursor_modal_restore(win);
|
|
}
|
|
}
|
|
|
|
static void gesture_modal_state_to_operator(wmOperator *op, int modal_state)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
switch (modal_state) {
|
|
case GESTURE_MODAL_SELECT:
|
|
case GESTURE_MODAL_DESELECT:
|
|
if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) {
|
|
RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT));
|
|
}
|
|
if ((prop = RNA_struct_find_property(op->ptr, "mode"))) {
|
|
RNA_property_enum_set(
|
|
op->ptr, prop, (modal_state == GESTURE_MODAL_DESELECT) ? SEL_OP_SUB : SEL_OP_ADD);
|
|
}
|
|
break;
|
|
case GESTURE_MODAL_IN:
|
|
case GESTURE_MODAL_OUT:
|
|
if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) {
|
|
RNA_property_boolean_set(op->ptr, prop, (modal_state == GESTURE_MODAL_OUT));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int UNUSED_FUNCTION(gesture_modal_state_from_operator)(wmOperator *op)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
if ((prop = RNA_struct_find_property(op->ptr, "deselect"))) {
|
|
if (RNA_property_is_set(op->ptr, prop)) {
|
|
return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_DESELECT :
|
|
GESTURE_MODAL_SELECT;
|
|
}
|
|
}
|
|
if ((prop = RNA_struct_find_property(op->ptr, "mode"))) {
|
|
if (RNA_property_is_set(op->ptr, prop)) {
|
|
return RNA_property_enum_get(op->ptr, prop) == SEL_OP_SUB ? GESTURE_MODAL_DESELECT :
|
|
GESTURE_MODAL_SELECT;
|
|
}
|
|
}
|
|
if ((prop = RNA_struct_find_property(op->ptr, "zoom_out"))) {
|
|
if (RNA_property_is_set(op->ptr, prop)) {
|
|
return RNA_property_boolean_get(op->ptr, prop) ? GESTURE_MODAL_OUT : GESTURE_MODAL_IN;
|
|
}
|
|
}
|
|
return GESTURE_MODAL_NOP;
|
|
}
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Border Gesture
|
|
*
|
|
* Border gesture has two types:
|
|
* -# #WM_GESTURE_CROSS_RECT: starts a cross, on mouse click it changes to border.
|
|
* -# #WM_GESTURE_RECT: starts immediate as a border, on mouse click or release it ends.
|
|
*
|
|
* It stores 4 values (xmin, xmax, ymin, ymax) and event it ended with (event_type).
|
|
*
|
|
* \{ */
|
|
|
|
static bool gesture_box_apply_rect(wmOperator *op)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (rect->xmin == rect->xmax || rect->ymin == rect->ymax) {
|
|
return 0;
|
|
}
|
|
|
|
/* operator arguments and storage. */
|
|
RNA_int_set(op->ptr, "xmin", min_ii(rect->xmin, rect->xmax));
|
|
RNA_int_set(op->ptr, "ymin", min_ii(rect->ymin, rect->ymax));
|
|
RNA_int_set(op->ptr, "xmax", max_ii(rect->xmin, rect->xmax));
|
|
RNA_int_set(op->ptr, "ymax", max_ii(rect->ymin, rect->ymax));
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool gesture_box_apply(bContext *C, wmOperator *op)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
|
|
int retval;
|
|
|
|
if (!gesture_box_apply_rect(op)) {
|
|
return 0;
|
|
}
|
|
|
|
if (gesture->wait_for_input) {
|
|
gesture_modal_state_to_operator(op, gesture->modal_state);
|
|
}
|
|
|
|
retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WM_gesture_box_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
const ARegion *region = CTX_wm_region(C);
|
|
const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input");
|
|
|
|
if (wait_for_input) {
|
|
op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_CROSS_RECT);
|
|
}
|
|
else {
|
|
op->customdata = WM_gesture_new(win, region, event, WM_GESTURE_RECT);
|
|
}
|
|
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
gesture->wait_for_input = wait_for_input;
|
|
}
|
|
|
|
/* add modal handler */
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
int WM_gesture_box_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
wmGesture *gesture = op->customdata;
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (event->type == EVT_MODAL_MAP) {
|
|
switch (event->val) {
|
|
case GESTURE_MODAL_MOVE: {
|
|
gesture->move = !gesture->move;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_BEGIN: {
|
|
if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) {
|
|
gesture->is_active = true;
|
|
wm_gesture_tag_redraw(win);
|
|
}
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_SELECT:
|
|
case GESTURE_MODAL_DESELECT:
|
|
case GESTURE_MODAL_IN:
|
|
case GESTURE_MODAL_OUT: {
|
|
if (gesture->wait_for_input) {
|
|
gesture->modal_state = event->val;
|
|
}
|
|
if (gesture_box_apply(C, op)) {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
case GESTURE_MODAL_CANCEL: {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch (event->type) {
|
|
case MOUSEMOVE: {
|
|
if (gesture->type == WM_GESTURE_CROSS_RECT && gesture->is_active == false) {
|
|
rect->xmin = rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymin = rect->ymax = event->y - gesture->winrct.ymin;
|
|
}
|
|
else if (gesture->move) {
|
|
BLI_rcti_translate(rect,
|
|
(event->x - gesture->winrct.xmin) - rect->xmax,
|
|
(event->y - gesture->winrct.ymin) - rect->ymax);
|
|
}
|
|
else {
|
|
rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymax = event->y - gesture->winrct.ymin;
|
|
}
|
|
gesture_box_apply_rect(op);
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
break;
|
|
}
|
|
#ifdef WITH_INPUT_NDOF
|
|
case NDOF_MOTION: {
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
#endif
|
|
|
|
#if 0 /* This allows view navigation, keep disabled as it's too unpredictable. */
|
|
default:
|
|
return OPERATOR_PASS_THROUGH;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
gesture->is_active_prev = gesture->is_active;
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
void WM_gesture_box_cancel(bContext *C, wmOperator *op)
|
|
{
|
|
gesture_modal_end(C, op);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Circle Gesture
|
|
*
|
|
* Currently only used for selection or modal paint stuff,
|
|
* calls #wmOperatorType.exec while hold mouse, exits on release
|
|
* (with no difference between cancel and confirm).
|
|
*
|
|
* \{ */
|
|
|
|
static void gesture_circle_apply(bContext *C, wmOperator *op);
|
|
|
|
int WM_gesture_circle_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
const bool wait_for_input = !ISTWEAK(event->type) && RNA_boolean_get(op->ptr, "wait_for_input");
|
|
|
|
op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_CIRCLE);
|
|
wmGesture *gesture = op->customdata;
|
|
rcti *rect = gesture->customdata;
|
|
|
|
/* Default or previously stored value. */
|
|
rect->xmax = RNA_int_get(op->ptr, "radius");
|
|
|
|
gesture->wait_for_input = wait_for_input;
|
|
|
|
/* Starting with the mode starts immediately,
|
|
* like having 'wait_for_input' disabled (some tools use this). */
|
|
if (gesture->wait_for_input == false) {
|
|
gesture->is_active = true;
|
|
gesture_circle_apply(C, op);
|
|
}
|
|
|
|
/* add modal handler */
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static void gesture_circle_apply(bContext *C, wmOperator *op)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (gesture->wait_for_input && (gesture->modal_state == GESTURE_MODAL_NOP)) {
|
|
return;
|
|
}
|
|
|
|
/* operator arguments and storage. */
|
|
RNA_int_set(op->ptr, "x", rect->xmin);
|
|
RNA_int_set(op->ptr, "y", rect->ymin);
|
|
RNA_int_set(op->ptr, "radius", rect->xmax);
|
|
|
|
/* When 'wait_for_input' is false,
|
|
* use properties to get the selection state (typically tool settings).
|
|
* This is done so executing as a mode can select & de-select, see: T58594. */
|
|
if (gesture->wait_for_input) {
|
|
gesture_modal_state_to_operator(op, gesture->modal_state);
|
|
}
|
|
|
|
if (op->type->exec) {
|
|
int retval;
|
|
retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
}
|
|
}
|
|
|
|
int WM_gesture_circle_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
wmGesture *gesture = op->customdata;
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (event->type == MOUSEMOVE) {
|
|
|
|
rect->xmin = event->x - gesture->winrct.xmin;
|
|
rect->ymin = event->y - gesture->winrct.ymin;
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
if (gesture->is_active) {
|
|
gesture_circle_apply(C, op);
|
|
}
|
|
}
|
|
else if (event->type == EVT_MODAL_MAP) {
|
|
bool is_circle_size = false;
|
|
bool is_finished = false;
|
|
float fac;
|
|
|
|
switch (event->val) {
|
|
case GESTURE_MODAL_CIRCLE_SIZE:
|
|
fac = 0.3f * (event->y - event->prevy);
|
|
if (fac > 0) {
|
|
rect->xmax += ceil(fac);
|
|
}
|
|
else {
|
|
rect->xmax += floor(fac);
|
|
}
|
|
if (rect->xmax < 1) {
|
|
rect->xmax = 1;
|
|
}
|
|
is_circle_size = true;
|
|
break;
|
|
case GESTURE_MODAL_CIRCLE_ADD:
|
|
rect->xmax += 2 + rect->xmax / 10;
|
|
is_circle_size = true;
|
|
break;
|
|
case GESTURE_MODAL_CIRCLE_SUB:
|
|
rect->xmax -= 2 + rect->xmax / 10;
|
|
if (rect->xmax < 1) {
|
|
rect->xmax = 1;
|
|
}
|
|
is_circle_size = true;
|
|
break;
|
|
case GESTURE_MODAL_SELECT:
|
|
case GESTURE_MODAL_DESELECT:
|
|
case GESTURE_MODAL_NOP: {
|
|
if (gesture->wait_for_input) {
|
|
gesture->modal_state = event->val;
|
|
}
|
|
if (event->val == GESTURE_MODAL_NOP) {
|
|
/* Single action, click-drag & release to exit. */
|
|
if (gesture->wait_for_input == false) {
|
|
is_finished = true;
|
|
}
|
|
}
|
|
else {
|
|
/* apply first click */
|
|
gesture->is_active = true;
|
|
gesture_circle_apply(C, op);
|
|
wm_gesture_tag_redraw(win);
|
|
}
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_CANCEL:
|
|
case GESTURE_MODAL_CONFIRM:
|
|
is_finished = true;
|
|
}
|
|
|
|
if (is_finished) {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_FINISHED; /* use finish or we don't get an undo */
|
|
}
|
|
|
|
if (is_circle_size) {
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
/* So next use remembers last seen size, even if we didn't apply it. */
|
|
RNA_int_set(op->ptr, "radius", rect->xmax);
|
|
}
|
|
}
|
|
#ifdef WITH_INPUT_NDOF
|
|
else if (event->type == NDOF_MOTION) {
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
/* Allow view navigation??? */
|
|
/* note, this gives issues:
|
|
* 1) other modal ops run on top (box select),
|
|
* 2) middle-mouse is used now 3) tablet/trackpad? */
|
|
else {
|
|
return OPERATOR_PASS_THROUGH;
|
|
}
|
|
#endif
|
|
|
|
gesture->is_active_prev = gesture->is_active;
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
void WM_gesture_circle_cancel(bContext *C, wmOperator *op)
|
|
{
|
|
gesture_modal_end(C, op);
|
|
}
|
|
|
|
#if 0
|
|
/* template to copy from */
|
|
void WM_OT_circle_gesture(wmOperatorType *ot)
|
|
{
|
|
ot->name = "Circle Gesture";
|
|
ot->idname = "WM_OT_circle_gesture";
|
|
ot->description = "Enter rotate mode with a circular gesture";
|
|
|
|
ot->invoke = WM_gesture_circle_invoke;
|
|
ot->modal = WM_gesture_circle_modal;
|
|
ot->poll = WM_operator_winactive;
|
|
|
|
/* properties */
|
|
WM_operator_properties_gesture_circle(ot);
|
|
}
|
|
#endif
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Tweak Gesture
|
|
* \{ */
|
|
|
|
static void gesture_tweak_modal(bContext *C, const wmEvent *event)
|
|
{
|
|
wmWindow *window = CTX_wm_window(C);
|
|
wmGesture *gesture = window->tweak;
|
|
rcti *rect = gesture->customdata;
|
|
bool gesture_end = false;
|
|
|
|
switch (event->type) {
|
|
case MOUSEMOVE:
|
|
case INBETWEEN_MOUSEMOVE: {
|
|
|
|
rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymax = event->y - gesture->winrct.ymin;
|
|
|
|
const int val = wm_gesture_evaluate(gesture, event);
|
|
if (val != 0) {
|
|
wmEvent tevent;
|
|
|
|
wm_event_init_from_window(window, &tevent);
|
|
/* We want to get coord from start of drag,
|
|
* not from point where it becomes a tweak event, see T40549. */
|
|
tevent.x = rect->xmin + gesture->winrct.xmin;
|
|
tevent.y = rect->ymin + gesture->winrct.ymin;
|
|
if (gesture->event_type == LEFTMOUSE) {
|
|
tevent.type = EVT_TWEAK_L;
|
|
}
|
|
else if (gesture->event_type == RIGHTMOUSE) {
|
|
tevent.type = EVT_TWEAK_R;
|
|
}
|
|
else {
|
|
tevent.type = EVT_TWEAK_M;
|
|
}
|
|
tevent.val = val;
|
|
tevent.is_repeat = false;
|
|
/* mouse coords! */
|
|
|
|
/* important we add immediately after this event, so future mouse releases
|
|
* (which may be in the queue already), are handled in order, see T44740 */
|
|
wm_event_add_ex(window, &tevent, event);
|
|
|
|
gesture_end = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case LEFTMOUSE:
|
|
case RIGHTMOUSE:
|
|
case MIDDLEMOUSE:
|
|
if (gesture->event_type == event->type) {
|
|
gesture_end = true;
|
|
|
|
/* when tweak fails we should give the other keymap entries a chance */
|
|
|
|
/* XXX, assigning to readonly, BAD JUJU! */
|
|
((wmEvent *)event)->val = KM_RELEASE;
|
|
}
|
|
break;
|
|
default:
|
|
if (!ISTIMER(event->type) && event->type != EVENT_NONE) {
|
|
gesture_end = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (gesture_end) {
|
|
/* Frees gesture itself, and unregisters from window. */
|
|
WM_gesture_end(window, gesture);
|
|
|
|
/* This isn't very nice but needed to redraw gizmos which are hidden while tweaking,
|
|
* See #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK for details. */
|
|
ARegion *region = CTX_wm_region(C);
|
|
if ((region != NULL) && (region->gizmo_map != NULL)) {
|
|
if (WM_gizmomap_tag_delay_refresh_for_tweak_check(region->gizmo_map)) {
|
|
ED_region_tag_redraw(region);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* standard tweak, called after window handlers passed on event */
|
|
void wm_tweakevent_test(bContext *C, const wmEvent *event, int action)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
if (win->tweak == NULL) {
|
|
const ARegion *region = CTX_wm_region(C);
|
|
|
|
if (region) {
|
|
if (event->val == KM_PRESS) {
|
|
if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
|
|
win->tweak = WM_gesture_new(win, region, event, WM_GESTURE_TWEAK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* no tweaks if event was handled */
|
|
if ((action & WM_HANDLER_BREAK)) {
|
|
WM_gesture_end(win, win->tweak);
|
|
}
|
|
else {
|
|
gesture_tweak_modal(C, event);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Lasso Gesture
|
|
* \{ */
|
|
|
|
int WM_gesture_lasso_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
PropertyRNA *prop;
|
|
|
|
op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_LASSO);
|
|
|
|
/* add modal handler */
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
|
|
WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
|
|
}
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
int WM_gesture_lines_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
PropertyRNA *prop;
|
|
|
|
op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_LINES);
|
|
|
|
/* add modal handler */
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
|
|
WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
|
|
}
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
static int gesture_lasso_apply(bContext *C, wmOperator *op)
|
|
{
|
|
int retval = OPERATOR_FINISHED;
|
|
wmGesture *gesture = op->customdata;
|
|
PointerRNA itemptr;
|
|
float loc[2];
|
|
int i;
|
|
const short *lasso = gesture->customdata;
|
|
|
|
/* operator storage as path. */
|
|
|
|
RNA_collection_clear(op->ptr, "path");
|
|
for (i = 0; i < gesture->points; i++, lasso += 2) {
|
|
loc[0] = lasso[0];
|
|
loc[1] = lasso[1];
|
|
RNA_collection_add(op->ptr, "path", &itemptr);
|
|
RNA_float_set_array(&itemptr, "loc", loc);
|
|
}
|
|
|
|
gesture_modal_end(C, op);
|
|
|
|
if (op->type->exec) {
|
|
retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
int WM_gesture_lasso_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
|
|
if (event->type == EVT_MODAL_MAP) {
|
|
switch (event->val) {
|
|
case GESTURE_MODAL_MOVE: {
|
|
gesture->move = !gesture->move;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch (event->type) {
|
|
case MOUSEMOVE:
|
|
case INBETWEEN_MOUSEMOVE: {
|
|
wm_gesture_tag_redraw(CTX_wm_window(C));
|
|
|
|
if (gesture->points == gesture->points_alloc) {
|
|
gesture->points_alloc *= 2;
|
|
gesture->customdata = MEM_reallocN(gesture->customdata,
|
|
sizeof(short[2]) * gesture->points_alloc);
|
|
}
|
|
|
|
{
|
|
short(*lasso)[2] = gesture->customdata;
|
|
|
|
const int x = ((event->x - gesture->winrct.xmin) - lasso[gesture->points - 1][0]);
|
|
const int y = ((event->y - gesture->winrct.ymin) - lasso[gesture->points - 1][1]);
|
|
|
|
/* move the lasso */
|
|
if (gesture->move) {
|
|
for (int i = 0; i < gesture->points; i++) {
|
|
lasso[i][0] += x;
|
|
lasso[i][1] += y;
|
|
}
|
|
}
|
|
/* Make a simple distance check to get a smoother lasso
|
|
* add only when at least 2 pixels between this and previous location. */
|
|
else if ((x * x + y * y) > pow2f(2.0f * UI_DPI_FAC)) {
|
|
lasso[gesture->points][0] = event->x - gesture->winrct.xmin;
|
|
lasso[gesture->points][1] = event->y - gesture->winrct.ymin;
|
|
gesture->points++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LEFTMOUSE:
|
|
case MIDDLEMOUSE:
|
|
case RIGHTMOUSE: {
|
|
if (event->val == KM_RELEASE) { /* key release */
|
|
return gesture_lasso_apply(C, op);
|
|
}
|
|
break;
|
|
}
|
|
case EVT_ESCKEY: {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
|
|
gesture->is_active_prev = gesture->is_active;
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
int WM_gesture_lines_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
return WM_gesture_lasso_modal(C, op, event);
|
|
}
|
|
|
|
void WM_gesture_lasso_cancel(bContext *C, wmOperator *op)
|
|
{
|
|
gesture_modal_end(C, op);
|
|
}
|
|
|
|
void WM_gesture_lines_cancel(bContext *C, wmOperator *op)
|
|
{
|
|
gesture_modal_end(C, op);
|
|
}
|
|
|
|
/**
|
|
* helper function, we may want to add options for conversion to view space
|
|
*
|
|
* caller must free.
|
|
*/
|
|
const int (*WM_gesture_lasso_path_to_array(bContext *UNUSED(C),
|
|
wmOperator *op,
|
|
int *r_mcoords_len))[2]
|
|
{
|
|
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "path");
|
|
int(*mcoords)[2] = NULL;
|
|
BLI_assert(prop != NULL);
|
|
|
|
if (prop) {
|
|
const int len = RNA_property_collection_length(op->ptr, prop);
|
|
|
|
if (len) {
|
|
int i = 0;
|
|
mcoords = MEM_mallocN(sizeof(int[2]) * len, __func__);
|
|
|
|
RNA_PROP_BEGIN (op->ptr, itemptr, prop) {
|
|
float loc[2];
|
|
|
|
RNA_float_get_array(&itemptr, "loc", loc);
|
|
mcoords[i][0] = (int)loc[0];
|
|
mcoords[i][1] = (int)loc[1];
|
|
i++;
|
|
}
|
|
RNA_PROP_END;
|
|
}
|
|
*r_mcoords_len = len;
|
|
}
|
|
else {
|
|
*r_mcoords_len = 0;
|
|
}
|
|
|
|
/* cast for 'const' */
|
|
return mcoords;
|
|
}
|
|
|
|
#if 0
|
|
/* template to copy from */
|
|
|
|
static int gesture_lasso_exec(bContext *C, wmOperator *op)
|
|
{
|
|
RNA_BEGIN (op->ptr, itemptr, "path") {
|
|
float loc[2];
|
|
|
|
RNA_float_get_array(&itemptr, "loc", loc);
|
|
printf("Location: %f %f\n", loc[0], loc[1]);
|
|
}
|
|
RNA_END;
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void WM_OT_lasso_gesture(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
ot->name = "Lasso Gesture";
|
|
ot->idname = "WM_OT_lasso_gesture";
|
|
ot->description = "Select objects within the lasso as you move the pointer";
|
|
|
|
ot->invoke = WM_gesture_lasso_invoke;
|
|
ot->modal = WM_gesture_lasso_modal;
|
|
ot->exec = gesture_lasso_exec;
|
|
|
|
ot->poll = WM_operator_winactive;
|
|
|
|
prop = RNA_def_property(ot->srna, "path", PROP_COLLECTION, PROP_NONE);
|
|
RNA_def_property_struct_runtime(ot->srna, prop, &RNA_OperatorMousePath);
|
|
}
|
|
#endif
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Straight Line Gesture
|
|
*
|
|
* Gesture defined by the start and end points of a line that is created between the position of
|
|
* the initial event and the position of the current event.
|
|
*
|
|
* Straight Line Gesture has two modal callbacks depending on the tool that is being implemented: a
|
|
* regular modal callback intended to update the data during the execution of the gesture and a
|
|
* one-shot callback that only updates the data once when the gesture finishes.
|
|
*
|
|
* It stores 4 values: `xstart, ystart, xend, yend`.
|
|
* \{ */
|
|
|
|
static bool gesture_straightline_apply(bContext *C, wmOperator *op)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (rect->xmin == rect->xmax && rect->ymin == rect->ymax) {
|
|
return 0;
|
|
}
|
|
|
|
/* operator arguments and storage. */
|
|
RNA_int_set(op->ptr, "xstart", rect->xmin);
|
|
RNA_int_set(op->ptr, "ystart", rect->ymin);
|
|
RNA_int_set(op->ptr, "xend", rect->xmax);
|
|
RNA_int_set(op->ptr, "yend", rect->ymax);
|
|
RNA_boolean_set(op->ptr, "flip", gesture->use_flip);
|
|
|
|
if (op->type->exec) {
|
|
int retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WM_gesture_straightline_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
PropertyRNA *prop;
|
|
|
|
op->customdata = WM_gesture_new(win, CTX_wm_region(C), event, WM_GESTURE_STRAIGHTLINE);
|
|
|
|
if (ISTWEAK(event->type)) {
|
|
wmGesture *gesture = op->customdata;
|
|
gesture->is_active = true;
|
|
}
|
|
|
|
/* add modal handler */
|
|
WM_event_add_modal_handler(C, op);
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
if ((prop = RNA_struct_find_property(op->ptr, "cursor"))) {
|
|
WM_cursor_modal_set(win, RNA_property_int_get(op->ptr, prop));
|
|
}
|
|
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
/**
|
|
* This invoke callback starts the straightline gesture with a viewport preview to the right side
|
|
* of the line.
|
|
*/
|
|
int WM_gesture_straightline_active_side_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
WM_gesture_straightline_invoke(C, op, event);
|
|
wmGesture *gesture = op->customdata;
|
|
gesture->draw_active_side = true;
|
|
gesture->use_flip = false;
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
#define STRAIGHTLINE_SNAP_DEG 15.0f
|
|
static void wm_gesture_straightline_do_angle_snap(rcti *rect)
|
|
{
|
|
const float line_start[2] = {rect->xmin, rect->ymin};
|
|
const float line_end[2] = {rect->xmax, rect->ymax};
|
|
const float x_axis[2] = {1.0f, 0.0f};
|
|
|
|
float line_direction[2];
|
|
sub_v2_v2v2(line_direction, line_end, line_start);
|
|
const float line_length = normalize_v2(line_direction);
|
|
|
|
const float angle = angle_signed_v2v2(x_axis, line_direction);
|
|
const float angle_deg = RAD2DEG(angle) + (STRAIGHTLINE_SNAP_DEG / 2.0f);
|
|
const float angle_snapped_deg = -floorf(angle_deg / STRAIGHTLINE_SNAP_DEG) *
|
|
STRAIGHTLINE_SNAP_DEG;
|
|
const float angle_snapped = DEG2RAD(angle_snapped_deg);
|
|
|
|
float line_snapped_end[2];
|
|
rotate_v2_v2fl(line_snapped_end, x_axis, angle_snapped);
|
|
mul_v2_fl(line_snapped_end, line_length);
|
|
add_v2_v2(line_snapped_end, line_start);
|
|
|
|
rect->xmax = (int)line_snapped_end[0];
|
|
rect->ymax = (int)line_snapped_end[1];
|
|
}
|
|
|
|
/**
|
|
* This modal callback calls exec once per mouse move event while the gesture is active with the
|
|
* updated line start and end values, so it can be used for tools that have a real time preview
|
|
* (like a gradient updating in real time over the mesh).
|
|
*/
|
|
int WM_gesture_straightline_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
wmWindow *win = CTX_wm_window(C);
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (event->type == EVT_MODAL_MAP) {
|
|
switch (event->val) {
|
|
case GESTURE_MODAL_MOVE: {
|
|
gesture->move = !gesture->move;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_BEGIN: {
|
|
if (gesture->is_active == false) {
|
|
gesture->is_active = true;
|
|
wm_gesture_tag_redraw(win);
|
|
}
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_SNAP: {
|
|
/* Toggle snapping on/off. */
|
|
gesture->use_snap = !gesture->use_snap;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_FLIP: {
|
|
/* Toggle snapping on/off. */
|
|
gesture->use_flip = !gesture->use_flip;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_SELECT: {
|
|
if (gesture_straightline_apply(C, op)) {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
case GESTURE_MODAL_CANCEL: {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch (event->type) {
|
|
case MOUSEMOVE: {
|
|
if (gesture->is_active == false) {
|
|
rect->xmin = rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymin = rect->ymax = event->y - gesture->winrct.ymin;
|
|
}
|
|
else if (gesture->move) {
|
|
BLI_rcti_translate(rect,
|
|
(event->x - gesture->winrct.xmin) - rect->xmax,
|
|
(event->y - gesture->winrct.ymin) - rect->ymax);
|
|
gesture_straightline_apply(C, op);
|
|
}
|
|
else {
|
|
rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymax = event->y - gesture->winrct.ymin;
|
|
gesture_straightline_apply(C, op);
|
|
}
|
|
|
|
if (gesture->use_snap) {
|
|
wm_gesture_straightline_do_angle_snap(rect);
|
|
}
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gesture->is_active_prev = gesture->is_active;
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
/**
|
|
* This modal one-shot callback only calls exec once after the gesture finishes without any updates
|
|
* during the gesture execution. Should be used for operations that are intended to be applied once
|
|
* without real time preview (like a trimming tool that only applies the bisect operation once
|
|
* after finishing the gesture as the bisect operation is too heavy to be computed in real time for
|
|
* a preview).
|
|
*/
|
|
int WM_gesture_straightline_oneshot_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
wmGesture *gesture = op->customdata;
|
|
wmWindow *win = CTX_wm_window(C);
|
|
rcti *rect = gesture->customdata;
|
|
|
|
if (event->type == EVT_MODAL_MAP) {
|
|
switch (event->val) {
|
|
case GESTURE_MODAL_MOVE: {
|
|
gesture->move = !gesture->move;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_BEGIN: {
|
|
if (gesture->is_active == false) {
|
|
gesture->is_active = true;
|
|
wm_gesture_tag_redraw(win);
|
|
}
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_SNAP: {
|
|
/* Toggle snapping on/off. */
|
|
gesture->use_snap = !gesture->use_snap;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_FLIP: {
|
|
/* Toggle flip on/off. */
|
|
gesture->use_flip = !gesture->use_flip;
|
|
break;
|
|
}
|
|
case GESTURE_MODAL_SELECT:
|
|
case GESTURE_MODAL_DESELECT:
|
|
case GESTURE_MODAL_IN:
|
|
case GESTURE_MODAL_OUT: {
|
|
if (gesture->wait_for_input) {
|
|
gesture->modal_state = event->val;
|
|
}
|
|
if (gesture_straightline_apply(C, op)) {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
case GESTURE_MODAL_CANCEL: {
|
|
gesture_modal_end(C, op);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
switch (event->type) {
|
|
case MOUSEMOVE: {
|
|
if (gesture->is_active == false) {
|
|
rect->xmin = rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymin = rect->ymax = event->y - gesture->winrct.ymin;
|
|
}
|
|
else if (gesture->move) {
|
|
BLI_rcti_translate(rect,
|
|
(event->x - gesture->winrct.xmin) - rect->xmax,
|
|
(event->y - gesture->winrct.ymin) - rect->ymax);
|
|
}
|
|
else {
|
|
rect->xmax = event->x - gesture->winrct.xmin;
|
|
rect->ymax = event->y - gesture->winrct.ymin;
|
|
}
|
|
|
|
if (gesture->use_snap) {
|
|
wm_gesture_straightline_do_angle_snap(rect);
|
|
}
|
|
|
|
wm_gesture_tag_redraw(win);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gesture->is_active_prev = gesture->is_active;
|
|
return OPERATOR_RUNNING_MODAL;
|
|
}
|
|
|
|
void WM_gesture_straightline_cancel(bContext *C, wmOperator *op)
|
|
{
|
|
gesture_modal_end(C, op);
|
|
}
|
|
|
|
#if 0
|
|
/* template to copy from */
|
|
void WM_OT_straightline_gesture(wmOperatorType *ot)
|
|
{
|
|
PropertyRNA *prop;
|
|
|
|
ot->name = "Straight Line Gesture";
|
|
ot->idname = "WM_OT_straightline_gesture";
|
|
ot->description = "Draw a straight line as you move the pointer";
|
|
|
|
ot->invoke = WM_gesture_straightline_invoke;
|
|
ot->modal = WM_gesture_straightline_modal;
|
|
ot->exec = gesture_straightline_exec;
|
|
|
|
ot->poll = WM_operator_winactive;
|
|
|
|
WM_operator_properties_gesture_straightline(ot, 0);
|
|
}
|
|
#endif
|
|
|
|
/** \} */
|