UI: Add Free Handle Types to CurveProfile Widget
authorHans Goudey <h.goudey@me.com>
Wed, 24 Jun 2020 15:50:01 +0000 (11:50 -0400)
committerHans Goudey <h.goudey@me.com>
Wed, 24 Jun 2020 15:50:01 +0000 (11:50 -0400)
Under the hood the CurveProfile widget (used for bevel custom profiles)
uses a bezier curve, but right now though it only supports two of the
bezier curve handle types, vector and auto. This patch adds support for
free handles and adds all of the logic for editing them.

This is the first step to the ability to import and export curve objects
in the widget.

There's some code cleanup in curveprofile.c. Movement for handles and
control points is abstracted to functions there rather than happening
in interface_handlers.c.

An "Apply Preset" button is also added, which solves a confusing issue
where you apply a preset, then change the number of samples and the
preset doesn't change. The button makes it clear that the preset needs
to be reapplied.

Reviewed By: Severin

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

source/blender/blenkernel/BKE_curveprofile.h
source/blender/blenkernel/intern/curveprofile.c
source/blender/editors/interface/interface_draw.c
source/blender/editors/interface/interface_handlers.c
source/blender/editors/interface/interface_templates.c
source/blender/makesdna/DNA_curveprofile_types.h
source/blender/makesrna/intern/rna_curveprofile.c

index bf50cde1efcac2d1490aa4e6633200881b4f30f3..877ab887138c8fe2106cf876d9ff945ea3f003c8 100644 (file)
@@ -45,6 +45,16 @@ void BKE_curveprofile_copy_data(struct CurveProfile *target, const struct CurveP
 
 struct CurveProfile *BKE_curveprofile_copy(const struct CurveProfile *profile);
 
+bool BKE_curveprofile_move_handle(struct CurveProfilePoint *point,
+                                  const bool handle_1,
+                                  const bool snap,
+                                  const float delta[2]);
+
+bool BKE_curveprofile_move_point(struct CurveProfile *profile,
+                                 struct CurveProfilePoint *point,
+                                 const bool snap,
+                                 const float delta[2]);
+
 bool BKE_curveprofile_remove_point(struct CurveProfile *profile, struct CurveProfilePoint *point);
 
 void BKE_curveprofile_remove_by_flag(struct CurveProfile *profile, const short flag);
@@ -65,7 +75,12 @@ void BKE_curveprofile_create_samples(struct CurveProfile *profile,
 void BKE_curveprofile_initialize(struct CurveProfile *profile, short segments_len);
 
 /* Called for a complete update of the widget after modifications */
-void BKE_curveprofile_update(struct CurveProfile *profile, const bool rem_doubles);
+enum {
+  PROF_UPDATE_NONE = 0,
+  PROF_UPDATE_REMOVE_DOUBLES = (1 << 0),
+  PROF_UPDATE_CLIP = (1 << 1),
+};
+void BKE_curveprofile_update(struct CurveProfile *profile, const int update_flags);
 
 /* Need to find the total length of the curve to sample a portion of it */
 float BKE_curveprofile_total_length(const struct CurveProfile *profile);
index f43e0355eaa61c20467d0d2d33028d3c4515be5b..7b7cadfcb1be95f35900dc98ba1c7aa586ca856e 100644 (file)
@@ -65,6 +65,11 @@ void BKE_curveprofile_copy_data(CurveProfile *target, const CurveProfile *profil
   target->path = MEM_dupallocN(profile->path);
   target->table = MEM_dupallocN(profile->table);
   target->segments = MEM_dupallocN(profile->segments);
+
+  /* Update the reference the points have to the profile. */
+  for (int i = 0; i < target->path_len; i++) {
+    target->path[i].profile = target;
+  }
 }
 
 CurveProfile *BKE_curveprofile_copy(const CurveProfile *profile)
@@ -77,6 +82,101 @@ CurveProfile *BKE_curveprofile_copy(const CurveProfile *profile)
   return NULL;
 }
 
+/**
+ * Move a point's handle, accounting for the alignment of handles with the HD_ALIGN type.
+ *
+ * \param handle_1 Whether to move the 1st or 2nd control point.
+ * \param new_location The *relative* change in the handle's position.
+ * \note Requires #BKE_curveprofile_update call after.
+ * \return Whether the handle moved from its start position.
+ */
+bool BKE_curveprofile_move_handle(struct CurveProfilePoint *point,
+                                  const bool handle_1,
+                                  const bool snap,
+                                  const float delta[2])
+{
+  short handle_type = (handle_1) ? point->h1 : point->h2;
+  float *handle_location = (handle_1) ? &point->h1_loc[0] : &point->h2_loc[0];
+
+  float start_position[2];
+  copy_v2_v2(start_position, handle_location);
+
+  /* Don't move the handle if it's not a free handle type. */
+  if (!ELEM(handle_type, HD_FREE, HD_ALIGN)) {
+    return false;
+  }
+
+  /* Move the handle. */
+  handle_location[0] += delta ? delta[0] : 0.0f;
+  handle_location[1] += delta ? delta[1] : 0.0f;
+  if (snap) {
+    handle_location[0] = 0.125f * roundf(8.0f * handle_location[0]);
+    handle_location[1] = 0.125f * roundf(8.0f * handle_location[1]);
+  }
+
+  /* Move the other handle if they are aligned. */
+  if (handle_type == HD_ALIGN) {
+    short other_handle_type = (handle_1) ? point->h2 : point->h1;
+    if (other_handle_type == HD_ALIGN) {
+      float *other_handle_location = (handle_1) ? &point->h2_loc[0] : &point->h1_loc[0];
+      other_handle_location[0] = 2.0f * point->x - handle_location[0];
+      other_handle_location[1] = 2.0f * point->y - handle_location[1];
+    }
+  }
+
+  if (!equals_v2v2(handle_location, start_position)) {
+    return true;
+  }
+  return false;
+}
+
+/**
+ * Moves a control point, accounting for clipping and snapping, and moving free handles.
+ *
+ * \param snap Whether to snap the point to the grid
+ * \param new_location The *relative* change of the point's location.
+ * \return Whether the point moved from its start position.
+ * \note Requires #BKE_curveprofile_update call after.
+ */
+bool BKE_curveprofile_move_point(struct CurveProfile *profile,
+                                 struct CurveProfilePoint *point,
+                                 const bool snap,
+                                 const float delta[2])
+{
+  float origx = point->x;
+  float origy = point->y;
+
+  point->x += delta[0];
+  point->y += delta[1];
+  if (snap) {
+    point->x = 0.125f * roundf(8.0f * point->x);
+    point->y = 0.125f * roundf(8.0f * point->y);
+  }
+
+  /* Clip here instead to test clipping here to stop handles from moving too. */
+  if (profile->flag & PROF_USE_CLIP) {
+    point->x = max_ff(point->x, profile->clip_rect.xmin);
+    point->x = min_ff(point->x, profile->clip_rect.xmax);
+    point->y = max_ff(point->y, profile->clip_rect.ymin);
+    point->y = min_ff(point->y, profile->clip_rect.ymax);
+  }
+
+  /* Also move free handles even when they aren't selected. */
+  if (ELEM(point->h1, HD_FREE, HD_ALIGN)) {
+    point->h1_loc[0] += point->x - origx;
+    point->h1_loc[1] += point->y - origy;
+  }
+  if (ELEM(point->h2, HD_FREE, HD_ALIGN)) {
+    point->h2_loc[0] += point->x - origx;
+    point->h2_loc[1] += point->y - origy;
+  }
+
+  if (point->x != origx || point->y != origy) {
+    return true;
+  }
+  return false;
+}
+
 /**
  * Removes a specific point from the path of control points.
  * \note Requires #BKE_curveprofile_update call after.
@@ -100,8 +200,10 @@ bool BKE_curveprofile_remove_point(CurveProfile *profile, CurveProfilePoint *poi
   uint i_delete = (uint)(point - profile->path);
 
   /* Copy the before and after the deleted point. */
-  memcpy(pts, profile->path, i_delete);
-  memcpy(pts + i_delete, profile->path + i_delete + 1, (size_t)profile->path_len - i_delete - 1);
+  memcpy(pts, profile->path, sizeof(CurveProfilePoint) * i_delete);
+  memcpy(pts + i_delete,
+         profile->path + i_delete + 1,
+         sizeof(CurveProfilePoint) * (profile->path_len - i_delete - 1));
 
   MEM_freeN(profile->path);
   profile->path = pts;
@@ -180,12 +282,9 @@ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float
                                            "profile path");
   for (int i_new = 0, i_old = 0; i_new < profile->path_len; i_new++) {
     if (i_new != i_insert) {
-      /* Insert old points */
-      new_pts[i_new].x = profile->path[i_old].x;
-      new_pts[i_new].y = profile->path[i_old].y;
-      new_pts[i_new].flag = profile->path[i_old].flag & ~PROF_SELECT; /* Deselect old points. */
-      new_pts[i_new].h1 = profile->path[i_old].h1;
-      new_pts[i_new].h2 = profile->path[i_old].h2;
+      /* Insert old points. */
+      memcpy(&new_pts[i_new], &profile->path[i_old], sizeof(CurveProfilePoint));
+      new_pts[i_new].flag &= ~PROF_SELECT; /* Deselect old points. */
       i_old++;
     }
     else {
@@ -201,6 +300,8 @@ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float
       else {
         new_pt->h1 = new_pt->h2 = HD_AUTO;
       }
+      /* Give new point a reference to the profile. */
+      new_pt->profile = profile;
     }
   }
 
@@ -212,35 +313,19 @@ CurveProfilePoint *BKE_curveprofile_insert(CurveProfile *profile, float x, float
 
 /**
  * Sets the handle type of the selected control points.
- * \param type_1, type_2: Either HD_VECT or HD_AUTO. Handle types for the first and second handles.
- *
+ * \param type_* Handle type for the first handle. HD_VECT, HD_AUTO, HD_FREE, or HD_ALIGN.
  * \note Requires #BKE_curveprofile_update call after.
  */
 void BKE_curveprofile_selected_handle_set(CurveProfile *profile, int type_1, int type_2)
 {
   for (int i = 0; i < profile->path_len; i++) {
-    if (profile->path[i].flag & PROF_SELECT) {
-      switch (type_1) {
-        case HD_AUTO:
-          profile->path[i].h1 = HD_AUTO;
-          break;
-        case HD_VECT:
-          profile->path[i].h1 = HD_VECT;
-          break;
-        default:
-          profile->path[i].h1 = HD_AUTO;
-          break;
-      }
-      switch (type_2) {
-        case HD_AUTO:
-          profile->path[i].h2 = HD_AUTO;
-          break;
-        case HD_VECT:
-          profile->path[i].h2 = HD_VECT;
-          break;
-        default:
-          profile->path[i].h1 = HD_AUTO;
-          break;
+    if (ELEM(profile->path[i].flag, PROF_SELECT, PROF_H1_SELECT, PROF_H2_SELECT)) {
+      profile->path[i].h1 = type_1;
+      profile->path[i].h2 = type_2;
+
+      if (type_1 == HD_ALIGN && type_2 == HD_ALIGN) {
+        /* Align the handles. */
+        BKE_curveprofile_move_handle(&profile->path[i], true, false, NULL);
       }
     }
   }
@@ -261,11 +346,24 @@ void BKE_curveprofile_reverse(CurveProfile *profile)
                                            "profile path");
   /* Mirror the new points across the y = x line */
   for (int i = 0; i < profile->path_len; i++) {
-    new_pts[profile->path_len - i - 1].x = profile->path[i].y;
-    new_pts[profile->path_len - i - 1].y = profile->path[i].x;
-    new_pts[profile->path_len - i - 1].flag = profile->path[i].flag;
-    new_pts[profile->path_len - i - 1].h1 = profile->path[i].h1;
-    new_pts[profile->path_len - i - 1].h2 = profile->path[i].h2;
+    int i_reversed = profile->path_len - i - 1;
+    BLI_assert(i_reversed >= 0);
+    new_pts[i_reversed].x = profile->path[i].y;
+    new_pts[i_reversed].y = profile->path[i].x;
+    new_pts[i_reversed].flag = profile->path[i].flag;
+    new_pts[i_reversed].h1 = profile->path[i].h2;
+    new_pts[i_reversed].h2 = profile->path[i].h1;
+    new_pts[i_reversed].profile = profile;
+
+    /* Mirror free handles, they can't be recalculated. */
+    if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
+      new_pts[i_reversed].h1_loc[0] = profile->path[i].h2_loc[1];
+      new_pts[i_reversed].h1_loc[1] = profile->path[i].h2_loc[0];
+    }
+    if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) {
+      new_pts[i_reversed].h2_loc[0] = profile->path[i].h1_loc[1];
+      new_pts[i_reversed].h2_loc[1] = profile->path[i].h1_loc[0];
+    }
   }
 
   /* Free the old points and use the new ones */
@@ -448,6 +546,13 @@ void BKE_curveprofile_reset(CurveProfile *profile)
       break;
   }
 
+  profile->flag &= ~PROF_DIRTY_PRESET;
+
+  /* Ensure each point has a reference to the profile. */
+  for (int i = 0; i < profile->path_len; i++) {
+    profile->path[i].profile = profile;
+  }
+
   if (profile->table) {
     MEM_freeN(profile->table);
     profile->table = NULL;
@@ -465,7 +570,7 @@ static bool is_curved_edge(BezTriple *bezt, int i)
 
 /**
  * Used to set bezier handle locations in the sample creation process. Reduced copy of
- * #calchandleNurb_intern code in curve.c.
+ * #calchandleNurb_intern code in curve.c, mostly changed by removing the third dimension.
  */
 static void calchandle_profile(BezTriple *bezt, const BezTriple *prev, const BezTriple *next)
 {
@@ -600,7 +705,7 @@ static int sort_points_curvature(const void *in_a, const void *in_b)
  * this is true and there are only vector edges the straight edges will still be sampled.
  * \param r_samples: An array of points to put the sampled positions. Must have length n_segments.
  * \return r_samples: Fill the array with the sampled locations and if the point corresponds
- * to a control point, its handle type
+ * to a control point, its handle type.
  */
 void BKE_curveprofile_create_samples(CurveProfile *profile,
                                      int n_segments,
@@ -621,21 +726,33 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
   for (i = 0; i < totpoints; i++) {
     bezt[i].vec[1][0] = profile->path[i].x;
     bezt[i].vec[1][1] = profile->path[i].y;
-    bezt[i].h1 = (profile->path[i].h1 == HD_VECT) ? HD_VECT : HD_AUTO;
-    bezt[i].h2 = (profile->path[i].h2 == HD_VECT) ? HD_VECT : HD_AUTO;
-  }
-  /* Give the first and last bezier points the same handle type as their neighbors. */
-  if (totpoints > 2) {
-    bezt[0].h1 = bezt[0].h2 = bezt[1].h1;
-    bezt[totpoints - 1].h1 = bezt[totpoints - 1].h2 = bezt[totpoints - 2].h2;
+    bezt[i].h1 = profile->path[i].h1;
+    bezt[i].h2 = profile->path[i].h2;
+    /* Copy handle locations if the handle type is free. */
+    if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
+      bezt[i].vec[0][0] = profile->path[i].h1_loc[0];
+      bezt[i].vec[0][1] = profile->path[i].h1_loc[1];
+    }
+    if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
+      bezt[i].vec[2][0] = profile->path[i].h2_loc[0];
+      bezt[i].vec[2][1] = profile->path[i].h2_loc[1];
+    }
   }
-  /* Get handle positions for the bezier points. */
+  /* Get handle positions for the non-free bezier points. */
   calchandle_profile(&bezt[0], NULL, &bezt[1]);
   for (i = 1; i < totpoints - 1; i++) {
     calchandle_profile(&bezt[i], &bezt[i - 1], &bezt[i + 1]);
   }
   calchandle_profile(&bezt[totpoints - 1], &bezt[totpoints - 2], NULL);
 
+  /* Copy the handle locations back to the control points. */
+  for (i = 0; i < totpoints; i++) {
+    profile->path[i].h1_loc[0] = bezt[i].vec[0][0];
+    profile->path[i].h1_loc[1] = bezt[i].vec[0][1];
+    profile->path[i].h2_loc[0] = bezt[i].vec[2][0];
+    profile->path[i].h2_loc[1] = bezt[i].vec[2][1];
+  }
+
   /* Create a list of edge indices with the most curved at the start, least curved at the end. */
   curve_sorted = MEM_callocN(sizeof(CurvatureSortPoint) * totedges, "curve sorted");
   for (i = 0; i < totedges; i++) {
@@ -720,7 +837,7 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
         BLI_assert(j < n_segments);
       }
 
-      /* Do the sampling from bezier points, X values first, then Y values. */
+      /* Sample from the bezier points. X then Y values. */
       BKE_curve_forward_diff_bezier(bezt[i].vec[1][0],
                                     bezt[i].vec[2][0],
                                     bezt[i + 1].vec[0][0],
@@ -740,7 +857,7 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
     BLI_assert(i_sample <= n_segments);
   }
 
-#ifdef DEBUG_profile_TABLE
+#ifdef DEBUG_PROFILE_TABLE
   printf("CURVEPROFILE CREATE SAMPLES\n");
   printf("n_segments: %d\n", n_segments);
   printf("totedges: %d\n", totedges);
@@ -757,6 +874,7 @@ void BKE_curveprofile_create_samples(CurveProfile *profile,
   }
   printf("\n");
 #endif
+
   MEM_freeN(bezt);
   MEM_freeN(curve_sorted);
   MEM_freeN(n_samples);
@@ -827,8 +945,10 @@ void BKE_curveprofile_set_defaults(CurveProfile *profile)
 
   profile->path[0].x = 1.0f;
   profile->path[0].y = 0.0f;
+  profile->path[0].profile = profile;
   profile->path[1].x = 1.0f;
   profile->path[1].y = 1.0f;
+  profile->path[1].profile = profile;
 
   profile->changed_timestamp = 0;
 }
@@ -852,13 +972,14 @@ struct CurveProfile *BKE_curveprofile_add(int preset)
 /**
  * Should be called after the widget is changed. Does profile and remove double checks and more
  * importantly, recreates the display / evaluation and segments tables.
+ * \param update_flags: Bitfield with fields defined in header file. Controls removing doubles and
+ * clipping.
  */
-void BKE_curveprofile_update(CurveProfile *profile, const bool remove_double)
+void BKE_curveprofile_update(CurveProfile *profile, const int update_flags)
 {
   CurveProfilePoint *points = profile->path;
   rctf *clipr = &profile->clip_rect;
   float thresh;
-  float dx, dy;
   int i;
 
   profile->changed_timestamp++;
@@ -866,11 +987,16 @@ void BKE_curveprofile_update(CurveProfile *profile, const bool remove_double)
   /* Clamp with the clipping rect in case something got past. */
   if (profile->flag & PROF_USE_CLIP) {
     /* Move points inside the clip rectangle. */
-    for (i = 0; i < profile->path_len; i++) {
-      points[i].x = max_ff(points[i].x, clipr->xmin);
-      points[i].x = min_ff(points[i].x, clipr->xmax);
-      points[i].y = max_ff(points[i].y, clipr->ymin);
-      points[i].y = min_ff(points[i].y, clipr->ymax);
+    if (update_flags & PROF_UPDATE_CLIP) {
+      for (i = 0; i < profile->path_len; i++) {
+        points[i].x = max_ff(points[i].x, clipr->xmin);
+        points[i].x = min_ff(points[i].x, clipr->xmax);
+        points[i].y = max_ff(points[i].y, clipr->ymin);
+        points[i].y = min_ff(points[i].y, clipr->ymax);
+
+        /* Extra sanity assert to make sure the points have the right profile pointer. */
+        BLI_assert(points[i].profile == profile);
+      }
     }
     /* Ensure zoom-level respects clipping. */
     if (BLI_rctf_size_x(&profile->view_rect) > BLI_rctf_size_x(&profile->clip_rect)) {
@@ -884,30 +1010,19 @@ void BKE_curveprofile_update(CurveProfile *profile, const bool remove_double)
   }
 
   /* Remove doubles with a threshold set at 1% of default range. */
-  thresh = 0.01f * BLI_rctf_size_x(clipr);
-  if (remove_double && profile->path_len > 2) {
+  thresh = pow2f(0.01f * BLI_rctf_size_x(clipr));
+  if (update_flags & PROF_UPDATE_REMOVE_DOUBLES && profile->path_len > 2) {
     for (i = 0; i < profile->path_len - 1; i++) {
-      dx = points[i].x - points[i + 1].x;
-      dy = points[i].y - points[i + 1].y;
-      if (sqrtf(dx * dx + dy * dy) < thresh) {
+      if (len_squared_v2v2(&points[i].x, &points[i + 1].x) < thresh) {
         if (i == 0) {
-          points[i + 1].flag |= HD_VECT;
-          if (points[i + 1].flag & PROF_SELECT) {
-            points[i].flag |= PROF_SELECT;
-          }
+          BKE_curveprofile_remove_point(profile, &points[1]);
         }
         else {
-          points[i].flag |= HD_VECT;
-          if (points[i].flag & PROF_SELECT) {
-            points[i + 1].flag |= PROF_SELECT;
-          }
+          BKE_curveprofile_remove_point(profile, &points[i]);
         }
-        break; /* Assumes 1 deletion per edit is ok. */
+        break; /* Assumes 1 deletion per update call is ok. */
       }
     }
-    if (i != profile->path_len - 1) {
-      BKE_curveprofile_remove_by_flag(profile, 2);
-    }
   }
 
   /* Create the high resolution table for drawing and some evaluation functions. */
@@ -927,10 +1042,13 @@ void BKE_curveprofile_update(CurveProfile *profile, const bool remove_double)
  */
 void BKE_curveprofile_initialize(CurveProfile *profile, short segments_len)
 {
+  if (segments_len != profile->segments_len) {
+    profile->flag |= PROF_DIRTY_PRESET;
+  }
   profile->segments_len = segments_len;
 
   /* Calculate the higher resolution / segments tables for display and evaluation. */
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
 }
 
 /**
@@ -1085,4 +1203,9 @@ void BKE_curveprofile_blend_read(struct BlendDataReader *reader, struct CurvePro
   BLO_read_data_address(reader, &profile->path);
   profile->table = NULL;
   profile->segments = NULL;
+
+  /* Reset the points' pointers to the profile. */
+  for (int i = 0; i < profile->path_len; i++) {
+    profile->path[i].profile = profile;
+  }
 }
index 1cb8565b38d67600ab714bec121a8436241dcf71..98cb85061f280fafa0c4ec7cb99db9e492e47f87 100644 (file)
@@ -25,6 +25,7 @@
 #include <string.h>
 
 #include "DNA_color_types.h"
+#include "DNA_curve_types.h"
 #include "DNA_curveprofile_types.h"
 #include "DNA_movieclip_types.h"
 #include "DNA_screen_types.h"
@@ -2159,7 +2160,19 @@ void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol,
   immUnbindProgram();
 }
 
-/** Used to draw a curve profile widget. Somewhat similar to ui_draw_but_CURVE */
+/**
+ * Helper for #ui_draw_but_CURVEPROFILE. Used to tell whether to draw a control point's handles.
+ */
+static bool point_draw_handles(CurveProfilePoint *point)
+{
+  return (point->flag & PROF_SELECT &&
+          (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) ||
+         ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT);
+}
+
+/**
+ *  Draws the curve profile widget. Somewhat similar to ui_draw_but_CURVE.
+ */
 void ui_draw_but_CURVEPROFILE(ARegion *region,
                               uiBut *but,
                               const uiWidgetColors *wcol,
@@ -2175,18 +2188,18 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
     profile = (CurveProfile *)but->poin;
   }
 
-  /* Calculate offset and zoom */
+  /* Calculate offset and zoom. */
   float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / BLI_rctf_size_x(&profile->view_rect);
   float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / BLI_rctf_size_y(&profile->view_rect);
   float offsx = profile->view_rect.xmin - (1.0f / zoomx);
   float offsy = profile->view_rect.ymin - (1.0f / zoomy);
 
-  /* Exit early if too narrow */
+  /* Exit early if too narrow. */
   if (zoomx == 0.0f) {
     return;
   }
 
-  /* Test needed because path can draw outside of boundary */
+  /* Test needed because path can draw outside of boundary. */
   int scissor[4];
   GPU_scissor_get_i(scissor);
   rcti scissor_new = {
@@ -2208,7 +2221,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
 
-  /* Backdrop */
+  /* Draw the backdrop. */
   float color_backdrop[4] = {0, 0, 0, 1};
   if (profile->flag & PROF_USE_CLIP) {
     gl_shaded_color_get_fl((uchar *)wcol->inner, -20, color_backdrop);
@@ -2227,33 +2240,33 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
     immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax);
   }
 
-  /* 0.25 step grid */
+  /* 0.25 step grid. */
   gl_shaded_color((uchar *)wcol->inner, -16);
   ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f);
-  /* 1.0 step grid */
+  /* 1.0 step grid. */
   gl_shaded_color((uchar *)wcol->inner, -24);
   ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f);
 
-  /* Draw the path's fill */
+  /* Draw the path's fill. */
   if (profile->table == NULL) {
-    BKE_curveprofile_update(profile, false);
+    BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
   }
   CurveProfilePoint *pts = profile->table;
-  /* Also add the last points on the right and bottom edges to close off the fill polygon */
+  /* Also add the last points on the right and bottom edges to close off the fill polygon. */
   bool add_left_tri = profile->view_rect.xmin < 0.0f;
   bool add_bottom_tri = profile->view_rect.ymin < 0.0f;
   uint tot_points = (uint)PROF_N_TABLE(profile->path_len) + 1 + add_left_tri + add_bottom_tri;
   uint tot_triangles = tot_points - 2;
 
-  /* Create array of the positions of the table's points */
+  /* Create array of the positions of the table's points. */
   float(*table_coords)[2] = MEM_mallocN(sizeof(*table_coords) * tot_points, "table x coords");
   for (i = 0; i < (uint)PROF_N_TABLE(profile->path_len);
-       i++) { /* Only add the points from the table here */
+       i++) { /* Only add the points from the table here. */
     table_coords[i][0] = pts[i].x;
     table_coords[i][1] = pts[i].y;
   }
   if (add_left_tri && add_bottom_tri) {
-    /* Add left side, bottom left corner, and bottom side points */
+    /* Add left side, bottom left corner, and bottom side points. */
     table_coords[tot_points - 3][0] = profile->view_rect.xmin;
     table_coords[tot_points - 3][1] = 1.0f;
     table_coords[tot_points - 2][0] = profile->view_rect.xmin;
@@ -2262,30 +2275,30 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
     table_coords[tot_points - 1][1] = profile->view_rect.ymin;
   }
   else if (add_left_tri) {
-    /* Add the left side and bottom left corner points */
+    /* Add the left side and bottom left corner points. */
     table_coords[tot_points - 2][0] = profile->view_rect.xmin;
     table_coords[tot_points - 2][1] = 1.0f;
     table_coords[tot_points - 1][0] = profile->view_rect.xmin;
     table_coords[tot_points - 1][1] = 0.0f;
   }
   else if (add_bottom_tri) {
-    /* Add the bottom side and bottom left corner points */
+    /* Add the bottom side and bottom left corner points. */
     table_coords[tot_points - 2][0] = 0.0f;
     table_coords[tot_points - 2][1] = profile->view_rect.ymin;
     table_coords[tot_points - 1][0] = 1.0f;
     table_coords[tot_points - 1][1] = profile->view_rect.ymin;
   }
   else {
-    /* Just add the bottom corner point. Side points would be redundant anyway */
+    /* Just add the bottom corner point. Side points would be redundant anyway. */
     table_coords[tot_points - 1][0] = 0.0f;
     table_coords[tot_points - 1][1] = 0.0f;
   }
 
-  /* Calculate the table point indices of the triangles for the profile's fill */
+  /* Calculate the table point indices of the triangles for the profile's fill. */
   uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, "return tri indices");
   BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices);
 
-  /* Draw the triangles for the profile fill */
+  /* Draw the triangles for the profile fill. */
   immUniformColor3ubvAlpha((const uchar *)wcol->item, 128);
   GPU_blend(true);
   GPU_polygon_smooth(false);
@@ -2301,7 +2314,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
   immEnd();
   MEM_freeN(tri_indices);
 
-  /* Draw the profile's path so the edge stands out a bit */
+  /* Draw the profile's path so the edge stands out a bit. */
   tot_points -= (add_left_tri + add_left_tri);
   GPU_line_width(1.0f);
   immUniformColor3ubvAlpha((const uchar *)wcol->item, 255);
@@ -2313,9 +2326,44 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
     immVertex2f(pos, fx, fy);
   }
   immEnd();
-  immUnbindProgram();
   MEM_freeN(table_coords);
 
+  /* Draw the handles for the selected control points. */
+  pts = profile->path;
+  tot_points = (uint)profile->path_len;
+  int selected_free_points = 0;
+  for (i = 0; i < tot_points; i++) {
+    if (point_draw_handles(&pts[i])) {
+      selected_free_points++;
+    }
+  }
+  /* Draw the lines to the handles from the points. */
+  if (selected_free_points > 0) {
+    GPU_line_width(1.0f);
+    gl_shaded_color((uchar *)wcol->inner, -24);
+    GPU_line_smooth(true);
+    immBegin(GPU_PRIM_LINES, selected_free_points * 4);
+    float ptx, pty;
+    for (i = 0; i < tot_points; i++) {
+      if (point_draw_handles(&pts[i])) {
+        ptx = rect->xmin + zoomx * (pts[i].x - offsx);
+        pty = rect->ymin + zoomy * (pts[i].y - offsy);
+
+        fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx);
+        fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy);
+        immVertex2f(pos, ptx, pty);
+        immVertex2f(pos, fx, fy);
+
+        fx = rect->xmin + zoomx * (pts[i].h2_loc[0] - offsx);
+        fy = rect->ymin + zoomy * (pts[i].h2_loc[1] - offsy);
+        immVertex2f(pos, ptx, pty);
+        immVertex2f(pos, fx, fy);
+      }
+    }
+    immEnd();
+  }
+  immUnbindProgram();
+
   /* New GPU instructions for control points and sampled points. */
   format = immVertexFormat();
   pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
@@ -2339,8 +2387,6 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
   }
 
   /* Draw the control points. */
-  pts = profile->path;
-  tot_points = (uint)profile->path_len;
   GPU_line_smooth(false);
   GPU_blend(false);
   GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f)));
@@ -2353,6 +2399,28 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
   }
   immEnd();
 
+  /* Draw the handle points. */
+  if (selected_free_points > 0) {
+    GPU_line_smooth(false);
+    GPU_blend(false);
+    GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 4.0f, 4.0f)));
+    immBegin(GPU_PRIM_POINTS, selected_free_points * 2);
+    for (i = 0; i < tot_points; i++) {
+      if (point_draw_handles(&pts[i])) {
+        fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx);
+        fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy);
+        immAttr4fv(col, (pts[i].flag & PROF_H1_SELECT) ? color_vert_select : color_vert);
+        immVertex2f(pos, fx, fy);
+
+        fx = rect->xmin + zoomx * (pts[i].h2_loc[0] - offsx);
+        fy = rect->ymin + zoomy * (pts[i].h2_loc[1] - offsy);
+        immAttr4fv(col, (pts[i].flag & PROF_H2_SELECT) ? color_vert_select : color_vert);
+        immVertex2f(pos, fx, fy);
+      }
+    }
+    immEnd();
+  }
+
   /* Draw the sampled points in addition to the control points if they have been created */
   pts = profile->segments;
   tot_points = (uint)profile->segments_len;
@@ -2367,7 +2435,6 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
     }
     immEnd();
   }
-
   immUnbindProgram();
 
   /* restore scissortest */
index 3f3f0513184553effe1a42edde5915e82d00365e..f6bfb492c925153696382e5eecb56327103fdbca 100644 (file)
@@ -6944,7 +6944,7 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
     d[0] = mx - data->dragstartx;
     d[1] = my - data->dragstarty;
 
-    if (len_squared_v2(d) < (3.0f * 3.0f)) {
+    if (len_squared_v2(d) < (9.0f * U.dpi_fac)) {
       snap = false;
     }
   }
@@ -6953,32 +6953,38 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
   fy = (my - dragy) / zoomy;
 
   if (data->dragsel != -1) {
-    CurveProfilePoint *point_last = NULL;
+    float last_x, last_y;
     const float mval_factor = ui_mouse_scale_warp_factor(shift);
     bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
 
     fx *= mval_factor;
     fy *= mval_factor;
 
-    /* Move all the points that aren't the last or the first */
-    for (a = 1; a < profile->path_len - 1; a++) {
-      if (pts[a].flag & PROF_SELECT) {
-        float origx = pts[a].x, origy = pts[a].y;
-        pts[a].x += fx;
-        pts[a].y += fy;
-        if (snap) {
-          pts[a].x = 0.125f * roundf(8.0f * pts[a].x);
-          pts[a].y = 0.125f * roundf(8.0f * pts[a].y);
+    /* Move all selected points. */
+    float delta[2] = {fx, fy};
+    for (a = 0; a < profile->path_len; a++) {
+      /* Don't move the last and first control points. */
+      if ((pts[a].flag & PROF_SELECT) && (a != 0) && (a != profile->path_len)) {
+        moved_point |= BKE_curveprofile_move_point(profile, &pts[a], snap, delta);
+        last_x = pts[a].x;
+        last_y = pts[a].y;
+      }
+      else {
+        /* Move handles when they're selected but the control point isn't. */
+        if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H1_SELECT) {
+          moved_point |= BKE_curveprofile_move_handle(&pts[a], true, snap, delta);
+          last_x = pts[a].h1_loc[0];
+          last_y = pts[a].h1_loc[1];
         }
-        if (!moved_point && (pts[a].x != origx || pts[a].y != origy)) {
-          moved_point = true;
+        if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H2_SELECT) {
+          moved_point |= BKE_curveprofile_move_handle(&pts[a], false, snap, delta);
+          last_x = pts[a].h2_loc[0];
+          last_y = pts[a].h2_loc[1];
         }
-
-        point_last = &pts[a];
       }
     }
 
-    BKE_curveprofile_update(profile, false);
+    BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
 
     if (moved_point) {
       data->draglastx = evtx;
@@ -6989,10 +6995,8 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
        * but in practice this isnt really an issue */
       if (ui_but_is_cursor_warp(but)) {
         /* OK but can go outside bounds */
-        data->ungrab_mval[0] = but->rect.xmin +
-                               ((point_last->x - profile->view_rect.xmin) * zoomx);
-        data->ungrab_mval[1] = but->rect.ymin +
-                               ((point_last->y - profile->view_rect.ymin) * zoomy);
+        data->ungrab_mval[0] = but->rect.xmin + ((last_x - profile->view_rect.xmin) * zoomx);
+        data->ungrab_mval[1] = but->rect.ymin + ((last_y - profile->view_rect.ymin) * zoomy);
         BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
       }
 #endif
@@ -7000,7 +7004,7 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
     data->dragchange = true; /* mark for selection */
   }
   else {
-    /* clamp for clip */
+    /* Clamp the view rect when clipping is on. */
     if (profile->flag & PROF_USE_CLIP) {
       if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) {
         fx = profile->view_rect.xmin - profile->clip_rect.xmin;
@@ -7030,6 +7034,16 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
   return changed;
 }
 
+/**
+ * Helper for #ui_do_but_CURVEPROFILE. Used to tell whether to select a control point's handles.
+ */
+static bool point_draw_handles(CurveProfilePoint *point)
+{
+  return (point->flag & PROF_SELECT &&
+          (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) ||
+         ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT);
+}
+
 /**
  * Interaction for curve profile widget.
  * \note Uses hardcoded keys rather than the keymap.
@@ -7037,10 +7051,10 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
 static int ui_do_but_CURVEPROFILE(
     bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
 {
-  int mx, my, i;
+  CurveProfile *profile = (CurveProfile *)but->poin;
+  int mx = event->x;
+  int my = event->y;
 
-  mx = event->x;
-  my = event->y;
   ui_window_to_block(data->region, block, &mx, &my);
 
   /* Move selected control points. */
@@ -7053,12 +7067,10 @@ static int ui_do_but_CURVEPROFILE(
     return WM_UI_HANDLER_BREAK;
   }
 
-  CurveProfile *profile = (CurveProfile *)but->poin;
-
   /* Delete selected control points. */
   if (event->type == EVT_XKEY && event->val == KM_RELEASE) {
     BKE_curveprofile_remove_by_flag(profile, PROF_SELECT);
-    BKE_curveprofile_update(profile, false);
+    BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
     button_activate_state(C, but, BUTTON_STATE_EXIT);
     return WM_UI_HANDLER_BREAK;
   }
@@ -7066,76 +7078,94 @@ static int ui_do_but_CURVEPROFILE(
   /* Selecting, adding, and starting point movements. */
   if (data->state == BUTTON_STATE_HIGHLIGHT) {
     if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
-      CurveProfilePoint *pts; /* Path or table. */
       const float m_xy[2] = {mx, my};
-      float dist_min_sq;
-      int i_selected = -1;
 
       if (event->ctrl) {
         float f_xy[2];
         BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
 
         BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
-        BKE_curveprofile_update(profile, false);
+        BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
       }
 
       /* Check for selecting of a point by finding closest point in radius. */
-      dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */
-      pts = profile->path;
-      for (i = 0; i < profile->path_len; i++) {
+      CurveProfilePoint *pts = profile->path;
+      float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */
+      int i_selected = -1;
+      short selection_type = 0; /* For handle selection. */
+      for (int i = 0; i < profile->path_len; i++) {
         float f_xy[2];
         BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
-        const float dist_sq = len_squared_v2v2(m_xy, f_xy);
+        float dist_sq = len_squared_v2v2(m_xy, f_xy);
         if (dist_sq < dist_min_sq) {
           i_selected = i;
+          selection_type = PROF_SELECT;
           dist_min_sq = dist_sq;
         }
+
+        /* Also select handles if the point is selected and it has the right handle type. */
+        if (point_draw_handles(&pts[i])) {
+          if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
+            BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h1_loc);
+            dist_sq = len_squared_v2v2(m_xy, f_xy);
+            if (dist_sq < dist_min_sq) {
+              i_selected = i;
+              selection_type = PROF_H1_SELECT;
+              dist_min_sq = dist_sq;
+            }
+          }
+          if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) {
+            BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h2_loc);
+            dist_sq = len_squared_v2v2(m_xy, f_xy);
+            if (dist_sq < dist_min_sq) {
+              i_selected = i;
+              selection_type = PROF_H2_SELECT;
+              dist_min_sq = dist_sq;
+            }
+          }
+        }
       }
 
-      /* Add a point if the click was close to the path but not a control point. */
-      if (i_selected == -1) { /* No control point selected. */
+      /* Add a point if the click was close to the path but not a control point or handle. */
+      if (i_selected == -1) {
         float f_xy[2], f_xy_prev[2];
-        pts = profile->table;
-        BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[0].x);
+        CurveProfilePoint *table = profile->table;
+        BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[0].x);
 
         dist_min_sq = square_f(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */
 
         /* Loop through the path's high resolution table and find what's near the click. */
-        for (i = 1; i <= PROF_N_TABLE(profile->path_len); i++) {
+        for (int i = 1; i <= PROF_N_TABLE(profile->path_len); i++) {
           copy_v2_v2(f_xy_prev, f_xy);
-          BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
+          BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x);
 
           if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
             BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
 
             CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
-            BKE_curveprofile_update(profile, false);
-
-            /* reset pts back to the control points. */
-            pts = profile->path;
+            BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
 
             /* Get the index of the newly added point. */
-            for (i = 0; i < profile->path_len; i++) {
-              if (&pts[i] == new_pt) {
-                i_selected = i;
-              }
-            }
+            i_selected = (int)(new_pt - profile->path);
+            BLI_assert(i_selected >= 0 && i_selected <= profile->path_len);
+            selection_type = PROF_SELECT;
             break;
           }
         }
       }
 
-      /* Change the flag for the point(s) if one was selected. */
+      /* Change the flag for the point(s) if one was selected or added. */
       if (i_selected != -1) {
         /* Deselect all if this one is deselected, except if we hold shift. */
-        if (!event->shift) {
-          for (i = 0; i < profile->path_len; i++) {
-            pts[i].flag &= ~PROF_SELECT;
-          }
-          pts[i_selected].flag |= PROF_SELECT;
+        if (event->shift) {
+          pts[i_selected].flag ^= selection_type;
         }
         else {
-          pts[i_selected].flag ^= PROF_SELECT;
+          for (int i = 0; i < profile->path_len; i++) {
+            // pts[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
+            profile->path[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
+          }
+          profile->path[i_selected].flag |= selection_type;
         }
       }
       else {
@@ -7166,19 +7196,13 @@ static int ui_do_but_CURVEPROFILE(
     else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
       /* Finish move. */
       if (data->dragsel != -1) {
-        CurveProfilePoint *pts = profile->path;
 
         if (data->dragchange == false) {
           /* Deselect all, select one. */
-          if (!event->shift) {
-            for (i = 0; i < profile->path_len; i++) {
-              pts[i].flag &= ~PROF_SELECT;
-            }
-            pts[data->dragsel].flag |= PROF_SELECT;
-          }
         }
         else {
-          BKE_curveprofile_update(profile, true); /* Remove doubles after move. */
+          /* Remove doubles, clip after move. */
+          BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP);
         }
       }
       button_activate_state(C, but, BUTTON_STATE_EXIT);
index 16b6b313f697c70452fec7ec74271e6fe2ace450..0d6717c8e126600963bf6c2ee5d3934a3c8519d3 100644 (file)
@@ -4463,7 +4463,7 @@ static void CurveProfile_presets_dofunc(bContext *C, void *profile_v, int event)
 
   profile->preset = event;
   BKE_curveprofile_reset(profile);
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
 
   ED_undo_push(C, "CurveProfile tools");
   ED_region_tag_redraw(CTX_wm_region(C));
@@ -4579,7 +4579,7 @@ static void CurveProfile_tools_dofunc(bContext *C, void *profile_v, int event)
   switch (event) {
     case UIPROFILE_FUNC_RESET: /* reset */
       BKE_curveprofile_reset(profile);
-      BKE_curveprofile_update(profile, false);
+      BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
       break;
     case UIPROFILE_FUNC_RESET_VIEW: /* reset view to clipping rect */
       profile->view_rect = profile->clip_rect;
@@ -4645,7 +4645,7 @@ static void CurveProfile_buttons_zoom_in(bContext *C, void *profile_v, void *UNU
   CurveProfile *profile = profile_v;
   float d;
 
-  /* we allow 20 times zoom */
+  /* Allow a 20x zoom. */
   if (BLI_rctf_size_x(&profile->view_rect) > 0.04f * BLI_rctf_size_x(&profile->clip_rect)) {
     d = 0.1154f * BLI_rctf_size_x(&profile->view_rect);
     profile->view_rect.xmin += d;
@@ -4709,7 +4709,7 @@ static void CurveProfile_clipping_toggle(bContext *C, void *cb_v, void *profile_
 
   profile->flag ^= PROF_USE_CLIP;
 
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
   rna_update_cb(C, cb_v, NULL);
 }
 
@@ -4718,7 +4718,7 @@ static void CurveProfile_buttons_reverse(bContext *C, void *cb_v, void *profile_
   CurveProfile *profile = profile_v;
 
   BKE_curveprofile_reverse(profile);
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
   rna_update_cb(C, cb_v, NULL);
 }
 
@@ -4727,35 +4727,23 @@ static void CurveProfile_buttons_delete(bContext *C, void *cb_v, void *profile_v
   CurveProfile *profile = profile_v;
 
   BKE_curveprofile_remove_by_flag(profile, SELECT);
-  BKE_curveprofile_update(profile, false);
-
-  rna_update_cb(C, cb_v, NULL);
-}
-
-static void CurveProfile_buttons_setsharp(bContext *C, void *cb_v, void *profile_v)
-{
-  CurveProfile *profile = profile_v;
-
-  BKE_curveprofile_selected_handle_set(profile, HD_VECT, HD_VECT);
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
 
   rna_update_cb(C, cb_v, NULL);
 }
 
-static void CurveProfile_buttons_setcurved(bContext *C, void *cb_v, void *profile_v)
+static void CurveProfile_buttons_update(bContext *C, void *arg1_v, void *profile_v)
 {
   CurveProfile *profile = profile_v;
-
-  BKE_curveprofile_selected_handle_set(profile, HD_AUTO, HD_AUTO);
-  BKE_curveprofile_update(profile, false);
-
-  rna_update_cb(C, cb_v, NULL);
+  BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP);
+  rna_update_cb(C, arg1_v, NULL);
 }
 
-static void CurveProfile_buttons_update(bContext *C, void *arg1_v, void *profile_v)
+static void CurveProfile_buttons_reset(bContext *C, void *arg1_v, void *profile_v)
 {
   CurveProfile *profile = profile_v;
-  BKE_curveprofile_update(profile, true);
+  BKE_curveprofile_reset(profile);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
   rna_update_cb(C, arg1_v, NULL);
 }
 
@@ -4774,7 +4762,7 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp
 
   UI_block_emboss_set(block, UI_EMBOSS);
 
-  uiLayoutRow(layout, false);
+  uiLayoutSetPropSep(layout, false);
 
   /* Preset selector */
   /* There is probably potential to use simpler "uiItemR" functions here, but automatic updating
@@ -4783,6 +4771,29 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp
       block, CurveProfile_buttons_presets, profile, "Preset", 0, 0, UI_UNIT_X, UI_UNIT_X, "");
   UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL);
 
+  /* Show a "re-apply" preset button when it has been changed from the preset. */
+  if (profile->flag & PROF_DIRTY_PRESET) {
+    /* Only for dynamic presets. */
+    if (ELEM(profile->preset, PROF_PRESET_STEPS, PROF_PRESET_SUPPORTS)) {
+      bt = uiDefIconTextBut(block,
+                            UI_BTYPE_BUT,
+                            0,
+                            ICON_NONE,
+                            "Apply Preset",
+                            0,
+                            0,
+                            UI_UNIT_X,
+                            UI_UNIT_X,
+                            NULL,
+                            0.0,
+                            0.0,
+                            0.0,
+                            0.0,
+                            "Reapply and update the preset, removing changes");
+      UI_but_funcN_set(bt, CurveProfile_buttons_reset, MEM_dupallocN(cb), profile);
+    }
+  }
+
   row = uiLayoutRow(layout, false);
 
   /* (Left aligned) */
@@ -4890,11 +4901,24 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp
            "");
 
   /* Position sliders for (first) selected point */
+  float *selection_x, *selection_y;
   for (i = 0; i < profile->path_len; i++) {
     if (profile->path[i].flag & PROF_SELECT) {
       point = &profile->path[i];
+      selection_x = &point->x;
+      selection_y = &point->y;
       break;
     }
+    else if (profile->path[i].flag & PROF_H1_SELECT) {
+      point = &profile->path[i];
+      selection_x = &point->h1_loc[0];
+      selection_y = &point->h1_loc[1];
+    }
+    else if (profile->path[i].flag & PROF_H2_SELECT) {
+      point = &profile->path[i];
+      selection_x = &point->h2_loc[0];
+      selection_y = &point->h2_loc[1];
+    }
   }
   if (i == 0 || i == profile->path_len - 1) {
     point_last_or_first = true;
@@ -4910,46 +4934,19 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp
       bounds.xmax = bounds.ymax = 1000.0;
     }
 
-    uiLayoutRow(layout, true);
-    UI_block_funcN_set(block, CurveProfile_buttons_update, MEM_dupallocN(cb), profile);
+    row = uiLayoutRow(layout, true);
 
-    /* Sharp / Smooth */
-    bt = uiDefIconBut(block,
-                      UI_BTYPE_BUT,
-                      0,
-                      ICON_LINCURVE,
-                      0,
-                      0,
-                      UI_UNIT_X,
-                      UI_UNIT_X,
-                      NULL,
-                      0.0,
-                      0.0,
-                      0.0,
-                      0.0,
-                      TIP_("Set the point's handle type to sharp"));
-    if (point_last_or_first) {
-      UI_but_flag_enable(bt, UI_BUT_DISABLED);
-    }
-    UI_but_funcN_set(bt, CurveProfile_buttons_setsharp, MEM_dupallocN(cb), profile);
-    bt = uiDefIconBut(block,
-                      UI_BTYPE_BUT,
-                      0,
-                      ICON_SMOOTHCURVE,
-                      0,
-                      0,
-                      UI_UNIT_X,
-                      UI_UNIT_X,
-                      NULL,
-                      0.0,
-                      0.0,
-                      0.0,
-                      0.0,
-                      TIP_("Set the point's handle type to smooth"));
-    UI_but_funcN_set(bt, CurveProfile_buttons_setcurved, MEM_dupallocN(cb), profile);
-    if (point_last_or_first) {
-      UI_but_flag_enable(bt, UI_BUT_DISABLED);
-    }
+    PointerRNA point_ptr;
+    RNA_pointer_create(ptr->owner_id, &RNA_CurveProfilePoint, point, &point_ptr);
+    PropertyRNA *prop_handle_type = RNA_struct_find_property(&point_ptr, "handle_type_1");
+    uiItemFullR(row,
+                &point_ptr,
+                prop_handle_type,
+                RNA_NO_INDEX,
+                0,
+                UI_ITEM_R_EXPAND | UI_ITEM_R_ICON_ONLY,
+                "",
+                ICON_NONE);
 
     /* Position */
     bt = uiDefButF(block,
@@ -4960,16 +4957,16 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp
                    2 * UI_UNIT_Y,
                    UI_UNIT_X * 10,
                    UI_UNIT_Y,
-                   &point->x,
+                   selection_x,
                    bounds.xmin,
                    bounds.xmax,
                    1,
                    5,
                    "");
+    UI_but_funcN_set(bt, CurveProfile_buttons_update, MEM_dupallocN(cb), profile);
     if (point_last_or_first) {
       UI_but_flag_enable(bt, UI_BUT_DISABLED);
     }
-
     bt = uiDefButF(block,
                    UI_BTYPE_NUM,
                    0,
@@ -4978,12 +4975,13 @@ static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUp
                    1 * UI_UNIT_Y,
                    UI_UNIT_X * 10,
                    UI_UNIT_Y,
-                   &point->y,
+                   selection_y,
                    bounds.ymin,
                    bounds.ymax,
                    1,
                    5,
                    "");
+    UI_but_funcN_set(bt, CurveProfile_buttons_update, MEM_dupallocN(cb), profile);
     if (point_last_or_first) {
       UI_but_flag_enable(bt, UI_BUT_DISABLED);
     }
index 63e1049b63620eccdfa1dba90342b687e8d7579c..ca00f783905df354275c93c220b0bf21a1293b55 100644 (file)
@@ -43,13 +43,22 @@ typedef struct CurveProfilePoint {
   float x, y;
   /** Flag selection state and others. */
   short flag;
-  /** Flags for both handle's type (eBezTriple_Handle). */
+  /** Flags for both handle's type (eBezTriple_Handle auto, vect, free, and aligned supported). */
   char h1, h2;
+  /** Handle locations, keep together.
+   * \note For now the two handle types are set to the same type in RNA. */
+  float h1_loc[2];
+  float h2_loc[2];
+  char _pad[4];
+  /** Runtime pointer to the point's profile for updating the curve with no direct reference. */
+  struct CurveProfile *profile;
 } CurveProfilePoint;
 
 /** #CurveProfilePoint.flag */
 enum {
   PROF_SELECT = (1 << 0),
+  PROF_H1_SELECT = (1 << 1),
+  PROF_H2_SELECT = (1 << 2),
 };
 
 /** Defines a profile. */
@@ -76,10 +85,11 @@ typedef struct CurveProfile {
 
 /** #CurveProfile.flag */
 enum {
-  PROF_USE_CLIP = (1 << 0),                    /* Keep control points inside bounding rectangle. */
-  /* PROF_SYMMETRY_MODE = (1 << 1),         */ /* Unused for now. */
-  PROF_SAMPLE_STRAIGHT_EDGES = (1 << 2),       /* Sample extra points on straight edges. */
-  PROF_SAMPLE_EVEN_LENGTHS = (1 << 3),         /* Put segments evenly spaced along the path. */
+  PROF_USE_CLIP = (1 << 0), /* Keep control points inside bounding rectangle. */
+  /* PROF_SYMMETRY_MODE = (1 << 1),         Unused for now. */
+  PROF_SAMPLE_STRAIGHT_EDGES = (1 << 2), /* Sample extra points on straight edges. */
+  PROF_SAMPLE_EVEN_LENGTHS = (1 << 3),   /* Put segments evenly spaced along the path. */
+  PROF_DIRTY_PRESET = (1 << 4),          /* Marks when the dynamic preset has been changed. */
 };
 
 typedef enum eCurveProfilePresets {
index 94a35bdede87d2254477967fc399d17ee0a073c6..ce91fc790852556e01d3bedb574f3c6e45f24a52 100644 (file)
 #  include "IMB_colormanagement.h"
 #  include "IMB_imbuf.h"
 
+/**
+ * Set both handle types for all selected points in the profile-- faster than changing types
+ * for many points individually. Also set both handles for the points.
+ */
+static void rna_CurveProfilePoint_handle_type_set(PointerRNA *ptr, int value)
+{
+  CurveProfilePoint *point = ptr->data;
+  CurveProfile *profile = point->profile;
+
+  if (profile) {
+    BKE_curveprofile_selected_handle_set(profile, value, value);
+    BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
+    WM_main_add_notifier(NC_GEOM | ND_DATA, NULL);
+  }
+}
+
 static void rna_CurveProfile_clip_set(PointerRNA *ptr, bool value)
 {
   CurveProfile *profile = (CurveProfile *)ptr->data;
@@ -73,7 +89,7 @@ static void rna_CurveProfile_clip_set(PointerRNA *ptr, bool value)
     profile->flag &= ~PROF_USE_CLIP;
   }
 
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_CLIP);
 }
 
 static void rna_CurveProfile_sample_straight_set(PointerRNA *ptr, bool value)
@@ -87,7 +103,7 @@ static void rna_CurveProfile_sample_straight_set(PointerRNA *ptr, bool value)
     profile->flag &= ~PROF_SAMPLE_STRAIGHT_EDGES;
   }
 
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
 }
 
 static void rna_CurveProfile_sample_even_set(PointerRNA *ptr, bool value)
@@ -101,7 +117,7 @@ static void rna_CurveProfile_sample_even_set(PointerRNA *ptr, bool value)
     profile->flag &= ~PROF_SAMPLE_EVEN_LENGTHS;
   }
 
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_NONE);
 }
 
 static void rna_CurveProfile_remove_point(CurveProfile *profile,
@@ -135,14 +151,16 @@ static void rna_CurveProfile_initialize(struct CurveProfile *profile, int segmen
 
 static void rna_CurveProfile_update(struct CurveProfile *profile)
 {
-  BKE_curveprofile_update(profile, false);
+  BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP);
 }
 
 #else
 
 static const EnumPropertyItem prop_handle_type_items[] = {
-    {HD_AUTO, "AUTO", 0, "Auto Handle", ""},
-    {HD_VECT, "VECTOR", 0, "Vector Handle", ""},
+    {HD_AUTO, "AUTO", ICON_HANDLE_AUTO, "Auto Handle", ""},
+    {HD_VECT, "VECTOR", ICON_HANDLE_VECTOR, "Vector Handle", ""},
+    {HD_FREE, "FREE", ICON_HANDLE_FREE, "Free Handle", ""},
+    {HD_ALIGN, "ALIGN", ICON_HANDLE_ALIGNED, "Aligned Free Handles", ""},
     {0, NULL, 0, NULL, NULL},
 };
 
@@ -162,14 +180,14 @@ static void rna_def_curveprofilepoint(BlenderRNA *brna)
   prop = RNA_def_property(srna, "handle_type_1", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "h1");
   RNA_def_property_enum_items(prop, prop_handle_type_items);
-  RNA_def_property_ui_text(
-      prop, "First Handle Type", "Path interpolation at this point: Bezier or vector");
+  RNA_def_property_enum_funcs(prop, NULL, "rna_CurveProfilePoint_handle_type_set", NULL);
+  RNA_def_property_ui_text(prop, "First Handle Type", "Path interpolation at this point");
 
   prop = RNA_def_property(srna, "handle_type_2", PROP_ENUM, PROP_NONE);
   RNA_def_property_enum_sdna(prop, NULL, "h2");
   RNA_def_property_enum_items(prop, prop_handle_type_items);
-  RNA_def_property_ui_text(
-      prop, "Second Handle Type", "Path interpolation at this point: Bezier or vector");
+  RNA_def_property_enum_funcs(prop, NULL, "rna_CurveProfilePoint_handle_type_set", NULL);
+  RNA_def_property_ui_text(prop, "Second Handle Type", "Path interpolation at this point");
 
   prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", PROF_SELECT);
@@ -260,7 +278,7 @@ static void rna_def_curveprofile(BlenderRNA *brna)
   RNA_def_property_boolean_funcs(prop, NULL, "rna_CurveProfile_sample_even_set");
 
   func = RNA_def_function(srna, "update", "rna_CurveProfile_update");
-  RNA_def_function_ui_description(func, "Update the profile");
+  RNA_def_function_ui_description(func, "Refresh internal data, remove doubles and clip points");
 
   func = RNA_def_function(srna, "initialize", "rna_CurveProfile_initialize");
   parm = RNA_def_int(func,