WM: Utilities for select operators to work with click-dragging items

Based on work by Bastien and Brecht in the Node Editor, this adds more
generalized support for selecting items so that click+drag actions on
items (nodes, makers, dopesheet keys, etc.) works as wanted.
Note that this only adds the barebones to support this in other editors,
it's not used yet (will be done in followup commits).

The behavior is supposed to work as follows:
* Clicking an unselected item immediately selects it, and deselects
  other items (doesn't wait for release events).
* Click+drag on an unselected item immediately selects it, deselects
  others and drags it in one go (don't require selecting it first!).
* Click+drag on a selected item won't change the selection state (and
  won't send an undo push) and start dragging all selected items as soon
  as the drag event is recognized.
* Clicking on a selected item will still deselect others, but that will
  only happen on mouse release, when we know the intention is not to drag
  the item.

Included in: https://developer.blender.org/D5979

Reviewed by: Brecht van Lommel, William Reynish
This commit is contained in:
Julian Eisel 2019-10-04 15:11:19 +02:00
parent 0bcada85b3
commit be2cd4bb53
Notes: blender-bot 2023-02-14 19:07:42 +01:00
Referenced by commit 7dea058546, UI: Move all Selected NLA-Strips when Dragging
Referenced by issue blender/blender-addons#70581, Ctrl+Shift+Click on a node does't work if the node is already selected (Link Viewer, Node Wrangler)
3 changed files with 93 additions and 0 deletions

View File

@ -335,6 +335,12 @@ void WM_event_timer_sleep(struct wmWindowManager *wm,
/* operator api, default callbacks */
/* invoke callback, uses enum property named "type" */
int WM_generic_select_modal(struct bContext *C,
struct wmOperator *op,
const struct wmEvent *event);
int WM_generic_select_invoke(struct bContext *C,
struct wmOperator *op,
const struct wmEvent *event);
void WM_operator_view3d_unit_defaults(struct bContext *C, struct wmOperator *op);
int WM_operator_smooth_viewtx_get(const struct wmOperator *op);
int WM_menu_invoke_ex(struct bContext *C, struct wmOperator *op, int opcontext);
@ -474,6 +480,7 @@ void WM_operator_properties_select_random(struct wmOperatorType *ot);
int WM_operator_properties_select_random_seed_increment_get(wmOperator *op);
void WM_operator_properties_select_operation(struct wmOperatorType *ot);
void WM_operator_properties_select_operation_simple(struct wmOperatorType *ot);
void WM_operator_properties_generic_select(struct wmOperatorType *ot);
struct CheckerIntervalParams {
int nth; /* bypass when set to zero */
int skip;

View File

@ -396,6 +396,16 @@ void WM_operator_properties_select_operation_simple(wmOperatorType *ot)
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
void WM_operator_properties_generic_select(wmOperatorType *ot)
{
PropertyRNA *prop = RNA_def_boolean(
ot->srna, "wait_to_deselect_others", true, "Wait to Deselect Others", "");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
RNA_def_int(ot->srna, "mouse_x", 0, INT_MIN, INT_MAX, "Mouse X", "", INT_MIN, INT_MAX);
RNA_def_int(ot->srna, "mouse_y", 0, INT_MIN, INT_MAX, "Mouse Y", "", INT_MIN, INT_MAX);
}
void WM_operator_properties_gesture_box_zoom(wmOperatorType *ot)
{
WM_operator_properties_border(ot);

View File

@ -700,6 +700,82 @@ void WM_operator_properties_free(PointerRNA *ptr)
/** \name Default Operator Callbacks
* \{ */
int WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
PropertyRNA *wait_to_deselect_prop = RNA_struct_find_property(op->ptr,
"wait_to_deselect_others");
const short init_event_type = (short)POINTER_AS_INT(op->customdata);
int ret_value = 0;
/* get settings from RNA properties for operator */
int mval[2];
mval[0] = RNA_int_get(op->ptr, "mouse_x");
mval[1] = RNA_int_get(op->ptr, "mouse_y");
if (init_event_type == 0) {
if (event->val == KM_PRESS) {
RNA_property_boolean_set(op->ptr, wait_to_deselect_prop, true);
ret_value = op->type->exec(C, op);
OPERATOR_RETVAL_CHECK(ret_value);
op->customdata = POINTER_FROM_INT((int)event->type);
if (ret_value & OPERATOR_RUNNING_MODAL) {
WM_event_add_modal_handler(C, op);
}
return ret_value | OPERATOR_PASS_THROUGH;
}
else {
/* If we are in init phase, and cannot validate init of modal operations,
* just fall back to basic exec.
*/
RNA_property_boolean_set(op->ptr, wait_to_deselect_prop, false);
ret_value = op->type->exec(C, op);
OPERATOR_RETVAL_CHECK(ret_value);
return ret_value | OPERATOR_PASS_THROUGH;
}
}
else if (event->type == init_event_type && event->val == KM_RELEASE) {
RNA_property_boolean_set(op->ptr, wait_to_deselect_prop, false);
ret_value = op->type->exec(C, op);
OPERATOR_RETVAL_CHECK(ret_value);
return ret_value | OPERATOR_PASS_THROUGH;
}
else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
const int drag_delta[2] = {
mval[0] - event->mval[0],
mval[1] - event->mval[1],
};
/* If user moves mouse more than defined threshold, we consider select operator as
* finished. Otherwise, it is still running until we get an 'release' event. In any
* case, we pass through event, but select op is not finished yet. */
if (WM_event_drag_test_with_delta(event, drag_delta)) {
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
else {
/* Important not to return anything other than PASS_THROUGH here,
* otherwise it prevents underlying tweak detection code to work properly. */
return OPERATOR_PASS_THROUGH;
}
}
return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
}
int WM_generic_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
RNA_int_set(op->ptr, "mouse_x", event->mval[0]);
RNA_int_set(op->ptr, "mouse_y", event->mval[1]);
op->customdata = POINTER_FROM_INT(0);
return op->type->modal(C, op, event);
}
void WM_operator_view3d_unit_defaults(struct bContext *C, struct wmOperator *op)
{
if (op->flag & OP_IS_INVOKE) {