#include #include #include #include "animate.h" #include "state.h" #include "log.h" double clamp(double x, double min, double max) { const double t = x < min ? min : x; return t > max ? max : t; } double smoothstep(double x, double edge0, double edge1) { x = clamp((x - edge0) / (edge1 - edge0), 0, 1); return x * x * (3.0 - 2.0 * x); } double quadratic_bezier(double x, double a, double b, double c) { g_assert(x >= 0 && x <= 1); g_assert(a >= 0 && a <= 1); g_assert(b >= 0 && b <= 1); g_assert(c >= 0 && c <= 1); const double t = 1 - x; return a * t * t + 2 * b * t * x + c * x * x; } double cubic_bezier(double x, double a, double b, double c, double d) { g_assert(x >= 0 && x <= 1); g_assert(a >= 0 && a <= 1); g_assert(b >= 0 && b <= 1); g_assert(c >= 0 && c <= 1); g_assert(d >= 0 && d <= 1); const double t = 1 - x; return a * (t * t * t) + 3 * b * (t * t * x) + 3 * c * (t * x * x) + d * (x * x * x); } typedef struct { Animation anim; int width; cairo_pattern_t *gradient; } AnimationShine; static bool shine_func(AnimationShine *shine, Layout *layout, cairo_t *cr) { gint64 end = shine->anim.start + shine->anim.duration; gint64 now = g_get_monotonic_time(); if (now > end) return false; double t = (double)(now - shine->anim.start) / (end - shine->anim.start); double pos = cubic_bezier(t, 0.19, 1.0, 0.22, 1.0); double angle = atan((double)layout->height / layout->width); // Make it double just to be sure int h = 2 * (double)layout->height / cos(angle); int x = layout->x + pos * layout->width - shine->width; int y = layout->y; cairo_matrix_t matrix; cairo_matrix_init_translate(&matrix, -x, -y); cairo_pattern_set_matrix(shine->gradient, &matrix); int dx = layout->x + layout->width / 2; int dy = layout->y + layout->height / 2; cairo_translate(cr, dx, dy); cairo_rotate(cr, -angle); cairo_translate(cr, -dx, -dy); cairo_set_operator(cr, CAIRO_OPERATOR_ATOP); cairo_set_source(cr, shine->gradient); cairo_rectangle(cr, x, y - (h - layout->height) / 2, shine->width, h); cairo_fill(cr); return true; } Animation *animation_shine_create(gint64 duration) { // Note the 0 initialization AnimationShine *shine = g_malloc0(sizeof(AnimationShine)); shine->anim.type = ANIM_SHINE; shine->anim.after_func = (DrawFunc)shine_func; shine->anim.duration = duration; // TODO: Change it depending on container size shine->width = 25; shine->gradient = cairo_pattern_create_linear(0, 0, shine->width, 0); cairo_pattern_add_color_stop_rgba(shine->gradient, 0, 1, 1, 1, 0); cairo_pattern_add_color_stop_rgba(shine->gradient, 0.5, 1, 1, 1, 0.2); cairo_pattern_add_color_stop_rgba(shine->gradient, 1, 1, 1, 1, 0); return (gpointer)shine; } static bool pulse_func(Animation *pulse, Layout *layout, cairo_t *cr) { gint64 end = pulse->start + pulse->duration; gint64 now = g_get_monotonic_time(); if (now > end) return false; // After half the duration we invert direction gint64 mid = pulse->start + pulse->duration / 2; double t = now <= mid ? (double)(now - pulse->start) / (mid - pulse->start) : 1.0 - (double)(now - mid) / (end - mid); double pos = cubic_bezier(t, 0.19, 1.0, 0.22, 1.0); int max = 0.15 * layout->height; layout->x_pad = layout->btn->x_pad + max * pos; layout->y_pad = layout->btn->y_pad + max * pos; return true; } Animation *animation_pulse_create(gint64 duration) { // Note the 0 initialization Animation *pulse = g_malloc0(sizeof(Animation)); pulse->type = ANIM_PULSE; pulse->before_func = (DrawFunc)pulse_func; pulse->duration = duration; return pulse; } static bool shrink_func(Animation *shrink, Layout *layout) { g_assert_false(layout->btn->simple); ButtonGroup *group = (gpointer)layout->btn; gint64 end = shrink->start + shrink->duration; gint64 now = g_get_monotonic_time(); if (now > end) { // NOTE: Actually remove the buttons from the group after the animation's end // This part is quite rough around the edges... if (group->children->next) { g_list_free(g_list_remove_link(group->children, group->children)); g_list_free(g_list_remove_link(layout->children, layout->children)); layout->width = CAST(layout->children->data, Layout)->width; return true; } return false; } double t = (double)(now - shrink->start) / (end - shrink->start); double pos = smoothstep(t, 0, 1); int target_w = CAST(layout->children->data, Layout)->width; int width = layout->width - pos * (layout->width - target_w); layout->width = width; for (GList *it = layout->children, *next = it->next; it; it = next, next = it ? it->next : NULL) { Layout *child = it->data; if (child->x + child->width > layout->x + width) layout->children = g_list_delete_link(layout->children, it); } return true; } Animation *animation_group_shrink_create(gint64 duration) { // Note the 0 initialization Animation *shrink = g_malloc0(sizeof(Animation)); shrink->type = ANIM_GROUP_SHRINK; shrink->layout_func = (LayoutFunc)shrink_func; shrink->duration = duration; return shrink; } void animation_destroy(Animation *anim) { if (anim == NULL) return; if (anim->type == ANIM_SHINE) cairo_pattern_destroy(CAST(anim, AnimationShine)->gradient); g_free(anim); } // vim: ts=4 sw=4 et