Dopesheet: drag to box select in dopesheet
authorJacques Lucke <mail@jlucke.com>
Wed, 5 Jun 2019 13:48:30 +0000 (15:48 +0200)
committerJacques Lucke <mail@jlucke.com>
Wed, 5 Jun 2019 13:48:30 +0000 (15:48 +0200)
This includes refactoring of the `mouse_action_keys` to
make it easier to just detect if there is a key under
the mouse. The refactoring mostly consists of extracting
methods and reducing vertical scope of variables.

Reviewers: billreynish, brecht

Differential Revision: https://developer.blender.org/D5024

release/scripts/presets/keyconfig/keymap_data/blender_default.py
source/blender/editors/space_action/action_select.c

index 2a803c2284a05ba62f02b03100a7f7db68392665..d940e81980d969468f93b51eb97cf6ec33522e17 100644 (file)
@@ -1894,6 +1894,10 @@ def km_dopesheet(params):
          {"properties": [("axis_range", False)]}),
         ("action.select_box", {"type": 'B', "value": 'PRESS', "alt": True},
          {"properties": [("axis_range", True)]}),
+        ("action.select_box", {"type": params.select_tweak, "value": 'ANY'},
+         {"properties": [("tweak", True), ("mode", 'SET')]}),
+        ("action.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
+         {"properties": [("tweak", True), ("mode", 'ADD')]}),
         ("action.select_lasso", {"type": params.action_tweak, "value": 'ANY', "ctrl": True},
          {"properties": [("mode", 'ADD')]}),
         ("action.select_lasso", {"type": params.action_tweak, "value": 'ANY', "shift": True, "ctrl": True},
index 3560b0e1c7a735adf4eff07cc8858ad08a14f774..fc2970f4c31f32253ca9668aa4a846ff91ddc396 100644 (file)
 /* ************************************************************************** */
 /* KEYFRAMES STUFF */
 
+static bAnimListElem *actkeys_find_list_element_at_position(bAnimContext *ac,
+                                                            int filter,
+                                                            float region_x,
+                                                            float region_y)
+{
+  View2D *v2d = &ac->ar->v2d;
+
+  float view_x, view_y;
+  int channel_index;
+  UI_view2d_region_to_view(v2d, region_x, region_y, &view_x, &view_y);
+  UI_view2d_listview_view_to_cell(
+      0, ACHANNEL_STEP(ac), 0, ACHANNEL_FIRST_TOP(ac), view_x, view_y, NULL, &channel_index);
+
+  ListBase anim_data = {NULL, NULL};
+  ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+
+  bAnimListElem *ale = BLI_findlink(&anim_data, channel_index);
+  if (ale != NULL) {
+    BLI_remlink(&anim_data, ale);
+    ale->next = ale->prev = NULL;
+  }
+  ANIM_animdata_freelist(&anim_data);
+
+  return ale;
+}
+
+static void actkeys_list_element_to_keylist(bAnimContext *ac,
+                                            DLRBT_Tree *anim_keys,
+                                            bAnimListElem *ale)
+{
+  AnimData *adt = ANIM_nla_mapping_get(ac, ale);
+
+  bDopeSheet *ads = NULL;
+  if (ELEM(ac->datatype, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) {
+    ads = ac->data;
+  }
+
+  if (ale->key_data) {
+    switch (ale->datatype) {
+      case ALE_SCE: {
+        Scene *scene = (Scene *)ale->key_data;
+        scene_to_keylist(ads, scene, anim_keys, 0);
+        break;
+      }
+      case ALE_OB: {
+        Object *ob = (Object *)ale->key_data;
+        ob_to_keylist(ads, ob, anim_keys, 0);
+        break;
+      }
+      case ALE_ACT: {
+        bAction *act = (bAction *)ale->key_data;
+        action_to_keylist(adt, act, anim_keys, 0);
+        break;
+      }
+      case ALE_FCURVE: {
+        FCurve *fcu = (FCurve *)ale->key_data;
+        fcurve_to_keylist(adt, fcu, anim_keys, 0);
+        break;
+      }
+    }
+  }
+  else if (ale->type == ANIMTYPE_SUMMARY) {
+    /* dopesheet summary covers everything */
+    summary_to_keylist(ac, anim_keys, 0);
+  }
+  else if (ale->type == ANIMTYPE_GROUP) {
+    // TODO: why don't we just give groups key_data too?
+    bActionGroup *agrp = (bActionGroup *)ale->data;
+    agroup_to_keylist(adt, agrp, anim_keys, 0);
+  }
+  else if (ale->type == ANIMTYPE_GPLAYER) {
+    // TODO: why don't we just give gplayers key_data too?
+    bGPDlayer *gpl = (bGPDlayer *)ale->data;
+    gpl_to_keylist(ads, gpl, anim_keys);
+  }
+  else if (ale->type == ANIMTYPE_MASKLAYER) {
+    // TODO: why don't we just give masklayers key_data too?
+    MaskLayer *masklay = (MaskLayer *)ale->data;
+    mask_to_keylist(ads, masklay, anim_keys);
+  }
+}
+
+static void actkeys_find_key_in_list_element(bAnimContext *ac,
+                                             bAnimListElem *ale,
+                                             float region_x,
+                                             float *r_selx,
+                                             float *r_frame,
+                                             bool *r_found)
+{
+  *r_found = false;
+
+  View2D *v2d = &ac->ar->v2d;
+
+  DLRBT_Tree anim_keys;
+  BLI_dlrbTree_init(&anim_keys);
+  actkeys_list_element_to_keylist(ac, &anim_keys, ale);
+
+  AnimData *adt = ANIM_nla_mapping_get(ac, ale);
+
+  /* standard channel height (to allow for some slop) */
+  float key_hsize = ACHANNEL_HEIGHT(ac) * 0.8f;
+  /* half-size (for either side), but rounded up to nearest int (for easier targeting) */
+  key_hsize = roundf(key_hsize / 2.0f);
+
+  float xmin = UI_view2d_region_to_view_x(v2d, region_x - (int)key_hsize);
+  float xmax = UI_view2d_region_to_view_x(v2d, region_x + (int)key_hsize);
+
+  for (ActKeyColumn *ak = anim_keys.root; ak; ak = (ak->cfra < xmin) ? ak->right : ak->left) {
+    if (IN_RANGE(ak->cfra, xmin, xmax)) {
+      /* set the frame to use, and apply inverse-correction for NLA-mapping
+       * so that the frame will get selected by the selection functions without
+       * requiring to map each frame once again...
+       */
+      *r_selx = BKE_nla_tweakedit_remap(adt, ak->cfra, NLATIME_CONVERT_UNMAP);
+      *r_frame = ak->cfra;
+      *r_found = true;
+      break;
+    }
+  }
+
+  /* cleanup temporary lists */
+  BLI_dlrbTree_free(&anim_keys);
+}
+
+static void actkeys_find_key_at_position(bAnimContext *ac,
+                                         int filter,
+                                         float region_x,
+                                         float region_y,
+                                         bAnimListElem **r_ale,
+                                         float *r_selx,
+                                         float *r_frame,
+                                         bool *r_found)
+
+{
+  *r_found = false;
+  *r_ale = actkeys_find_list_element_at_position(ac, filter, region_x, region_y);
+
+  if (*r_ale != NULL) {
+    actkeys_find_key_in_list_element(ac, *r_ale, region_x, r_selx, r_frame, r_found);
+  }
+}
+
+static bool actkeys_is_key_at_position(bAnimContext *ac, float region_x, float region_y)
+{
+  bAnimListElem *ale;
+  float selx, frame;
+  bool found;
+
+  int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS;
+  actkeys_find_key_at_position(ac, filter, region_x, region_y, &ale, &selx, &frame, &found);
+
+  if (ale != NULL) {
+    MEM_freeN(ale);
+  }
+  return found;
+}
+
 /* ******************** Deselect All Operator ***************************** */
 /* This operator works in one of three ways:
  * 1) (de)select all (AKEY) - test if select all or deselect all
@@ -355,6 +512,21 @@ static void box_select_action(bAnimContext *ac, const rcti rect, short mode, sho
 
 /* ------------------- */
 
+static int actkeys_box_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  bAnimContext ac;
+  if (ANIM_animdata_get_context(C, &ac) == 0) {
+    return OPERATOR_CANCELLED;
+  }
+
+  bool tweak = RNA_boolean_get(op->ptr, "tweak");
+  if (tweak && actkeys_is_key_at_position(&ac, event->mval[0], event->mval[1])) {
+    return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
+  }
+
+  return WM_gesture_box_invoke(C, op, event);
+}
+
 static int actkeys_box_select_exec(bContext *C, wmOperator *op)
 {
   bAnimContext ac;
@@ -412,7 +584,7 @@ void ACTION_OT_select_box(wmOperatorType *ot)
   ot->description = "Select all keyframes within the specified region";
 
   /* api callbacks */
-  ot->invoke = WM_gesture_box_invoke;
+  ot->invoke = actkeys_box_select_invoke;
   ot->exec = actkeys_box_select_exec;
   ot->modal = WM_gesture_box_modal;
   ot->cancel = WM_gesture_box_cancel;
@@ -428,6 +600,10 @@ void ACTION_OT_select_box(wmOperatorType *ot)
   /* properties */
   WM_operator_properties_gesture_box(ot);
   WM_operator_properties_select_operation_simple(ot);
+
+  PropertyRNA *prop = RNA_def_boolean(
+      ot->srna, "tweak", 0, "Tweak", "Operator has been activated using a tweak event");
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 }
 
 /* ******************** Region Select Operators ***************************** */
@@ -1491,133 +1667,13 @@ static void mouse_action_keys(bAnimContext *ac,
                               const bool column,
                               const bool same_channel)
 {
-  ListBase anim_data = {NULL, NULL};
-  DLRBT_Tree anim_keys;
-  bAnimListElem *ale;
-  int filter;
+  int filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS;
 
-  View2D *v2d = &ac->ar->v2d;
-  bDopeSheet *ads = NULL;
-  int channel_index;
+  bAnimListElem *ale = NULL;
   bool found = false;
   float frame = 0.0f; /* frame of keyframe under mouse - NLA corrections not applied/included */
   float selx = 0.0f;  /* frame of keyframe under mouse */
-  float key_hsize;
-  float x, y;
-  rctf rectf;
-
-  /* get dopesheet info */
-  if (ELEM(ac->datatype, ANIMCONT_DOPESHEET, ANIMCONT_TIMELINE)) {
-    ads = ac->data;
-  }
-
-  /* use View2D to determine the index of the channel (i.e a row in the list) where keyframe was */
-  UI_view2d_region_to_view(v2d, mval[0], mval[1], &x, &y);
-  UI_view2d_listview_view_to_cell(
-      0, ACHANNEL_STEP(ac), 0, ACHANNEL_FIRST_TOP(ac), x, y, NULL, &channel_index);
-
-  /* x-range to check is +/- 7px for standard keyframe under standard dpi/y-scale
-   * (in screen/region-space), on either side of mouse click (size of keyframe icon).
-   */
-
-  /* standard channel height (to allow for some slop) */
-  key_hsize = ACHANNEL_HEIGHT(ac) * 0.8f;
-  /* half-size (for either side), but rounded up to nearest int (for easier targeting) */
-  key_hsize = roundf(key_hsize / 2.0f);
-
-  UI_view2d_region_to_view(v2d, mval[0] - (int)key_hsize, mval[1], &rectf.xmin, &rectf.ymin);
-  UI_view2d_region_to_view(v2d, mval[0] + (int)key_hsize, mval[1], &rectf.xmax, &rectf.ymax);
-
-  /* filter data */
-  filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_LIST_VISIBLE | ANIMFILTER_LIST_CHANNELS);
-  ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
-
-  /* try to get channel */
-  ale = BLI_findlink(&anim_data, channel_index);
-  if (ale != NULL) {
-    /* found match - must return here... */
-    AnimData *adt = ANIM_nla_mapping_get(ac, ale);
-    ActKeyColumn *ak, *akn = NULL;
-
-    /* make list of keyframes */
-    BLI_dlrbTree_init(&anim_keys);
-
-    if (ale->key_data) {
-      switch (ale->datatype) {
-        case ALE_SCE: {
-          Scene *scene = (Scene *)ale->key_data;
-          scene_to_keylist(ads, scene, &anim_keys, 0);
-          break;
-        }
-        case ALE_OB: {
-          Object *ob = (Object *)ale->key_data;
-          ob_to_keylist(ads, ob, &anim_keys, 0);
-          break;
-        }
-        case ALE_ACT: {
-          bAction *act = (bAction *)ale->key_data;
-          action_to_keylist(adt, act, &anim_keys, 0);
-          break;
-        }
-        case ALE_FCURVE: {
-          FCurve *fcu = (FCurve *)ale->key_data;
-          fcurve_to_keylist(adt, fcu, &anim_keys, 0);
-          break;
-        }
-      }
-    }
-    else if (ale->type == ANIMTYPE_SUMMARY) {
-      /* dopesheet summary covers everything */
-      summary_to_keylist(ac, &anim_keys, 0);
-    }
-    else if (ale->type == ANIMTYPE_GROUP) {
-      // TODO: why don't we just give groups key_data too?
-      bActionGroup *agrp = (bActionGroup *)ale->data;
-      agroup_to_keylist(adt, agrp, &anim_keys, 0);
-    }
-    else if (ale->type == ANIMTYPE_GPLAYER) {
-      // TODO: why don't we just give gplayers key_data too?
-      bGPDlayer *gpl = (bGPDlayer *)ale->data;
-      gpl_to_keylist(ads, gpl, &anim_keys);
-    }
-    else if (ale->type == ANIMTYPE_MASKLAYER) {
-      // TODO: why don't we just give masklayers key_data too?
-      MaskLayer *masklay = (MaskLayer *)ale->data;
-      mask_to_keylist(ads, masklay, &anim_keys);
-    }
-
-    /* start from keyframe at root of BST,
-     * traversing until we find one within the range that was clicked on */
-    for (ak = anim_keys.root; ak; ak = akn) {
-      if (IN_RANGE(ak->cfra, rectf.xmin, rectf.xmax)) {
-        /* set the frame to use, and apply inverse-correction for NLA-mapping
-         * so that the frame will get selected by the selection functions without
-         * requiring to map each frame once again...
-         */
-        selx = BKE_nla_tweakedit_remap(adt, ak->cfra, NLATIME_CONVERT_UNMAP);
-        frame = ak->cfra;
-        found = true;
-        break;
-      }
-      else if (ak->cfra < rectf.xmin) {
-        akn = ak->right;
-      }
-      else {
-        akn = ak->left;
-      }
-    }
-
-    /* Remove active channel from list of channels for separate treatment
-     * (since it's needed later on). */
-    BLI_remlink(&anim_data, ale);
-    ale->next = ale->prev = NULL;
-
-    /* cleanup temporary lists */
-    BLI_dlrbTree_free(&anim_keys);
-  }
-
-  /* free list of channels, since it's not used anymore */
-  ANIM_animdata_freelist(&anim_data);
+  actkeys_find_key_at_position(ac, filter, mval[0], mval[1], &ale, &selx, &frame, &found);
 
   /* For replacing selection, if we have something to select, we have to clear existing selection.
    * The same goes if we found nothing to select, and deselect_all is true
@@ -1700,7 +1756,7 @@ static void mouse_action_keys(bAnimContext *ac,
     /* flush tagged updates
      * NOTE: We temporarily add this channel back to the list so that this can happen
      */
-    anim_data.first = anim_data.last = ale;
+    ListBase anim_data = {ale, ale};
     ANIM_animdata_update(ac, &anim_data);
 
     /* free this channel */