Fix T63994: Node Editor: Move All Selected Nodes when dragging.
authorBastien Montagne <montagne29@wanadoo.fr>
Tue, 14 May 2019 13:51:49 +0000 (15:51 +0200)
committerBastien Montagne <montagne29@wanadoo.fr>
Tue, 14 May 2019 13:56:17 +0000 (15:56 +0200)
Left-click select broke that behavior, since it puts both action and
select buttons on the same physical mouse button...

To support this behavior again, we have to split selection process in
two steps, hence make it modal... While I remain rather skeptical about
that global design decision, and complexity it adds to many UI/UX areas,
this solution ends up being OK-ish I think.

Thanks to @brecht for some final tweaks on the patch.

source/blender/editors/space_node/node_select.c

index 944a54b..aab3282 100644 (file)
@@ -429,19 +429,24 @@ void node_select_single(bContext *C, bNode *node)
   WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
 }
 
-static int node_mouse_select(Main *bmain,
-                             SpaceNode *snode,
-                             ARegion *ar,
+static int node_mouse_select(bContext *C,
                              const int mval[2],
                              const bool extend,
                              const bool socket_select,
-                             const bool deselect_all)
+                             const bool deselect_all,
+                             const bool wait_to_deselect_others)
 {
+  Main *bmain = CTX_data_main(C);
+  SpaceNode *snode = CTX_wm_space_node(C);
+  ARegion *ar = CTX_wm_region(C);
   bNode *node, *tnode;
   bNodeSocket *sock = NULL;
   bNodeSocket *tsock;
   float cursor[2];
-  bool selected = false;
+  int ret_value = OPERATOR_CANCELLED;
+
+  /* Waiting to deselect others is only allowed for basic selection. */
+  BLI_assert(!(extend || socket_select) || !wait_to_deselect_others);
 
   /* get mouse coordinates in view2d space */
   UI_view2d_region_to_view(&ar->v2d, mval[0], mval[1], &cursor[0], &cursor[1]);
@@ -452,7 +457,7 @@ static int node_mouse_select(Main *bmain,
       /* NOTE: SOCK_IN does not take into account the extend case...
        * This feature is not really used anyway currently? */
       node_socket_toggle(node, sock, true);
-      selected = true;
+      ret_value = OPERATOR_FINISHED;
     }
     else if (node_find_indicated_socket(snode, &node, &sock, cursor, SOCK_OUT)) {
       if (sock->flag & SELECT) {
@@ -460,7 +465,7 @@ static int node_mouse_select(Main *bmain,
           node_socket_deselect(node, sock, true);
         }
         else {
-          selected = true;
+          ret_value = OPERATOR_FINISHED;
         }
       }
       else {
@@ -485,7 +490,7 @@ static int node_mouse_select(Main *bmain,
           }
         }
         node_socket_select(node, sock);
-        selected = true;
+        ret_value = OPERATOR_FINISHED;
       }
     }
   }
@@ -501,39 +506,52 @@ static int node_mouse_select(Main *bmain,
         if (!((node->flag & SELECT) && (node->flag & NODE_ACTIVE) == 0)) {
           node_toggle(node);
         }
-        selected = true;
+        ret_value = OPERATOR_FINISHED;
       }
     }
-    else {
-      if (node != NULL || deselect_all) {
+    else if (deselect_all && node == NULL) {
+      /* Deselect in empty space. */
+      for (tnode = snode->edittree->nodes.first; tnode; tnode = tnode->next) {
+        nodeSetSelected(tnode, false);
+      }
+      ret_value = OPERATOR_FINISHED;
+    }
+    else if (node != NULL) {
+      /* When clicking on an already selected node, we want to wait to deselect
+       * others and allow the user to start moving the node without that. */
+      if (wait_to_deselect_others && (node->flag & SELECT)) {
+        ret_value = OPERATOR_RUNNING_MODAL;
+      }
+      else {
+        nodeSetSelected(node, true);
+
         for (tnode = snode->edittree->nodes.first; tnode; tnode = tnode->next) {
-          nodeSetSelected(tnode, false);
-        }
-        selected = true;
-        if (node != NULL) {
-          nodeSetSelected(node, true);
+          if (tnode != node) {
+            nodeSetSelected(tnode, false);
+          }
         }
+
+        ret_value = OPERATOR_FINISHED;
       }
     }
   }
 
   /* update node order */
-  if (selected) {
-    if (node != NULL) {
+  if (ret_value != OPERATOR_CANCELLED) {
+    if (node != NULL && ret_value != OPERATOR_RUNNING_MODAL) {
       ED_node_set_active(bmain, snode->edittree, node);
     }
     ED_node_set_active_viewer_key(snode);
     ED_node_sort(snode->edittree);
+
+    WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
   }
 
-  return selected;
+  return ret_value;
 }
 
 static int node_select_exec(bContext *C, wmOperator *op)
 {
-  Main *bmain = CTX_data_main(C);
-  SpaceNode *snode = CTX_wm_space_node(C);
-  ARegion *ar = CTX_wm_region(C);
   int mval[2];
 
   /* get settings from RNA properties for operator */
@@ -546,17 +564,70 @@ static int node_select_exec(bContext *C, wmOperator *op)
   const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
 
   /* perform the select */
-  if (node_mouse_select(bmain, snode, ar, mval, extend, socket_select, deselect_all)) {
-    /* send notifiers */
-    WM_event_add_notifier(C, NC_NODE | NA_SELECTED, NULL);
+  const int ret_value = node_mouse_select(C, mval, extend, socket_select, deselect_all, false);
+
+  /* allow tweak event to work too */
+  return ret_value | OPERATOR_PASS_THROUGH;
+}
 
-    /* allow tweak event to work too */
-    return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
+static int node_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  const short init_event_type = (short)POINTER_AS_INT(op->customdata);
+
+  /* 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");
+
+  const bool extend = RNA_boolean_get(op->ptr, "extend");
+  /* always do socket_select when extending selection. */
+  const bool socket_select = extend || RNA_boolean_get(op->ptr, "socket_select");
+  const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
+
+  /* These cases are never modal. */
+  if (extend || socket_select) {
+    return node_select_exec(C, op);
   }
-  else {
-    /* allow tweak event to work too */
-    return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
+
+  if (init_event_type == 0) {
+    if (event->val == KM_PRESS) {
+      const int ret_value = node_mouse_select(C, mval, extend, socket_select, deselect_all, true);
+
+      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.
+       */
+      return node_select_exec(C, op);
+    }
+  }
+  else if (event->type == init_event_type && event->val == KM_RELEASE) {
+    const int ret_value = node_mouse_select(C, mval, extend, socket_select, deselect_all, false);
+    return ret_value | OPERATOR_PASS_THROUGH;
   }
+  else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
+    const int dx = mval[0] - event->mval[0];
+    const int dy = mval[1] - event->mval[1];
+    const float tweak_threshold = U.tweak_threshold * U.dpi_fac;
+    /* 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 (abs(dx) + abs(dy) > tweak_threshold) {
+      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;
 }
 
 static int node_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
@@ -564,7 +635,9 @@ static int node_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]);
 
-  return node_select_exec(C, op);
+  op->customdata = POINTER_FROM_INT(0);
+
+  return node_select_modal(C, op, event);
 }
 
 void NODE_OT_select(wmOperatorType *ot)
@@ -577,6 +650,7 @@ void NODE_OT_select(wmOperatorType *ot)
   /* api callbacks */
   ot->invoke = node_select_invoke;
   ot->exec = node_select_exec;
+  ot->modal = node_select_modal;
   ot->poll = ED_operator_node_active;
 
   /* flags */