123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543 |
- /*******************************************************************************************
- *
- * CurveEdit v1.0 - A cubic Hermite editor for making animation curves
- *
- * MODULE USAGE:
- * #define GUI_CURVE_EDITOR_IMPLEMENTATION
- * #include "gui_curve_edit.h"
- *
- * INIT: GuiCurveEditState state = InitCurveEdit();
- * EVALUATE: float y = EvalGuiCurve(&state, t); // 0 <= t <= 1
- * DRAW: BeginScissorMode(bounds.x,bounds.y,bounds.width,bounds.height);
- * GuiCurveEdit(&state, bounds, pointSize);
- * EndScissorMode();
- *
- * NOTE: See 'Module Structures Declaration' section for more informations.
- *
- * NOTE: This module uses functions of the stdlib:
- * - qsort
- *
- * NOTE: Built-in interactions:
- * - Left click to move/add point or move tangents
- * - While moving a tangent, hold (left/right) SHIFT to disable tangent symetry
- * - Right click to remove a point
- *
- *
- * LICENSE: zlib/libpng
- *
- * Copyright (c) 2023 Pierre Jaffuer (@smallcluster)
- *
- * This software is provided "as-is", without any express or implied warranty. In no event
- * will the authors be held liable for any damages arising from the use of this software.
- *
- * Permission is granted to anyone to use this software for any purpose, including commercial
- * applications, and to alter it and redistribute it freely, subject to the following restrictions:
- *
- * 1. The origin of this software must not be misrepresented; you must not claim that you
- * wrote the original software. If you use this software in a product, an acknowledgment
- * in the product documentation would be appreciated but is not required.
- *
- * 2. Altered source versions must be plainly marked as such, and must not be misrepresented
- * as being the original software.
- *
- * 3. This notice may not be removed or altered from any source distribution.
- *
- **********************************************************************************************/
- #include "raylib.h"
- #ifndef GUI_CURVE_EDITOR_H
- #define GUI_CURVE_EDITOR_H
- #ifndef GUI_CURVE_EDITOR_MAX_POINTS
- #define GUI_CURVE_EDITOR_MAX_POINTS 30
- #endif
- //----------------------------------------------------------------------------------
- // Module Structures Declaration
- //----------------------------------------------------------------------------------
- typedef struct {
- Vector2 position; // In normalized space [0.0f, 1.0f]
- Vector2 tangents; // The derivatives (left/right) of the 1D curve
- // Let the curve editor calculate tangents to linearize part of the curve
- bool leftLinear;
- bool rightLinear;
- } GuiCurveEditorPoint;
- typedef struct {
- float start; // Value at y = 0
- float end; // Value at y = 1
- // Always valid (unless you manualy change state's point array). Make sure to set it to -1 before init
- int selectedIndex;
- // Unsorted array with at least one point (constant curve)
- GuiCurveEditorPoint points[GUI_CURVE_EDITOR_MAX_POINTS];
- int numPoints;
- // Private variables
- bool editLeftTangent;
- bool editRightTangent;
- Vector2 mouseOffset;
- } GuiCurveEditorState;
- #ifdef __cplusplus
- extern "C" { // Prevents name mangling of functions
- #endif
- //----------------------------------------------------------------------------------
- // Module Functions Declaration
- //----------------------------------------------------------------------------------
- GuiCurveEditorState InitGuiCurveEditor(); // Initialize curve editor state
- void GuiCurveEditor(GuiCurveEditorState *state, Rectangle bounds); // Draw and update curve control
- // 1D Interpolation
- // Returns the y value (in [start, end]) of the curve at x = t
- // t must be normalized [0.f, 1.f]
- float GuiCurveEval(GuiCurveEditorState *state, float t);
- #ifdef __cplusplus
- }
- #endif
- #endif // GUI_CURVE_EDITOR_H
- /***********************************************************************************
- *
- * GUI_CURVE_EDITOR IMPLEMENTATION
- *
- ************************************************************************************/
- #if defined(GUI_CURVE_EDITOR_IMPLEMENTATION)
- #include "../../src/raygui.h" // Change this to fit your project
- #include "stdlib.h" // Required for qsort
- //----------------------------------------------------------------------------------
- // Module Functions Definition
- //----------------------------------------------------------------------------------
- GuiCurveEditorState InitGuiCurveEditor()
- {
- GuiCurveEditorState state = { 0 };
- state.start = 0;
- state.end = 1;
- state.selectedIndex = 0;
- state.editLeftTangent = false;
- state.editRightTangent = false;
- state.mouseOffset = (Vector2){ 0.0f, 0.0f };
- // At least one point (AVG by default)
- state.numPoints = 1;
- state.points[0].position = (Vector2){ 0.5f, 0.5f };
- state.points[0].tangents = (Vector2){ 0.0f, 0.0f };
- state.points[0].leftLinear = false;
- state.points[0].rightLinear = false;
- return state;
- }
- static int CompareGuiCurveEditPointPtr(const void *a, const void *b)
- {
- float fa = (*(GuiCurveEditorPoint**)a)->position.x;
- float fb = (*(GuiCurveEditorPoint**)b)->position.x;
- return ((fa > fb) - (fa < fb));
- }
- float GuiCurveEval(GuiCurveEditorState *state, float t)
- {
- // Sort points
- GuiCurveEditorPoint* sortedPoints[GUI_CURVE_EDITOR_MAX_POINTS];
- for (int i=0; i < state->numPoints; i++) sortedPoints[i] = &state->points[i];
- qsort(sortedPoints, state->numPoints, sizeof(GuiCurveEditorPoint*), CompareGuiCurveEditPointPtr);
- if (state->numPoints == 0) return state->start;
- // Constants part on edges
- if (t <= sortedPoints[0]->position.x) return state->start + (state->end-state->start)*sortedPoints[0]->position.y;
- if (t >= sortedPoints[state->numPoints-1]->position.x) return state->start + (state->end-state->start)*sortedPoints[state->numPoints-1]->position.y;
- // Find curve portion
- for (int i=0; i < state->numPoints-1; i++)
- {
- const GuiCurveEditorPoint *p1 = sortedPoints[i];
- const GuiCurveEditorPoint *p2 = sortedPoints[i+1];
- // Skip this range
- if (!((t >= p1->position.x) && (t < p2->position.x)) || (p1->position.x == p2->position.x)) continue;
- float scale = (p2->position.x-p1->position.x);
- float T = (t-p1->position.x)/scale;
- float startTangent = scale*p1->tangents.y;
- float endTangent = scale*p2->tangents.x;
- float T2 = T*T;
- float T3 = T*T*T;
- 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));
- }
- return state->start;
- }
- void GuiCurveEditor(GuiCurveEditorState *state, Rectangle bounds)
- {
- // CONST
- //----------------------------------------------------------------------------------
- const float pointSize = 10.0f;
- const float fontSize = GuiGetStyle(DEFAULT, TEXT_SIZE);
- const float handleLength = pointSize*2.5f;
- const float handleSize = pointSize/1.5f;
- const Rectangle innerBounds = (Rectangle){ bounds.x + fontSize, bounds.y + fontSize, bounds.width - 2*fontSize, bounds.height - 2*fontSize };
- const Vector2 mouse = GetMousePosition();
- const Vector2 mouseLocal = (Vector2){ (mouse.x - innerBounds.x)/innerBounds.width, (innerBounds.y + innerBounds.height-mouse.y)/innerBounds.height};
- //----------------------------------------------------------------------------------
- // UPDATE STATE
- //----------------------------------------------------------------------------------
- // Find first point under mouse (-1 if not found)
- int hoveredPointIndex = -1;
- for (int i = 0; i < state->numPoints; i++)
- {
- const GuiCurveEditorPoint *p = &state->points[i];
- const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height-p->position.y*innerBounds.height };
- const Rectangle pointRect = (Rectangle){ screenPos.x - pointSize/2.0f, screenPos.y - pointSize/2.0f, pointSize, pointSize };
- if (CheckCollisionPointRec(mouse, pointRect))
- {
- hoveredPointIndex = i;
- break;
- }
- }
- // Unselect tangents
- if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT))
- {
- state->editLeftTangent = false;
- state->editRightTangent = false;
- }
- // Select a tangent if possible
- if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (state->selectedIndex != -1) && CheckCollisionPointRec(mouse, bounds))
- {
- const GuiCurveEditorPoint* p = &state->points[state->selectedIndex];
- const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width+innerBounds.x, innerBounds.y+innerBounds.height-p->position.y*innerBounds.height };
- // Left control
- Vector2 target = (Vector2){ (p->position.x-1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y-p->tangents.x)*innerBounds.height };
- Vector2 dir = (Vector2){ target.x-screenPos.x, target.y-screenPos.y };
- float d = sqrt(dir.x*dir.x + dir.y*dir.y);
- Vector2 control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
- Rectangle controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
- // Edit left tangent
- if (CheckCollisionPointRec(mouse, controlRect)) state->editLeftTangent = true;
- // Right control
- target = (Vector2){ (p->position.x + 1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y + p->tangents.y)*innerBounds.height };
- dir = (Vector2){ target.x-screenPos.x, target.y-screenPos.y };
- d = sqrt(dir.x*dir.x + dir.y*dir.y);
- control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
- controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
- // Edit right tangent
- if (CheckCollisionPointRec(mouse, controlRect)) state->editRightTangent = true;
- }
- // Move tangents
- if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && state->editRightTangent)
- {
- // editRightTangent == true implies selectedIndex != -1
- GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
- const Vector2 dir = (Vector2){ mouseLocal.x - p->position.x, mouseLocal.y - p->position.y};
- // Calculate right tangent slope
- p->tangents.y = (dir.x < 0.001f)? dir.y/0.001f : dir.y/dir.x;
- p->rightLinear = false; // Stop right linearization update
- // Tangents are symetric by default unless SHIFT is pressed
- if (!(IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)))
- {
- p->tangents.x = p->tangents.y;
- p->leftLinear = false; // Stop left linearization update
- }
- }
- else if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && state->editLeftTangent)
- {
- // editLeftTangent == true implies selectedIndex != -1
- GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
- const Vector2 dir = (Vector2){ mouseLocal.x - p->position.x, mouseLocal.y - p->position.y };
- // Calculate left tangent slope
- p->tangents.x = (dir.x > -0.001f)? dir.y/(-0.001f) : dir.y/dir.x;
- p->leftLinear = false; // Stop left linearization update
- // Tangents are symetric by default unless SHIFT is pressed
- if (!(IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)))
- {
- p->tangents.y = p->tangents.x;
- p->rightLinear = false; // Stop right linearization update
- }
- }
- // Select a point
- else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && (hoveredPointIndex != -1) && CheckCollisionPointRec(mouse, bounds))
- {
- state->selectedIndex = hoveredPointIndex;
- const GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
- state->mouseOffset = (Vector2){ p->position.x - mouseLocal.x, p->position.y - mouseLocal.y };
- }
- // Remove a point (check against bounds)
- else if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) && (hoveredPointIndex != -1) && CheckCollisionPointRec(mouse, bounds) && (state->numPoints > 1))
- {
- // Deselect everything
- state->selectedIndex = 0; // select first point by default
- state->editLeftTangent = false;
- state->editRightTangent = false;
- // Remove point
- state->numPoints -= 1;
- for (int i = hoveredPointIndex; i < state->numPoints; i++) state->points[i] = state->points[i+1];
- }
- // Add a point (check against innerBounds)
- else if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mouse, innerBounds) && (state->numPoints < GUI_CURVE_EDITOR_MAX_POINTS))
- {
- state->editLeftTangent = false;
- state->editRightTangent = false;
- // Create new point
- GuiCurveEditorPoint p;
- p.tangents = (Vector2){ 0.0f, 0.0f };
- p.position = mouseLocal;
- p.leftLinear = false;
- p.rightLinear = false;
- // Append point
- state->points[state->numPoints] = p;
- state->selectedIndex = state->numPoints; // select new point
- state->numPoints += 1;
- // Point is add on mouse pos
- state->mouseOffset = (Vector2){ 0, 0 };
- }
- // Move selected point
- else if ((state->selectedIndex != -1) && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && CheckCollisionPointRec(mouse, bounds))
- {
- GuiCurveEditorPoint *p = &state->points[state->selectedIndex];
- // use mouse offset on click to prevent point teleporting to mouse
- const Vector2 newLocalPos = (Vector2){ mouseLocal.x + state->mouseOffset.x, mouseLocal.y + state->mouseOffset.y };
- // Clamp to innerbounds
- p->position.x = (newLocalPos.x < 0)? 0 : ((newLocalPos.x > 1)? 1 : newLocalPos.x);
- p->position.y = (newLocalPos.y < 0)? 0 : ((newLocalPos.y > 1)? 1 : newLocalPos.y);
- }
- // Sort points
- GuiCurveEditorPoint *sortedPoints[GUI_CURVE_EDITOR_MAX_POINTS] = { 0 };
- for (int i = 0; i < state->numPoints; i++) sortedPoints[i] = &state->points[i];
- qsort(sortedPoints, state->numPoints, sizeof(GuiCurveEditorPoint*), CompareGuiCurveEditPointPtr);
- // Update linear tangents
- for (int i = 0; i < state->numPoints; i++)
- {
- GuiCurveEditorPoint *p = sortedPoints[i];
- // Left tangent
- if ((i > 0) && p->leftLinear)
- {
- const GuiCurveEditorPoint *p2 = sortedPoints[i - 1];
- Vector2 dir = (Vector2){ p2->position.x - p->position.x, p2->position.y - p->position.y };
- p->tangents.x = (dir.x == 0)? 0 : dir.y/dir.x;
- }
- // Right tangent
- if ((i < state->numPoints - 1) && p->rightLinear)
- {
- const GuiCurveEditorPoint *p2 = sortedPoints[i + 1];
- Vector2 dir = (Vector2){ p2->position.x - p->position.x, p2->position.y - p->position.y };
- p->tangents.y = (dir.x == 0)? 0 : dir.y/dir.x;
- }
- }
- //----------------------------------------------------------------------------------
- // DRAWING
- //----------------------------------------------------------------------------------
- DrawRectangle(bounds.x, bounds.y, bounds.width, bounds.height, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR)));
- // Draw grid
- // H lines
- const Color lineColor = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL));
- DrawLine(bounds.x, innerBounds.y, bounds.x+bounds.width, innerBounds.y, lineColor); // end
- DrawLine(bounds.x, innerBounds.y+innerBounds.height/2, bounds.x+bounds.width, innerBounds.y+innerBounds.height/2, lineColor); // avg
- DrawLine(bounds.x, innerBounds.y+innerBounds.height, bounds.x+bounds.width, innerBounds.y+innerBounds.height, lineColor); // start
- // V lines
- DrawLine(innerBounds.x, bounds.y, innerBounds.x, bounds.y+bounds.height, lineColor); // 0
- DrawLine(innerBounds.x + innerBounds.width/4, bounds.y, innerBounds.x + innerBounds.width/4, bounds.y + bounds.height, lineColor); // 0.25
- DrawLine(innerBounds.x + innerBounds.width/2, bounds.y, innerBounds.x + innerBounds.width/2, bounds.y + bounds.height, lineColor); // 0.5
- DrawLine(innerBounds.x + 3*innerBounds.width/4, bounds.y, innerBounds.x + 3*innerBounds.width/4, bounds.y + bounds.height, lineColor); // 0.75
- DrawLine(innerBounds.x + innerBounds.width, bounds.y, innerBounds.x + innerBounds.width, bounds.y + bounds.height, lineColor); // 1
- Font font = GuiGetFont();
- // V labels
- DrawTextEx(font, "0", (Vector2){ innerBounds.x, bounds.y + bounds.height-fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- DrawTextEx(font, "0.25", (Vector2){ innerBounds.x + innerBounds.width/4.0f, bounds.y + bounds.height - fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- DrawTextEx(font, "0.5", (Vector2){ innerBounds.x + innerBounds.width/2.0f, bounds.y + bounds.height - fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- DrawTextEx(font, "0.75", (Vector2){ innerBounds.x + 3.0f*innerBounds.width/4.0f, bounds.y + bounds.height-fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- DrawTextEx(font, "1", (Vector2){ innerBounds.x + innerBounds.width, bounds.y+bounds.height - fontSize}, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- // H labels
- DrawTextEx(font, TextFormat("%.2f", state->start), (Vector2){ innerBounds.x, innerBounds.y - fontSize+innerBounds.height }, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- 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);
- DrawTextEx(font, TextFormat("%.2f", state->end), (Vector2){ innerBounds.x, innerBounds.y }, fontSize, GuiGetStyle(DEFAULT, TEXT_SPACING), lineColor);
- // Draw contours
- if (CheckCollisionPointRec(mouse, bounds)) DrawRectangleLines(bounds.x, bounds.y, bounds.width, bounds.height, GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_FOCUSED)));
- else DrawRectangleLines(bounds.x, bounds.y, bounds.width, bounds.height, GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL)));
- // Draw points
- for (int i = 0; i < state->numPoints; i++)
- {
- const GuiCurveEditorPoint *p = sortedPoints[i];
- const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p->position.y*innerBounds.height };
- const Rectangle pointRect = (Rectangle){ screenPos.x - pointSize/2.0f, screenPos.y - pointSize/2.0f, pointSize, pointSize };
- Color pointColor = { 0 };
- Color pointBorderColor = { 0 };
- // Draw point
- if (&state->points[state->selectedIndex] == p)
- {
- // Draw left handle
- if (i > 0)
- {
- const Vector2 target = (Vector2){ (p->position.x - 1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y - p->tangents.x)*innerBounds.height };
- const Vector2 dir = (Vector2){ target.x - screenPos.x, target.y - screenPos.y };
- const float d = sqrt(dir.x*dir.x + dir.y*dir.y);
- const Vector2 control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
- const Rectangle controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
- Color controlColor = { 0 };
- if (state->editLeftTangent)
- {
- controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED));
- }
- else if (CheckCollisionPointRec(mouse, controlRect))
- {
- controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED));
- }
- else
- {
- controlColor = GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL));
- }
- DrawLine(screenPos.x,screenPos.y, control.x, control.y, controlColor);
- DrawRectangle(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
- DrawRectangleLines(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
- }
- // Draw right handle
- if (i < state->numPoints - 1)
- {
- const Vector2 target = (Vector2){ (p->position.x + 1)*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - (p->position.y + p->tangents.y)*innerBounds.height };
- const Vector2 dir = (Vector2){ target.x - screenPos.x, target.y - screenPos.y };
- const float d = sqrt(dir.x*dir.x + dir.y*dir.y);
- const Vector2 control = (Vector2){ screenPos.x + dir.x/d*handleLength, screenPos.y + dir.y/d*handleLength };
- const Rectangle controlRect = (Rectangle){ control.x - handleSize/2.0f, control.y - handleSize/2.0f, handleSize, handleSize };
- Color controlColor = { 0 };
- if (state->editRightTangent)
- {
- controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED));
- }
- else if (CheckCollisionPointRec(mouse, controlRect))
- {
- controlColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED));
- }
- else
- {
- controlColor = GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL));
- }
- DrawLine(screenPos.x,screenPos.y, control.x, control.y, controlColor);
- DrawRectangle(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
- DrawRectangleLines(controlRect.x, controlRect.y, controlRect.width, controlRect.height, controlColor);
- }
- pointColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_PRESSED));
- pointBorderColor = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL));
- }
- else if (&state->points[hoveredPointIndex] == p)
- {
- pointColor = GetColor(GuiGetStyle(DEFAULT, BASE_COLOR_FOCUSED));
- pointBorderColor = GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_NORMAL));
- }
- else
- {
- pointColor = GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL));
- pointBorderColor = GetColor(GuiGetStyle(BUTTON, BORDER_COLOR_NORMAL));
- }
- DrawRectangle(pointRect.x, pointRect.y, pointRect.width, pointRect.height, pointColor);
- DrawRectangleLines(pointRect.x, pointRect.y, pointRect.width, pointRect.height, pointBorderColor);
- }
- // Draw curve
- Color curveColor = GetColor(GuiGetStyle(LABEL, TEXT_COLOR_FOCUSED));
- if (state->numPoints == 1)
- {
- const GuiCurveEditorPoint *p = sortedPoints[0];
- const Vector2 screenPos = (Vector2){ p->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p->position.y*innerBounds.height };
- DrawLine(innerBounds.x, screenPos.y, innerBounds.x+innerBounds.width, screenPos.y, curveColor);
- }
- else
- {
- for (int i = 0; i < state->numPoints - 1; i++)
- {
- const GuiCurveEditorPoint *p1 = sortedPoints[i];
- const GuiCurveEditorPoint *p2 = sortedPoints[i + 1];
- const Vector2 screenPos1 = (Vector2){ p1->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p1->position.y*innerBounds.height };
- const Vector2 screenPos2 = (Vector2){ p2->position.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - p2->position.y*innerBounds.height };
- // Constant on edge
- if ((screenPos1.x > innerBounds.x) && (i == 0))
- {
- DrawLine(innerBounds.x, screenPos1.y, screenPos1.x, screenPos1.y, curveColor);
- }
- if ((screenPos2.x < innerBounds.x + innerBounds.width) && (i == (state->numPoints - 2)))
- {
- DrawLine(screenPos2.x, screenPos2.y, innerBounds.x+innerBounds.width, screenPos2.y, curveColor);
- }
- // Draw cubic Hermite curve
- const float scale = (p2->position.x - p1->position.x)/3.0f;
- const Vector2 offset1 = (Vector2){ scale, scale*p1->tangents.y };
- // negative endTangent => top part => need to invert value to calculate offset
- const Vector2 offset2 = (Vector2){ -scale, -scale*p2->tangents.x };
- const Vector2 c1 = (Vector2){ p1->position.x + offset1.x, p1->position.y + offset1.y };
- const Vector2 c2 = (Vector2){ p2->position.x + offset2.x, p2->position.y + offset2.y };
- const Vector2 screenC1 = (Vector2){ c1.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - c1.y*innerBounds.height };
- const Vector2 screenC2 = (Vector2){ c2.x*innerBounds.width + innerBounds.x, innerBounds.y + innerBounds.height - c2.y*innerBounds.height };
- DrawSplineSegmentBezierCubic(screenPos1, screenC1, screenC2, screenPos2, 1, curveColor);
- }
- }
- }
- #endif // GUI_CURVE_EDITOR_IMPLEMENTATION
|