gui_curve_editor.h 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /*******************************************************************************************
  2. *
  3. * CurveEdit v1.0 - A cubic Hermite editor for making animation curves
  4. *
  5. * MODULE USAGE:
  6. * #define GUI_CURVE_EDITOR_IMPLEMENTATION
  7. * #include "gui_curve_edit.h"
  8. *
  9. * INIT: GuiCurveEditState state = InitCurveEdit();
  10. * EVALUATE: float y = EvalGuiCurve(&state, t); // 0 <= t <= 1
  11. * DRAW: BeginScissorMode(bounds.x,bounds.y,bounds.width,bounds.height);
  12. * GuiCurveEdit(&state, bounds, pointSize);
  13. * EndScissorMode();
  14. *
  15. * NOTE: See 'Module Structures Declaration' section for more informations.
  16. *
  17. * NOTE: This module uses functions of the stdlib:
  18. * - qsort
  19. *
  20. * NOTE: Built-in interactions:
  21. * - Left click to move/add point or move tangents
  22. * - While moving a tangent, hold (left/right) SHIFT to disable tangent symetry
  23. * - Right click to remove a point
  24. *
  25. *
  26. * LICENSE: zlib/libpng
  27. *
  28. * Copyright (c) 2023 Pierre Jaffuer (@smallcluster)
  29. *
  30. * This software is provided "as-is", without any express or implied warranty. In no event
  31. * will the authors be held liable for any damages arising from the use of this software.
  32. *
  33. * Permission is granted to anyone to use this software for any purpose, including commercial
  34. * applications, and to alter it and redistribute it freely, subject to the following restrictions:
  35. *
  36. * 1. The origin of this software must not be misrepresented; you must not claim that you
  37. * wrote the original software. If you use this software in a product, an acknowledgment
  38. * in the product documentation would be appreciated but is not required.
  39. *
  40. * 2. Altered source versions must be plainly marked as such, and must not be misrepresented
  41. * as being the original software.
  42. *
  43. * 3. This notice may not be removed or altered from any source distribution.
  44. *
  45. **********************************************************************************************/
  46. #include "raylib.h"
  47. #ifndef GUI_CURVE_EDITOR_H
  48. #define GUI_CURVE_EDITOR_H
  49. #ifndef GUI_CURVE_EDITOR_MAX_POINTS
  50. #define GUI_CURVE_EDITOR_MAX_POINTS 30
  51. #endif
  52. //----------------------------------------------------------------------------------
  53. // Module Structures Declaration
  54. //----------------------------------------------------------------------------------
  55. typedef struct {
  56. Vector2 position; // In normalized space [0.0f, 1.0f]
  57. Vector2 tangents; // The derivatives (left/right) of the 1D curve
  58. // Let the curve editor calculate tangents to linearize part of the curve
  59. bool leftLinear;
  60. bool rightLinear;
  61. } GuiCurveEditorPoint;
  62. typedef struct {
  63. float start; // Value at y = 0
  64. float end; // Value at y = 1
  65. // Always valid (unless you manualy change state's point array). Make sure to set it to -1 before init
  66. int selectedIndex;
  67. // Unsorted array with at least one point (constant curve)
  68. GuiCurveEditorPoint points[GUI_CURVE_EDITOR_MAX_POINTS];
  69. int numPoints;
  70. // Private variables
  71. bool editLeftTangent;
  72. bool editRightTangent;
  73. Vector2 mouseOffset;
  74. } GuiCurveEditorState;
  75. #ifdef __cplusplus
  76. extern "C" { // Prevents name mangling of functions
  77. #endif
  78. //----------------------------------------------------------------------------------
  79. // Module Functions Declaration
  80. //----------------------------------------------------------------------------------
  81. GuiCurveEditorState InitGuiCurveEditor(); // Initialize curve editor state
  82. void GuiCurveEditor(GuiCurveEditorState *state, Rectangle bounds); // Draw and update curve control
  83. // 1D Interpolation
  84. // Returns the y value (in [start, end]) of the curve at x = t
  85. // t must be normalized [0.f, 1.f]
  86. float GuiCurveEval(GuiCurveEditorState *state, float t);
  87. #ifdef __cplusplus
  88. }
  89. #endif
  90. #endif // GUI_CURVE_EDITOR_H
  91. /***********************************************************************************
  92. *
  93. * GUI_CURVE_EDITOR IMPLEMENTATION
  94. *
  95. ************************************************************************************/
  96. #if defined(GUI_CURVE_EDITOR_IMPLEMENTATION)
  97. #include "../../src/raygui.h" // Change this to fit your project
  98. #include "stdlib.h" // Required for qsort
  99. //----------------------------------------------------------------------------------
  100. // Module Functions Definition
  101. //----------------------------------------------------------------------------------
  102. GuiCurveEditorState InitGuiCurveEditor()
  103. {
  104. GuiCurveEditorState state = { 0 };
  105. state.start = 0;
  106. state.end = 1;
  107. state.selectedIndex = 0;
  108. state.editLeftTangent = false;
  109. state.editRightTangent = false;
  110. state.mouseOffset = (Vector2){ 0.0f, 0.0f };
  111. // At least one point (AVG by default)
  112. state.numPoints = 1;
  113. state.points[0].position = (Vector2){ 0.5f, 0.5f };
  114. state.points[0].tangents = (Vector2){ 0.0f, 0.0f };
  115. state.points[0].leftLinear = false;
  116. state.points[0].rightLinear = false;
  117. return state;
  118. }
  119. static int CompareGuiCurveEditPointPtr(const void *a, const void *b)
  120. {
  121. float fa = (*(GuiCurveEditorPoint**)a)->position.x;
  122. float fb = (*(GuiCurveEditorPoint**)b)->position.x;
  123. return ((fa > fb) - (fa < fb));
  124. }
  125. float GuiCurveEval(GuiCurveEditorState *state, float t)
  126. {
  127. // Sort points
  128. GuiCurveEditorPoint* sortedPoints[GUI_CURVE_EDITOR_MAX_POINTS];
  129. for (int i=0; i < state->numPoints; i++) sortedPoints[i] = &state->points[i];
  130. qsort(sortedPoints, state->numPoints, sizeof(GuiCurveEditorPoint*), CompareGuiCurveEditPointPtr);
  131. if (state->numPoints == 0) return state->start;
  132. // Constants part on edges
  133. if (t <= sortedPoints[0]->position.x) return state->start + (state->end-state->start)*sortedPoints[0]->position.y;
  134. if (t >= sortedPoints[state->numPoints-1]->position.x) return state->start + (state->end-state->start)*sortedPoints[state->numPoints-1]->position.y;
  135. // Find curve portion
  136. for (int i=0; i < state->numPoints-1; i++)
  137. {
  138. const GuiCurveEditorPoint *p1 = sortedPoints[i];
  139. const GuiCurveEditorPoint *p2 = sortedPoints[i+1];
  140. // Skip this range
  141. if (!((t >= p1->position.x) && (t < p2->position.x)) || (p1->position.x == p2->position.x)) continue;
  142. float scale = (p2->position.x-p1->position.x);
  143. float T = (t-p1->position.x)/scale;
  144. float startTangent = scale*p1->tangents.y;
  145. float endTangent = scale*p2->tangents.x;
  146. float T2 = T*T;
  147. float T3 = T*T*T;
  148. return (state->start + (state->end-state->start)*((2*T3 - 3*T2 + 1)*p1->position.y + (T3 - 2*T2 + T)*startTangent + (3*T2 - 2*T3)*p2->position.y + (T3 - T2)*endTangent));
  149. }
  150. return state->start;
  151. }
  152. void GuiCurveEditor(GuiCurveEditorState *state, Rectangle bounds)
  153. {
  154. // CONST
  155. //----------------------------------------------------------------------------------
  156. const float pointSize = 10.0f;
  157. const float fontSize = GuiGetStyle(DEFAULT, TEXT_SIZE);
  158. const float handleLength = pointSize*2.5f;
  159. const float handleSize = pointSize/1.5f;
  160. const Rectangle innerBounds = (Rectangle){ bounds.x + fontSize, bounds.y + fontSize, bounds.width - 2*fontSize, bounds.height - 2*fontSize };
  161. const Vector2 mouse = GetMousePosition();
  162. const Vector2 mouseLocal = (Vector2){ (mouse.x - innerBounds.x)/innerBounds.width, (innerBounds.y + innerBounds.height-mouse.y)/innerBounds.height};
  163. //----------------------------------------------------------------------------------
  164. // UPDATE STATE
  165. //----------------------------------------------------------------------------------
  166. // Find first point under mouse (-1 if not found)
  167. int hoveredPointIndex = -1;
  168. for (int i = 0; i < state->numPoints; i++)
  169. {
  170. const GuiCurveEditorPoint *p = &state->points[i];
  171. const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height-p->position.y*innerBounds.height };
  172. const Rectangle pointRect = (Rectangle){ screenPos.x - pointSize/2.0f, screenPos.y - pointSize/2.0f, pointSize, pointSize };
  173. if (CheckCollisionPointRec(mouse, pointRect))
  174. {
  175. hoveredPointIndex = i;
  176. break;
  177. }
  178. }
  179. // Unselect tangents
  180. if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT))
  181. {
  182. state->editLeftTangent = false;
  183. state->editRightTangent = false;
  184. }
  185. // Select a tangent if possible
  186. if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (state->selectedIndex != -1) && CheckCollisionPointRec(mouse, bounds))
  187. {
  188. const GuiCurveEditorPoint* p = &state->points[state->selectedIndex];
  189. const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width+innerBounds.x, innerBounds.y+innerBounds.height-p->position.y*innerBounds.height };
  190. // Left control
  191. Vector2 target = (Vector2){ (p->position.x-1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y-p->tangents.x)*innerBounds.height };
  192. Vector2 dir = (Vector2){ target.x-screenPos.x, target.y-screenPos.y };
  193. float d = sqrt(dir.x*dir.x + dir.y*dir.y);
  194. Vector2 control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
  195. Rectangle controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
  196. // Edit left tangent
  197. if (CheckCollisionPointRec(mouse, controlRect)) state->editLeftTangent = true;
  198. // Right control
  199. target = (Vector2){ (p->position.x + 1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y + p->tangents.y)*innerBounds.height };
  200. dir = (Vector2){ target.x-screenPos.x, target.y-screenPos.y };
  201. d = sqrt(dir.x*dir.x + dir.y*dir.y);
  202. control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
  203. controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
  204. // Edit right tangent
  205. if (CheckCollisionPointRec(mouse, controlRect)) state->editRightTangent = true;
  206. }
  207. // Move tangents
  208. if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && state->editRightTangent)
  209. {
  210. // editRightTangent == true implies selectedIndex != -1
  211. GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
  212. const Vector2 dir = (Vector2){ mouseLocal.x - p->position.x, mouseLocal.y - p->position.y};
  213. // Calculate right tangent slope
  214. p->tangents.y = (dir.x < 0.001f)? dir.y/0.001f : dir.y/dir.x;
  215. p->rightLinear = false; // Stop right linearization update
  216. // Tangents are symetric by default unless SHIFT is pressed
  217. if (!(IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)))
  218. {
  219. p->tangents.x = p->tangents.y;
  220. p->leftLinear = false; // Stop left linearization update
  221. }
  222. }
  223. else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && state->editLeftTangent)
  224. {
  225. // editLeftTangent == true implies selectedIndex != -1
  226. GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
  227. const Vector2 dir = (Vector2){ mouseLocal.x - p->position.x, mouseLocal.y - p->position.y };
  228. // Calculate left tangent slope
  229. p->tangents.x = (dir.x > -0.001f)? dir.y/(-0.001f) : dir.y/dir.x;
  230. p->leftLinear = false; // Stop left linearization update
  231. // Tangents are symetric by default unless SHIFT is pressed
  232. if (!(IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)))
  233. {
  234. p->tangents.y = p->tangents.x;
  235. p->rightLinear = false; // Stop right linearization update
  236. }
  237. }
  238. // Select a point
  239. else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (hoveredPointIndex != -1) && CheckCollisionPointRec(mouse, bounds))
  240. {
  241. state->selectedIndex = hoveredPointIndex;
  242. const GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
  243. state->mouseOffset = (Vector2){ p->position.x - mouseLocal.x, p->position.y - mouseLocal.y };
  244. }
  245. // Remove a point (check against bounds)
  246. else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) && (hoveredPointIndex != -1) && CheckCollisionPointRec(mouse, bounds) && (state->numPoints > 1))
  247. {
  248. // Deselect everything
  249. state->selectedIndex = 0; // select first point by default
  250. state->editLeftTangent = false;
  251. state->editRightTangent = false;
  252. // Remove point
  253. state->numPoints -= 1;
  254. for (int i = hoveredPointIndex; i < state->numPoints; i++) state->points[i] = state->points[i+1];
  255. }
  256. // Add a point (check against innerBounds)
  257. else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mouse, innerBounds) && (state->numPoints < GUI_CURVE_EDITOR_MAX_POINTS))
  258. {
  259. state->editLeftTangent = false;
  260. state->editRightTangent = false;
  261. // Create new point
  262. GuiCurveEditorPoint p;
  263. p.tangents = (Vector2){ 0.0f, 0.0f };
  264. p.position = mouseLocal;
  265. p.leftLinear = false;
  266. p.rightLinear = false;
  267. // Append point
  268. state->points[state->numPoints] = p;
  269. state->selectedIndex = state->numPoints; // select new point
  270. state->numPoints += 1;
  271. // Point is add on mouse pos
  272. state->mouseOffset = (Vector2){ 0, 0 };
  273. }
  274. // Move selected point
  275. else if ((state->selectedIndex != -1) && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mouse, bounds))
  276. {
  277. GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
  278. // use mouse offset on click to prevent point teleporting to mouse
  279. const Vector2 newLocalPos = (Vector2){ mouseLocal.x + state->mouseOffset.x, mouseLocal.y + state->mouseOffset.y };
  280. // Clamp to innerbounds
  281. p->position.x = (newLocalPos.x < 0)? 0 : ((newLocalPos.x > 1)? 1 : newLocalPos.x);
  282. p->position.y = (newLocalPos.y < 0)? 0 : ((newLocalPos.y > 1)? 1 : newLocalPos.y);
  283. }
  284. // Sort points
  285. GuiCurveEditorPoint *sortedPoints[GUI_CURVE_EDITOR_MAX_POINTS] = { 0 };
  286. for (int i = 0; i < state->numPoints; i++) sortedPoints[i] = &state->points[i];
  287. qsort(sortedPoints, state->numPoints, sizeof(GuiCurveEditorPoint*), CompareGuiCurveEditPointPtr);
  288. // Update linear tangents
  289. for (int i = 0; i < state->numPoints; i++)
  290. {
  291. GuiCurveEditorPoint *p = sortedPoints[i];
  292. // Left tangent
  293. if ((i > 0) && p->leftLinear)
  294. {
  295. const GuiCurveEditorPoint *p2 = sortedPoints[i - 1];
  296. Vector2 dir = (Vector2){ p2->position.x - p->position.x, p2->position.y - p->position.y };
  297. p->tangents.x = (dir.x == 0)? 0 : dir.y/dir.x;
  298. }
  299. // Right tangent
  300. if ((i < state->numPoints - 1) && p->rightLinear)
  301. {
  302. const GuiCurveEditorPoint *p2 = sortedPoints[i + 1];
  303. Vector2 dir = (Vector2){ p2->position.x - p->position.x, p2->position.y - p->position.y };
  304. p->tangents.y = (dir.x == 0)? 0 : dir.y/dir.x;
  305. }
  306. }
  307. //----------------------------------------------------------------------------------
  308. // DRAWING
  309. //----------------------------------------------------------------------------------
  310. DrawRectangle(bounds.x, bounds.y, bounds.width, bounds.height, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
  311. // Draw grid
  312. // H lines
  313. const Color lineColor = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL));
  314. DrawLine(bounds.x, innerBounds.y, bounds.x+bounds.width, innerBounds.y, lineColor); // end
  315. DrawLine(bounds.x, innerBounds.y+innerBounds.height/2, bounds.x+bounds.width, innerBounds.y+innerBounds.height/2, lineColor); // avg
  316. DrawLine(bounds.x, innerBounds.y+innerBounds.height, bounds.x+bounds.width, innerBounds.y+innerBounds.height, lineColor); // start
  317. // V lines
  318. DrawLine(innerBounds.x, bounds.y, innerBounds.x, bounds.y+bounds.height, lineColor); // 0
  319. DrawLine(innerBounds.x + innerBounds.width/4, bounds.y, innerBounds.x + innerBounds.width/4, bounds.y + bounds.height, lineColor); // 0.25
  320. DrawLine(innerBounds.x + innerBounds.width/2, bounds.y, innerBounds.x + innerBounds.width/2, bounds.y + bounds.height, lineColor); // 0.5
  321. DrawLine(innerBounds.x + 3*innerBounds.width/4, bounds.y, innerBounds.x + 3*innerBounds.width/4, bounds.y + bounds.height, lineColor); // 0.75
  322. DrawLine(innerBounds.x + innerBounds.width, bounds.y, innerBounds.x + innerBounds.width, bounds.y + bounds.height, lineColor); // 1
  323. Font font = GuiGetFont();
  324. // V labels
  325. DrawTextEx(font, "0", (Vector2){ innerBounds.x, bounds.y + bounds.height-fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  326. DrawTextEx(font, "0.25", (Vector2){ innerBounds.x + innerBounds.width/4.0f, bounds.y + bounds.height - fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  327. DrawTextEx(font, "0.5", (Vector2){ innerBounds.x + innerBounds.width/2.0f, bounds.y + bounds.height - fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  328. DrawTextEx(font, "0.75", (Vector2){ innerBounds.x + 3.0f*innerBounds.width/4.0f, bounds.y + bounds.height-fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  329. DrawTextEx(font, "1", (Vector2){ innerBounds.x + innerBounds.width, bounds.y+bounds.height - fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  330. // H labels
  331. DrawTextEx(font, TextFormat("%.2f", state->start), (Vector2){ innerBounds.x, innerBounds.y - fontSize+innerBounds.height }, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  332. DrawTextEx(font, TextFormat("%.2f", state->start + (state->end-state->start)/2.f), (Vector2){ innerBounds.x, innerBounds.y - fontSize + innerBounds.height/2.0f }, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  333. DrawTextEx(font, TextFormat("%.2f", state->end), (Vector2){ innerBounds.x, innerBounds.y }, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
  334. // Draw contours
  335. if (CheckCollisionPointRec(mouse, bounds)) DrawRectangleLines(bounds.x, bounds.y, bounds.width, bounds.height, GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED)));
  336. else DrawRectangleLines(bounds.x, bounds.y, bounds.width, bounds.height, GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL)));
  337. // Draw points
  338. for (int i = 0; i < state->numPoints; i++)
  339. {
  340. const GuiCurveEditorPoint *p = sortedPoints[i];
  341. const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p->position.y*innerBounds.height };
  342. const Rectangle pointRect = (Rectangle){ screenPos.x - pointSize/2.0f, screenPos.y - pointSize/2.0f, pointSize, pointSize };
  343. Color pointColor = { 0 };
  344. Color pointBorderColor = { 0 };
  345. // Draw point
  346. if (&state->points[state->selectedIndex] == p)
  347. {
  348. // Draw left handle
  349. if (i > 0)
  350. {
  351. const Vector2 target = (Vector2){ (p->position.x - 1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y - p->tangents.x)*innerBounds.height };
  352. const Vector2 dir = (Vector2){ target.x - screenPos.x, target.y - screenPos.y };
  353. const float d = sqrt(dir.x*dir.x + dir.y*dir.y);
  354. const Vector2 control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
  355. const Rectangle controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
  356. Color controlColor = { 0 };
  357. if (state->editLeftTangent)
  358. {
  359. controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED));
  360. }
  361. else if (CheckCollisionPointRec(mouse, controlRect))
  362. {
  363. controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED));
  364. }
  365. else
  366. {
  367. controlColor = GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL));
  368. }
  369. DrawLine(screenPos.x,screenPos.y, control.x, control.y, controlColor);
  370. DrawRectangle(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
  371. DrawRectangleLines(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
  372. }
  373. // Draw right handle
  374. if (i < state->numPoints - 1)
  375. {
  376. const Vector2 target = (Vector2){ (p->position.x + 1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y + p->tangents.y)*innerBounds.height };
  377. const Vector2 dir = (Vector2){ target.x - screenPos.x, target.y - screenPos.y };
  378. const float d = sqrt(dir.x*dir.x + dir.y*dir.y);
  379. const Vector2 control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
  380. const Rectangle controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
  381. Color controlColor = { 0 };
  382. if (state->editRightTangent)
  383. {
  384. controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED));
  385. }
  386. else if (CheckCollisionPointRec(mouse, controlRect))
  387. {
  388. controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED));
  389. }
  390. else
  391. {
  392. controlColor = GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL));
  393. }
  394. DrawLine(screenPos.x,screenPos.y, control.x, control.y, controlColor);
  395. DrawRectangle(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
  396. DrawRectangleLines(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
  397. }
  398. pointColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED));
  399. pointBorderColor = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL));
  400. }
  401. else if (&state->points[hoveredPointIndex] == p)
  402. {
  403. pointColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED));
  404. pointBorderColor = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL));
  405. }
  406. else
  407. {
  408. pointColor = GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL));
  409. pointBorderColor = GetColor(GuiGetStyle(BUTTON, BORDER_COLOR_NORMAL));
  410. }
  411. DrawRectangle(pointRect.x, pointRect.y, pointRect.width, pointRect.height, pointColor);
  412. DrawRectangleLines(pointRect.x, pointRect.y, pointRect.width, pointRect.height, pointBorderColor);
  413. }
  414. // Draw curve
  415. Color curveColor = GetColor(GuiGetStyle(LABEL, TEXT_COLOR_FOCUSED));
  416. if (state->numPoints == 1)
  417. {
  418. const GuiCurveEditorPoint *p = sortedPoints[0];
  419. const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p->position.y*innerBounds.height };
  420. DrawLine(innerBounds.x, screenPos.y, innerBounds.x+innerBounds.width, screenPos.y, curveColor);
  421. }
  422. else
  423. {
  424. for (int i = 0; i < state->numPoints - 1; i++)
  425. {
  426. const GuiCurveEditorPoint *p1 = sortedPoints[i];
  427. const GuiCurveEditorPoint *p2 = sortedPoints[i + 1];
  428. const Vector2 screenPos1 = (Vector2){ p1->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p1->position.y*innerBounds.height };
  429. const Vector2 screenPos2 = (Vector2){ p2->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p2->position.y*innerBounds.height };
  430. // Constant on edge
  431. if ((screenPos1.x > innerBounds.x) && (i == 0))
  432. {
  433. DrawLine(innerBounds.x, screenPos1.y, screenPos1.x, screenPos1.y, curveColor);
  434. }
  435. if ((screenPos2.x < innerBounds.x + innerBounds.width) && (i == (state->numPoints - 2)))
  436. {
  437. DrawLine(screenPos2.x, screenPos2.y, innerBounds.x+innerBounds.width, screenPos2.y, curveColor);
  438. }
  439. // Draw cubic Hermite curve
  440. const float scale = (p2->position.x - p1->position.x)/3.0f;
  441. const Vector2 offset1 = (Vector2){ scale, scale*p1->tangents.y };
  442. // negative endTangent => top part => need to invert value to calculate offset
  443. const Vector2 offset2 = (Vector2){ -scale, -scale*p2->tangents.x };
  444. const Vector2 c1 = (Vector2){ p1->position.x + offset1.x, p1->position.y + offset1.y };
  445. const Vector2 c2 = (Vector2){ p2->position.x + offset2.x, p2->position.y + offset2.y };
  446. const Vector2 screenC1 = (Vector2){ c1.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - c1.y*innerBounds.height };
  447. const Vector2 screenC2 = (Vector2){ c2.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - c2.y*innerBounds.height };
  448. DrawSplineSegmentBezierCubic(screenPos1, screenC1, screenC2, screenPos2, 1, curveColor);
  449. }
  450. }
  451. }
  452. #endif // GUI_CURVE_EDITOR_IMPLEMENTATION