X-Git-Url: https://git.cworth.org/git?p=xoboot;a=blobdiff_plain;f=xoboot.c;h=1feb5dccbcb3059b4d445821168f4ef96e6079db;hp=ee6828997d3cd596492267b39ee4d4d70dcbe8af;hb=HEAD;hpb=eaaa3520562b9e72d3ced47dc5e85f7118944a10 diff --git a/xoboot.c b/xoboot.c index ee68289..1feb5dc 100644 --- a/xoboot.c +++ b/xoboot.c @@ -1,11 +1,85 @@ -/* gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) olpc-boot.c -o olpc-boot */ +/* xoboot - A simple demonstration of what might be an XO boot animation + * + * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) xoboot.c -o xboot + * + * Copyright © 2007 Carl Worth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA." + */ + +/* This is simply a proof-of-concept for using cairo to implement a + * design idea for an OLPC boot-time animation. The animation here is + * implemented to mimic an animation originally provided as a + * Quicktime movie by Rebecca Allen . + * + * So far, this animation has been developed exclusively on an x86 + * laptop with GTK+ targeting the X Window System. No guarantees are + * provided as to how this might perform on an OLPC XO machine. What's + * likely desired is to write the application to use cairo image + * surfaces, and augment cairo to support targeting the XO's 565 image + * format directly. + * + * And obviously, to serve as a real boot-time animation, code will + * have to be written to advance the animation according to various + * system events, instead of using the hard-coded timeline below. + */ #include #include +#include + +typedef enum { + ANIM_BLANK, + ANIM_HEAD, + ANIM_BODY, + ANIM_SPIN, + ANIM_SPIN_DONE, + ANIM_OUTLINE, + ANIM_FADE, + ANIM_DONE +} anim_stage_t; + +typedef struct _timeline { + double duration; + anim_stage_t state; + double transition; +} timeline_t; + +#define TIMELINE_ADVANCE_TIMER_PERIOD_MS 500 +#define TIMELINE_TRANSITION_FPS 20 + +timeline_t timeline[] = { + { 0.5, ANIM_BLANK, 0.0}, + { 1.0, ANIM_HEAD, 0.0}, + { 2.0, ANIM_BODY, 0.0}, + { 7.0, ANIM_SPIN, 6.0}, + { 1.0, ANIM_SPIN_DONE, 0.0}, + { 1.0, ANIM_OUTLINE, 0.1}, + { 3.0, ANIM_FADE, 1.0}, + { 0.0, ANIM_DONE } +}; typedef struct _state { GtkWidget *drawing_area; double progress; + anim_stage_t anim_stage; + int stage_ticks_remaining; + /* frames are used during a transition */ + int num_frames; + int frame; + double transition; } state_t; typedef struct _color { @@ -21,25 +95,192 @@ color_t background[2] = { HEX_COLOR (0xdb, 0xdc, 0xdf) }; +color_t xo_green = HEX_COLOR (0x4f, 0xff, 0x12); +color_t xo_black = HEX_COLOR (0x30, 0x30, 0x30); +color_t dot_gray = HEX_COLOR (0xe5, 0xe5, 0xe5); +color_t ring_white = HEX_COLOR (0xf1, 0xf1, 0xf1); + #define LERP(a,b,t) ((a) + (t) * ((b) - (a))) #define LERP_COLORS(c0, c1, t) LERP((c0).r, (c1).r, (t)), \ LERP((c0).g, (c1).g, (t)), \ LERP((c0).b, (c1).b, (t)) + +static void +set_color (cairo_t *cr, color_t *color) +{ + cairo_set_source_rgb (cr, color->r, color->g, color->b); +} + +#define XO_HEAD_CENTER_X 320 +#define XO_HEAD_CENTER_Y 211.5 +#define XO_HEAD_RADIUS 12.25 +#define XO_BODY_CENTER_X 320 +#define XO_BODY_CENTER_Y 250 +#define XO_BODY_DELTA 20.5 +#define XO_BODY_LINE_WIDTH 12.25 +#define XO_OUTLINE_EXTRA 2.5 +#define XO_OUTLINE_SHRINK 1.1 +#define XO_DOTS_CENTER_X 320 +#define XO_DOTS_CENTER_Y 240 +#define XO_DOTS_POSITION_RADIUS 112.5 +#define XO_DOTS_DOT_RADIUS 5.5 +#define XO_DOTS_NUM_DOTS 30 +#define XO_RING_CENTER_X 320 +#define XO_RING_CENTER_Y 240 +#define XO_RING_INNER_RADIUS 63 +#define XO_RING_OUTER_RADIUS 118 + +static void +draw_head (cairo_t *cr, double extra) +{ + cairo_arc (cr, + XO_HEAD_CENTER_X, + XO_HEAD_CENTER_Y, + XO_HEAD_RADIUS + extra, + 0, 2 * M_PI); + cairo_fill (cr); +} + +static void +draw_body (cairo_t *cr, double extra, double spin_transition) +{ + double angle, x, y; + + cairo_save (cr); + + cairo_move_to (cr, + XO_BODY_CENTER_X - XO_BODY_DELTA, + XO_BODY_CENTER_Y + XO_BODY_DELTA); + cairo_rel_line_to (cr, XO_BODY_DELTA, -XO_BODY_DELTA); + cairo_rel_line_to (cr, XO_BODY_DELTA, XO_BODY_DELTA); + + cairo_translate (cr, XO_HEAD_CENTER_X, XO_HEAD_CENTER_Y); + + angle = 2 * M_PI * spin_transition; + y = cos (angle) * XO_DOTS_POSITION_RADIUS; + x = sin (angle) * XO_DOTS_POSITION_RADIUS; + angle = atan2 (x, y + XO_BODY_CENTER_Y - XO_HEAD_CENTER_Y); + + cairo_rotate (cr, angle); + cairo_translate (cr, -XO_HEAD_CENTER_X, -XO_HEAD_CENTER_Y); + + cairo_move_to (cr, + XO_BODY_CENTER_X - XO_BODY_DELTA, + XO_BODY_CENTER_Y - XO_BODY_DELTA); + cairo_rel_line_to (cr, XO_BODY_DELTA, XO_BODY_DELTA); + cairo_rel_line_to (cr, XO_BODY_DELTA, -XO_BODY_DELTA); + + cairo_set_line_width (cr, XO_BODY_LINE_WIDTH + 2 * extra); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_stroke (cr); + + cairo_restore (cr); +} + +static void +draw_dots (cairo_t *cr, double num_dots) +{ + int i; + + cairo_save (cr); + + set_color (cr, &dot_gray); + + cairo_translate (cr, XO_DOTS_CENTER_X, XO_DOTS_CENTER_Y); + cairo_rotate (cr, - 1.5 * M_PI); + + for (i = 0; i <= num_dots; i++) { + if (i != 0) { + cairo_arc (cr, + XO_DOTS_POSITION_RADIUS, 0, + XO_DOTS_DOT_RADIUS, + 0, 2 * M_PI); + cairo_fill (cr); + } + cairo_rotate (cr, 2 * M_PI / XO_DOTS_NUM_DOTS); + } + + cairo_restore (cr); +} + +static void +draw_ring (cairo_t *cr) +{ + cairo_save (cr); + + cairo_arc (cr, + XO_RING_CENTER_X, + XO_RING_CENTER_Y, + XO_RING_INNER_RADIUS, + 0, 2 * M_PI); + cairo_arc (cr, + XO_RING_CENTER_X, + XO_RING_CENTER_Y, + XO_RING_OUTER_RADIUS, + 0, 2 * M_PI); + set_color (cr, &ring_white); + + cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill (cr); + + cairo_restore (cr); +} + static gboolean xoboot_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer closure) { state_t *state = closure; + anim_stage_t anim_stage = state->anim_stage; cairo_t *cr; + double shrink; + double spin_transition = 1.0; cr = gdk_cairo_create (widget->window); - cairo_set_source_rgb (cr, LERP_COLORS (background[0], - background[1], - state->progress)); + if (anim_stage < ANIM_FADE) + set_color (cr, &background[0]); + else + cairo_set_source_rgb (cr, LERP_COLORS (background[0], + background[1], + state->transition)); + cairo_paint (cr); + if (anim_stage == ANIM_BLANK) + goto DONE; + + if (anim_stage == ANIM_SPIN) + spin_transition = state->transition; + + shrink = 0.0; + if (anim_stage >= ANIM_OUTLINE) { + set_color (cr, &xo_black); + draw_head (cr, XO_OUTLINE_EXTRA); + draw_body (cr, XO_OUTLINE_EXTRA, spin_transition); + shrink = - XO_OUTLINE_SHRINK; + if (anim_stage == ANIM_OUTLINE) + shrink *= state->transition; + } + + set_color (cr, &xo_green); + + draw_head (cr, shrink); + if (anim_stage == ANIM_HEAD) + goto DONE; + + draw_body (cr, shrink, spin_transition); + if (anim_stage == ANIM_BODY) + goto DONE; + + if (anim_stage < ANIM_FADE) { + draw_dots (cr, (XO_DOTS_NUM_DOTS) * spin_transition); + } else { + draw_ring (cr); + } + + DONE: cairo_destroy (cr); return TRUE; @@ -71,15 +312,51 @@ create_window (state_t *state) } static gint -timeout_callback (gpointer closure) +transition_advance (gpointer closure) { state_t *state = closure; - state->progress += 0.01; - if (state->progress > 1.0) - state->progress = 1.0; - else - gtk_widget_queue_draw (state->drawing_area); + if (state->frame >= state->num_frames - 1) { + state->frame = state->num_frames - 1; + return FALSE; + } + + state->frame++; + state->transition = (double) state->frame / (state->num_frames - 1); + + gtk_widget_queue_draw (state->drawing_area); + + return TRUE; +} + +static gint +timeline_advance (gpointer closure) +{ + state_t *state = closure; + timeline_t *tl; + + if (state->anim_stage == ANIM_DONE) + return FALSE; + + if (--state->stage_ticks_remaining) + return TRUE; + + state->anim_stage++; + tl = &timeline[state->anim_stage]; + state->stage_ticks_remaining = (tl->duration * 1000 + / TIMELINE_ADVANCE_TIMER_PERIOD_MS); + gtk_widget_queue_draw (state->drawing_area); + + if (tl->transition > 1.0 / TIMELINE_TRANSITION_FPS) { + state->num_frames = tl->transition * TIMELINE_TRANSITION_FPS; + state->frame = 0; + state->transition = 0.0; + g_timeout_add (1000 / TIMELINE_TRANSITION_FPS, transition_advance, state); + } else { + state->num_frames = 1; + state->frame = 0; + state->transition = 1.0; + } return TRUE; } @@ -93,10 +370,17 @@ main (int argc, char *argv[]) state.drawing_area = create_window (&state); state.progress = 0.0; + state.anim_stage = 0; + state.num_frames = 1; + state.frame = 0; + state.transition = 1.0; + state.stage_ticks_remaining = (timeline[state.anim_stage].duration * 1000 + / TIMELINE_ADVANCE_TIMER_PERIOD_MS); gtk_widget_show_all (gtk_widget_get_toplevel (state.drawing_area)); - g_timeout_add (100, timeout_callback, &state); + g_timeout_add (TIMELINE_ADVANCE_TIMER_PERIOD_MS, + timeline_advance, &state); gtk_main ();