From 8eaf6705976e5be96895522d64c00377dc722cf4 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:38:15 -0700 Subject: [PATCH 01/12] refactor: rename Id::isValid to is_valid and GrabbedHandle enumerators to PascalCase Brings the public API in line with the project's naming conventions: methods are lower_snake_case, enumerators are PascalCase. Both are breaking changes for callers, but mechanical ones: Id::isValid() -> Id::is_valid() GrabbedHandle::none -> GrabbedHandle::None GrabbedHandle::in_handle -> GrabbedHandle::InHandle GrabbedHandle::out_handle -> GrabbedHandle::OutHandle GrabbedHandle was the only enum left lowercase after the earlier PascalCase conversion. --- include/anim/channel.hpp | 8 ++++---- include/anim/handle_utils.hpp | 10 +++++----- include/anim/id.hpp | 4 ++-- src/channel.cpp | 4 ++-- src/handle_utils.cpp | 4 ++-- tests/test_handle_utils.cpp | 10 +++++----- tests/test_id_functionality.cpp | 20 ++++++++++---------- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/include/anim/channel.hpp b/include/anim/channel.hpp index 5c890f3..1409c94 100644 --- a/include/anim/channel.hpp +++ b/include/anim/channel.hpp @@ -215,17 +215,17 @@ class Channel { using KeyframeIt = std::vector::iterator; const Keyframe& create_default_keyframe(const Point& position, Function function, HandleMode handle_mode); - const Keyframe& insert_keyframe(Keyframe&& keyframe, GrabbedHandle grabbed_handle = GrabbedHandle::none); - const Keyframe& insert_keyframe(KeyframeIt it, Keyframe&& keyframe, GrabbedHandle grabbed_handle = GrabbedHandle::none); + const Keyframe& insert_keyframe(Keyframe&& keyframe, GrabbedHandle grabbed_handle = GrabbedHandle::None); + const Keyframe& insert_keyframe(KeyframeIt it, Keyframe&& keyframe, GrabbedHandle grabbed_handle = GrabbedHandle::None); void update_keyframe_position(KeyframeIt it, const Point& position); void clamp_keyframe_time(KeyframeIt it, double time); - void update_local_handles(KeyframeIt it, GrabbedHandle grabbed_handle = GrabbedHandle::none); + void update_local_handles(KeyframeIt it, GrabbedHandle grabbed_handle = GrabbedHandle::None); void update_handles( Keyframe& keyframe, Keyframe* prev_keyframe_ptr, Keyframe* next_keyframe_ptr, - GrabbedHandle grabbed_handle = GrabbedHandle::none); + GrabbedHandle grabbed_handle = GrabbedHandle::None); void apply_last_keyframe_inheritance(bool restore_cache = true) const; void invalidate_cache() const; diff --git a/include/anim/handle_utils.hpp b/include/anim/handle_utils.hpp index 58fec70..f80a10c 100644 --- a/include/anim/handle_utils.hpp +++ b/include/anim/handle_utils.hpp @@ -8,9 +8,9 @@ namespace anim { /// @brief Identifies which handle of a keyframe is being dragged, driving alignment behavior. enum class GrabbedHandle { - none, ///< No specific handle; the implementation picks a source automatically. - in_handle, ///< The incoming handle is the one being manipulated. - out_handle ///< The outgoing handle is the one being manipulated. + None, ///< No specific handle; the implementation picks a source automatically. + InHandle, ///< The incoming handle is the one being manipulated. + OutHandle ///< The outgoing handle is the one being manipulated. }; /** @@ -66,7 +66,7 @@ namespace anim { Vector reflect(const Vector& vec, const Vector& normal_unit_vector); /// @brief Returns @p vec with both components negated. - Vector invert(const Vector& vec) ; + Vector invert(const Vector& vec); /// @brief Clamps a keyframe's in-handle time into [prev keyframe time, keyframe time]. void constrain_in_handle_time(Keyframe& keyframe, const Keyframe& prev_keyframe); @@ -149,7 +149,7 @@ namespace anim { Keyframe& keyframe, const Keyframe* prev_keyframe_ptr, const Keyframe* next_keyframe_ptr, - GrabbedHandle grabbed_handle = GrabbedHandle::none); + GrabbedHandle grabbed_handle = GrabbedHandle::None); } // namespace anim diff --git a/include/anim/id.hpp b/include/anim/id.hpp index fc8a929..124b371 100644 --- a/include/anim/id.hpp +++ b/include/anim/id.hpp @@ -40,14 +40,14 @@ struct Id { /** * @brief Returns the sentinel "invalid" Id (the maximum uint64_t value). - * @return An Id for which isValid() is false. + * @return An Id for which is_valid() is false. */ static Id invalid() { return Id(static_cast(-1)); } /// @brief True unless this Id equals the invalid() sentinel. - bool isValid() const { + bool is_valid() const { return id != static_cast(-1); } }; diff --git a/src/channel.cpp b/src/channel.cpp index 1defed5..9aa88f8 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -200,7 +200,7 @@ void Channel::set_keyframe_in_handle(size_t index, const Point& in_handle) } auto it = m_keyframes.begin() + index; it->in_handle = in_handle; - update_local_handles(it, GrabbedHandle::in_handle); + update_local_handles(it, GrabbedHandle::InHandle); // Invalidate cache if we updated the last keyframe's handles if (index == m_keyframes.size() - 1) { @@ -215,7 +215,7 @@ void Channel::set_keyframe_out_handle(size_t index, const Point& out_handle) } auto it = m_keyframes.begin() + index; it->out_handle = out_handle; - update_local_handles(it, GrabbedHandle::out_handle); + update_local_handles(it, GrabbedHandle::OutHandle); // Invalidate cache if we updated the last keyframe's handles if (index == m_keyframes.size() - 1) { diff --git a/src/handle_utils.cpp b/src/handle_utils.cpp index 5019d56..1c54894 100644 --- a/src/handle_utils.cpp +++ b/src/handle_utils.cpp @@ -314,11 +314,11 @@ namespace anim { // Determine source and target handles - if (grabbed_handle == GrabbedHandle::out_handle) { + if (grabbed_handle == GrabbedHandle::OutHandle) { source_handle_ptr = &(keyframe.out_handle); target_handle_ptr = &(keyframe.in_handle); source_is_out = true; - } else if (grabbed_handle == GrabbedHandle::in_handle) { + } else if (grabbed_handle == GrabbedHandle::InHandle) { source_handle_ptr = &(keyframe.in_handle); target_handle_ptr = &(keyframe.out_handle); source_is_out = false; diff --git a/tests/test_handle_utils.cpp b/tests/test_handle_utils.cpp index a2e003d..d2ea772 100644 --- a/tests/test_handle_utils.cpp +++ b/tests/test_handle_utils.cpp @@ -265,7 +265,7 @@ TEST_CASE("Handle mode functions", "[handle_utils]") { kf.out_handle = Point(7.0, 7.0); kf.in_handle = Point(3.0, 4.0); - apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::out_handle); // out_handle is source + apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::OutHandle); // out_handle is source // in_handle should be aligned with out_handle Vector in_vec = vector(kf.in_handle, kf.position); @@ -510,15 +510,15 @@ TEST_CASE("apply_aligned_handles source selection and modes", "[handle_utils]") Keyframe kf(5.0, 5.0); kf.in_handle = Point(3.0, 4.0); kf.out_handle = Point(7.0, 7.0); - apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::in_handle); + apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::InHandle); REQUIRE(collinearity(kf) < 1e-9); } - SECTION("GrabbedHandle::none picks the larger handle as source and aligns") { + SECTION("GrabbedHandle::None picks the larger handle as source and aligns") { Keyframe kf(5.0, 5.0); kf.in_handle = Point(4.5, 4.8); // small magnitude kf.out_handle = Point(9.0, 9.0); // large magnitude -> chosen as source - apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::none); + apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::None); REQUIRE(collinearity(kf) < 1e-9); } @@ -527,7 +527,7 @@ TEST_CASE("apply_aligned_handles source selection and modes", "[handle_utils]") kf.handle_mode = HandleMode::AlignStrict; kf.out_handle = Point(7.0, 7.0); kf.in_handle = Point(4.0, 4.5); - apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::out_handle); + apply_aligned_handles(kf, nullptr, nullptr, GrabbedHandle::OutHandle); double in_mag = distance(kf.position, kf.in_handle); double out_mag = distance(kf.position, kf.out_handle); diff --git a/tests/test_id_functionality.cpp b/tests/test_id_functionality.cpp index 19c80f4..e8ec8a7 100644 --- a/tests/test_id_functionality.cpp +++ b/tests/test_id_functionality.cpp @@ -22,9 +22,9 @@ TEST_CASE("Channel ID Management", "[Animation][Id]") { REQUIRE(ch2.id() != ch3.id()); // IDs should be valid - REQUIRE(ch1.id().isValid()); - REQUIRE(ch2.id().isValid()); - REQUIRE(ch3.id().isValid()); + REQUIRE(ch1.id().is_valid()); + REQUIRE(ch2.id().is_valid()); + REQUIRE(ch3.id().is_valid()); // IDs should be incrementing (implementation detail but good to check) REQUIRE(static_cast(ch2.id()) > static_cast(ch1.id())); @@ -50,7 +50,7 @@ TEST_CASE("Channel ID Management", "[Animation][Id]") { Animation animation("test"); Channel& ch = animation.create_channel("managed_channel"); - REQUIRE(ch.id().isValid()); + REQUIRE(ch.id().is_valid()); REQUIRE(ch.name() == "managed_channel"); } } @@ -104,9 +104,9 @@ TEST_CASE("Channel ID functionality", "[Channel][Id]") { Channel& ch3 = animation.create_channel("channel3"); // Check that IDs are assigned and unique - REQUIRE(ch1.id().isValid()); - REQUIRE(ch2.id().isValid()); - REQUIRE(ch3.id().isValid()); + REQUIRE(ch1.id().is_valid()); + REQUIRE(ch2.id().is_valid()); + REQUIRE(ch3.id().is_valid()); REQUIRE(ch1.id() != ch2.id()); REQUIRE(ch1.id() != ch3.id()); @@ -125,7 +125,7 @@ TEST_CASE("Channel ID functionality", "[Channel][Id]") { Channel& ch = animation.create_channel("test_channel"); Id channel_id = ch.id(); - REQUIRE(channel_id.isValid()); + REQUIRE(channel_id.is_valid()); REQUIRE(static_cast(channel_id) >= 1); } @@ -528,10 +528,10 @@ TEST_CASE("Id ordering and hashing", "[Id]") { SECTION("explicit conversion and invalid() sentinel") { Id id(12345); REQUIRE(static_cast(id) == 12345); - REQUIRE(id.isValid()); + REQUIRE(id.is_valid()); Id invalid = Id::invalid(); - REQUIRE_FALSE(invalid.isValid()); + REQUIRE_FALSE(invalid.is_valid()); REQUIRE(invalid.id == static_cast(-1)); } } From 6cae5fa4ff6193957142d775e3f9179a6dc78617 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:29 -0700 Subject: [PATCH 02/12] chore: remove unreferenced examples/gl_loader.cpp A hand-rolled Windows OpenGL loader left over from before the examples moved to glad. No build file or source has referenced it since; it was not compiled by any target. --- examples/gl_loader.cpp | 173 ----------------------------------------- 1 file changed, 173 deletions(-) delete mode 100644 examples/gl_loader.cpp diff --git a/examples/gl_loader.cpp b/examples/gl_loader.cpp deleted file mode 100644 index ed3b126..0000000 --- a/examples/gl_loader.cpp +++ /dev/null @@ -1,173 +0,0 @@ -// Simple OpenGL loader for Windows -#ifdef _WIN32 -#include -#include - -// Define the OpenGL constants and function pointers that ImGui needs -#define GL_ARRAY_BUFFER 0x8892 -#define GL_ELEMENT_ARRAY_BUFFER 0x8893 -#define GL_STATIC_DRAW 0x88E4 -#define GL_DYNAMIC_DRAW 0x88E8 -#define GL_VERTEX_SHADER 0x8B31 -#define GL_FRAGMENT_SHADER 0x8B30 -#define GL_COMPILE_STATUS 0x8B81 -#define GL_LINK_STATUS 0x8B82 -#define GL_TEXTURE0 0x84C0 -#define GL_BGRA 0x80E1 -#define GL_CLAMP_TO_EDGE 0x812F -#define GL_MAJOR_VERSION 0x821B -#define GL_MINOR_VERSION 0x821C -#define GL_NUM_EXTENSIONS 0x821D -#define GL_SCISSOR_TEST 0x0C11 -#define GL_BLEND 0x0BE2 -#define GL_SRC_ALPHA 0x0302 -#define GL_ONE_MINUS_SRC_ALPHA 0x0303 -#define GL_ONE 0x0001 -#define GL_FUNC_ADD 0x8006 -#define GL_CULL_FACE 0x0B44 -#define GL_DEPTH_TEST 0x0B71 -#define GL_STENCIL_TEST 0x0B90 -#define GL_FRONT_AND_BACK 0x0408 -#define GL_FILL 0x1B02 -#define GL_FALSE 0 -#define GL_TRUE 1 -#define GL_TEXTURE_BINDING_2D 0x8069 - -typedef unsigned int GLenum; -typedef unsigned char GLboolean; -typedef unsigned int GLbitfield; -typedef signed char GLbyte; -typedef short GLshort; -typedef int GLint; -typedef int GLsizei; -typedef unsigned char GLubyte; -typedef unsigned short GLushort; -typedef unsigned int GLuint; -typedef float GLfloat; -typedef float GLclampf; -typedef double GLdouble; -typedef double GLclampd; -typedef void GLvoid; -typedef char GLchar; - -// Function pointer types -typedef void (APIENTRY *PFNGLGENBUFFERSPROC)(GLsizei n, GLuint *buffers); -typedef void (APIENTRY *PFNGLDELETEBUFFERSPROC)(GLsizei n, const GLuint *buffers); -typedef void (APIENTRY *PFNGLBINDBUFFERPROC)(GLenum target, GLuint buffer); -typedef void (APIENTRY *PFNGLBUFFERDATAPROC)(GLenum target, ptrdiff_t size, const GLvoid *data, GLenum usage); -typedef GLuint (APIENTRY *PFNGLCREATESHADERPROC)(GLenum type); -typedef void (APIENTRY *PFNGLDELETESHADERPROC)(GLuint shader); -typedef void (APIENTRY *PFNGLSHADERSOURCEPROC)(GLuint shader, GLsizei count, const GLchar* const *string, const GLint *length); -typedef void (APIENTRY *PFNGLCOMPILESHADERPROC)(GLuint shader); -typedef void (APIENTRY *PFNGLGETSHADERIVPROC)(GLuint shader, GLenum pname, GLint *params); -typedef GLuint (APIENTRY *PFNGLCREATEPROGRAMPROC)(void); -typedef void (APIENTRY *PFNGLDELETEPROGRAMPROC)(GLuint program); -typedef void (APIENTRY *PFNGLATTACHSHADERPROC)(GLuint program, GLuint shader); -typedef void (APIENTRY *PFNGLDETACHSHADERPROC)(GLuint program, GLuint shader); -typedef void (APIENTRY *PFNGLLINKPROGRAMPROC)(GLuint program); -typedef void (APIENTRY *PFNGLGETPROGRAMIVPROC)(GLuint program, GLenum pname, GLint *params); -typedef void (APIENTRY *PFNGLUSEPROGRAMPROC)(GLuint program); -typedef GLint (APIENTRY *PFNGLGETUNIFORMLOCATIONPROC)(GLuint program, const GLchar *name); -typedef void (APIENTRY *PFNGLUNIFORM1IPROC)(GLint location, GLint v0); -typedef void (APIENTRY *PFNGLUNIFORMMATRIX4FVPROC)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); -typedef GLint (APIENTRY *PFNGLGETATTRIBLOCATIONPROC)(GLuint program, const GLchar *name); -typedef void (APIENTRY *PFNGLGENVERTEXARRAYSPROC)(GLsizei n, GLuint *arrays); -typedef void (APIENTRY *PFNGLDELETEVERTEXARRAYSPROC)(GLsizei n, const GLuint *arrays); -typedef void (APIENTRY *PFNGLBINDVERTEXARRAYPROC)(GLuint array); -typedef void (APIENTRY *PFNGLENABLEVERTEXATTRIBARRAYPROC)(GLuint index); -typedef void (APIENTRY *PFNGLDISABLEVERTEXATTRIBARRAYPROC)(GLuint index); -typedef void (APIENTRY *PFNGLVERTEXATTRIBPOINTERPROC)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); -typedef void (APIENTRY *PFNGLACTIVETEXTUREPROC)(GLenum texture); -typedef void (APIENTRY *PFNGLGETINTEGERVPROC)(GLenum pname, GLint *data); -typedef const GLubyte* (APIENTRY *PFNGLGETSTRINGPROC)(GLenum name); -typedef const GLubyte* (APIENTRY *PFNGLGETSTRINGIPROC)(GLenum name, GLuint index); -typedef void (APIENTRY *PFNGLPOLYGONMODEPROC)(GLenum face, GLenum mode); -typedef void (APIENTRY *PFNGLSCISSORPROC)(GLint x, GLint y, GLsizei width, GLsizei height); -typedef void (APIENTRY *PFNGLDRAWELEMENTSPROC)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); -typedef void (APIENTRY *PFNGLBLENDEQUATIONPROC)(GLenum mode); -typedef void (APIENTRY *PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); - -// Function pointers -extern "C" { -PFNGLGENBUFFERSPROC glGenBuffers; -PFNGLDELETEBUFFERSPROC glDeleteBuffers; -PFNGLBINDBUFFERPROC glBindBuffer; -PFNGLBUFFERDATAPROC glBufferData; -PFNGLCREATESHADERPROC glCreateShader; -PFNGLDELETESHADERPROC glDeleteShader; -PFNGLSHADERSOURCEPROC glShaderSource; -PFNGLCOMPILESHADERPROC glCompileShader; -PFNGLGETSHADERIVPROC glGetShaderiv; -PFNGLCREATEPROGRAMPROC glCreateProgram; -PFNGLDELETEPROGRAMPROC glDeleteProgram; -PFNGLATTACHSHADERPROC glAttachShader; -PFNGLDETACHSHADERPROC glDetachShader; -PFNGLLINKPROGRAMPROC glLinkProgram; -PFNGLGETPROGRAMIVPROC glGetProgramiv; -PFNGLUSEPROGRAMPROC glUseProgram; -PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation; -PFNGLUNIFORM1IPROC glUniform1i; -PFNGLUNIFORMMATRIX4FVPROC glUniformMatrix4fv; -PFNGLGETATTRIBLOCATIONPROC glGetAttribLocation; -PFNGLGENVERTEXARRAYSPROC glGenVertexArrays; -PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays; -PFNGLBINDVERTEXARRAYPROC glBindVertexArray; -PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray; -PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray; -PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer; -PFNGLACTIVETEXTUREPROC glActiveTexture; -PFNGLGETINTEGERVPROC glGetIntegerv; -PFNGLGETSTRINGPROC glGetString; -PFNGLGETSTRINGIPROC glGetStringi; -PFNGLPOLYGONMODEPROC glPolygonMode; -PFNGLSCISSORPROC glScissor; -PFNGLDRAWELEMENTSPROC glDrawElements; -PFNGLBLENDEQUATIONPROC glBlendEquation; -PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate; -} - -bool ImGui_ImplOpenGL3_Init(const char*) { - // Load OpenGL functions - HMODULE opengl32 = GetModuleHandle(L"opengl32.dll"); - if (!opengl32) return false; - - glGenBuffers = (PFNGLGENBUFFERSPROC)wglGetProcAddress("glGenBuffers"); - glDeleteBuffers = (PFNGLDELETEBUFFERSPROC)wglGetProcAddress("glDeleteBuffers"); - glBindBuffer = (PFNGLBINDBUFFERPROC)wglGetProcAddress("glBindBuffer"); - glBufferData = (PFNGLBUFFERDATAPROC)wglGetProcAddress("glBufferData"); - glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader"); - glDeleteShader = (PFNGLDELETESHADERPROC)wglGetProcAddress("glDeleteShader"); - glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource"); - glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader"); - glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv"); - glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram"); - glDeleteProgram = (PFNGLDELETEPROGRAMPROC)wglGetProcAddress("glDeleteProgram"); - glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader"); - glDetachShader = (PFNGLDETACHSHADERPROC)wglGetProcAddress("glDetachShader"); - glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram"); - glGetProgramiv = (PFNGLGETPROGRAMIVPROC)wglGetProcAddress("glGetProgramiv"); - glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram"); - glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation"); - glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i"); - glUniformMatrix4fv = (PFNGLUNIFORMMATRIX4FVPROC)wglGetProcAddress("glUniformMatrix4fv"); - glGetAttribLocation = (PFNGLGETATTRIBLOCATIONPROC)wglGetProcAddress("glGetAttribLocation"); - glGenVertexArrays = (PFNGLGENVERTEXARRAYSPROC)wglGetProcAddress("glGenVertexArrays"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)wglGetProcAddress("glDeleteVertexArrays"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYPROC)wglGetProcAddress("glBindVertexArray"); - glEnableVertexAttribArray = (PFNGLENABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glEnableVertexAttribArray"); - glDisableVertexAttribArray = (PFNGLDISABLEVERTEXATTRIBARRAYPROC)wglGetProcAddress("glDisableVertexAttribArray"); - glVertexAttribPointer = (PFNGLVERTEXATTRIBPOINTERPROC)wglGetProcAddress("glVertexAttribPointer"); - glActiveTexture = (PFNGLACTIVETEXTUREPROC)wglGetProcAddress("glActiveTexture"); - glGetIntegerv = (PFNGLGETINTEGERVPROC)GetProcAddress(opengl32, "glGetIntegerv"); - glGetString = (PFNGLGETSTRINGPROC)GetProcAddress(opengl32, "glGetString"); - glGetStringi = (PFNGLGETSTRINGIPROC)wglGetProcAddress("glGetStringi"); - glPolygonMode = (PFNGLPOLYGONMODEPROC)GetProcAddress(opengl32, "glPolygonMode"); - glScissor = (PFNGLSCISSORPROC)GetProcAddress(opengl32, "glScissor"); - glDrawElements = (PFNGLDRAWELEMENTSPROC)GetProcAddress(opengl32, "glDrawElements"); - glBlendEquation = (PFNGLBLENDEQUATIONPROC)wglGetProcAddress("glBlendEquation"); - glBlendFuncSeparate = (PFNGLBLENDFUNCSEPARATEPROC)wglGetProcAddress("glBlendFuncSeparate"); - - return true; -} - -#endif // _WIN32 From 68146a9967af5e8fa7062a84fff1eddd2f83d41d Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:29 -0700 Subject: [PATCH 03/12] refactor: replace placeholder blocks and a vacuous test assertion curve_visualization built its five demo curves inside five 'if (true)' blocks with the bodies duplicated per interpolation style. Replaced with a table of curve specs and one loop; the curves produced are unchanged. The 'Cannot create channel without ID' test asserted REQUIRE(true) and explained the real check in a comment. Replaced with static_asserts that actually enforce it: Channel must not be default-constructible, copy- constructible, or copy-assignable. --- examples/curve_visualization.cpp | 58 ++++++++++++-------------------- tests/test_id_functionality.cpp | 16 ++++++--- 2 files changed, 32 insertions(+), 42 deletions(-) diff --git a/examples/curve_visualization.cpp b/examples/curve_visualization.cpp index db06285..ce441f6 100644 --- a/examples/curve_visualization.cpp +++ b/examples/curve_visualization.cpp @@ -61,46 +61,30 @@ void CreateExampleCurves() { // Reset selection when curves change s_selection = Selection{}; - if (true) { // Placeholder for future curves, currently only sine wave - // Curve 1: Simple Sine Wave (8 points) - only curve for now - anim::Channel& sine_curve = animation.create_channel("Sine Wave"); - for (float t = 0; t <= 32.f; t += 8.f) { - sine_curve.create_keyframe(static_cast(t), static_cast(sin(t))); + // The same sine wave once per interpolation style, offset in value so the + // curves stack legibly in the plot rather than overlapping. + struct CurveSpec { + const char* name; + double value_offset; + anim::Function function; + anim::HandleMode handle_mode; + }; + static const CurveSpec curve_specs[] = { + {"Sine Wave", 0.00, anim::Function::Bezier, anim::HandleMode::Smooth}, + {"Sine Wave Linear", 0.25, anim::Function::Linear, anim::HandleMode::Smooth}, + {"Sine Wave Flat", 0.50, anim::Function::Bezier, anim::HandleMode::Flat}, + {"Sine Wave Aligned", 0.75, anim::Function::Bezier, anim::HandleMode::Aligned}, + {"Sine Wave Free", 1.00, anim::Function::Bezier, anim::HandleMode::Free}, + }; + + for (const CurveSpec& spec : curve_specs) { + anim::Channel& sine_curve = animation.create_channel(spec.name); + for (double t = 0.0; t <= 32.0; t += 8.0) { + sine_curve.create_keyframe(t, sin(t) + spec.value_offset, + spec.function, spec.handle_mode); } } - if (true){ - // Curve 2: Simple Sine Wave (8 points) - linear interpolation - anim::Channel& sine_curve = animation.create_channel("Sine Wave Linear"); - for (float t = 0; t <= 32.f; t += 8.f) { - sine_curve.create_keyframe(static_cast(t), static_cast(sin(t)) + .25, anim::Function::Linear); - } - } - - if (true){ - // Curve 3: Simple Sine Wave (8 points) - flat handles - anim::Channel& sine_curve = animation.create_channel("Sine Wave Flat"); - for (float t = 0; t <= 32.f; t += 8.f) { - sine_curve.create_keyframe(static_cast(t), static_cast(sin(t)) + .5, anim::Function::Bezier, anim::HandleMode::Flat); - } - } - - if (true){ - // Curve 4: Simple Sine Wave (8 points) - aligned handles - anim::Channel& sine_curve = animation.create_channel("Sine Wave Aligned"); - for (float t = 0; t <= 32.f; t += 8.f) { - sine_curve.create_keyframe(static_cast(t), static_cast(sin(t)) + .75, anim::Function::Bezier, anim::HandleMode::Aligned); - } - } - - if (true){ - // Curve 5: Simple Sine Wave (8 points) - free handles - anim::Channel& sine_curve = animation.create_channel("Sine Wave Free"); - for (float t = 0; t <= 32.f; t += 8.f) { - sine_curve.create_keyframe(static_cast(t), static_cast(sin(t)) + 1.0, anim::Function::Bezier, anim::HandleMode::Free); - } - } - // Initialize visibility data after curves are created s_keyframe_visibilities.resize(animation.num_channels()); for(size_t i = 0; i < animation.num_channels(); ++i) { diff --git a/tests/test_id_functionality.cpp b/tests/test_id_functionality.cpp index e8ec8a7..dcc40ca 100644 --- a/tests/test_id_functionality.cpp +++ b/tests/test_id_functionality.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace anim; @@ -434,11 +435,16 @@ TEST_CASE("Static channel ID counter", "[Animation][Id]") { TEST_CASE("Edge cases and error conditions", "[Animation][Id]") { SECTION("Cannot create channel without ID") { - // This test verifies that the Channel() default constructor is deleted - // The test is implicit - if this compiles, the test passes - // If Channel() were available, this would not compile: - // Channel ch; // This should not compile - REQUIRE(true); // Placeholder assertion + // Channels may only be created by Animation, which is what guarantees + // every channel gets a unique Id. Enforce that contract at compile time + // rather than asserting it in prose. + static_assert(!std::is_default_constructible_v, + "Channel must not be default-constructible; Animation owns creation."); + static_assert(!std::is_copy_constructible_v, + "Channel must not be copy-constructible; a copy would duplicate its Id."); + static_assert(!std::is_copy_assignable_v, + "Channel must not be copy-assignable; assignment would overwrite its Id."); + SUCCEED("Channel construction is restricted to Animation"); } SECTION("Remove non-existent channel by ID throws exception") { From 2d16dcbc759beff3affc52c8fe2e9f5dd21e0e00 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:29 -0700 Subject: [PATCH 04/12] fix: correct build script directory handling and ctest configuration Both scripts ran ctest without -C, which finds no tests at all on multi-config generators such as Visual Studio, and build.ps1 called Pop-Location with no matching Push-Location. They also disagreed on whether to build the examples. Both now configure out-of-source from the repository root, pass the configuration through to both the build and ctest, and build the library and tests only. Use examples/run_example.* for the viewer. --- build.ps1 | 35 +++++++++++++++-------------------- build.sh | 24 +++++++++++++----------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/build.ps1 b/build.ps1 index 61229ed..7779c20 100644 --- a/build.ps1 +++ b/build.ps1 @@ -3,27 +3,22 @@ param( [string]$Config = "Release" ) -# Store the current location to restore it later -$originalLocation = Get-Location +# Configure, build, and test the anim library (library + test suite). +# To build and run the examples, use examples\run_example.ps1 instead. +$ErrorActionPreference = "Stop" -try { - - # Create build directory - $null = New-Item -Path .\build -ItemType Directory -Force - Set-Location -Path .\build - - # Configure using CMake - # Explicitly enable tests and examples for CI/standalone builds - cmake .. -DANIM_BUILD_TESTS=ON -DANIM_BUILD_EXAMPLES=OFF - - # Build - cmake --build . --config $Config # --verbose +# Run from the repository root regardless of where the script was invoked from. +$RepoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path - # Run tests - ctest # --verbose +Push-Location $RepoRoot +try { + # CMAKE_BUILD_TYPE covers single-config generators (Ninja/Make); + # --config covers multi-config generators (Visual Studio). + cmake -B build -S . -DCMAKE_BUILD_TYPE="$Config" -DANIM_BUILD_TESTS=ON -DANIM_BUILD_EXAMPLES=OFF + cmake --build build --config $Config - Pop-Location + # -C is required for multi-config generators; without it ctest finds no tests. + ctest --test-dir build -C $Config --output-on-failure } finally { - # Ensure we always return to the original directory, even if errors occur - Set-Location -Path $originalLocation -} \ No newline at end of file + Pop-Location +} diff --git a/build.sh b/build.sh index 441c63b..a44c05a 100755 --- a/build.sh +++ b/build.sh @@ -1,16 +1,18 @@ #!/bin/bash -# Simple build script for the anim library +# Configure, build, and test the anim library (library + test suite). +# To build and run the examples, use examples/run_example.sh instead. +set -e -# Create build directory -mkdir -p build -cd build +CONFIG="${1:-Release}" -# Configure using CMake -# Explicitly enable tests and examples for CI/standalone builds -cmake .. -DANIM_BUILD_TESTS=ON -DANIM_BUILD_EXAMPLES=ON +# Run from the repository root regardless of where the script was invoked from. +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +cd "$SCRIPT_DIR" -# Build -cmake --build . +# CMAKE_BUILD_TYPE covers single-config generators (Ninja/Make); +# --config covers multi-config generators (Xcode). +cmake -B build -S . -DCMAKE_BUILD_TYPE="$CONFIG" -DANIM_BUILD_TESTS=ON -DANIM_BUILD_EXAMPLES=OFF +cmake --build build --config "$CONFIG" -# Run tests -ctest +# -C is required for multi-config generators; without it ctest finds no tests. +ctest --test-dir build -C "$CONFIG" --output-on-failure From d8f05a7128a571b0f15f5fdebdcf0d19f4df8d57 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:30 -0700 Subject: [PATCH 05/12] fix: stop .gitignore from ignoring hand-written cmake modules The '*.cmake' rule matched repository-wide, so any module added under cmake/ would be silently untracked. The accompanying '!anim-config.cmake.in' negation was a no-op, since that filename never matched '*.cmake' to begin with. Generated files are now listed by name. Out-of-source artifacts were already covered by the build directory rules. --- .gitignore | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c9cd3d2..8b11db9 100644 --- a/.gitignore +++ b/.gitignore @@ -43,13 +43,17 @@ cmake-build-*/ *.swp *~ -# CMake generated files +# CMake generated files (from an accidental in-source build; out-of-source +# artifacts are already covered by the build directory rules above). +# Listed by name rather than as *.cmake so hand-written modules under cmake/ +# are not silently ignored. CMakeCache.txt CMakeFiles/ cmake_install.cmake +CTestTestfile.cmake +CPackConfig.cmake +CPackSourceConfig.cmake Makefile -*.cmake -!anim-config.cmake.in # Generated test outputs Testing/ From d2418a29c72ede9a8e5232c28b3c170884ea0335 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:40 -0700 Subject: [PATCH 06/12] build: single-source the project version from CMakeLists.txt The version was repeated in CMakeLists.txt, docs/Doxyfile and docs/sphinx/conf.py, so a release bump had to touch three files and could silently drift. project() in CMakeLists.txt is now the only place it is written. Doxyfile reads it from $(ANIM_VERSION), which both docs build scripts export after parsing CMakeLists.txt, and conf.py parses the same file. Also corrects the Sphinx copyright to match LICENSE (2025-2026). --- docs/Doxyfile | 2 +- docs/build_docs.ps1 | 11 +++++++++++ docs/build_docs.sh | 10 ++++++++++ docs/sphinx/conf.py | 20 ++++++++++++++++++-- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/docs/Doxyfile b/docs/Doxyfile index 4865401..4de1032 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -3,7 +3,7 @@ # Run from the docs/ directory: doxygen Doxyfile PROJECT_NAME = "anim" -PROJECT_NUMBER = 0.1.2 +PROJECT_NUMBER = $(ANIM_VERSION) PROJECT_BRIEF = "Animation curve library" OUTPUT_DIRECTORY = doxygen diff --git a/docs/build_docs.ps1 b/docs/build_docs.ps1 index 96cb84c..56d7c9e 100644 --- a/docs/build_docs.ps1 +++ b/docs/build_docs.ps1 @@ -4,6 +4,17 @@ $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path Set-Location $ScriptDir +# The version lives only in CMakeLists.txt; Doxyfile reads it as $(ANIM_VERSION) +# and conf.py parses the same file, so there is one source of truth. +$cmakeLists = Join-Path (Split-Path -Parent $ScriptDir) "CMakeLists.txt" +$versionMatch = [regex]::Match((Get-Content -Raw $cmakeLists), 'project\s*\(\s*anim\s+VERSION\s+([0-9]+(?:\.[0-9]+)*)') +if (-not $versionMatch.Success) { + Write-Error "Could not parse the project version from $cmakeLists" + exit 1 +} +$env:ANIM_VERSION = $versionMatch.Groups[1].Value +Write-Host "Building docs for anim $($env:ANIM_VERSION)" + Write-Host "Running Doxygen..." doxygen Doxyfile diff --git a/docs/build_docs.sh b/docs/build_docs.sh index 2b36c69..b581083 100755 --- a/docs/build_docs.sh +++ b/docs/build_docs.sh @@ -5,6 +5,16 @@ set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" cd "$SCRIPT_DIR" +# The version lives only in CMakeLists.txt; Doxyfile reads it as $(ANIM_VERSION) +# and conf.py parses the same file, so there is one source of truth. +ANIM_VERSION="$(grep -oE 'project[[:space:]]*\([[:space:]]*anim[[:space:]]+VERSION[[:space:]]+[0-9.]+' ../CMakeLists.txt | grep -oE '[0-9.]+$')" +if [ -z "$ANIM_VERSION" ]; then + echo "Error: could not parse the project version from ../CMakeLists.txt" >&2 + exit 1 +fi +export ANIM_VERSION +echo "Building docs for anim $ANIM_VERSION" + echo "Running Doxygen..." doxygen Doxyfile diff --git a/docs/sphinx/conf.py b/docs/sphinx/conf.py index b0d4e92..ce2e957 100644 --- a/docs/sphinx/conf.py +++ b/docs/sphinx/conf.py @@ -3,10 +3,26 @@ # The API reference is generated from the Doxygen XML (produced by running # doxygen on docs/Doxyfile) via Breathe + Exhale. See docs/build_docs.sh. +import pathlib +import re + project = 'anim' -copyright = '2025, Actualize Interactive Inc.' +copyright = '2025-2026, Actualize Interactive Inc.' author = 'Actualize Interactive Inc.' -release = '0.1.2' + + +def _version_from_cmake(): + """Read the single source of truth for the version: project() in CMakeLists.txt.""" + cmakelists = pathlib.Path(__file__).resolve().parents[2] / 'CMakeLists.txt' + match = re.search(r'project\s*\(\s*anim\s+VERSION\s+([0-9]+(?:\.[0-9]+)*)', + cmakelists.read_text(encoding='utf-8')) + if not match: + raise RuntimeError(f'Could not parse the project version from {cmakelists}') + return match.group(1) + + +release = _version_from_cmake() +version = release extensions = [ 'breathe', From 52ee13b3121d5cd7ef47b99a3bc8ddcea7985740 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:40 -0700 Subject: [PATCH 07/12] docs: pin the documentation toolchain to tested version ranges The requirements were entirely unpinned, so any upstream release could break the Pages deploy with no change on our side. Pinned to compatible-release ranges, verified by building the full site cleanly against Sphinx 9.1.0, breathe 4.36.0, exhale 0.3.7 and sphinx-rtd-theme 3.1.0. Dependabot will propose major bumps as pull requests that CI validates. --- docs/sphinx/requirements.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt index ef182b1..804e841 100644 --- a/docs/sphinx/requirements.txt +++ b/docs/sphinx/requirements.txt @@ -1,4 +1,11 @@ -sphinx -breathe -exhale -sphinx_rtd_theme +# Pinned so an upstream release cannot break the Pages deploy without a +# reviewed change. Compatible-release ranges allow patch/minor updates while +# holding majors back until they are tested; Dependabot proposes major bumps +# as reviewable pull requests that CI validates. +# +# Verified building the full site cleanly against: Sphinx 9.1.0, breathe 4.36.0, +# exhale 0.3.7, sphinx-rtd-theme 3.1.0, docutils 0.22.4. +sphinx~=9.1 +breathe~=4.36 +exhale~=0.3.7 +sphinx-rtd-theme~=3.1 From d3eca04165b62528c4691d4ef6995d88a303e4ea Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:40 -0700 Subject: [PATCH 08/12] chore: bump version to 0.2.0 The Id::is_valid and GrabbedHandle renames are breaking changes to the public API, so this is a minor bump rather than a patch. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8721976..28059a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(anim VERSION 0.1.2 LANGUAGES CXX) +project(anim VERSION 0.2.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) From 0aece39f18c0107adaac8a36fcb6e554fbe5afeb Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:55 -0700 Subject: [PATCH 09/12] chore: add changelog, code of conduct, security policy and templates Community health files ahead of making the repository public. Reporting runs entirely through GitHub: security issues via private vulnerability reporting, everything else via issues. No email addresses are published. Changelog entries for 0.1.x are summarized retrospectively, since history was rewritten between the v0.1.1 and v0.1.2 tags and cannot be reconstructed commit by commit. Dependabot covers the workflow actions and the docs toolchain. The C++ dependencies are FetchContent git tags, which it cannot parse. --- .github/ISSUE_TEMPLATE/bug_report.yml | 78 ++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 32 +++++ .github/PULL_REQUEST_TEMPLATE.md | 31 +++++ .github/dependabot.yml | 29 +++++ CHANGELOG.md | 85 +++++++++++++ CODE_OF_CONDUCT.md | 135 +++++++++++++++++++++ SECURITY.md | 49 ++++++++ 8 files changed, 447 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 SECURITY.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..d45f152 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,78 @@ +name: Bug report +description: Something in the library behaves incorrectly +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to file a report. If this is a **security** + issue, please use private reporting instead — see [SECURITY.md](../blob/main/SECURITY.md). + + - type: textarea + id: summary + attributes: + label: What happened? + description: What did you expect, and what did you get instead? + placeholder: | + Evaluating a channel with HandleMode::Flat at t=... returns ..., but I expected ... + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Reproduction + description: > + The smallest program that shows the problem. A few lines using + `anim::Animation` and `create_keyframe` is usually enough, and it + will be rendered as C++ automatically. + render: cpp + placeholder: | + #include + + int main() { + anim::Animation animation("repro"); + anim::Channel& channel = animation.create_channel("value"); + channel.create_keyframe(0.0, 0.0); + channel.create_keyframe(1.0, 1.0); + // ... + } + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: Release tag or commit SHA. + placeholder: "v0.2.0, or 1a2b3c4" + validations: + required: true + + - type: input + id: environment + attributes: + label: Compiler and platform + placeholder: "MSVC 2022 / Windows 11, GCC 13 / Ubuntu 24.04, AppleClang 16 / macOS 15" + validations: + required: true + + - type: dropdown + id: build_type + attributes: + label: Build configuration + options: + - Release + - Debug + - RelWithDebInfo + - Other / not sure + validations: + required: true + + - type: textarea + id: notes + attributes: + label: Anything else? + description: Stack traces, sanitizer output, or a failing test case. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c356ff0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: API documentation + url: https://actualize-interactive.github.io/anim/ + about: The full API reference and guide — worth checking before filing. + - name: Report a security vulnerability + url: https://github.com/Actualize-Interactive/anim/security/advisories/new + about: Please report security issues privately, not as a public issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..9425afd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: Feature request +description: Suggest a capability or API addition +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: What problem would this solve? + description: > + Describe the use case rather than the implementation. What are you + trying to do that the library makes hard or impossible today? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed API + description: > + If you have a shape in mind, sketch it. Additive changes are much + easier to accept than changes to existing public signatures. + render: cpp + validations: + required: false + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Anything you tried, or workarounds you are using now. + validations: + required: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..ee39ea2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ + + +## What does this change? + + + +## Why? + + + +## Checklist + +- [ ] The build and full test suite pass locally (`./build.sh` or `.\build.ps1`). +- [ ] New or changed behavior is covered by tests. +- [ ] Public API changes are documented with Doxygen comments in `include/`. +- [ ] Naming follows the conventions in CONTRIBUTING.md (PascalCase types and + enumerators, lower_snake_case functions and variables). +- [ ] `CHANGELOG.md` has an entry under `[Unreleased]`, if user-visible. + +## Breaking changes + + + +None diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..75d56f5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,29 @@ +version: 2 + +updates: + # Keep the workflow actions current. The repository has no package-manager + # manifest for Dependabot to track: C++ dependencies (Catch2, GLFW, ImGui, + # ImPlot, glad) are pinned as FetchContent git tags in CMakeLists.txt, which + # Dependabot cannot parse, so those are bumped by hand. + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + commit-message: + prefix: "ci" + groups: + github-actions: + patterns: + - "*" + + # Documentation toolchain (Sphinx, Breathe, Exhale, the RTD theme). + - package-ecosystem: "pip" + directory: "/docs/sphinx" + schedule: + interval: "monthly" + commit-message: + prefix: "docs" + groups: + sphinx: + patterns: + - "*" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..7d5fb8e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,85 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +While the major version is `0`, breaking changes may land in a minor release. + +## [Unreleased] + +## [0.2.0] - 2026-07-25 + +First release prepared for the public repository. It contains two small +breaking renames that bring the public API in line with the project's naming +conventions; both are mechanical find-and-replace changes for callers. + +### Changed + +- **Breaking:** `Id::isValid()` is now `Id::is_valid()`, matching the + lower_snake_case convention used by every other method. +- **Breaking:** `GrabbedHandle` enumerators are now PascalCase, matching every + other enum in the library: + `GrabbedHandle::none` → `GrabbedHandle::None`, + `GrabbedHandle::in_handle` → `GrabbedHandle::InHandle`, + `GrabbedHandle::out_handle` → `GrabbedHandle::OutHandle`. +- `build.sh` and `build.ps1` now build the library and test suite only, run + from the repository root regardless of the working directory, and pass the + build configuration through to `ctest`. Use `examples/run_example.*` to build + and launch the viewer. +- The project version is now defined only in `CMakeLists.txt`; the Doxygen and + Sphinx configurations derive it from there instead of repeating it. +- Documentation dependencies are pinned to tested version ranges so an upstream + release cannot break the Pages deploy without a reviewed change. +- CI, docs, and release workflows updated to current action versions, with + explicit least-privilege `permissions` blocks. + +### Added + +- `CHANGELOG.md`, `CODE_OF_CONDUCT.md`, and `SECURITY.md`. +- Issue and pull request templates, and a Dependabot configuration that keeps + GitHub Actions up to date. +- Naming conventions documented in `CONTRIBUTING.md` (PascalCase for types and + enumerators, lower_snake_case for functions and variables). + +### Fixed + +- `build.ps1` called `Pop-Location` without a matching `Push-Location`, and ran + `ctest` without `-C`, which finds no tests on multi-config generators such as + Visual Studio. +- `.gitignore` matched `*.cmake` repository-wide, silently ignoring any + hand-written CMake module added under `cmake/`. + +### Removed + +- `examples/gl_loader.cpp`, which was unreferenced by any build file or source + since the examples moved to glad. + +## [0.1.2] - 2026-05-28 + +Repository prepared for publication: MIT license, contributor guide, Doxygen + +Sphinx API documentation published to GitHub Pages, and a release workflow that +publishes a single source archive. + +## [0.1.1] - 2025-07-05 + +Enumerators converted to PascalCase; per-channel `Extend` control for +out-of-range evaluation; equality operators for `Animation` and `Channel`; +explicit `Animation` copy methods; additional `Channel::create_keyframe` +overloads and a `keyframes()` accessor; stable per-channel `Id`. + +## [0.1.0] - 2025-05-29 + +Initial release: `Animation` and `Channel` containers, keyframes with +`Constant` / `Linear` / `Bezier` interpolation, the `HandleMode` family of +Bézier handle constraints, sampling helpers, and the Catch2 test suite. + + + +[Unreleased]: https://github.com/Actualize-Interactive/anim/compare/v0.2.0...HEAD +[0.2.0]: https://github.com/Actualize-Interactive/anim/compare/v0.1.2...v0.2.0 +[0.1.2]: https://github.com/Actualize-Interactive/anim/releases/tag/v0.1.2 +[0.1.1]: https://github.com/Actualize-Interactive/anim/releases/tag/v0.1.1 +[0.1.0]: https://github.com/Actualize-Interactive/anim/releases/tag/v0.1.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a62559b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,135 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by [opening an issue](https://github.com/Actualize-Interactive/anim/issues/new/choose) +in this repository. + +Note that repository issues are public. If a report would expose you by being +public — or if it concerns a maintainer — use GitHub's +[report abuse](https://github.com/contact/report-abuse) form instead, which +goes privately to GitHub Support rather than to this project. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla]. + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. + +[homepage]: https://www.contributor-covenant.org +[mozilla]: https://github.com/mozilla/inclusion diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..590d160 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,49 @@ +# Security Policy + +## Supported versions + +`anim` is pre-1.0 and is developed on a single line. Security fixes are applied +to the latest release only; there are no maintained backport branches. + +| Version | Supported | +| --- | --- | +| 0.2.x | ✅ | +| < 0.2 | ❌ | + +## Reporting a vulnerability + +**Please do not report security issues through public GitHub issues.** + +Report privately through GitHub's +[private vulnerability reporting](https://github.com/Actualize-Interactive/anim/security/advisories/new). +The form is the only reporting channel; it lets us discuss and fix the issue +with you before anything becomes public, and it requires nothing more than a +GitHub account. + +Please include: + +- the affected version or commit, +- a description of the issue and its impact, +- the steps, input, or minimal program needed to reproduce it, +- and any suggested fix, if you have one. + +You can expect an acknowledgement within a few business days. We will keep you +informed as we investigate, and will credit you in the release notes when the +fix ships unless you prefer otherwise. + +## Scope + +`anim` is a library with no network, filesystem, or process boundary of its +own — it evaluates animation curves from data supplied by the calling +application. The issues most relevant here are memory-safety problems reachable +from library inputs, such as: + +- out-of-bounds reads or writes from keyframe, channel, or index arguments, +- crashes, unbounded loops, or excessive allocation triggered by unusual but + legitimate input (extreme times, NaN or infinite values, degenerate handles), +- undefined behavior surfaced by the sanitizers or by checked iterators. + +Because the library trusts its caller by design, a report that depends on the +application passing deliberately corrupt internal state is likely to be treated +as a normal bug rather than a vulnerability. Report it as a regular issue and we +will still fix it. From c569b94097ab80ddfe8c88c6923c5a900ba409d8 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:55 -0700 Subject: [PATCH 10/12] ci: update actions to current versions and restrict token permissions The workflows pinned actions that were several majors behind (checkout v4, cache v4, setup-python v5, the Pages actions, and action-gh-release v2), and pulled lukka/get-cmake from a floating @latest tag. All are now on current versions and get-cmake is pinned. The FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 workaround is removed: it was added for a 2026-06-02 cutover that has passed, and every action now ships on Node 24. ci and release gain an explicit top-level 'permissions: contents: read'; the release job keeps its own contents: write. The source archive now includes CHANGELOG.md. --- .github/workflows/ci.yml | 12 +++++------- .github/workflows/docs.yml | 13 ++++--------- .github/workflows/release.yml | 18 ++++++++---------- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f8f350..5179db7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,10 +7,8 @@ on: branches: [ main ] workflow_dispatch: -# checkout/cache still ship on Node.js 20; opt their JS actions into Node 24 -# ahead of the forced cutover (2026-06-02). Remove once the actions ship Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true +permissions: + contents: read jobs: build: @@ -22,15 +20,15 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Setup cmake - uses: lukka/get-cmake@latest + uses: lukka/get-cmake@v4.4.0 with: cmakeVersion: '3.26.0' - name: Cache CMake Build - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | build diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0c54cdc..2f22696 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,19 +18,14 @@ concurrency: group: pages cancel-in-progress: false -# checkout still ships on Node.js 20; opt JS actions into Node 24 ahead of the -# forced cutover (2026-06-02). Remove once the actions ship Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: '3.x' @@ -46,7 +41,7 @@ jobs: run: bash docs/build_docs.sh - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: path: docs/build/html @@ -61,4 +56,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6266bce..ebfcbe1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,10 +6,8 @@ on: - 'v*.*.*' workflow_dispatch: -# checkout/cache still ship on Node.js 20; opt their JS actions into Node 24 -# ahead of the forced cutover (2026-06-02). Remove once the actions ship Node 24. -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true +permissions: + contents: read jobs: # Build and test on every platform as the release gate. @@ -22,17 +20,17 @@ jobs: timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - name: Setup cmake - uses: lukka/get-cmake@latest + uses: lukka/get-cmake@v4.4.0 with: cmakeVersion: '3.26.0' - name: Cache CMake Build - uses: actions/cache@v4 + uses: actions/cache@v6 with: path: | build @@ -66,15 +64,15 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 # One platform-independent, buildable source archive (headers + sources + # CMake config). GitHub also attaches auto-generated "Source code" archives. - name: Package source archive - run: zip -r anim-${{ github.ref_name }}-src.zip include src cmake CMakeLists.txt README.md LICENSE CONTRIBUTING.md + run: zip -r anim-${{ github.ref_name }}-src.zip include src cmake CMakeLists.txt README.md LICENSE CONTRIBUTING.md CHANGELOG.md - name: Create Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: anim-${{ github.ref_name }}-src.zip name: Release ${{ github.ref_name }} From c6c64737e08f90d35a87d887cc038901254bdf0f Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 13:42:55 -0700 Subject: [PATCH 11/12] docs: document naming conventions and the examples and docs workflows CONTRIBUTING stated only that enumerators are PascalCase, which left the lower_snake_case method convention implicit and did not explain the m_ prefix. Spells all three out, and asks contributors to add a changelog entry for user-visible changes. README now documents the PowerShell docs build script alongside the bash one, and points at examples/run_example.* for the viewer, since the top-level build scripts no longer build the examples. --- CONTRIBUTING.md | 10 +++++++++- README.md | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 753dd62..afa75eb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,7 +36,13 @@ automatically by CMake) and lives in `tests/`. ## Coding conventions - **C++20**, no compiler extensions. -- **Enumerators are PascalCase** (e.g. `Function::Bezier`, `HandleMode::Smooth`). +- **Naming:** + - Types, classes, structs, enums, and enumerators are **PascalCase** — + `Animation`, `Channel`, `Keyframe`, `Function::Bezier`, `HandleMode::Smooth`, + `GrabbedHandle::OutHandle`. + - Functions, methods, parameters, and variables are **lower_snake_case** — + `create_keyframe`, `evaluate_range_by_rate`, `is_valid`, `handle_mode`. + - Private data members are prefixed `m_` — `m_keyframes`, `m_channel_map`. - **Public interfaces are stable.** Avoid changing existing public signatures; prefer additive changes. Call out any unavoidable break in your PR. - **Document the public API in the headers** with Doxygen comments @@ -62,6 +68,8 @@ same change. - Use clear, imperative commit messages with a type prefix (`fix:`, `feat:`, `docs:`, `test:`, `chore:`, `ci:`). - Make sure the build and tests pass before opening the PR. +- Add an entry under `[Unreleased]` in [CHANGELOG.md](CHANGELOG.md) for any + user-visible change, and call out breaking changes explicitly. ## License diff --git a/README.md b/README.md index a19147e..33bc60d 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,17 @@ With `ANIM_BUILD_EXAMPLES` enabled, the `examples/` directory builds: - **`curve_visualization`** — an interactive ImGui/ImPlot viewer for inspecting curves and handle modes. +The helper scripts above build the library and tests only. To build and launch +the viewer (which fetches GLFW, ImGui and ImPlot on first configure): + +```bash +./examples/run_example.sh # Linux / macOS +``` + +```powershell +.\examples\run_example.ps1 # Windows (PowerShell) +``` + ## Documentation The full API reference and guide are published at @@ -184,7 +195,11 @@ The full API reference and guide are published at documentation with Doxygen and Sphinx. To build the docs locally: ```bash -bash docs/build_docs.sh # output in docs/build/html +bash docs/build_docs.sh # Linux / macOS — output in docs/build/html +``` + +```powershell +.\docs\build_docs.ps1 # Windows (PowerShell) ``` ## License From 43f98d51f5d022a3308808c691d6fddfa3249392 Mon Sep 17 00:00:00 2001 From: keithlostracco Date: Sat, 25 Jul 2026 14:05:40 -0700 Subject: [PATCH 12/12] ci: build with CMake 4.4 to support the Visual Studio 2026 runner image The windows-latest image moved from windows-2025 to windows-2025-vs2026. CMake 3.26, which the workflows pinned, has no generator for Visual Studio 2026, so it fell back to NMake Makefiles, found no nmake, and failed configure with CMAKE_CXX_COMPILER not set. A generator for it first appears in CMake 4.2. Building with CMake 4 removes compatibility with cmake_minimum_required below 3.5, which glad 0.1.36 declares, so the examples set CMAKE_POLICY_VERSION_MINIMUM to 3.5. That is scoped to the examples directory: neither the library, the test suite, nor anything consuming anim inherits it. It can go once glad is bumped. The build cache key is namespaced by CMake version so caches written by 3.26 are not restored into a 4.4 configure. Verified locally against CMake 4.4.0: library, tests and both examples build, and all 70 tests pass. --- .github/workflows/ci.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- CHANGELOG.md | 3 +++ examples/CMakeLists.txt | 7 +++++++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5179db7..84f4a49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Setup cmake uses: lukka/get-cmake@v4.4.0 with: - cmakeVersion: '3.26.0' + cmakeVersion: '4.4.0' - name: Cache CMake Build uses: actions/cache@v6 @@ -35,9 +35,9 @@ jobs: ~/.ccache ~/Library/Caches/ccache # macOS ~\AppData\Local\ccache # Windows - key: ${{ runner.os }}-cmake-${{ hashFiles('**/CMakeLists.txt') }} + key: ${{ runner.os }}-cmake4.4.0-${{ hashFiles('**/CMakeLists.txt') }} restore-keys: | - ${{ runner.os }}-cmake- + ${{ runner.os }}-cmake4.4.0- - name: Install system dependencies (Ubuntu) if: matrix.os == 'ubuntu-latest' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ebfcbe1..82542be 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - name: Setup cmake uses: lukka/get-cmake@v4.4.0 with: - cmakeVersion: '3.26.0' + cmakeVersion: '4.4.0' - name: Cache CMake Build uses: actions/cache@v6 @@ -37,9 +37,9 @@ jobs: ~/.ccache ~/Library/Caches/ccache # macOS ~\AppData\Local\ccache # Windows - key: ${{ runner.os }}-cmake-${{ hashFiles('**/CMakeLists.txt') }} + key: ${{ runner.os }}-cmake4.4.0-${{ hashFiles('**/CMakeLists.txt') }} restore-keys: | - ${{ runner.os }}-cmake- + ${{ runner.os }}-cmake4.4.0- - name: Install system dependencies (Ubuntu) if: matrix.os == 'ubuntu-latest' diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5fb8e..3b28390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ conventions; both are mechanical find-and-replace changes for callers. release cannot break the Pages deploy without a reviewed change. - CI, docs, and release workflows updated to current action versions, with explicit least-privilege `permissions` blocks. +- CI now builds with CMake 4.4. The `windows-latest` runner image ships Visual + Studio 2026, for which a generator first exists in CMake 4.2; the previously + pinned CMake 3.26 fell back to NMake and could not find a compiler at all. ### Added diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index cd0532a..7fb298e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,6 +7,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) set(FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps CACHE PATH "Base directory for FetchContent downloads") +# glad 0.1.36 declares cmake_minimum_required(VERSION 3.0), and CMake 4.0 +# removed compatibility with anything below 3.5, so configuring the examples +# fails outright without this. Scoped to the examples: the library and the test +# suite do not depend on it, and it is not inherited by anyone consuming anim. +# Remove once glad is bumped to a release that requires 3.5 or newer. +set(CMAKE_POLICY_VERSION_MINIMUM 3.5) + # Fetch GLFW first (required by ImGui) FetchContent_Declare( glfw