ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cubic Bezier widget / Curve editor

r-lyeh-archived opened this issue · comments

Based on #55.
Feat. some code ideas by @nem0 and @jarikomppa
Thanks everybody!

gif

// ImGui Bezier widget. @r-lyeh, public domain
// v1.02: add BezierValue(); comments; usage
// v1.01: out-of-bounds coord snapping; custom border width; spacing; cosmetics
// v1.00: initial version
//
// [ref] http://robnapier.net/faster-bezier
// [ref] http://easings.net/es#easeInSine
//
// Usage:
// {  static float v[] = { 0.390f, 0.575f, 0.565f, 1.000f }; 
//    ImGui::Bezier( "easeOutSine", v );       // draw
//    float y = ImGui::BezierValue( 0.5f, v ); // x delta in [0..1] range
// }

//#define IMGUI_DEFINE_MATH_OPERATORS
//#include <imgui.h>
//#include <imgui_internal.h>

namespace ImGui 
{
  template<int steps>
  void bezier_table(ImVec2 P[4], ImVec2 results[steps+1] ) {
      static float C[ (steps+1) * 4 ], *K = 0;
      if( !K ) {
          K = C;
          for (unsigned step = 0; step <= steps; ++step) {
              float t = (float)step/(float)steps;
              C[step*4+0] = (1-t)*(1-t)*(1-t);   // * P0
              C[step*4+1] = 3 * (1-t)*(1-t) * t; // * P1
              C[step*4+2] = 3 * (1-t) * t*t;     // * P2
              C[step*4+3] = t*t*t;               // * P3
          }
      }
      for (unsigned step = 0; step <= steps; ++step) {
          ImVec2 point = {
              K[step*4+0]*P[0].x + K[step*4+1]*P[1].x + K[step*4+2]*P[2].x + K[step*4+3]*P[3].x,
              K[step*4+0]*P[0].y + K[step*4+1]*P[1].y + K[step*4+2]*P[2].y + K[step*4+3]*P[3].y
          };
          results[step] = point;
      }
  }

  float BezierValue( float dt01, float P[4] ) {
    enum { STEPS = 256 };
    ImVec2 Q[4] = { { 0, 0 }, { P[0], P[1] }, { P[2], P[3] }, { 1, 1 } };
    ImVec2 results[STEPS+1];
    bezier_table<STEPS>( Q, results );
    return results[ (int)((dt01 < 0 ? 0 : dt01 > 1 ? 1 : dt01) * STEPS) ].y;
  }

  int Bezier( const char *label, float P[4] ) {
    // visuals
    enum { SMOOTHNESS = 64 }; // curve smoothness: the higher number of segments, the smoother curve
    enum { CURVE_WIDTH = 4 }; // main curved line width
    enum { LINE_WIDTH  = 1 }; // handlers: small lines width
    enum { GRAB_RADIUS = 6 }; // handlers: circle radius
    enum { GRAB_BORDER = 2 }; // handlers: circle border width

    const ImGuiStyle& Style = GetStyle();
    const ImGuiIO& IO = GetIO();
    ImDrawList* DrawList = GetWindowDrawList();
    ImGuiWindow* Window = GetCurrentWindow();
    if (Window->SkipItems)
        return false;

    // header and spacing
    int changed = SliderFloat4(label, P, 0, 1, "%.3f", 1.0f);
    int hovered = IsItemActive() || IsItemHovered(); // IsItemDragged() ?
    Dummy(ImVec2(0,3));

    // prepare canvas
    const float avail = GetContentRegionAvailWidth();
    const float dim = ImMin(avail, 128.f);
    ImVec2 Canvas(dim, dim);

    ImRect bb(Window->DC.CursorPos, Window->DC.CursorPos + Canvas);
    ItemSize(bb);
    if (!ItemAdd(bb, NULL))
        return changed;

    const ImGuiID id = Window->GetID(label);
    hovered |= 0 != IsHovered(ImRect(bb.Min, bb.Min + ImVec2(avail,dim)), id);

    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, Style.FrameRounding);

    // background grid
    for (int i = 0; i <= Canvas.x; i += (Canvas.x/4)) {
        DrawList->AddLine(
            ImVec2(bb.Min.x + i, bb.Min.y),
            ImVec2(bb.Min.x + i, bb.Max.y),
            GetColorU32(ImGuiCol_TextDisabled));
    }   
    for (int i = 0; i <= Canvas.y; i += (Canvas.y/4)) {
        DrawList->AddLine(
            ImVec2(bb.Min.x, bb.Min.y + i),
            ImVec2(bb.Max.x, bb.Min.y + i),
            GetColorU32(ImGuiCol_TextDisabled));
    }   

    // eval curve
    ImVec2 Q[4] = { { 0, 0 }, { P[0], P[1] }, { P[2], P[3] }, { 1, 1 } };
    ImVec2 results[SMOOTHNESS+1];
    bezier_table<SMOOTHNESS>( Q, results );

    // control points: 2 lines and 2 circles
    {
      char buf[128];
      sprintf(buf, "0##%s", label);

      // handle grabbers
      for(int i = 0; i < 2; ++i)
      {
        ImVec2 pos = ImVec2( P[i*2+0], 1 - P[i*2+1] ) * (bb.Max - bb.Min) + bb.Min;
        SetCursorScreenPos(pos - ImVec2(GRAB_RADIUS, GRAB_RADIUS));
        InvisibleButton((buf[0]++, buf), ImVec2(2 * GRAB_RADIUS, 2 * GRAB_RADIUS));
        if (IsItemActive() || IsItemHovered())
        {
          SetTooltip("(%4.3f, %4.3f)", P[i*2+0], P[i*2+1]);
        }
        if (IsItemActive() && IsMouseDragging(0))
        {
          P[i*2+0] += GetIO().MouseDelta.x / Canvas.x;
          P[i*2+1] -= GetIO().MouseDelta.y / Canvas.y;
          changed = true;
        }
      }

      if(hovered||changed) DrawList->PushClipRectFullScreen();

        // draw curve
        {
          ImColor color( GetStyle().Colors[ ImGuiCol_PlotLines ] );
          for( int i = 0; i < SMOOTHNESS; ++i ) {
            ImVec2 p = { results[i+0].x, 1 - results[i+0].y };
            ImVec2 q = { results[i+1].x, 1 - results[i+1].y };
            ImVec2 r( p.x * (bb.Max.x - bb.Min.x) + bb.Min.x, p.y * (bb.Max.y - bb.Min.y) + bb.Min.y);
            ImVec2 s( q.x * (bb.Max.x - bb.Min.x) + bb.Min.x, q.y * (bb.Max.y - bb.Min.y) + bb.Min.y);
            DrawList->AddLine(r, s, color, CURVE_WIDTH);
          }
        }

        // draw lines and grabbers
        float luma = IsItemActive() || IsItemHovered() ? 0.5f : 1.0f;
        ImVec4 pink( 1.00f, 0.00f, 0.75f, luma ), cyan( 0.00f, 0.75f, 1.00f, luma );
        ImVec4 white( GetStyle().Colors[ImGuiCol_Text] );
        ImVec2 p1 = ImVec2( P[0], 1 - P[1] ) * (bb.Max - bb.Min) + bb.Min;
        ImVec2 p2 = ImVec2( P[2], 1 - P[3] ) * (bb.Max - bb.Min) + bb.Min;
        DrawList->AddLine(ImVec2(bb.Min.x,bb.Max.y), p1, ImColor(white), LINE_WIDTH);
        DrawList->AddLine(ImVec2(bb.Max.x,bb.Min.y), p2, ImColor(white), LINE_WIDTH);
        DrawList->AddCircleFilled(p1, GRAB_RADIUS, ImColor(white));
        DrawList->AddCircleFilled(p1, GRAB_RADIUS-GRAB_BORDER, ImColor(pink));
        DrawList->AddCircleFilled(p2, GRAB_RADIUS, ImColor(white));
        DrawList->AddCircleFilled(p2, GRAB_RADIUS-GRAB_BORDER, ImColor(cyan));

      if(hovered||changed) DrawList->PopClipRect();

      // restore cursor pos
      SetCursorScreenPos(ImVec2(bb.Min.x,bb.Max.y + GRAB_RADIUS)); // :P
    }

    return changed;
  }

  void ShowBezierDemo()
  {
    { static float v[] = { 0.000f, 0.000f, 1.000f, 1.000f }; Bezier("easeLinear",     v ); }
    { static float v[] = { 0.470f, 0.000f, 0.745f, 0.715f }; Bezier("easeInSine",     v ); }
    { static float v[] = { 0.390f, 0.575f, 0.565f, 1.000f }; Bezier("easeOutSine",    v ); }
    { static float v[] = { 0.445f, 0.050f, 0.550f, 0.950f }; Bezier("easeInOutSine",  v ); }
    { static float v[] = { 0.550f, 0.085f, 0.680f, 0.530f }; Bezier("easeInQuad",     v ); }
    { static float v[] = { 0.250f, 0.460f, 0.450f, 0.940f }; Bezier("easeOutQuad",    v ); }
    { static float v[] = { 0.455f, 0.030f, 0.515f, 0.955f }; Bezier("easeInOutQuad",  v ); }
    { static float v[] = { 0.550f, 0.055f, 0.675f, 0.190f }; Bezier("easeInCubic",    v ); }
    { static float v[] = { 0.215f, 0.610f, 0.355f, 1.000f }; Bezier("easeOutCubic",   v ); }
    { static float v[] = { 0.645f, 0.045f, 0.355f, 1.000f }; Bezier("easeInOutCubic", v ); }
    { static float v[] = { 0.895f, 0.030f, 0.685f, 0.220f }; Bezier("easeInQuart",    v ); }
    { static float v[] = { 0.165f, 0.840f, 0.440f, 1.000f }; Bezier("easeOutQuart",   v ); }
    { static float v[] = { 0.770f, 0.000f, 0.175f, 1.000f }; Bezier("easeInOutQuart", v ); }
    { static float v[] = { 0.755f, 0.050f, 0.855f, 0.060f }; Bezier("easeInQuint",    v ); }
    { static float v[] = { 0.230f, 1.000f, 0.320f, 1.000f }; Bezier("easeOutQuint",   v ); }
    { static float v[] = { 0.860f, 0.000f, 0.070f, 1.000f }; Bezier("easeInOutQuint", v ); }
    { static float v[] = { 0.950f, 0.050f, 0.795f, 0.035f }; Bezier("easeInExpo",     v ); }
    { static float v[] = { 0.190f, 1.000f, 0.220f, 1.000f }; Bezier("easeOutExpo",    v ); }
    { static float v[] = { 1.000f, 0.000f, 0.000f, 1.000f }; Bezier("easeInOutExpo",  v ); }
    { static float v[] = { 0.600f, 0.040f, 0.980f, 0.335f }; Bezier("easeInCirc",     v ); }
    { static float v[] = { 0.075f, 0.820f, 0.165f, 1.000f }; Bezier("easeOutCirc",    v ); }
    { static float v[] = { 0.785f, 0.135f, 0.150f, 0.860f }; Bezier("easeInOutCirc",  v ); }
    { static float v[] = { 0.600f, -0.28f, 0.735f, 0.045f }; Bezier("easeInBack",     v ); }
    { static float v[] = { 0.175f, 0.885f, 0.320f, 1.275f }; Bezier("easeOutBack",    v ); }
    { static float v[] = { 0.680f, -0.55f, 0.265f, 1.550f }; Bezier("easeInOutBack",  v ); }
    // easeInElastic: not a bezier
    // easeOutElastic: not a bezier
    // easeInOutElastic: not a bezier
    // easeInBounce: not a bezier
    // easeOutBounce: not a bezier
    // easeInOutBounce: not a bezier
  }
}
commented

Also posting our old stuff from #55 that I have now an excuse to close (finallyyyy :)

EDIT Link fixed
https://gist.github.com/r-lyeh-archived/40d4fd0ea157ab3a58a4

v1.2
image

EDIT added copy of 1.22
curve_v122.zip

@r-lyeh Great, I planned to improved my widget, you've saved me some work. Thanks.

commented

Looking at this reminds me how eventually I would like to rework the PlotXXX functions to allow for grid/panning/zooming/unit labelling etc. Hopefully later some the Plot helpers as I imagine them to be designed would be usable as base helpers for this sort of curve editor (for easy grids, labelling, transforming).

Btw, link to 1 became 2 after I archived (and renamed) my old github account.
Same for all of my imgui snippets floating around, if any :)
Apologies!

edit: @ocornut, could you just edit & paste the gist contents of the second post in this thread? that would keep the snippet safer here in github issues. ty!

v1.03 Update

image

// ImGui Bezier widget. @r-lyeh, public domain
// v1.03: improve grabbing, confine grabbers to area option, adaptive size, presets, preview.
// v1.02: add BezierValue(); comments; usage
// v1.01: out-of-bounds coord snapping; custom border width; spacing; cosmetics
// v1.00: initial version
//
// [ref] http://robnapier.net/faster-bezier
// [ref] http://easings.net/es#easeInSine
//
// Usage:
// {  static float v[5] = { 0.390f, 0.575f, 0.565f, 1.000f }; 
//    ImGui::Bezier( "easeOutSine", v );       // draw
//    float y = ImGui::BezierValue( 0.5f, v ); // x delta in [0..1] range
// }

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui.h>
#include <imgui_internal.h>
#include <time.h>

namespace ImGui
{
    template<int steps>
    void bezier_table(ImVec2 P[4], ImVec2 results[steps + 1]) {
        static float C[(steps + 1) * 4], *K = 0;
        if (!K) {
            K = C;
            for (unsigned step = 0; step <= steps; ++step) {
                float t = (float) step / (float) steps;
                C[step * 4 + 0] = (1 - t)*(1 - t)*(1 - t);   // * P0
                C[step * 4 + 1] = 3 * (1 - t)*(1 - t) * t; // * P1
                C[step * 4 + 2] = 3 * (1 - t) * t*t;     // * P2
                C[step * 4 + 3] = t * t*t;               // * P3
            }
        }
        for (unsigned step = 0; step <= steps; ++step) {
            ImVec2 point = {
                K[step * 4 + 0] * P[0].x + K[step * 4 + 1] * P[1].x + K[step * 4 + 2] * P[2].x + K[step * 4 + 3] * P[3].x,
                K[step * 4 + 0] * P[0].y + K[step * 4 + 1] * P[1].y + K[step * 4 + 2] * P[2].y + K[step * 4 + 3] * P[3].y
            };
            results[step] = point;
        }
    }

    float BezierValue(float dt01, float P[4]) {
        enum { STEPS = 256 };
        ImVec2 Q[4] = { { 0, 0 }, { P[0], P[1] }, { P[2], P[3] }, { 1, 1 } };
        ImVec2 results[STEPS + 1];
        bezier_table<STEPS>(Q, results);
        return results[(int) ((dt01 < 0 ? 0 : dt01 > 1 ? 1 : dt01) * STEPS)].y;
    }

    int Bezier(const char *label, float P[5]) {
        // visuals
        enum { SMOOTHNESS = 64 }; // curve smoothness: the higher number of segments, the smoother curve
        enum { CURVE_WIDTH = 4 }; // main curved line width
        enum { LINE_WIDTH = 1 }; // handlers: small lines width
        enum { GRAB_RADIUS = 8 }; // handlers: circle radius
        enum { GRAB_BORDER = 2 }; // handlers: circle border width
        enum { AREA_CONSTRAINED = true }; // should grabbers be constrained to grid area?
        enum { AREA_WIDTH = 128 }; // area width in pixels. 0 for adaptive size (will use max avail width)

        // curve presets
        static struct { const char *name; float points[4]; } presets [] = {
            { "Linear", 0.000f, 0.000f, 1.000f, 1.000f },

            { "In Sine", 0.470f, 0.000f, 0.745f, 0.715f },
            { "In Quad", 0.550f, 0.085f, 0.680f, 0.530f },
            { "In Cubic", 0.550f, 0.055f, 0.675f, 0.190f },
            { "In Quart", 0.895f, 0.030f, 0.685f, 0.220f },
            { "In Quint", 0.755f, 0.050f, 0.855f, 0.060f },
            { "In Expo", 0.950f, 0.050f, 0.795f, 0.035f },
            { "In Circ", 0.600f, 0.040f, 0.980f, 0.335f },
            { "In Back", 0.600f, -0.28f, 0.735f, 0.045f },

            { "Out Sine", 0.390f, 0.575f, 0.565f, 1.000f },
            { "Out Quad", 0.250f, 0.460f, 0.450f, 0.940f },
            { "Out Cubic", 0.215f, 0.610f, 0.355f, 1.000f },
            { "Out Quart", 0.165f, 0.840f, 0.440f, 1.000f },
            { "Out Quint", 0.230f, 1.000f, 0.320f, 1.000f },
            { "Out Expo", 0.190f, 1.000f, 0.220f, 1.000f },
            { "Out Circ", 0.075f, 0.820f, 0.165f, 1.000f },
            { "Out Back", 0.175f, 0.885f, 0.320f, 1.275f },

            { "InOut Sine", 0.445f, 0.050f, 0.550f, 0.950f },
            { "InOut Quad", 0.455f, 0.030f, 0.515f, 0.955f },
            { "InOut Cubic", 0.645f, 0.045f, 0.355f, 1.000f },
            { "InOut Quart", 0.770f, 0.000f, 0.175f, 1.000f },
            { "InOut Quint", 0.860f, 0.000f, 0.070f, 1.000f },
            { "InOut Expo", 1.000f, 0.000f, 0.000f, 1.000f },
            { "InOut Circ", 0.785f, 0.135f, 0.150f, 0.860f },
            { "InOut Back", 0.680f, -0.55f, 0.265f, 1.550f },

            // easeInElastic: not a bezier
            // easeOutElastic: not a bezier
            // easeInOutElastic: not a bezier
            // easeInBounce: not a bezier
            // easeOutBounce: not a bezier
            // easeInOutBounce: not a bezier
        };


        // preset selector

        bool reload = 0;
        ImGui::PushID(label);
        if (ImGui::ArrowButton("##lt", ImGuiDir_Left)) { // ImGui::ArrowButton(ImGui::GetCurrentWindow()->GetID("##lt"), ImGuiDir_Left, ImVec2(0, 0), 0)
            if (--P[4] >= 0) reload = 1; else ++P[4];
        }
        ImGui::SameLine();

        if (ImGui::Button("Presets")) {
            ImGui::OpenPopup("!Presets");
        }
        if (ImGui::BeginPopup("!Presets")) {
            for (int i = 0; i < IM_ARRAYSIZE(presets); ++i) {
                if( i == 1 || i == 9 || i == 17 ) ImGui::Separator();
                if (ImGui::MenuItem(presets[i].name, NULL, P[4] == i)) {
                    P[4] = i;
                    reload = 1;
                }
            }
            ImGui::EndPopup();
        }
        ImGui::SameLine();

        if (ImGui::ArrowButton("##rt", ImGuiDir_Right)) { // ImGui::ArrowButton(ImGui::GetCurrentWindow()->GetID("##rt"), ImGuiDir_Right, ImVec2(0, 0), 0)
            if (++P[4] < IM_ARRAYSIZE(presets)) reload = 1; else --P[4];
        }
        ImGui::SameLine();
        ImGui::PopID();

        if (reload) {
            memcpy(P, presets[(int) P[4]].points, sizeof(float) * 4);
        }

        // bezier widget

        const ImGuiStyle& Style = GetStyle();
        const ImGuiIO& IO = GetIO();
        ImDrawList* DrawList = GetWindowDrawList();
        ImGuiWindow* Window = GetCurrentWindow();
        if (Window->SkipItems)
            return false;

        // header and spacing
        int changed = SliderFloat4(label, P, 0, 1, "%.3f", 1.0f);
        int hovered = IsItemActive() || IsItemHovered(); // IsItemDragged() ?
        Dummy(ImVec2(0, 3));

        // prepare canvas
        const float avail = GetContentRegionAvailWidth();
        const float dim = AREA_WIDTH > 0 ? AREA_WIDTH : avail;
        ImVec2 Canvas(dim, dim);

        ImRect bb(Window->DC.CursorPos, Window->DC.CursorPos + Canvas);
        ItemSize(bb);
        if (!ItemAdd(bb, NULL))
            return changed;

        const ImGuiID id = Window->GetID(label);
        hovered |= 0 != ItemHoverable(ImRect(bb.Min, bb.Min + ImVec2(avail, dim)), id);

        RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg, 1), true, Style.FrameRounding);

        // background grid
        for (int i = 0; i <= Canvas.x; i += (Canvas.x / 4)) {
            DrawList->AddLine(
                ImVec2(bb.Min.x + i, bb.Min.y),
                ImVec2(bb.Min.x + i, bb.Max.y),
                GetColorU32(ImGuiCol_TextDisabled));
        }
        for (int i = 0; i <= Canvas.y; i += (Canvas.y / 4)) {
            DrawList->AddLine(
                ImVec2(bb.Min.x, bb.Min.y + i),
                ImVec2(bb.Max.x, bb.Min.y + i),
                GetColorU32(ImGuiCol_TextDisabled));
        }

        // eval curve
        ImVec2 Q[4] = { { 0, 0 }, { P[0], P[1] }, { P[2], P[3] }, { 1, 1 } };
        ImVec2 results[SMOOTHNESS + 1];
        bezier_table<SMOOTHNESS>(Q, results);

        // control points: 2 lines and 2 circles
        {
            // handle grabbers
            ImVec2 mouse = GetIO().MousePos, pos[2];
            float distance[2];

            for (int i = 0; i < 2; ++i) {
                pos[i] = ImVec2(P[i * 2 + 0], 1 - P[i * 2 + 1]) * (bb.Max - bb.Min) + bb.Min;
                distance[i] = (pos[i].x - mouse.x) * (pos[i].x - mouse.x) + (pos[i].y - mouse.y) * (pos[i].y - mouse.y);
            }

            int selected = distance[0] < distance[1] ? 0 : 1;
            if( distance[selected] < (4*GRAB_RADIUS * 4*GRAB_RADIUS) )
            {
                SetTooltip("(%4.3f, %4.3f)", P[selected * 2 + 0], P[selected * 2 + 1]);

                if (/*hovered &&*/ (IsMouseClicked(0) || IsMouseDragging(0))) {
                    float &px = (P[selected * 2 + 0] += GetIO().MouseDelta.x / Canvas.x);
                    float &py = (P[selected * 2 + 1] -= GetIO().MouseDelta.y / Canvas.y);

                    if (AREA_CONSTRAINED) {
                        px = (px < 0 ? 0 : (px > 1 ? 1 : px));
                        py = (py < 0 ? 0 : (py > 1 ? 1 : py));
                    }

                    changed = true;
                }
            }
        }

        // if (hovered || changed) DrawList->PushClipRectFullScreen();

        // draw curve
        {
            ImColor color(GetStyle().Colors[ImGuiCol_PlotLines]);
            for (int i = 0; i < SMOOTHNESS; ++i) {
                ImVec2 p = { results[i + 0].x, 1 - results[i + 0].y };
                ImVec2 q = { results[i + 1].x, 1 - results[i + 1].y };
                ImVec2 r(p.x * (bb.Max.x - bb.Min.x) + bb.Min.x, p.y * (bb.Max.y - bb.Min.y) + bb.Min.y);
                ImVec2 s(q.x * (bb.Max.x - bb.Min.x) + bb.Min.x, q.y * (bb.Max.y - bb.Min.y) + bb.Min.y);
                DrawList->AddLine(r, s, color, CURVE_WIDTH);
            }
        }

        // draw preview (cycles every 1s)
        static clock_t epoch = clock();
        ImVec4 white(GetStyle().Colors[ImGuiCol_Text]);
        for (int i = 0; i < 3; ++i) {
            double now = ((clock() - epoch) / (double)CLOCKS_PER_SEC);
            float delta = ((int) (now * 1000) % 1000) / 1000.f; delta += i / 3.f; if (delta > 1) delta -= 1;
            int idx = (int) (delta * SMOOTHNESS);
            float evalx = results[idx].x; // 
            float evaly = results[idx].y; // ImGui::BezierValue( delta, P );
            ImVec2 p0 = ImVec2(evalx, 1 - 0) * (bb.Max - bb.Min) + bb.Min;
            ImVec2 p1 = ImVec2(0, 1 - evaly) * (bb.Max - bb.Min) + bb.Min;
            ImVec2 p2 = ImVec2(evalx, 1 - evaly) * (bb.Max - bb.Min) + bb.Min;
            DrawList->AddCircleFilled(p0, GRAB_RADIUS / 2, ImColor(white));
            DrawList->AddCircleFilled(p1, GRAB_RADIUS / 2, ImColor(white));
            DrawList->AddCircleFilled(p2, GRAB_RADIUS / 2, ImColor(white));
        }

        // draw lines and grabbers
        float luma = IsItemActive() || IsItemHovered() ? 0.5f : 1.0f;
        ImVec4 pink(1.00f, 0.00f, 0.75f, luma), cyan(0.00f, 0.75f, 1.00f, luma);
        ImVec2 p1 = ImVec2(P[0], 1 - P[1]) * (bb.Max - bb.Min) + bb.Min;
        ImVec2 p2 = ImVec2(P[2], 1 - P[3]) * (bb.Max - bb.Min) + bb.Min;
        DrawList->AddLine(ImVec2(bb.Min.x, bb.Max.y), p1, ImColor(white), LINE_WIDTH);
        DrawList->AddLine(ImVec2(bb.Max.x, bb.Min.y), p2, ImColor(white), LINE_WIDTH);
        DrawList->AddCircleFilled(p1, GRAB_RADIUS, ImColor(white));
        DrawList->AddCircleFilled(p1, GRAB_RADIUS - GRAB_BORDER, ImColor(pink));
        DrawList->AddCircleFilled(p2, GRAB_RADIUS, ImColor(white));
        DrawList->AddCircleFilled(p2, GRAB_RADIUS - GRAB_BORDER, ImColor(cyan));

        // if (hovered || changed) DrawList->PopClipRect();

        return changed;
    }

    void ShowBezierDemo() {
        { static float v[5] = { 0.950f, 0.050f, 0.795f, 0.035f }; Bezier("easeInExpo", v); }
    }
}

Also posting our old stuff from #55 that I have now an excuse to close (finallyyyy :)

EDIT Link fixed
gist.github.com/r-lyeh-archived/40d4fd0ea157ab3a58a4

v1.2
image

EDIT added copy of 1.22
curve_v122.zip

@ocornut I tried to use this one, I made these changes and I think they're equivalent, but it wouldn't compile without them:

// ImGuiState& g = *GImGui;
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
if (window->SkipItems)
    return 0;

ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
ItemSize(bb);
if (!ItemAdd(bb, NULL))
    return 0;

// const bool hovered = IsHovered(bb, id);
const bool hovered = IsItemHovered();
// Towards the very end of the file:
RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + style.FramePadding.y), bb.Max, str, NULL, NULL, ImGuiAlign_Center);
// ImGuiAlign_Center doesn't exist, I think maybe this is equivalent?
RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + style.FramePadding.y), bb.Max, str, NULL, NULL, bb.GetCenter());
commented

(Sorry misposted previous answer before I finished the message)

// const bool hovered = IsHovered(bb, id);
const bool hovered = IsItemHovered();>

No. Should use ItemHoverable().

// ImGuiAlign_Center doesn't exist, I think maybe this (bb.GetCenter()) is equivalent?

No. Should use ImVec2(0.5f, 0.5f) for centering.

Either way- you downloaded an old piece of code from 2016 whereas if you scroll down you'll find a better 2019 version.

(Sorry misposted previous answer before I finished the message)

// const bool hovered = IsHovered(bb, id);
const bool hovered = IsItemHovered();>

No. Should use ItemHoverable().

// ImGuiAlign_Center doesn't exist, I think maybe this (bb.GetCenter()) is equivalent?

No. Should use ImVec2(0.5f, 0.5f) for centering.

Either way- you downloaded an old piece of code from 2016 whereas if you scroll down you'll find a better 2019 version.

Ah my mistake -- I have both but they look different so I thought they were individuals/unrelated.
Apologies 😅

@GavinRay97 Did you track down this 2019 version? I can see an update to the bezier widget but not the generic curve editor.

@GavinRay97 Did you track down this 2019 version? I can see an update to the bezier widget but not the generic curve editor.

Hey, I think I did actually looking back on this.

I also extracted the curve editor that @nem0 posted into a self-contained header and updated it for use with current version (it had some dependencies on custom operators defined within their project in other files).

Here are the single-header up to date implementations. I did these with ~2 weeks of C++ experience at the time so they may not be entirely correct but someone could probably correct minor mistakes quickly.

All of these should have identically named .cpp/.hpp counterparts in that folder if you want the implementation code:

Here's what they looked like, some of them were half broken in my user code but I think that was because of something I did. (Please ignore the theme-switching and toggle button at the beginning, this GIF was originally used here: https://forum.cockos.com/showpost.php?p=2422082&postcount=68)

@GavinRay97 Terrific thanks so much for going to all that effort.

No problemo, that's what the internet is for =)

@GavinRay97 What is the license on those?

@SirNate0 Oh, you will have to excuse this but embarrassingly I:

  • Never publish a license on my own code (I don't care what people do with it, as long as it isn't evil)
  • Don't really have a habit of checking other people's licenses (presumably because they also don't want me either making money off, claiming original authorship, or doing evil things with their code, none of which I (intentionally) do)

I'm probably guilty of breaking attribution laws if there wasn't a license header on the files I copy pasted from, but if you're asking about my code you can do what you like with it, provided a particular component falls under the licenses of the original author I suppose.

For each component, on at least one of the files I believe I provided a comment near the top with SRC and a link to the original author's source code.

Nemo's (lumix) I believe is MIT licensed, the other two IIRC were more like code snippets posted here as comments

I hereby multi-license all my snippets and/or contributions to this repository as public domain, mit-0, 0-bsd, unlicense and wtfpl2. choose whichever you prefer :D

commented

I've tried to improve curve editor
https://gist.github.com/SoapyMan/1d32b56dd267e7cd9a2408047fd5c90c

Fixed some critical usability issues and added value range
2022-12-05 16-10-08