From 2f4561cb78c1f53ace26f56f28050d7b75c6414d Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sun, 20 Jan 2019 16:32:34 -0800 Subject: Animation support. Logo animates in and out now --- meson.build | 1 + src/animation.c | 34 ++++++++++++++++++ src/animation.h | 59 ++++++++++++++++++++++++++++++++ src/main.c | 70 +++++++++++++++++++++++++------------- src/render.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/render.h | 17 +++++++-- 6 files changed, 258 insertions(+), 27 deletions(-) create mode 100644 src/animation.c create mode 100644 src/animation.h diff --git a/meson.build b/meson.build index 2bbb9b3..0e639c4 100644 --- a/meson.build +++ b/meson.build @@ -6,6 +6,7 @@ cc = meson.get_compiler('c') sources = [ 'src/auth.c', + 'src/animation.c', 'src/main.c', 'src/render.c', 'src/x11_support.c', diff --git a/src/animation.c b/src/animation.c new file mode 100644 index 0000000..15093d3 --- /dev/null +++ b/src/animation.c @@ -0,0 +1,34 @@ +/* + * animation.c + * + * Created by buzzert 2019-01-20 + */ + +#include "animation.h" +#include + +anim_time_interval_t anim_now() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + + long ms = (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); + return (ms / 1000.0); +} + +/* + * Easing functions + */ + +double anim_qubic_ease_out(double p) +{ + double f = (p - 1.0); + return (f * f * f) + 1; +} + +double anim_quad_ease_out(double p) +{ + return -(p * (p - 2.0)); +} + + diff --git a/src/animation.h b/src/animation.h new file mode 100644 index 0000000..3064d1b --- /dev/null +++ b/src/animation.h @@ -0,0 +1,59 @@ +/* + * animation.h + * + * Created by buzzert 2019-01-20 + */ + +#pragma once +#include +#include + +typedef double anim_time_interval_t; + +struct animation_t; +typedef void(*AnimationCompletion)(struct animation_t *anim, void *context); + +typedef enum { + _EmptyAnimationType, + ACursorAnimation, + ALogoAnimation, +} AnimationType; + +typedef struct { + AnimationType type; + + bool cursor_animating; + double cursor_fade_direction; +} CursorAnimation; + +typedef struct { + AnimationType type; + + bool direction; // false: in, true: out +} LogoAnimation; + +typedef union { + AnimationType type; + + CursorAnimation cursor_anim; + LogoAnimation logo_anim; +} Animation; + +typedef struct { + bool completed; + anim_time_interval_t start_time; + + AnimationCompletion completion_func; + void *completion_func_context; + + Animation anim; +} animation_t; + +// Convenience: returns current time as anim_time_interval_t +anim_time_interval_t anim_now(); + +// Easing functions +double anim_qubic_ease_out(double p); +double anim_quad_ease_out(double p); + + diff --git a/src/main.c b/src/main.c index 74c2f53..c70ff9d 100644 --- a/src/main.c +++ b/src/main.c @@ -152,8 +152,6 @@ static void clear_password(saver_state_t *state) static void accept_password(saver_state_t *state) { - state->cursor_animating = false; - size_t pw_length = strlen(state->password_buffer); char *password_buf = malloc(pw_length); strncpy(password_buf, state->password_buffer, pw_length + 1); @@ -164,9 +162,30 @@ static void accept_password(saver_state_t *state) auth_attempt_authentication(state->auth_handle, response); // Block input until we hear back from the auth thread + state->is_processing = true; state->input_allowed = false; } +static void ending_animation_completed(struct animation_t *animation, void *context) +{ + saver_state_t *state = saver_state(context); + state->is_authenticated = true; +} + +static void authentication_accepted(saver_state_t *state) +{ + animation_t out_animation = { + .completed = false, + .completion_func = ending_animation_completed, + .completion_func_context = state, + .anim.logo_anim = { + .type = ALogoAnimation, + .direction = true + } + }; + schedule_animation(state, out_animation); +} + /* * Auth callbacks */ @@ -189,15 +208,15 @@ void callback_prompt_user(const char *prompt, void *context) saver_state_t *state = saver_state(context); state->password_prompt = new_prompt; - state->cursor_animating = true; state->input_allowed = true; + state->is_processing = false; } void callback_authentication_result(int result, void *context) { saver_state_t *state = saver_state(context); if (result == 0) { - state->is_authenticated = true; + authentication_accepted(state); } else { // Try again clear_password(state); @@ -210,23 +229,7 @@ void callback_authentication_result(int result, void *context) static void update(saver_state_t *state) { - if (state->cursor_animating) { - const double cursor_fade_speed = 0.03; - if (state->cursor_fade_direction > 0) { - state->cursor_opacity += cursor_fade_speed; - if (state->cursor_opacity > 1.0) { - state->cursor_fade_direction *= -1; - } - } else { - state->cursor_opacity -= cursor_fade_speed; - if (state->cursor_opacity <= 0.0) { - state->cursor_fade_direction *= -1; - } - } - } else { - state->cursor_opacity = 1.0; - } - + update_animations(state); poll_events(state); } @@ -253,15 +256,36 @@ static int runloop(cairo_surface_t *surface) state.ctx = cr; state.surface = surface; state.cursor_opacity = 1.0; - state.cursor_fade_direction = -1.0; state.pango_layout = pango_layout; state.status_font = status_font; state.password_buffer = calloc(1, kMaxPasswordLength); state.password_buffer_len = kMaxPasswordLength; - state.cursor_animating = false; state.input_allowed = false; state.password_prompt = ""; state.is_authenticated = false; + state.is_processing = false; + + // Add initial animations + // Cursor animation -- repeats indefinitely + animation_t cursor_animation = { + .completed = false, + .anim.cursor_anim = { + .type = ACursorAnimation, + .cursor_fade_direction = -1.0, + .cursor_animating = true + } + }; + schedule_animation(&state, cursor_animation); + + // Logo incoming animation + animation_t logo_animation = { + .completed = false, + .anim.logo_anim = { + .type = ALogoAnimation, + .direction = false + } + }; + schedule_animation(&state, logo_animation); x11_get_display_bounds(&state.canvas_width, &state.canvas_height); diff --git a/src/render.c b/src/render.c index 7860189..a7ff973 100644 --- a/src/render.c +++ b/src/render.c @@ -6,6 +6,8 @@ #include "render.h" #include "resources.h" + +#include #include static const double kLogoBackgroundWidth = 500.0; @@ -30,6 +32,103 @@ GBytes* get_data_for_resource(const char *resource_path) return result; } +static void update_single_animation(saver_state_t *state, animation_t *anim) +{ + // Cursor animation + if (anim->anim.type == ACursorAnimation) { + CursorAnimation *ca = &anim->anim.cursor_anim; + + if (ca->cursor_animating) { + const double cursor_fade_speed = 0.01; + if (ca->cursor_fade_direction > 0) { + state->cursor_opacity += cursor_fade_speed; + if (state->cursor_opacity > 1.0) { + ca->cursor_fade_direction *= -1; + } + } else { + state->cursor_opacity -= cursor_fade_speed; + if (state->cursor_opacity <= 0.0) { + ca->cursor_fade_direction *= -1; + } + } + } else { + state->cursor_opacity = 1.0; + } + } + + // Logo animation + else if (anim->anim.type == ALogoAnimation) { + const double logo_duration = 0.6; + + anim_time_interval_t now = anim_now(); + double progress = (now - anim->start_time) / logo_duration; + + state->logo_fill_progress = anim_qubic_ease_out(progress); + if (anim->anim.logo_anim.direction) { + state->logo_fill_progress = 1.0 - anim_qubic_ease_out(progress); + } + + bool completed = (state->logo_fill_progress >= 1.0); + if (anim->anim.logo_anim.direction) { + completed = (state->logo_fill_progress <= 0.0); + } + + anim->completed = completed; + } +} + +static unsigned next_anim_index(saver_state_t *state, unsigned cur_idx) +{ + unsigned idx = cur_idx + 1; + for (; idx < kMaxAnimations; idx++) { + animation_t anim = state->animations[idx]; + if (anim.anim.type != _EmptyAnimationType) break; + } + + return idx; +} + +void schedule_animation(saver_state_t *state, animation_t anim) +{ + anim.start_time = anim_now(); + + // Find next empty element + for (unsigned idx = 0; idx < kMaxAnimations; idx++) { + animation_t check_anim = state->animations[idx]; + if (check_anim.anim.type == _EmptyAnimationType) { + state->animations[idx] = anim; + state->num_animations++; + break; + } + } +} + +void update_animations(saver_state_t *state) +{ + unsigned idx = 0; + unsigned processed_animations = 0; + unsigned completed_animations = 0; + while (processed_animations < state->num_animations) { + animation_t *anim = &state->animations[idx]; + + update_single_animation(state, anim); + if (anim->completed) { + state->animations[idx].anim.type = _EmptyAnimationType; + if (anim->completion_func != NULL) { + anim->completion_func((struct animation_t *)anim, anim->completion_func_context); + } + + completed_animations++; + } + + processed_animations++; + idx = next_anim_index(state, idx); + if (idx == kMaxAnimations) break; + } + + state->num_animations -= completed_animations; +} + void draw_logo(saver_state_t *state) { if (state->logo_svg_handle == NULL) { @@ -51,7 +150,8 @@ void draw_logo(saver_state_t *state) cairo_save(cr); cairo_set_source_rgb(cr, (208.0 / 255.0), (69.0 / 255.0), (255.0 / 255.0)); - cairo_rectangle(cr, 0, 0, kLogoBackgroundWidth, state->canvas_height); + double fill_height = (state->canvas_height * state->logo_fill_progress); + cairo_rectangle(cr, 0, 0, kLogoBackgroundWidth, fill_height); cairo_fill(cr); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); @@ -132,7 +232,7 @@ void draw_password_field(saver_state_t *state) // Draw cursor cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, state->cursor_opacity); - if (state->cursor_animating) { + if (!state->is_processing) { cairo_rectangle(cr, field_x + cursor_offset_x, field_y, cursor_width, cursor_height); } else { // Fill asterisks diff --git a/src/render.h b/src/render.h index 902763d..7fc315d 100644 --- a/src/render.h +++ b/src/render.h @@ -6,6 +6,7 @@ #pragma once +#include "animation.h" #include "auth.h" #include @@ -14,6 +15,8 @@ #include #include +#define kMaxAnimations 32 + typedef struct { cairo_t *ctx; cairo_surface_t *surface; @@ -22,24 +25,34 @@ typedef struct { PangoFontDescription *status_font; RsvgHandle *logo_svg_handle; + double logo_fill_progress; + RsvgHandle *asterisk_svg_handle; int canvas_width; int canvas_height; bool input_allowed; - bool cursor_animating; double cursor_opacity; - double cursor_fade_direction; + bool is_processing; bool is_authenticated; const char *password_prompt; char *password_buffer; size_t password_buffer_len; + animation_t animations[kMaxAnimations]; + unsigned num_animations; + struct auth_handle_t *auth_handle; } saver_state_t; +// Start an animation +void schedule_animation(saver_state_t *state, animation_t anim); + +// Update all running animations +void update_animations(saver_state_t *state); + // The purple sidebar void draw_logo(saver_state_t *state); -- cgit v1.2.3