-/* 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 <rallen@arts.ucla.edu>.
+ *
+ * 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 <gtk/gtk.h>
#include <cairo.h>
+#include <math.h>
+
+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 {
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;
}
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;
}
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 ();