]> git.cworth.org Git - xoboot/blob - xoboot.c
Fix angular speed of the arrow to follow the points
[xoboot] / xoboot.c
1 /* xoboot - A simple demonstration of what might be an XO boot animation
2  *
3  * gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) xoboot.c -o xboot
4  *
5  * Copyright © 2007 Carl Worth
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."
20  */
21
22 /* This is simply a proof-of-concept for using cairo to implement a
23  * design idea for an OLPC boot-time animation. The animation here is
24  * implemented to mimic an animation originally provided as a
25  * Quicktime movie by Rebecca Allen <rallen@arts.ucla.edu>.
26  *
27  * So far, this animation has been developed exclusively on an x86
28  * laptop with GTK+ targeting the X Window System. No guarantees are
29  * provided as to how this might perform on an OLPC XO machine. What's
30  * likely desired is to write the application to use cairo image
31  * surfaces, and augment cairo to support targeting the XO's 565 image
32  * format directly.
33  *
34  * And obviously, to serve as a real boot-time animation, code will
35  * have to be written to advance the animation according to various
36  * system events, instead of using the hard-coded timeline below.
37  */
38
39 #include <gtk/gtk.h>
40 #include <cairo.h>
41 #include <math.h>
42
43 typedef enum {
44     ANIM_BLANK,
45     ANIM_HEAD,
46     ANIM_BODY,
47     ANIM_SPIN,
48     ANIM_SPIN_DONE,
49     ANIM_OUTLINE,
50     ANIM_FADE,
51     ANIM_DONE
52 } anim_stage_t;
53
54 typedef struct _timeline {
55     double duration;
56     anim_stage_t state;
57     double transition;
58 } timeline_t;
59
60 #define TIMELINE_ADVANCE_TIMER_PERIOD_MS        500
61 #define TIMELINE_TRANSITION_FPS                 20
62
63 timeline_t timeline[] = {
64     { 0.5, ANIM_BLANK,      0.0},
65     { 1.0, ANIM_HEAD,       0.0},
66     { 2.0, ANIM_BODY,       0.0},
67     { 7.0, ANIM_SPIN,       6.0},
68     { 1.0, ANIM_SPIN_DONE,  0.0},
69     { 1.0, ANIM_OUTLINE,    0.1},
70     { 3.0, ANIM_FADE,       1.0},
71     { 0.0, ANIM_DONE }
72 };
73
74 typedef struct _state {
75     GtkWidget *drawing_area;
76     double progress;
77     anim_stage_t anim_stage;
78     int stage_ticks_remaining;
79     /* frames are used during a transition */
80     int num_frames;
81     int frame;
82     double transition;
83 } state_t;
84
85 typedef struct _color {
86     double r;
87     double g;
88     double b;
89 } color_t;
90
91 #define HEX_COLOR(r,g,b) {(r) / 255.0, (g) / 255.0, (b) / 255.0}
92
93 color_t background[2] = {
94     HEX_COLOR (0x75, 0x75, 0x75),
95     HEX_COLOR (0xdb, 0xdc, 0xdf)
96 };
97
98 color_t xo_green   = HEX_COLOR (0x4f, 0xff, 0x12);
99 color_t xo_black   = HEX_COLOR (0x30, 0x30, 0x30);
100 color_t dot_gray   = HEX_COLOR (0xe5, 0xe5, 0xe5);
101 color_t ring_white = HEX_COLOR (0xf1, 0xf1, 0xf1);
102
103 #define LERP(a,b,t) ((a) + (t) * ((b) - (a)))
104 #define LERP_COLORS(c0, c1, t) LERP((c0).r, (c1).r, (t)), \
105                                LERP((c0).g, (c1).g, (t)), \
106                                LERP((c0).b, (c1).b, (t))
107
108 static void
109 set_color (cairo_t *cr, color_t *color)
110 {
111     cairo_set_source_rgb (cr, color->r, color->g, color->b);
112 }
113
114 #define XO_HEAD_CENTER_X        320
115 #define XO_HEAD_CENTER_Y        211.5
116 #define XO_HEAD_RADIUS          12.25
117 #define XO_BODY_CENTER_X        320
118 #define XO_BODY_CENTER_Y        250
119 #define XO_BODY_DELTA           20.5
120 #define XO_BODY_LINE_WIDTH      12.25
121 #define XO_OUTLINE_EXTRA        2.5
122 #define XO_OUTLINE_SHRINK       1.1
123 #define XO_DOTS_CENTER_X        320
124 #define XO_DOTS_CENTER_Y        240
125 #define XO_DOTS_POSITION_RADIUS 112.5
126 #define XO_DOTS_DOT_RADIUS      5.5
127 #define XO_DOTS_NUM_DOTS        30
128 #define XO_RING_CENTER_X        320
129 #define XO_RING_CENTER_Y        240
130 #define XO_RING_INNER_RADIUS    63
131 #define XO_RING_OUTER_RADIUS    118
132
133 static void
134 draw_head (cairo_t *cr, double extra)
135 {
136     cairo_arc (cr,
137                XO_HEAD_CENTER_X,
138                XO_HEAD_CENTER_Y,
139                XO_HEAD_RADIUS + extra,
140                0, 2 * M_PI);
141     cairo_fill (cr);
142 }
143
144 static void
145 draw_body (cairo_t *cr, double extra, double spin_transition)
146 {
147     double angle, x, y;
148
149     cairo_save (cr);
150
151     cairo_move_to (cr,
152                    XO_BODY_CENTER_X - XO_BODY_DELTA,
153                    XO_BODY_CENTER_Y + XO_BODY_DELTA);
154     cairo_rel_line_to (cr, XO_BODY_DELTA, -XO_BODY_DELTA);
155     cairo_rel_line_to (cr, XO_BODY_DELTA, XO_BODY_DELTA);
156
157     cairo_translate (cr, XO_HEAD_CENTER_X, XO_HEAD_CENTER_Y);
158
159     angle = 2 * M_PI * spin_transition;
160     y = cos (angle) * XO_DOTS_POSITION_RADIUS;
161     x = sin (angle) * XO_DOTS_POSITION_RADIUS;
162     angle = atan2 (x, y + XO_BODY_CENTER_Y - XO_HEAD_CENTER_Y);
163
164     cairo_rotate (cr, angle);
165     cairo_translate (cr, -XO_HEAD_CENTER_X, -XO_HEAD_CENTER_Y);
166         
167     cairo_move_to (cr,
168                    XO_BODY_CENTER_X - XO_BODY_DELTA,
169                    XO_BODY_CENTER_Y - XO_BODY_DELTA);
170     cairo_rel_line_to (cr, XO_BODY_DELTA, XO_BODY_DELTA);
171     cairo_rel_line_to (cr, XO_BODY_DELTA, -XO_BODY_DELTA);
172
173     cairo_set_line_width (cr, XO_BODY_LINE_WIDTH + 2 * extra);
174     cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
175     cairo_stroke (cr);
176     
177     cairo_restore (cr);
178 }
179
180 static void
181 draw_dots (cairo_t *cr, double num_dots)
182 {
183     int i;
184
185     cairo_save (cr);
186
187     set_color (cr, &dot_gray);
188
189     cairo_translate (cr, XO_DOTS_CENTER_X, XO_DOTS_CENTER_Y);
190     cairo_rotate (cr, - 1.5 * M_PI);
191
192     for (i = 0; i <= num_dots; i++) {
193         if (i != 0) {
194             cairo_arc (cr,
195                        XO_DOTS_POSITION_RADIUS, 0,
196                        XO_DOTS_DOT_RADIUS,
197                        0, 2 * M_PI);
198             cairo_fill (cr);
199         }
200         cairo_rotate (cr, 2 * M_PI / XO_DOTS_NUM_DOTS);
201     }
202     
203     cairo_restore (cr);
204 }
205
206 static void
207 draw_ring (cairo_t *cr)
208 {
209     cairo_save (cr);
210
211     cairo_arc (cr,
212                XO_RING_CENTER_X,
213                XO_RING_CENTER_Y,
214                XO_RING_INNER_RADIUS,
215                0, 2 * M_PI);
216     cairo_arc (cr,
217                XO_RING_CENTER_X,
218                XO_RING_CENTER_Y,
219                XO_RING_OUTER_RADIUS,
220                0, 2 * M_PI);
221     set_color (cr, &ring_white);
222
223     cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
224     cairo_fill (cr);
225
226     cairo_restore (cr);
227 }
228
229 static gboolean
230 xoboot_expose_event (GtkWidget      *widget,
231                       GdkEventExpose *event,
232                       gpointer        closure)
233 {
234     state_t *state = closure;
235     anim_stage_t anim_stage = state->anim_stage;
236     cairo_t *cr;
237     double shrink;
238     double spin_transition = 1.0;
239
240     cr = gdk_cairo_create (widget->window);
241
242     if (anim_stage < ANIM_FADE)
243         set_color (cr, &background[0]);
244     else
245         cairo_set_source_rgb (cr, LERP_COLORS (background[0],
246                                                background[1],
247                                                state->transition));
248
249     cairo_paint (cr);
250
251     if (anim_stage == ANIM_BLANK)
252         goto DONE;
253
254     if (anim_stage == ANIM_SPIN)
255         spin_transition = state->transition;
256
257     shrink = 0.0;
258     if (anim_stage >= ANIM_OUTLINE) {
259         set_color (cr, &xo_black);
260         draw_head (cr, XO_OUTLINE_EXTRA);
261         draw_body (cr, XO_OUTLINE_EXTRA, spin_transition);
262         shrink = - XO_OUTLINE_SHRINK;
263         if (anim_stage == ANIM_OUTLINE)
264             shrink *= state->transition;
265     }
266
267     set_color (cr, &xo_green);
268
269     draw_head (cr, shrink);
270     if (anim_stage == ANIM_HEAD)
271         goto DONE;
272
273     draw_body (cr, shrink, spin_transition);
274     if (anim_stage == ANIM_BODY)
275         goto DONE;
276
277     if (anim_stage < ANIM_FADE) {
278         draw_dots (cr, (XO_DOTS_NUM_DOTS) * spin_transition);
279     } else {
280         draw_ring (cr);
281     }
282
283   DONE:
284     cairo_destroy (cr);
285
286     return TRUE;
287 }
288
289 static GtkWidget *
290 create_window (state_t *state)
291 {
292     GtkWidget *window;
293     GtkWidget *drawing_area;
294
295     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
296     gtk_window_set_title (GTK_WINDOW (window), "OLPC Boot Animation Demo");
297
298     g_signal_connect (window, "destroy",
299                       G_CALLBACK (gtk_main_quit), &window);
300
301     drawing_area = gtk_drawing_area_new ();
302
303     /* We want to force 640x480 to emulate the OLPC display */
304     gtk_widget_set_size_request (drawing_area, 640, 480);
305
306     gtk_container_add (GTK_CONTAINER (window), drawing_area);
307
308     g_signal_connect (drawing_area, "expose_event",
309                       G_CALLBACK (xoboot_expose_event), state);
310
311     return drawing_area;
312 }
313
314 static gint
315 transition_advance (gpointer closure)
316 {
317     state_t *state = closure;
318
319     if (state->frame >= state->num_frames - 1) {
320         state->frame = state->num_frames - 1;
321         return FALSE;
322     }
323
324     state->frame++;
325     state->transition = (double) state->frame / (state->num_frames - 1);
326
327     gtk_widget_queue_draw (state->drawing_area);
328
329     return TRUE;
330 }
331
332 static gint
333 timeline_advance (gpointer closure)
334 {
335     state_t *state = closure;
336     timeline_t *tl;
337
338     if (state->anim_stage == ANIM_DONE)
339         return FALSE;
340
341     if (--state->stage_ticks_remaining)
342         return TRUE;
343
344     state->anim_stage++;
345     tl = &timeline[state->anim_stage];
346     state->stage_ticks_remaining = (tl->duration * 1000
347                                     / TIMELINE_ADVANCE_TIMER_PERIOD_MS);
348     gtk_widget_queue_draw (state->drawing_area);
349
350     if (tl->transition > 1.0 / TIMELINE_TRANSITION_FPS) {
351         state->num_frames = tl->transition * TIMELINE_TRANSITION_FPS;
352         state->frame = 0;
353         state->transition = 0.0;
354         g_timeout_add (1000 / TIMELINE_TRANSITION_FPS, transition_advance, state);
355     } else {
356         state->num_frames = 1;
357         state->frame = 0;
358         state->transition = 1.0;
359     }
360
361     return TRUE;
362 }
363
364 int
365 main (int argc, char *argv[])
366 {
367     state_t state;
368
369     gtk_init (&argc, &argv);
370
371     state.drawing_area = create_window (&state);
372     state.progress = 0.0;
373     state.anim_stage = 0;
374     state.num_frames = 1;
375     state.frame = 0;
376     state.transition = 1.0;
377     state.stage_ticks_remaining = (timeline[state.anim_stage].duration * 1000
378                                    / TIMELINE_ADVANCE_TIMER_PERIOD_MS);
379
380     gtk_widget_show_all (gtk_widget_get_toplevel (state.drawing_area));
381
382     g_timeout_add (TIMELINE_ADVANCE_TIMER_PERIOD_MS,
383                    timeline_advance, &state);
384
385     gtk_main ();
386
387     return 0;
388 }