Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,6 @@ man/*.1
# Subproject files
subprojects/libconfig
subprojects/.wraplock

# Worktrees
.worktrees/
101 changes: 101 additions & 0 deletions src/transition/curve.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,92 @@ struct curve parse_cubic_bezier(const char *input_str, const char **out_end, cha
return curve_new_cubic_bezier(numbers[0], numbers[1], numbers[2], numbers[3]);
}

/// Parse spring curve: spring(stiffness, dampening, mass) or spring(stiffness, dampening, mass, clamping)
/// Default clamping is true (prevents overshoot).
struct curve parse_spring(const char *input_str, const char **out_end, char **err) {
const char *str = input_str;
*err = NULL;
if (*str != '(') {
casprintf(err, "Invalid spring %s.", str);
return CURVE_INVALID_INIT;
}
str += 1;

double stiffness, dampening, mass;
bool clamping = true; // Default: clamp to prevent overshoot

// Parse stiffness
str = skip_space(str);
const char *end = NULL;
stiffness = strtod_simple(str, &end);
if (end == str || stiffness <= 0) {
casprintf(err, "Invalid spring stiffness at \"%s\". Must be positive.", str);
return CURVE_INVALID_INIT;
}
str = skip_space(end);
if (*str != ',') {
casprintf(err, "Invalid spring argument list \"%s\".", input_str);
return CURVE_INVALID_INIT;
}
str = skip_space(str + 1);

// Parse dampening
dampening = strtod_simple(str, &end);
if (end == str || dampening < 0) {
casprintf(err, "Invalid spring dampening at \"%s\". Must be non-negative.", str);
return CURVE_INVALID_INIT;
}
str = skip_space(end);
if (*str != ',') {
casprintf(err, "Invalid spring argument list \"%s\".", input_str);
return CURVE_INVALID_INIT;
}
str = skip_space(str + 1);

// Parse mass
mass = strtod_simple(str, &end);
if (end == str || mass <= 0) {
casprintf(err, "Invalid spring mass at \"%s\". Must be positive.", str);
return CURVE_INVALID_INIT;
}
str = skip_space(end);

// Optional: parse clamping boolean
if (*str == ',') {
str = skip_space(str + 1);
if (strncasecmp(str, "true", 4) == 0) {
clamping = true;
str += 4;
} else if (strncasecmp(str, "false", 5) == 0) {
clamping = false;
str += 5;
} else {
casprintf(err, "Invalid spring clamping value at \"%s\". "
"Expected 'true' or 'false'.",
str);
return CURVE_INVALID_INIT;
}
str = skip_space(str);
}

if (*str != ')') {
casprintf(err, "Invalid spring argument list \"%s\".", input_str);
return CURVE_INVALID_INIT;
}
*out_end = str + 1;

return (struct curve){
.type = CURVE_SPRING,
.spring =
{
.stiffness = stiffness,
.dampening = dampening,
.mass = mass,
.clamping = clamping,
},
};
}

typedef struct curve (*curve_parser)(const char *str, const char **end, char **err);

static const struct {
Expand All @@ -199,6 +285,7 @@ static const struct {
{parse_cubic_bezier, "cubic-bezier"},
{parse_linear, "linear"},
{parse_steps, "steps"},
{parse_spring, "spring"},
};

struct curve curve_parse(const char *str, const char **end, char **err) {
Expand All @@ -219,16 +306,30 @@ double curve_sample(const struct curve *curve, double progress) {
case CURVE_STEP: return curve_sample_step(&curve->step, progress);
case CURVE_CUBIC_BEZIER:
return curve_sample_cubic_bezier(&curve->bezier, progress);
case CURVE_SPRING:
// Spring curves are stateful and handled via INST_SPRING, not curve_sample
unreachable();
case CURVE_INVALID:
default: unreachable();
}
}

static char *curve_spring_to_c(const struct curve_spring *this) {
char *buf = NULL;
casprintf(&buf,
"{.type = CURVE_SPRING, .spring = { .stiffness = %a, "
".dampening = %a, .mass = %a, .clamping = %s }},",
this->stiffness, this->dampening, this->mass,
this->clamping ? "true" : "false");
return buf;
}

char *curve_to_c(const struct curve *curve) {
switch (curve->type) {
case CURVE_LINEAR: return curve_linear_to_c(curve);
case CURVE_STEP: return curve_step_to_c(&curve->step);
case CURVE_CUBIC_BEZIER: return curve_cubic_bezier_to_c(&curve->bezier);
case CURVE_SPRING: return curve_spring_to_c(&curve->spring);
case CURVE_INVALID:
default: unreachable();
}
Expand Down
11 changes: 11 additions & 0 deletions src/transition/curve.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ enum curve_type {
CURVE_LINEAR,
CURVE_CUBIC_BEZIER,
CURVE_STEP,
CURVE_SPRING,
CURVE_INVALID,
};

Expand All @@ -23,6 +24,12 @@ struct curve {
int steps;
bool jump_start, jump_end;
} step;
struct curve_spring {
double stiffness;
double dampening;
double mass;
bool clamping;
} spring;
};
};

Expand Down Expand Up @@ -50,3 +57,7 @@ struct curve curve_parse(const char *str, const char **end, char **err);
/// Calculate the value of the curve at `progress`.
double curve_sample(const struct curve *curve, double progress);
char *curve_to_c(const struct curve *curve);
/// Check if a curve is a spring (stateful, requires special handling).
static inline bool curve_is_spring(const struct curve *curve) {
return curve->type == CURVE_SPRING;
}
Loading