UI: Changes to Graph Editor selection and transform
authorJulian Eisel <eiseljulian@gmail.com>
Fri, 22 Nov 2019 15:46:15 +0000 (16:46 +0100)
committerJulian Eisel <eiseljulian@gmail.com>
Fri, 22 Nov 2019 15:54:43 +0000 (16:54 +0100)
When introducing "drag-all-selected" support all over Blender, we
figured this wouldn't work well with the Graph Editor
selection/transform behavior.
Hence, William and I worked on the following changes, although we used
this chance to improve the behavior in general too.
For more info see T70634.

* Handles now always move with the key, regardless if they are selected
  or not.
* Selecting the key doesn't select the handles anymore, their selection
  is separate.
* Multiple keys and handles can now be dragged.
* Dragging a handle moves all selected handles **on the same side**.
* Tweak-dragging any handle can never affect any keyframe location,
  only handles.
* G/R/S should behave as before.
* Changing the handle type with a key selected always applies the change
  to both handles.
* Box selection with Ctrl+Drag now allows deselecting handles (used to
  act on entire triple only).
* Box selection //Include Handles// option now only acts on visible
  handles, wasn't the case with Only Selected Keyframes Handles enabled.
* Box selection //Include Handles// is now enabled by default in all
  bundled keymaps.

The changes have been tested for some days by the animators here in the
Blender Animation Studio. Some changes are based on their feedback.

Also, this improves/adds comments for related code.

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

Reviewed by: Sybren Stüvel, William Reynish

19 files changed:
release/scripts/presets/keyconfig/keymap_data/blender_default.py
release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py
source/blender/blenkernel/BKE_curve.h
source/blender/blenkernel/BKE_fcurve.h
source/blender/blenkernel/intern/curve.c
source/blender/blenkernel/intern/fcurve.c
source/blender/editors/animation/keyframes_edit.c
source/blender/editors/include/ED_keyframes_edit.h
source/blender/editors/space_graph/graph_buttons.c
source/blender/editors/space_graph/graph_select.c
source/blender/editors/space_graph/space_graph.c
source/blender/editors/transform/transform.c
source/blender/editors/transform/transform.h
source/blender/editors/transform/transform_convert.c
source/blender/editors/transform/transform_convert_graph.c
source/blender/editors/transform/transform_generics.c
source/blender/makesdna/DNA_space_types.h
source/blender/windowmanager/intern/wm_operator_props.c
source/blender/windowmanager/intern/wm_operators.c

index abbc8ad103e59fd667885c7d97b58efce69ac5e6..cc4e3e269ed7a8c3e31e60d09acd045c806cd8ee 100644 (file)
@@ -1411,13 +1411,9 @@ def km_graph_editor(params):
          {"properties": [("mode", 'RIGHT'), ("extend", False)]}),
         *_template_items_select_actions(params, "graph.select_all"),
         ("graph.select_box", {"type": 'B', "value": 'PRESS'},
-         {"properties": [("axis_range", False), ("include_handles", False)]}),
+         {"properties": [("axis_range", False)]}),
         ("graph.select_box", {"type": 'B', "value": 'PRESS', "alt": True},
-         {"properties": [("axis_range", True), ("include_handles", False)]}),
-        ("graph.select_box", {"type": 'B', "value": 'PRESS', "ctrl": True},
-         {"properties": [("axis_range", False), ("include_handles", True)]}),
-        ("graph.select_box", {"type": 'B', "value": 'PRESS', "ctrl": True, "alt": True},
-         {"properties": [("axis_range", True), ("include_handles", True)]}),
+         {"properties": [("axis_range", True)]}),
         ("graph.select_box", {"type": params.select_tweak, "value": 'ANY'},
          {"properties": [("tweak", True), ("mode", 'SET')]}),
         ("graph.select_box", {"type": params.select_tweak, "value": 'ANY', "shift": True},
index 2fa747b98ab9f50bb10525b21710044d7ae3a0d1..7d8ada2d117cc196da0129abbe5542901867e800 100644 (file)
@@ -860,19 +860,15 @@ def km_graph_editor(params):
         ("graph.select_all", {"type": 'A', "value": 'PRESS', "ctrl": True, "shift": True}, {"properties": [("action", 'DESELECT')]}),
         ("graph.select_all", {"type": 'I', "value": 'PRESS', "ctrl": True}, {"properties": [("action", 'INVERT')]}),
         ("graph.select_box", {"type": 'Q', "value": 'PRESS'},
-         {"properties": [("axis_range", False), ("include_handles", False)]}),
+         {"properties": [("axis_range", False)]}),
         ("graph.select_box", {"type": 'Q', "value": 'PRESS', "alt": True},
-         {"properties": [("axis_range", True), ("include_handles", False)]}),
-        ("graph.select_box", {"type": 'Q', "value": 'PRESS', "ctrl": True},
-         {"properties": [("axis_range", False), ("include_handles", True)]}),
-        ("graph.select_box", {"type": 'Q', "value": 'PRESS', "ctrl": True, "alt": True},
-         {"properties": [("axis_range", True), ("include_handles", True)]}),
+         {"properties": [("axis_range", True)]}),
         ("graph.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY'},
-         {"properties":[("tweak", True), ("axis_range", False), ("include_handles", False), ("mode", 'SET')]}),
+         {"properties":[("tweak", True), ("axis_range", False), ("mode", 'SET')]}),
         ("graph.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "shift": True},
-         {"properties":[("tweak", True), ("axis_range", False), ("include_handles", False), ("mode", 'ADD')]}),
+         {"properties":[("tweak", True), ("axis_range", False), ("mode", 'ADD')]}),
         ("graph.select_box", {"type": 'EVT_TWEAK_L', "value": 'ANY', "ctrl": True},
-         {"properties":[("tweak", True), ("axis_range", False),("include_handles", False), ("mode", 'SUB')]}),
+         {"properties":[("tweak", True), ("axis_range", False), ("mode", 'SUB')]}),
         ("graph.select_more", {"type": 'UP_ARROW', "value": 'PRESS'}, None),
         ("graph.select_less", {"type": 'DOWN_ARROW', "value": 'PRESS'}, None),
         ("graph.select_linked", {"type": 'RIGHT_BRACKET', "value": 'PRESS'}, None),
index 630c5fa1856bb5782d08169ec6ebf7d5d64d076b..e03780cc027299bcb1086964334efe7df790d79d 100644 (file)
@@ -35,6 +35,8 @@ struct Path;
 struct TextBox;
 struct rctf;
 
+typedef int eBezTriple_Flag__Alias;
+
 typedef struct CurveCache {
   ListBase disp;
   ListBase bev;
@@ -242,6 +244,12 @@ void BKE_nurb_handle_calc(struct BezTriple *bezt,
                           struct BezTriple *next,
                           const bool is_fcurve,
                           const char smoothing);
+void BKE_nurb_handle_calc_ex(struct BezTriple *bezt,
+                             struct BezTriple *prev,
+                             struct BezTriple *next,
+                             const eBezTriple_Flag__Alias handle_sel_flag,
+                             const bool is_fcurve,
+                             const char smoothing);
 void BKE_nurb_handle_calc_simple(struct Nurb *nu, struct BezTriple *bezt);
 void BKE_nurb_handle_calc_simple_auto(struct Nurb *nu, struct BezTriple *bezt);
 
@@ -249,7 +257,9 @@ void BKE_nurb_handle_smooth_fcurve(struct BezTriple *bezt, int total, bool cycli
 
 void BKE_nurb_handles_calc(struct Nurb *nu);
 void BKE_nurb_handles_autocalc(struct Nurb *nu, int flag);
-void BKE_nurb_bezt_handle_test(struct BezTriple *bezt, const bool use_handle);
+void BKE_nurb_bezt_handle_test(struct BezTriple *bezt,
+                               const eBezTriple_Flag__Alias sel_flag,
+                               const bool use_handle);
 void BKE_nurb_handles_test(struct Nurb *nu, const bool use_handles);
 
 /* **** Depsgraph evaluation **** */
index 5be9a35b1689ecbd01791b84bf9285544de722c5..426e0ed4b0ea96e3cc10391c99938520e9ed7180 100644 (file)
@@ -328,7 +328,8 @@ eFCU_Cycle_Type BKE_fcurve_get_cycle_type(struct FCurve *fcu);
 /* -------- Curve Sanity --------  */
 
 void calchandles_fcurve(struct FCurve *fcu);
-void testhandles_fcurve(struct FCurve *fcu, const bool use_handle);
+void calchandles_fcurve_ex(struct FCurve *fcu, eBezTriple_Flag handle_sel_flag);
+void testhandles_fcurve(struct FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle);
 void sort_time_fcurve(struct FCurve *fcu);
 short test_time_fcurve(struct FCurve *fcu);
 
index d81d250a305a0dd4ce033595e50627c84015b627..12bb7b573bde38e146c896bb4995b02d1af5a247 100644 (file)
@@ -3201,6 +3201,7 @@ void BKE_curve_bevelList_make(Object *ob, ListBase *nurbs, bool for_render)
 static void calchandleNurb_intern(BezTriple *bezt,
                                   const BezTriple *prev,
                                   const BezTriple *next,
+                                  eBezTriple_Flag handle_sel_flag,
                                   bool is_fcurve,
                                   bool skip_align,
                                   char fcurve_smoothing)
@@ -3402,7 +3403,7 @@ static void calchandleNurb_intern(BezTriple *bezt,
 
   len_ratio = len_a / len_b;
 
-  if (bezt->f1 & SELECT) {                               /* order of calculation */
+  if (bezt->f1 & handle_sel_flag) {                      /* order of calculation */
     if (ELEM(bezt->h2, HD_ALIGN, HD_ALIGN_DOUBLESIDE)) { /* aligned */
       if (len_a > eps) {
         len = 1.0f / len_ratio;
@@ -3443,7 +3444,7 @@ static void calchandleNurb_intern(BezTriple *bezt,
 #undef p2_h2
 }
 
-static void calchandlesNurb_intern(Nurb *nu, bool skip_align)
+static void calchandlesNurb_intern(Nurb *nu, eBezTriple_Flag handle_sel_flag, bool skip_align)
 {
   BezTriple *bezt, *prev, *next;
   int a;
@@ -3466,7 +3467,7 @@ static void calchandlesNurb_intern(Nurb *nu, bool skip_align)
   next = bezt + 1;
 
   while (a--) {
-    calchandleNurb_intern(bezt, prev, next, 0, skip_align, 0);
+    calchandleNurb_intern(bezt, prev, next, handle_sel_flag, 0, skip_align, 0);
     prev = bezt;
     if (a == 1) {
       if (nu->flagu & CU_NURB_CYCLIC) {
@@ -4038,15 +4039,36 @@ void BKE_nurb_handle_smooth_fcurve(BezTriple *bezt, int total, bool cycle)
   }
 }
 
+/**
+ * Recalculate the handles of a nurb bezier-triple. Acts based on handle selection with `SELECT`
+ * flag. To use a different flag, use #BKE_nurb_handle_calc_ex().
+ */
 void BKE_nurb_handle_calc(
     BezTriple *bezt, BezTriple *prev, BezTriple *next, const bool is_fcurve, const char smoothing)
 {
-  calchandleNurb_intern(bezt, prev, next, is_fcurve, false, smoothing);
+  calchandleNurb_intern(bezt, prev, next, SELECT, is_fcurve, false, smoothing);
+}
+
+/**
+ * Variant of #BKE_nurb_handle_calc() that allows calculating based on a different select flag.
+ *
+ * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`,
+ *                  but may want to use a different one at times (if caller does not operate on
+ *                  selection).
+ */
+void BKE_nurb_handle_calc_ex(BezTriple *bezt,
+                             BezTriple *prev,
+                             BezTriple *next,
+                             const eBezTriple_Flag__Alias handle_sel_flag,
+                             const bool is_fcurve,
+                             const char smoothing)
+{
+  calchandleNurb_intern(bezt, prev, next, handle_sel_flag, is_fcurve, false, smoothing);
 }
 
 void BKE_nurb_handles_calc(Nurb *nu) /* first, if needed, set handle flags */
 {
-  calchandlesNurb_intern(nu, false);
+  calchandlesNurb_intern(nu, SELECT, false);
 }
 
 /**
@@ -4101,11 +4123,21 @@ void BKE_nurb_handle_calc_simple_auto(Nurb *nu, BezTriple *bezt)
 }
 
 /**
+ * Update selected handle types to ensure valid state, e.g. deduce "Auto" types to concrete ones.
+ * Thereby \a sel_flag defines what qualifies as selected.
  * Use when something has changed handle positions.
  *
  * The caller needs to recalculate handles.
+ *
+ * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`,
+ *                  but may want to use a different one at times (if caller does not operate on
+ *                  selection).
+ * \param use_handle: Check selection state of individual handles, otherwise always update both
+ *                    handles if the key is selected.
  */
-void BKE_nurb_bezt_handle_test(BezTriple *bezt, const bool use_handle)
+void BKE_nurb_bezt_handle_test(BezTriple *bezt,
+                               const eBezTriple_Flag__Alias sel_flag,
+                               const bool use_handle)
 {
   short flag = 0;
 
@@ -4114,18 +4146,18 @@ void BKE_nurb_bezt_handle_test(BezTriple *bezt, const bool use_handle)
 #define SEL_F3 (1 << 2)
 
   if (use_handle) {
-    if (bezt->f1 & SELECT) {
+    if (bezt->f1 & sel_flag) {
       flag |= SEL_F1;
     }
-    if (bezt->f2 & SELECT) {
+    if (bezt->f2 & sel_flag) {
       flag |= SEL_F2;
     }
-    if (bezt->f3 & SELECT) {
+    if (bezt->f3 & sel_flag) {
       flag |= SEL_F3;
     }
   }
   else {
-    flag = (bezt->f2 & SELECT) ? (SEL_F1 | SEL_F2 | SEL_F3) : 0;
+    flag = (bezt->f2 & sel_flag) ? (SEL_F1 | SEL_F2 | SEL_F3) : 0;
   }
 
   /* check for partial selection */
@@ -4166,7 +4198,7 @@ void BKE_nurb_handles_test(Nurb *nu, const bool use_handle)
   bezt = nu->bezt;
   a = nu->pntsu;
   while (a--) {
-    BKE_nurb_bezt_handle_test(bezt, use_handle);
+    BKE_nurb_bezt_handle_test(bezt, SELECT, use_handle);
     bezt++;
   }
 
@@ -4639,7 +4671,7 @@ void BKE_curve_nurbs_vert_coords_apply_with_mat4(ListBase *lb,
       }
     }
 
-    calchandlesNurb_intern(nu, true);
+    calchandlesNurb_intern(nu, SELECT, true);
   }
 }
 
@@ -4677,7 +4709,7 @@ void BKE_curve_nurbs_vert_coords_apply(ListBase *lb,
       }
     }
 
-    calchandlesNurb_intern(nu, true);
+    calchandlesNurb_intern(nu, SELECT, true);
   }
 }
 
index 0f4845a32c0be2cdf78434ffd1320fb6475a1b4f..3876033eaaae5d59e39762a6e5b9a64cc250b473 100644 (file)
@@ -1036,10 +1036,14 @@ static BezTriple *cycle_offset_triple(
   return out;
 }
 
-/* This function recalculates the handles of an F-Curve
- * If the BezTriples have been rearranged, sort them first before using this.
+/**
+ * Variant of #calchandles_fcurve() that allows calculating based on a different select flag.
+ *
+ * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`,
+ *                  but may want to use a different one at times (if caller does not operate on
+ *                  selection).
  */
-void calchandles_fcurve(FCurve *fcu)
+void calchandles_fcurve_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag)
 {
   BezTriple *bezt, *prev, *next;
   int a = fcu->totvert;
@@ -1075,7 +1079,7 @@ void calchandles_fcurve(FCurve *fcu)
     }
 
     /* calculate auto-handles */
-    BKE_nurb_handle_calc(bezt, prev, next, true, fcu->auto_smoothing);
+    BKE_nurb_handle_calc_ex(bezt, prev, next, handle_sel_flag, true, fcu->auto_smoothing);
 
     /* for automatic ease in and out */
     if (BEZT_IS_AUTOH(bezt) && !cycle) {
@@ -1121,7 +1125,29 @@ void calchandles_fcurve(FCurve *fcu)
   }
 }
 
-void testhandles_fcurve(FCurve *fcu, const bool use_handle)
+/**
+ * This function recalculates the handles of an F-Curve. Acts based on selection with `SELECT`
+ * flag. To use a different flag, use #calchandles_fcurve_ex().
+ *
+ * If the BezTriples have been rearranged, sort them first before using this.
+ */
+void calchandles_fcurve(FCurve *fcu)
+{
+  calchandles_fcurve_ex(fcu, SELECT);
+}
+
+/**
+ * Update handles, making sure the handle-types are valid (e.g. correctly deduced from an "Auto"
+ * type), and recalculating their position vectors.
+ * Use when something has changed handle positions.
+ *
+ * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`,
+ *                  but may want to use a different one at times (if caller does not operate on
+ *                  selection).
+ * \param use_handle: Check selection state of individual handles, otherwise always update both
+ *                    handles if the key is selected.
+ */
+void testhandles_fcurve(FCurve *fcu, eBezTriple_Flag sel_flag, const bool use_handle)
 {
   BezTriple *bezt;
   unsigned int a;
@@ -1133,11 +1159,11 @@ void testhandles_fcurve(FCurve *fcu, const bool use_handle)
 
   /* loop over beztriples */
   for (a = 0, bezt = fcu->bezt; a < fcu->totvert; a++, bezt++) {
-    BKE_nurb_bezt_handle_test(bezt, use_handle);
+    BKE_nurb_bezt_handle_test(bezt, sel_flag, use_handle);
   }
 
   /* recalculate handles */
-  calchandles_fcurve(fcu);
+  calchandles_fcurve_ex(fcu, sel_flag);
 }
 
 /* This function sorts BezTriples so that they are arranged in chronological order,
index f8328e9f07f38d0cf4501f04319525d433f0591d..dc413e936ebd5b8c2dd59408be5b5a49460b2aa3 100644 (file)
@@ -501,10 +501,16 @@ void ANIM_editkeyframes_refresh(bAnimContext *ac)
       ok |= KEYFRAME_OK_KEY; \
 \
     if (ked && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES)) { \
-      if (check(0)) \
-        ok |= KEYFRAME_OK_H1; \
-      if (check(2)) \
-        ok |= KEYFRAME_OK_H2; \
+      /* Only act on visible items, so check handle visiblity state. */ \
+      const bool handles_visible = ((ked->iterflags & KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE) ? \
+                                        (BEZT_ISSEL_ANY(bezt)) : \
+                                        true); \
+      if (handles_visible) { \
+        if (check(0)) \
+          ok |= KEYFRAME_OK_H1; \
+        if (check(2)) \
+          ok |= KEYFRAME_OK_H2; \
+      } \
     } \
   } \
   (void)0
@@ -1054,7 +1060,11 @@ KeyframeEditFunc ANIM_editkeyframes_mirror(short type)
 /* Sets the selected bezier handles to type 'auto' */
 static short set_bezier_auto(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 {
-  if ((bezt->f1 & SELECT) || (bezt->f3 & SELECT)) {
+  /* If the key is selected, always apply to both handles. */
+  if (bezt->f2 & SELECT) {
+    bezt->h1 = bezt->h2 = HD_AUTO;
+  }
+  else {
     if (bezt->f1 & SELECT) {
       bezt->h1 = HD_AUTO;
     }
@@ -1064,6 +1074,7 @@ static short set_bezier_auto(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 
     ENSURE_HANDLES_MATCH(bezt);
   }
+
   return 0;
 }
 
@@ -1072,7 +1083,11 @@ static short set_bezier_auto(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
  */
 static short set_bezier_auto_clamped(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 {
-  if ((bezt->f1 & SELECT) || (bezt->f3 & SELECT)) {
+  /* If the key is selected, always apply to both handles. */
+  if (bezt->f2 & SELECT) {
+    bezt->h1 = bezt->h2 = HD_AUTO_ANIM;
+  }
+  else {
     if (bezt->f1 & SELECT) {
       bezt->h1 = HD_AUTO_ANIM;
     }
@@ -1082,18 +1097,26 @@ static short set_bezier_auto_clamped(KeyframeEditData *UNUSED(ked), BezTriple *b
 
     ENSURE_HANDLES_MATCH(bezt);
   }
+
   return 0;
 }
 
 /* Sets the selected bezier handles to type 'vector'  */
 static short set_bezier_vector(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 {
-  if (bezt->f1 & SELECT) {
-    bezt->h1 = HD_VECT;
+  /* If the key is selected, always apply to both handles. */
+  if (bezt->f2 & SELECT) {
+    bezt->h1 = bezt->h2 = HD_VECT;
   }
-  if (bezt->f3 & SELECT) {
-    bezt->h2 = HD_VECT;
+  else {
+    if (bezt->f1 & SELECT) {
+      bezt->h1 = HD_VECT;
+    }
+    if (bezt->f3 & SELECT) {
+      bezt->h2 = HD_VECT;
+    }
   }
+
   return 0;
 }
 
@@ -1114,24 +1137,38 @@ static short bezier_isfree(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 /* Sets selected bezier handles to type 'align' */
 static short set_bezier_align(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 {
-  if (bezt->f1 & SELECT) {
-    bezt->h1 = HD_ALIGN;
+  /* If the key is selected, always apply to both handles. */
+  if (bezt->f2 & SELECT) {
+    bezt->h1 = bezt->h2 = HD_ALIGN;
   }
-  if (bezt->f3 & SELECT) {
-    bezt->h2 = HD_ALIGN;
+  else {
+    if (bezt->f1 & SELECT) {
+      bezt->h1 = HD_ALIGN;
+    }
+    if (bezt->f3 & SELECT) {
+      bezt->h2 = HD_ALIGN;
+    }
   }
+
   return 0;
 }
 
 /* Sets selected bezier handles to type 'free'  */
 static short set_bezier_free(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
 {
-  if (bezt->f1 & SELECT) {
-    bezt->h1 = HD_FREE;
+  /* If the key is selected, always apply to both handles. */
+  if (bezt->f2 & SELECT) {
+    bezt->h1 = bezt->h2 = HD_FREE;
   }
-  if (bezt->f3 & SELECT) {
-    bezt->h2 = HD_FREE;
+  else {
+    if (bezt->f1 & SELECT) {
+      bezt->h1 = HD_FREE;
+    }
+    if (bezt->f3 & SELECT) {
+      bezt->h2 = HD_FREE;
+    }
   }
+
   return 0;
 }
 
@@ -1422,8 +1459,13 @@ KeyframeEditFunc ANIM_editkeyframes_easing(short mode)
 
 static short select_bezier_add(KeyframeEditData *ked, BezTriple *bezt)
 {
+  /* Only act on visible items, so check handle visiblity state. */
+  const bool handles_visible = ked && ((ked->iterflags & KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE) ?
+                                           (BEZT_ISSEL_ANY(bezt)) :
+                                           true);
+
   /* if we've got info on what to select, use it, otherwise select all */
-  if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES)) {
+  if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES) && handles_visible) {
     if (ked->curflags & KEYFRAME_OK_KEY) {
       bezt->f2 |= SELECT;
     }
@@ -1443,8 +1485,13 @@ static short select_bezier_add(KeyframeEditData *ked, BezTriple *bezt)
 
 static short select_bezier_subtract(KeyframeEditData *ked, BezTriple *bezt)
 {
+  /* Only act on visible items, so check handle visiblity state. */
+  const bool handles_visible = ked && ((ked->iterflags & KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE) ?
+                                           (BEZT_ISSEL_ANY(bezt)) :
+                                           true);
+
   /* if we've got info on what to deselect, use it, otherwise deselect all */
-  if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES)) {
+  if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES) && handles_visible) {
     if (ked->curflags & KEYFRAME_OK_KEY) {
       bezt->f2 &= ~SELECT;
     }
index 621a325eabd9ccb90e9bb0cb5f62c8ab0ba0d218..8181cebfe3ce3a6bc0d58e5ef15de1a5f10ab715 100644 (file)
@@ -141,6 +141,12 @@ typedef enum eKeyframeIterFlags {
 
   /* Perform NLA time remapping (global -> strip) for the "f2" parameter */
   KED_F2_NLA_UNMAP = (1 << 2),
+
+  /* Set this when handles aren't visible by default and you want to perform additional checks to
+   * get the actual visibility state. E.g. in some cases handles are only drawn if either a handle
+   * or their control point is selected. The selection state will have to be checked in the
+   * iterator callbacks then. */
+  KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE = (1 << 3),
 } eKeyframeIterFlags;
 
 /* --- Generic Properties for Keyframe Edit Tools ----- */
index 33cb1cb007508f1657ff36f4602788e5ba590a97..6c95e74e834c9a3df2b964ccc0af5392d7a8719c 100644 (file)
@@ -281,7 +281,7 @@ static void graphedit_activekey_handles_cb(bContext *C, void *fcu_ptr, void *bez
     bezt->h2 = HD_ALIGN;
   }
   else {
-    BKE_nurb_bezt_handle_test(bezt, true);
+    BKE_nurb_bezt_handle_test(bezt, SELECT, true);
   }
 
   /* now call standard updates */
index 4bd738fca95f1aecd2657955ef73359db8f94a17..9c1d63ef68ae3b8495c0725ca6e1210ee4afe0aa 100644 (file)
@@ -552,6 +552,10 @@ static void box_select_graphkeys(bAnimContext *ac,
     ked.data = &scaled_rectf;
   }
 
+  if (sipo->flag & SIPO_SELVHANDLESONLY) {
+    ked.iterflags |= KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE;
+  }
+
   /* treat handles separately? */
   if (incl_handles) {
     ked.iterflags |= KEYFRAME_ITER_INCL_HANDLES;
@@ -722,7 +726,7 @@ void GRAPH_OT_select_box(wmOperatorType *ot)
   ot->prop = RNA_def_boolean(ot->srna, "axis_range", 0, "Axis Range", "");
   RNA_def_boolean(ot->srna,
                   "include_handles",
-                  0,
+                  true,
                   "Include Handles",
                   "Are handles tested individually against the selection criteria");
 
@@ -1417,23 +1421,42 @@ void GRAPH_OT_select_leftright(wmOperatorType *ot)
  */
 
 /* option 1) select keyframe directly under mouse */
-static void mouse_graph_keys(bAnimContext *ac,
-                             const int mval[2],
-                             short select_mode,
-                             const bool deselect_all,
-                             const bool curves_only)
+static int mouse_graph_keys(bAnimContext *ac,
+                            const int mval[2],
+                            eEditKeyframes_Select select_mode,
+                            const bool deselect_all,
+                            const bool curves_only,
+                            bool wait_to_deselect_others)
 {
   SpaceGraph *sipo = (SpaceGraph *)ac->sl;
   tNearestVertInfo *nvi;
   BezTriple *bezt = NULL;
+  bool run_modal = false;
 
   /* find the beztriple that we're selecting, and the handle that was clicked on */
   nvi = find_nearest_fcurve_vert(ac, mval);
 
+  if (select_mode != SELECT_REPLACE) {
+    /* The modal execution to delay deselecting other items is only needed for normal click
+     * selection, i.e. for SELECT_REPLACE. */
+    wait_to_deselect_others = false;
+  }
+
+  sipo->runtime.flag &= ~(SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT |
+                          SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT);
+
+  const bool already_selected =
+      (nvi != NULL) && (((nvi->hpoint == NEAREST_HANDLE_KEY) && (nvi->bezt->f2 & SELECT)) ||
+                        ((nvi->hpoint == NEAREST_HANDLE_LEFT) && (nvi->bezt->f1 & SELECT)) ||
+                        ((nvi->hpoint == NEAREST_HANDLE_RIGHT) && (nvi->bezt->f3 & SELECT)));
+
+  if (wait_to_deselect_others && nvi && already_selected) {
+    run_modal = true;
+  }
   /* 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
    * (deselect on nothing behavior). */
-  if ((nvi != NULL && select_mode == SELECT_REPLACE) || (nvi == NULL && deselect_all)) {
+  else if ((nvi != NULL && select_mode == SELECT_REPLACE) || (nvi == NULL && deselect_all)) {
     /* reset selection mode */
     select_mode = SELECT_ADD;
 
@@ -1450,7 +1473,7 @@ static void mouse_graph_keys(bAnimContext *ac,
   }
 
   if (nvi == NULL) {
-    return;
+    return deselect_all ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
   }
 
   /* if points can be selected on this F-Curve */
@@ -1461,17 +1484,9 @@ static void mouse_graph_keys(bAnimContext *ac,
       bezt = nvi->bezt; /* used to check bezt seletion is set */
       /* depends on selection mode */
       if (select_mode == SELECT_INVERT) {
-        /* keyframe - invert select of all */
         if (nvi->hpoint == NEAREST_HANDLE_KEY) {
-          if (BEZT_ISSEL_ANY(bezt)) {
-            BEZT_DESEL_ALL(bezt);
-          }
-          else {
-            BEZT_SEL_ALL(bezt);
-          }
+          bezt->f2 ^= SELECT;
         }
-
-        /* handles - toggle selection of relevant handle */
         else if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
           /* toggle selection */
           bezt->f1 ^= SELECT;
@@ -1482,11 +1497,9 @@ static void mouse_graph_keys(bAnimContext *ac,
         }
       }
       else {
-        /* if the keyframe was clicked on, select all verts of given beztriple */
         if (nvi->hpoint == NEAREST_HANDLE_KEY) {
-          BEZT_SEL_ALL(bezt);
+          bezt->f2 |= SELECT;
         }
-        /* otherwise, select the handle that applied */
         else if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
           bezt->f1 |= SELECT;
         }
@@ -1547,8 +1560,17 @@ static void mouse_graph_keys(bAnimContext *ac,
     ANIM_set_active_channel(ac, ac->data, ac->datatype, filter, nvi->fcu, nvi->ctype);
   }
 
+  if (nvi->hpoint == NEAREST_HANDLE_LEFT) {
+    sipo->runtime.flag |= SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT;
+  }
+  else if (nvi->hpoint == NEAREST_HANDLE_RIGHT) {
+    sipo->runtime.flag |= SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT;
+  }
+
   /* free temp sample data for filtering */
   MEM_freeN(nvi);
+
+  return run_modal ? OPERATOR_RUNNING_MODAL : OPERATOR_FINISHED;
 }
 
 /* Option 2) Selects all the keyframes on either side of the current frame
@@ -1556,11 +1578,15 @@ static void mouse_graph_keys(bAnimContext *ac,
 /* (see graphkeys_select_leftright) */
 
 /* Option 3) Selects all visible keyframes in the same frame as the mouse click */
-static void graphkeys_mselect_column(bAnimContext *ac, const int mval[2], short select_mode)
+static int graphkeys_mselect_column(bAnimContext *ac,
+                                    const int mval[2],
+                                    eEditKeyframes_Select select_mode,
+                                    bool wait_to_deselect_others)
 {
   ListBase anim_data = {NULL, NULL};
   bAnimListElem *ale;
   int filter;
+  bool run_modal = false;
 
   KeyframeEditFunc select_cb, ok_cb;
   KeyframeEditData ked;
@@ -1572,15 +1598,22 @@ static void graphkeys_mselect_column(bAnimContext *ac, const int mval[2], short
 
   /* check if anything to select */
   if (nvi == NULL) {
-    return;
+    return OPERATOR_CANCELLED;
   }
 
   /* get frame number on which elements should be selected */
   // TODO: should we restrict to integer frames only?
   selx = nvi->frame;
 
-  /* if select mode is replace, deselect all keyframes first */
-  if (select_mode == SELECT_REPLACE) {
+  if (select_mode != SELECT_REPLACE) {
+    /* Doesn't need to deselect anything -> Pass. */
+  }
+  else if (wait_to_deselect_others && (nvi->bezt->f2 & SELECT)) {
+    run_modal = true;
+  }
+  /* If select mode is replace (and we don't do delayed deselection on mouse release), deselect all
+   * keyframes first. */
+  else {
     /* reset selection mode to add to selection */
     select_mode = SELECT_ADD;
 
@@ -1622,12 +1655,14 @@ static void graphkeys_mselect_column(bAnimContext *ac, const int mval[2], short
   MEM_freeN(nvi);
   BLI_freelistN(&ked.list);
   ANIM_animdata_freelist(&anim_data);
+
+  return run_modal ? OPERATOR_RUNNING_MODAL : OPERATOR_FINISHED;
 }
 
 /* ------------------- */
 
 /* handle clicking */
-static int graphkeys_clickselect_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+static int graphkeys_clickselect_exec(bContext *C, wmOperator *op)
 {
   bAnimContext ac;
 
@@ -1639,27 +1674,37 @@ static int graphkeys_clickselect_invoke(bContext *C, wmOperator *op, const wmEve
   /* select mode is either replace (deselect all, then add) or add/extend */
   const short selectmode = RNA_boolean_get(op->ptr, "extend") ? SELECT_INVERT : SELECT_REPLACE;
   const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
+  /* See #WM_operator_properties_generic_select() for a detailed description of the how and why of
+   * this. */
+  const bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
+  int mval[2];
+  int ret_val;
+
+  mval[0] = RNA_int_get(op->ptr, "mouse_x");
+  mval[1] = RNA_int_get(op->ptr, "mouse_y");
 
   /* figure out action to take */
   if (RNA_boolean_get(op->ptr, "column")) {
     /* select all keyframes in the same frame as the one that was under the mouse */
-    graphkeys_mselect_column(&ac, event->mval, selectmode);
+    ret_val = graphkeys_mselect_column(&ac, mval, selectmode, wait_to_deselect_others);
   }
   else if (RNA_boolean_get(op->ptr, "curves")) {
     /* select all keyframes in the same F-Curve as the one under the mouse */
-    mouse_graph_keys(&ac, event->mval, selectmode, deselect_all, true);
+    ret_val = mouse_graph_keys(&ac, mval, selectmode, deselect_all, true, wait_to_deselect_others);
   }
   else {
     /* select keyframe under mouse */
-    mouse_graph_keys(&ac, event->mval, selectmode, deselect_all, false);
+    ret_val = mouse_graph_keys(
+        &ac, mval, selectmode, deselect_all, false, wait_to_deselect_others);
   }
 
-  /* set notifier that keyframe selection (and also channel selection in some cases) has changed */
+  /* set notifier that keyframe selection (and also channel selection in some cases) has
+   * changed */
   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
   WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_SELECTED, NULL);
 
   /* for tweak grab to work */
-  return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
+  return ret_val | OPERATOR_PASS_THROUGH;
 }
 
 void GRAPH_OT_clickselect(wmOperatorType *ot)
@@ -1672,19 +1717,22 @@ void GRAPH_OT_clickselect(wmOperatorType *ot)
   ot->description = "Select keyframes by clicking on them";
 
   /* callbacks */
-  ot->invoke = graphkeys_clickselect_invoke;
   ot->poll = graphop_visible_keyframes_poll;
+  ot->exec = graphkeys_clickselect_exec;
+  ot->invoke = WM_generic_select_invoke;
+  ot->modal = WM_generic_select_modal;
 
   /* flags */
   ot->flag = OPTYPE_UNDO;
 
   /* properties */
-  prop = RNA_def_boolean(
-      ot->srna,
-      "extend",
-      0,
-      "Extend Select",
-      "Toggle keyframe selection instead of leaving newly selected keyframes only");  // SHIFTKEY
+  WM_operator_properties_generic_select(ot);
+  prop = RNA_def_boolean(ot->srna,
+                         "extend",
+                         0,
+                         "Extend Select",
+                         "Toggle keyframe selection instead of leaving newly selected "
+                         "keyframes only");  // SHIFTKEY
   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 
   prop = RNA_def_boolean(ot->srna,
@@ -1694,12 +1742,12 @@ void GRAPH_OT_clickselect(wmOperatorType *ot)
                          "Deselect all when nothing under the cursor");
   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 
-  prop = RNA_def_boolean(
-      ot->srna,
-      "column",
-      0,
-      "Column Select",
-      "Select all keyframes that occur on the same frame as the one under the mouse");  // ALTKEY
+  prop = RNA_def_boolean(ot->srna,
+                         "column",
+                         0,
+                         "Column Select",
+                         "Select all keyframes that occur on the same frame as the one under "
+                         "the mouse");  // ALTKEY
   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 
   prop = RNA_def_boolean(ot->srna,
index 95ad56a35e6da5639e20d09d099ecc20f0779969..b8d9d3b791ffcfa21c0e0c683f8ff81b76fbdfe6 100644 (file)
@@ -796,6 +796,9 @@ static void graph_refresh(const bContext *C, ScrArea *sa)
     ED_area_tag_redraw(sa);
   }
 
+  sipo->runtime.flag &= ~(SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT |
+                          SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT);
+
   /* init/adjust F-Curve colors */
   graph_refresh_fcurve_colors(C);
 }
index b493bbe886788b00f10bc60d2a73703d58e7cd61..f3d26f85471469bc6264f78e25da4389df245d7b 100644 (file)
@@ -2359,6 +2359,7 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve
 
   /* Needed to translate tweak events to mouse buttons. */
   t->launch_event = event ? WM_userdef_event_type_from_keymap_type(event->type) : -1;
+  t->is_launch_event_tweak = event ? ISTWEAK(event->type) : false;
 
   /* XXX Remove this when wm_operator_call_internal doesn't use window->eventstate
    * (which can have type = 0) */
index 0264069c368a3a41b8a9d72df2333ae2baa8211e..e5e7be025e41f18dea3e5d59af3d47585bc467da 100644 (file)
@@ -213,7 +213,8 @@ typedef struct TransData2D {
   float ih1[2], ih2[2];
 } TransData2D;
 
-/** Used to store 2 handles for each #TransData in case the other handle wasn't selected. */
+/** Used to store 2 handles for each #TransData in case the other handle wasn't selected. Also to
+ * unset temporary flags. */
 typedef struct TransDataCurveHandleFlags {
   char ih1, ih2;
   char *h1, *h2;
@@ -631,6 +632,9 @@ typedef struct TransInfo {
   /*************** NEW STUFF *********************/
   /** event type used to launch transform. */
   short launch_event;
+  /** Is the actual launch event a tweak event? (launch_event above is set to the corresponding
+   * mouse button then.) */
+  bool is_launch_event_tweak;
 
   struct {
     /** Orientation type when when we're not constrained.
index 0c49d67f7a24c4a175e0fc597c92e5e2e6357b52..db8f36883f8a7b11575685ccffb772d2f54aceb2 100644 (file)
@@ -921,10 +921,17 @@ typedef struct tRetainedKeyframe {
   size_t del_count; /* number of keyframes of this sort that have been deleted so far */
 } tRetainedKeyframe;
 
-/* Called during special_aftertrans_update to make sure selected keyframes replace
+/**
+ * Called during special_aftertrans_update to make sure selected keyframes replace
  * any other keyframes which may reside on that frame (that is not selected).
+ *
+ * \param sel_flag: The flag (bezt.f1/2/3) value to use to determine selection. Usually `SELECT`,
+ *                  but may want to use a different one at times (if caller does not operate on
+ *                  selection).
  */
-static void posttrans_fcurve_clean(FCurve *fcu, const bool use_handle)
+static void posttrans_fcurve_clean(FCurve *fcu,
+                                   const eBezTriple_Flag sel_flag,
+                                   const bool use_handle)
 {
   /* NOTE: We assume that all keys are sorted */
   ListBase retained_keys = {NULL, NULL};
@@ -1036,7 +1043,7 @@ static void posttrans_fcurve_clean(FCurve *fcu, const bool use_handle)
   }
 
   /* 3) Recalculate handles */
-  testhandles_fcurve(fcu, use_handle);
+  testhandles_fcurve(fcu, sel_flag, use_handle);
 
   /* cleanup */
   BLI_freelistN(&retained_keys);
@@ -1064,11 +1071,11 @@ static void posttrans_action_clean(bAnimContext *ac, bAction *act)
 
     if (adt) {
       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 0, 0);
-      posttrans_fcurve_clean(ale->key_data, false); /* only use handles in graph editor */
+      posttrans_fcurve_clean(ale->key_data, SELECT, false); /* only use handles in graph editor */
       ANIM_nla_mapping_apply_fcurve(adt, ale->key_data, 1, 0);
     }
     else {
-      posttrans_fcurve_clean(ale->key_data, false); /* only use handles in graph editor */
+      posttrans_fcurve_clean(ale->key_data, SELECT, false); /* only use handles in graph editor */
     }
   }
 
@@ -1090,7 +1097,7 @@ typedef struct BeztMap {
 /* This function converts an FCurve's BezTriple array to a BeztMap array
  * NOTE: this allocates memory that will need to get freed later
  */
-static BeztMap *bezt_to_beztmaps(BezTriple *bezts, int totvert, const short UNUSED(use_handle))
+static BeztMap *bezt_to_beztmaps(BezTriple *bezts, int totvert)
 {
   BezTriple *bezt = bezts;
   BezTriple *prevbezt = NULL;
@@ -1118,7 +1125,7 @@ static BeztMap *bezt_to_beztmaps(BezTriple *bezts, int totvert, const short UNUS
 }
 
 /* This function copies the code of sort_time_ipocurve, but acts on BeztMap structs instead */
-static void sort_time_beztmaps(BeztMap *bezms, int totvert, const short UNUSED(use_handle))
+static void sort_time_beztmaps(BeztMap *bezms, int totvert)
 {
   BeztMap *bezm;
   int i, ok = 1;
@@ -1163,8 +1170,7 @@ static void sort_time_beztmaps(BeztMap *bezms, int totvert, const short UNUSED(u
 }
 
 /* This function firstly adjusts the pointers that the transdata has to each BezTriple */
-static void beztmap_to_data(
-    TransInfo *t, FCurve *fcu, BeztMap *bezms, int totvert, const short UNUSED(use_handle))
+static void beztmap_to_data(TransInfo *t, FCurve *fcu, BeztMap *bezms, int totvert)
 {
   BezTriple *bezts = fcu->bezt;
   BeztMap *bezm;
@@ -1270,9 +1276,9 @@ void remake_graph_transdata(TransInfo *t, ListBase *anim_data)
 
       /* adjust transform-data pointers */
       /* note, none of these functions use 'use_handle', it could be removed */
-      bezm = bezt_to_beztmaps(fcu->bezt, fcu->totvert, use_handle);
-      sort_time_beztmaps(bezm, fcu->totvert, use_handle);
-      beztmap_to_data(t, fcu, bezm, fcu->totvert, use_handle);
+      bezm = bezt_to_beztmaps(fcu->bezt, fcu->totvert);
+      sort_time_beztmaps(bezm, fcu->totvert);
+      beztmap_to_data(t, fcu, bezm, fcu->totvert);
 
       /* free mapping stuff */
       MEM_freeN(bezm);
@@ -1281,7 +1287,7 @@ void remake_graph_transdata(TransInfo *t, ListBase *anim_data)
       sort_time_fcurve(fcu);
 
       /* make sure handles are all set correctly */
-      testhandles_fcurve(fcu, use_handle);
+      testhandles_fcurve(fcu, BEZT_FLAG_TEMP_TAG, use_handle);
     }
   }
 }
@@ -1959,11 +1965,11 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
         if ((saction->flag & SACTION_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) {
           if (adt) {
             ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0);
-            posttrans_fcurve_clean(fcu, false); /* only use handles in graph editor */
+            posttrans_fcurve_clean(fcu, SELECT, false); /* only use handles in graph editor */
             ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0);
           }
           else {
-            posttrans_fcurve_clean(fcu, false); /* only use handles in graph editor */
+            posttrans_fcurve_clean(fcu, SELECT, false); /* only use handles in graph editor */
           }
         }
       }
@@ -2103,11 +2109,11 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
         if ((sipo->flag & SIPO_NOTRANSKEYCULL) == 0 && ((canceled == 0) || (duplicate))) {
           if (adt) {
             ANIM_nla_mapping_apply_fcurve(adt, fcu, 0, 0);
-            posttrans_fcurve_clean(fcu, use_handle);
+            posttrans_fcurve_clean(fcu, BEZT_FLAG_TEMP_TAG, use_handle);
             ANIM_nla_mapping_apply_fcurve(adt, fcu, 1, 0);
           }
           else {
-            posttrans_fcurve_clean(fcu, use_handle);
+            posttrans_fcurve_clean(fcu, BEZT_FLAG_TEMP_TAG, use_handle);
           }
         }
       }
index f3d7592127cb6073b67e2241be7ca73017c88ecc..f6f982e854a459eaa735ad8f3cc11ec2903fdcec 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*
  * 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
@@ -156,21 +156,55 @@ static bool graph_edit_use_local_center(TransInfo *t)
   return ((t->around == V3D_AROUND_LOCAL_ORIGINS) && (graph_edit_is_translation_mode(t) == false));
 }
 
+/**
+ * Get the effective selection of a triple for transform, i.e. return if the left handle, right
+ * handle and/or the center point should be affected by transform.
+ */
+static void graph_bezt_get_transform_selection(const TransInfo *t,
+                                               const BezTriple *bezt,
+                                               const bool use_handle,
+                                               bool *r_left_handle,
+                                               bool *r_key,
+                                               bool *r_right_handle)
+{
+  SpaceGraph *sipo = (SpaceGraph *)t->sa->spacedata.first;
+  bool key = (bezt->f2 & SELECT) != 0;
+  bool left = use_handle ? ((bezt->f1 & SELECT) != 0) : key;
+  bool right = use_handle ? ((bezt->f3 & SELECT) != 0) : key;
+
+  if (use_handle && t->is_launch_event_tweak) {
+    if (sipo->runtime.flag & SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT) {
+      key = right = false;
+    }
+    else if (sipo->runtime.flag & SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT) {
+      left = key = false;
+    }
+  }
+
+  /* Whenever we move the key, we also move both handles. */
+  if (key) {
+    left = right = true;
+  }
+
+  *r_key = key;
+  *r_left_handle = left;
+  *r_right_handle = right;
+}
+
 static void graph_key_shortest_dist(
     TransInfo *t, FCurve *fcu, TransData *td_start, TransData *td, int cfra, bool use_handle)
 {
   int j = 0;
   TransData *td_iter = td_start;
+  bool sel_key, sel_left, sel_right;
 
   td->dist = FLT_MAX;
   for (; j < fcu->totvert; j++) {
     BezTriple *bezt = fcu->bezt + j;
     if (FrameOnMouseSide(t->frame_side, bezt->vec[1][0], cfra)) {
-      const bool sel2 = (bezt->f2 & SELECT) != 0;
-      const bool sel1 = use_handle ? (bezt->f1 & SELECT) != 0 : sel2;
-      const bool sel3 = use_handle ? (bezt->f3 & SELECT) != 0 : sel2;
+      graph_bezt_get_transform_selection(t, bezt, use_handle, &sel_left, &sel_key, &sel_right);
 
-      if (sel1 || sel2 || sel3) {
+      if (sel_left || sel_key || sel_right) {
         td->dist = td->rdist = min_ff(td->dist, fabs(td_iter->center[0] - td->center[0]));
       }
 
@@ -179,6 +213,15 @@ static void graph_key_shortest_dist(
   }
 }
 
+/**
+ * It is important to note that this doesn't always act on the selection (like it's usually done),
+ * it acts on a subset of it. E.g. the selection code may leave a hint that we just dragged on a
+ * left or right handle (SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT/RIGHT) and then we only transform the
+ * selected left or right handles accordingly.
+ * The points to be transformed are tagged with BEZT_FLAG_TEMP_TAG; some lower level curve
+ * functions may need to be made aware of this. It's ugly that these act based on selection state
+ * anyway.
+ */
 void createTransGraphEditData(bContext *C, TransInfo *t)
 {
   SpaceGraph *sipo = (SpaceGraph *)t->sa->spacedata.first;
@@ -198,11 +241,11 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
   BezTriple *bezt;
   int count = 0, i;
   float mtx[3][3], smtx[3][3];
-  const bool is_translation_mode = graph_edit_is_translation_mode(t);
   const bool use_handle = !(sipo->flag & SIPO_NOHANDLES);
   const bool use_local_center = graph_edit_use_local_center(t);
   const bool is_prop_edit = (t->flag & T_PROP_EDIT) != 0;
   short anim_map_flag = ANIM_UNITCONV_ONLYSEL | ANIM_UNITCONV_SELVERTS;
+  bool sel_key, sel_left, sel_right;
 
   /* determine what type of data we are operating on */
   if (ANIM_animdata_get_context(C, &ac) == 0) {
@@ -253,33 +296,29 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
       cfra = (float)CFRA;
     }
 
-    /* Only include BezTriples whose 'keyframe'
-     * occurs on the same side of the current frame as mouse. */
     for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
+      /* Only include BezTriples whose 'keyframe'
+       * occurs on the same side of the current frame as mouse. */
       if (FrameOnMouseSide(t->frame_side, bezt->vec[1][0], cfra)) {
-        const bool sel2 = (bezt->f2 & SELECT) != 0;
-        const bool sel1 = use_handle ? (bezt->f1 & SELECT) != 0 : sel2;
-        const bool sel3 = use_handle ? (bezt->f3 & SELECT) != 0 : sel2;
+        graph_bezt_get_transform_selection(t, bezt, use_handle, &sel_left, &sel_key, &sel_right);
 
         if (is_prop_edit) {
           curvecount += 3;
-          if (sel2 || sel1 || sel3) {
+          if (sel_key || sel_left || sel_right) {
             selected = true;
           }
         }
         else {
-          if (!is_translation_mode || !(sel2)) {
-            if (sel1) {
-              count++;
-            }
+          if (sel_left) {
+            count++;
+          }
 
-            if (sel3) {
-              count++;
-            }
+          if (sel_right) {
+            count++;
           }
 
           /* only include main vert if selected */
-          if (sel2 && !use_local_center) {
+          if (sel_key && !use_local_center) {
             count++;
           }
         }
@@ -366,19 +405,21 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
     unit_scale = ANIM_unit_mapping_get_factor(
         ac.scene, ale->id, ale->key_data, anim_map_flag, &offset);
 
-    /* only include BezTriples whose 'keyframe' occurs on the same side
-     * of the current frame as mouse (if applicable) */
     for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
-      if (FrameOnMouseSide(t->frame_side, bezt->vec[1][0], cfra)) {
-        const bool sel2 = (bezt->f2 & SELECT) != 0;
-        const bool sel1 = use_handle ? (bezt->f1 & SELECT) != 0 : sel2;
-        const bool sel3 = use_handle ? (bezt->f3 & SELECT) != 0 : sel2;
+      /* Ensure temp flag is cleared for all triples, we use it. */
+      bezt->f1 &= ~BEZT_FLAG_TEMP_TAG;
+      bezt->f2 &= ~BEZT_FLAG_TEMP_TAG;
+      bezt->f3 &= ~BEZT_FLAG_TEMP_TAG;
 
+      /* only include BezTriples whose 'keyframe' occurs on the same side
+       * of the current frame as mouse (if applicable) */
+      if (FrameOnMouseSide(t->frame_side, bezt->vec[1][0], cfra)) {
         TransDataCurveHandleFlags *hdata = NULL;
-        /* short h1=1, h2=1; */ /* UNUSED */
+
+        graph_bezt_get_transform_selection(t, bezt, use_handle, &sel_left, &sel_key, &sel_right);
 
         if (is_prop_edit) {
-          bool is_sel = (sel2 || sel1 || sel3);
+          bool is_sel = (sel_key || sel_left || sel_right);
           /* we always select all handles for proportional editing if central handle is selected */
           initTransDataCurveHandles(td, bezt);
           bezt_to_transdata(td++,
@@ -422,69 +463,69 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
                             smtx,
                             unit_scale,
                             offset);
+
+          if (is_sel) {
+            bezt->f1 |= BEZT_FLAG_TEMP_TAG;
+            bezt->f2 |= BEZT_FLAG_TEMP_TAG;
+            bezt->f3 |= BEZT_FLAG_TEMP_TAG;
+          }
         }
         else {
           /* only include handles if selected, irrespective of the interpolation modes.
            * also, only treat handles specially if the center point isn't selected.
            */
-          if (!is_translation_mode || !(sel2)) {
-            if (sel1) {
-              hdata = initTransDataCurveHandles(td, bezt);
-              bezt_to_transdata(td++,
-                                td2d++,
-                                tdg++,
-                                adt,
-                                bezt,
-                                0,
-                                sel1,
-                                true,
-                                intvals,
-                                mtx,
-                                smtx,
-                                unit_scale,
-                                offset);
-            }
-            else {
-              /* h1 = 0; */ /* UNUSED */
-            }
+          if (sel_left) {
+            hdata = initTransDataCurveHandles(td, bezt);
+            bezt_to_transdata(td++,
+                              td2d++,
+                              tdg++,
+                              adt,
+                              bezt,
+                              0,
+                              sel_left,
+                              true,
+                              intvals,
+                              mtx,
+                              smtx,
+                              unit_scale,
+                              offset);
+            bezt->f1 |= BEZT_FLAG_TEMP_TAG;
+          }
 
-            if (sel3) {
-              if (hdata == NULL) {
-                hdata = initTransDataCurveHandles(td, bezt);
-              }
-              bezt_to_transdata(td++,
-                                td2d++,
-                                tdg++,
-                                adt,
-                                bezt,
-                                2,
-                                sel3,
-                                true,
-                                intvals,
-                                mtx,
-                                smtx,
-                                unit_scale,
-                                offset);
-            }
-            else {
-              /* h2 = 0; */ /* UNUSED */
+          if (sel_right) {
+            if (hdata == NULL) {
+              hdata = initTransDataCurveHandles(td, bezt);
             }
+            bezt_to_transdata(td++,
+                              td2d++,
+                              tdg++,
+                              adt,
+                              bezt,
+                              2,
+                              sel_right,
+                              true,
+                              intvals,
+                              mtx,
+                              smtx,
+                              unit_scale,
+                              offset);
+            bezt->f3 |= BEZT_FLAG_TEMP_TAG;
           }
 
           /* only include main vert if selected */
-          if (sel2 && !use_local_center) {
+          if (sel_key && !use_local_center) {
             /* move handles relative to center */
-            if (is_translation_mode) {
-              if (sel1) {
+            if (graph_edit_is_translation_mode(t)) {
+              if (sel_left) {
                 td->flag |= TD_MOVEHANDLE1;
               }
-              if (sel3) {
+              if (sel_right) {
                 td->flag |= TD_MOVEHANDLE2;
               }
             }
 
             /* if handles were not selected, store their selection status */
-            if (!(sel1) || !(sel3)) {
+            if (!(sel_left) || !(sel_right)) {
               if (hdata == NULL) {
                 hdata = initTransDataCurveHandles(td, bezt);
               }
@@ -496,13 +537,14 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
                               adt,
                               bezt,
                               1,
-                              sel2,
+                              sel_key,
                               false,
                               intvals,
                               mtx,
                               smtx,
                               unit_scale,
                               offset);
+            bezt->f2 |= BEZT_FLAG_TEMP_TAG;
           }
           /* Special hack (must be done after #initTransDataCurveHandles(),
            * as that stores handle settings to restore...):
@@ -513,7 +555,7 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
            */
           if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM) &&
               ELEM(t->mode, TFM_ROTATION, TFM_RESIZE)) {
-            if (hdata && (sel1) && (sel3)) {
+            if (hdata && (sel_left) && (sel_right)) {
               bezt->h1 = HD_ALIGN;
               bezt->h2 = HD_ALIGN;
             }
@@ -523,7 +565,7 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
     }
 
     /* Sets handles based on the selection */
-    testhandles_fcurve(fcu, use_handle);
+    testhandles_fcurve(fcu, BEZT_FLAG_TEMP_TAG, use_handle);
   }
 
   if (is_prop_edit) {
@@ -551,15 +593,13 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
         cfra = (float)CFRA;
       }
 
-      /* only include BezTriples whose 'keyframe' occurs on the
-       * same side of the current frame as mouse (if applicable) */
       for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
+        /* only include BezTriples whose 'keyframe' occurs on the
+         * same side of the current frame as mouse (if applicable) */
         if (FrameOnMouseSide(t->frame_side, bezt->vec[1][0], cfra)) {
-          const bool sel2 = (bezt->f2 & SELECT) != 0;
-          const bool sel1 = use_handle ? (bezt->f1 & SELECT) != 0 : sel2;
-          const bool sel3 = use_handle ? (bezt->f3 & SELECT) != 0 : sel2;
+          graph_bezt_get_transform_selection(t, bezt, use_handle, &sel_left, &sel_key, &sel_right);
 
-          if (sel1 || sel2) {
+          if (sel_left || sel_key) {
             td->dist = td->rdist = 0.0f;
           }
           else {
@@ -567,7 +607,7 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
           }
           td++;
 
-          if (sel2) {
+          if (sel_key) {
             td->dist = td->rdist = 0.0f;
           }
           else {
@@ -575,7 +615,7 @@ void createTransGraphEditData(bContext *C, TransInfo *t)
           }
           td++;
 
-          if (sel3 || sel2) {
+          if (sel_right || sel_key) {
             td->dist = td->rdist = 0.0f;
           }
           else {
index 0df0b70a56a97abdbd483f0f9940853f3952e58f..548944e2d74082392ba5af7fde9778a28906c9f2 100644 (file)
@@ -448,7 +448,7 @@ static void recalcData_graphedit(TransInfo *t)
       dosort++;
     }
     else {
-      calchandles_fcurve(fcu);
+      calchandles_fcurve_ex(fcu, BEZT_FLAG_TEMP_TAG);
     }
 
     /* set refresh tags for objects using this animation,
index 8670d8f663365c6ab1af374c1657b6f41bda98d4..576a0a85b6d1458ed0c9dc9e535c7139c95a03f1 100644 (file)
@@ -483,6 +483,13 @@ typedef enum eGraphEdit_Runtime_Flag {
   SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC = (1 << 0),
   /** Temporary flag to force fcurves to recalculate colors. */
   SIPO_RUNTIME_FLAG_NEED_CHAN_SYNC_COLOR = (1 << 1),
+
+  /**
+   * These flags are for the mouse-select code to communicate with the transform code. Click
+   * dragging (tweaking) a handle sets the according left/right flag which transform code uses then
+   * to limit translation to this side. */
+  SIPO_RUNTIME_FLAG_TWEAK_HANDLES_LEFT = (1 << 2),
+  SIPO_RUNTIME_FLAG_TWEAK_HANDLES_RIGHT = (1 << 3),
 } eGraphEdit_Runtime_Flag;
 
 /** \} */
index 21636153904522118f3aeead6a7b8b08d4593851..72646718e41c11b8923642cff7b67d1a37b77766 100644 (file)
@@ -396,8 +396,38 @@ void WM_operator_properties_select_operation_simple(wmOperatorType *ot)
   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 }
 
+/**
+ * Selecting and tweaking items are overlapping operations. Getting both to work without conflicts
+ * requires special care. See
+ * https://wiki.blender.org/wiki/Human_Interface_Guidelines/Selection#Select-tweaking for the
+ * desired behavior.
+ *
+ * For default click selection (with no modifier keys held), the select operators can do the
+ * following:
+ * - On a mouse press on an unselected item, change selection and finish immidiately after.
+ *   This sends an undo push and allows transform to take over should a tweak event be caught now.
+ * - On a mouse press on a selected item, don't change selection state, but start modal execution
+ *   of the operator. Idea is that we wait with deselecting other items until we know that the
+ *   intention wasn't to tweak (mouse press+drag) all selected items.
+ * - If a tweak is recognized before the release event happens, cancel the operator, so that
+ *   transform can take over and no undo-push is sent.
+ * - If the release event occurs rather than a tweak one, deselect all items but the one under the
+ *   cursor, and finish the modal operator.
+ *
+ * This utility, together with #WM_generic_select_invoke() and #WM_generic_select_modal() should
+ * help getting the wanted behavior to work. Most generic logic should be handled in these, so that
+ * the select operators only have to care for the case dependent handling.
+ *
+ * Every select operator has slightly diferent requirements, e.g. VSE strip selection also needs to
+ * account for handle selection. This should be the baseline behavior though.
+ */
 void WM_operator_properties_generic_select(wmOperatorType *ot)
 {
+  /* On the initial mouse press, this is set by #WM_generic_select_modal() to let the select
+   * operator exec callback know that it should not __yet__ deselect other items when clicking on
+   * an already selected one. Instead should make sure the operator executes modal then (see
+   * #WM_generic_select_modal()), so that the exec callback can be called a second time on the
+   * mouse release event to do this part. */
   PropertyRNA *prop = RNA_def_boolean(
       ot->srna, "wait_to_deselect_others", false, "Wait to Deselect Others", "");
   RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
index 83551a862118f810370b372b9884de8675ee33e0..9c3c433d67551c9dfa048b0de1f5fe4371983516 100644 (file)
@@ -721,6 +721,13 @@ void WM_operator_properties_free(PointerRNA *ptr)
 /** \name Default Operator Callbacks
  * \{ */
 
+/**
+ * Helper to get select and tweak-transform to work conflict free and as desired. See
+ * #WM_operator_properties_generic_select() for details.
+ *
+ * To be used together with #WM_generic_select_invoke() and
+ * #WM_operator_properties_generic_select().
+ */
 int WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
 {
   PropertyRNA *wait_to_deselect_prop = RNA_struct_find_property(op->ptr,
@@ -787,6 +794,13 @@ int WM_generic_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
   return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
 }
 
+/**
+ * Helper to get select and tweak-transform to work conflict free and as desired. See
+ * #WM_operator_properties_generic_select() for details.
+ *
+ * To be used together with #WM_generic_select_modal() and
+ * #WM_operator_properties_generic_select().
+ */
 int WM_generic_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
 {
   RNA_int_set(op->ptr, "mouse_x", event->mval[0]);