aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Magahern <james@magahern.com>2019-01-20 16:32:34 -0800
committerJames Magahern <james@magahern.com>2019-01-20 16:33:42 -0800
commit2f4561cb78c1f53ace26f56f28050d7b75c6414d (patch)
treeda4b6a15b0eeb6e518652dd3fb5c23ec80a1e911
parentAlso don't allow input in xsecurelock path (TODO: unify these) (diff)
Animation support. Logo animates in and out now
-rw-r--r--meson.build1
-rw-r--r--src/animation.c34
-rw-r--r--src/animation.h59
-rw-r--r--src/main.c70
-rw-r--r--src/render.c104
-rw-r--r--src/render.h17
6 files changed, 258 insertions, 27 deletions
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 <buzzert@buzzert.net> 2019-01-20
+ */
+
+#include "animation.h"
+#include <stdlib.h>
+
+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 <buzzert@buzzert.net> 2019-01-20
+ */
+
+#pragma once
+#include <stdbool.h>
+#include <time.h>
+
+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 <assert.h>
#include <gio/gio.h>
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 <cairo/cairo.h>
@@ -14,6 +15,8 @@
#include <pango/pangocairo.h>
#include <stdbool.h>
+#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);