]> git.cworth.org Git - xoboot/blob - xoboot.c
2d7fe962b2026458ee27fde5b72933747f252132
[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 #include <gtk/gtk.h>
23 #include <cairo.h>
24 #include <math.h>
25
26 typedef enum {
27     ANIM_BLANK,
28     ANIM_HEAD,
29     ANIM_BODY,
30     ANIM_SPIN,
31     ANIM_SPIN_DONE,
32     ANIM_OUTLINE,
33     ANIM_FADE,
34     ANIM_DONE
35 } anim_stage_t;
36
37 typedef struct _timeline {
38     double duration;
39     anim_stage_t state;
40     double transition;
41 } timeline_t;
42
43 #define TIMELINE_ADVANCE_TIMER_PERIOD_MS        500
44 #define TIMELINE_TRANSITION_FPS                 20
45
46 timeline_t timeline[] = {
47     { 0.5, ANIM_BLANK,      0.0},
48     { 1.0, ANIM_HEAD,       0.0},
49     { 2.0, ANIM_BODY,       0.0},
50     { 7.0, ANIM_SPIN,       6.0},
51     { 1.0, ANIM_SPIN_DONE,  0.0},
52     { 1.0, ANIM_OUTLINE,    0.1},
53     { 3.0, ANIM_FADE,       1.0},
54     { 0.0, ANIM_DONE }
55 };
56
57 typedef struct _state {
58     GtkWidget *drawing_area;
59     double progress;
60     anim_stage_t anim_stage;
61     int stage_ticks_remaining;
62     /* frames are used during a transition */
63     int num_frames;
64     int frame;
65     double transition;
66 } state_t;
67
68 typedef struct _color {
69     double r;
70     double g;
71     double b;
72 } color_t;
73
74 #define HEX_COLOR(r,g,b) {(r) / 255.0, (g) / 255.0, (b) / 255.0}
75
76 color_t background[2] = {
77     HEX_COLOR (0x75, 0x75, 0x75),
78     HEX_COLOR (0xdb, 0xdc, 0xdf)
79 };
80
81 color_t xo_green   = HEX_COLOR (0x4f, 0xff, 0x12);
82 color_t xo_black   = HEX_COLOR (0x30, 0x30, 0x30);
83 color_t dot_gray   = HEX_COLOR (0xe5, 0xe5, 0xe5);
84 color_t ring_white = HEX_COLOR (0xf1, 0xf1, 0xf1);
85
86 #define LERP(a,b,t) ((a) + (t) * ((b) - (a)))
87 #define LERP_COLORS(c0, c1, t) LERP((c0).r, (c1).r, (t)), \
88                                LERP((c0).g, (c1).g, (t)), \
89                                LERP((c0).b, (c1).b, (t))
90
91 static void
92 set_color (cairo_t *cr, color_t *color)
93 {
94     cairo_set_source_rgb (cr, color->r, color->g, color->b);
95 }
96
97 #define XO_HEAD_CENTER_X        320
98 #define XO_HEAD_CENTER_Y        211.5
99 #define XO_HEAD_RADIUS          12.25
100 #define XO_BODY_CENTER_X        320
101 #define XO_BODY_CENTER_Y        250
102 #define XO_BODY_DELTA           20.5
103 #define XO_BODY_LINE_WIDTH      12.25
104 #define XO_OUTLINE_EXTRA        2.5
105 #define XO_OUTLINE_SHRINK       1.1
106 #define XO_DOTS_CENTER_X        320
107 #define XO_DOTS_CENTER_Y        240
108 #define XO_DOTS_POSITION_RADIUS 112.5
109 #define XO_DOTS_DOT_RADIUS      5.5
110 #define XO_DOTS_NUM_DOTS        30
111 #define XO_RING_CENTER_X        320
112 #define XO_RING_CENTER_Y        240
113 #define XO_RING_INNER_RADIUS    63
114 #define XO_RING_OUTER_RADIUS    118
115
116 static void
117 draw_head (cairo_t *cr, double extra)
118 {
119     cairo_arc (cr,
120                XO_HEAD_CENTER_X,
121                XO_HEAD_CENTER_Y,
122                XO_HEAD_RADIUS + extra,
123                0, 2 * M_PI);
124     cairo_fill (cr);
125 }
126
127 static void
128 draw_body (cairo_t *cr, double extra, double spin_transition)
129 {
130     cairo_save (cr);
131
132     cairo_move_to (cr,
133                    XO_BODY_CENTER_X - XO_BODY_DELTA,
134                    XO_BODY_CENTER_Y + XO_BODY_DELTA);
135     cairo_rel_line_to (cr, XO_BODY_DELTA, -XO_BODY_DELTA);
136     cairo_rel_line_to (cr, XO_BODY_DELTA, XO_BODY_DELTA);
137
138     cairo_translate (cr, XO_HEAD_CENTER_X, XO_HEAD_CENTER_Y);
139     cairo_rotate (cr, 2 * M_PI * spin_transition);
140     cairo_translate (cr, -XO_HEAD_CENTER_X, -XO_HEAD_CENTER_Y);
141         
142     cairo_move_to (cr,
143                    XO_BODY_CENTER_X - XO_BODY_DELTA,
144                    XO_BODY_CENTER_Y - XO_BODY_DELTA);
145     cairo_rel_line_to (cr, XO_BODY_DELTA, XO_BODY_DELTA);
146     cairo_rel_line_to (cr, XO_BODY_DELTA, -XO_BODY_DELTA);
147
148     cairo_set_line_width (cr, XO_BODY_LINE_WIDTH + 2 * extra);
149     cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
150     cairo_stroke (cr);
151     
152     cairo_restore (cr);
153 }
154
155 static void
156 draw_dots (cairo_t *cr, double num_dots)
157 {
158     int i;
159
160     cairo_save (cr);
161
162     set_color (cr, &dot_gray);
163
164     cairo_translate (cr, XO_DOTS_CENTER_X, XO_DOTS_CENTER_Y);
165     cairo_rotate (cr, - 1.5 * M_PI);
166
167     for (i = 0; i < num_dots; i++) {
168         if (i != 0) {
169             cairo_arc (cr,
170                        XO_DOTS_POSITION_RADIUS, 0,
171                        XO_DOTS_DOT_RADIUS,
172                        0, 2 * M_PI);
173             cairo_fill (cr);
174         }
175         cairo_rotate (cr, 2 * M_PI / XO_DOTS_NUM_DOTS);
176     }
177     
178     cairo_restore (cr);
179 }
180
181 static void
182 draw_ring (cairo_t *cr)
183 {
184     cairo_save (cr);
185
186     cairo_arc (cr,
187                XO_RING_CENTER_X,
188                XO_RING_CENTER_Y,
189                XO_RING_INNER_RADIUS,
190                0, 2 * M_PI);
191     cairo_arc (cr,
192                XO_RING_CENTER_X,
193                XO_RING_CENTER_Y,
194                XO_RING_OUTER_RADIUS,
195                0, 2 * M_PI);
196     set_color (cr, &ring_white);
197
198     cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
199     cairo_fill (cr);
200
201     cairo_restore (cr);
202 }
203
204 static gboolean
205 xoboot_expose_event (GtkWidget      *widget,
206                       GdkEventExpose *event,
207                       gpointer        closure)
208 {
209     state_t *state = closure;
210     anim_stage_t anim_stage = state->anim_stage;
211     cairo_t *cr;
212     double shrink;
213     double spin_transition = 1.0;
214
215     cr = gdk_cairo_create (widget->window);
216
217     if (anim_stage < ANIM_FADE)
218         set_color (cr, &background[0]);
219     else
220         cairo_set_source_rgb (cr, LERP_COLORS (background[0],
221                                                background[1],
222                                                state->transition));
223
224     cairo_paint (cr);
225
226     if (anim_stage == ANIM_BLANK)
227         goto DONE;
228
229     if (anim_stage == ANIM_SPIN)
230         spin_transition = state->transition;
231
232     shrink = 0.0;
233     if (anim_stage >= ANIM_OUTLINE) {
234         set_color (cr, &xo_black);
235         draw_head (cr, XO_OUTLINE_EXTRA);
236         draw_body (cr, XO_OUTLINE_EXTRA, spin_transition);
237         shrink = - XO_OUTLINE_SHRINK;
238         if (anim_stage == ANIM_OUTLINE)
239             shrink *= state->transition;
240     }
241
242     set_color (cr, &xo_green);
243
244     draw_head (cr, shrink);
245     if (anim_stage == ANIM_HEAD)
246         goto DONE;
247
248     draw_body (cr, shrink, spin_transition);
249     if (anim_stage == ANIM_BODY)
250         goto DONE;
251
252     if (anim_stage < ANIM_FADE) {
253         draw_dots (cr, (XO_DOTS_NUM_DOTS + 1) * spin_transition);
254     } else {
255         draw_ring (cr);
256     }
257
258   DONE:
259     cairo_destroy (cr);
260
261     return TRUE;
262 }
263
264 static GtkWidget *
265 create_window (state_t *state)
266 {
267     GtkWidget *window;
268     GtkWidget *drawing_area;
269
270     window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
271     gtk_window_set_title (GTK_WINDOW (window), "OLPC Boot Animation Demo");
272
273     g_signal_connect (window, "destroy",
274                       G_CALLBACK (gtk_main_quit), &window);
275
276     drawing_area = gtk_drawing_area_new ();
277
278     /* We want to force 640x480 to emulate the OLPC display */
279     gtk_widget_set_size_request (drawing_area, 640, 480);
280
281     gtk_container_add (GTK_CONTAINER (window), drawing_area);
282
283     g_signal_connect (drawing_area, "expose_event",
284                       G_CALLBACK (xoboot_expose_event), state);
285
286     return drawing_area;
287 }
288
289 static gint
290 transition_advance (gpointer closure)
291 {
292     state_t *state = closure;
293
294     if (state->frame >= state->num_frames - 1) {
295         state->frame = state->num_frames - 1;
296         return FALSE;
297     }
298
299     state->frame++;
300     state->transition = (double) state->frame / (state->num_frames - 1);
301
302     gtk_widget_queue_draw (state->drawing_area);
303
304     return TRUE;
305 }
306
307 static gint
308 timeline_advance (gpointer closure)
309 {
310     state_t *state = closure;
311     timeline_t *tl;
312
313     if (state->anim_stage == ANIM_DONE)
314         return FALSE;
315
316     if (--state->stage_ticks_remaining)
317         return TRUE;
318
319     state->anim_stage++;
320     tl = &timeline[state->anim_stage];
321     state->stage_ticks_remaining = (tl->duration * 1000
322                                     / TIMELINE_ADVANCE_TIMER_PERIOD_MS);
323     gtk_widget_queue_draw (state->drawing_area);
324
325     if (tl->transition > 1.0 / TIMELINE_TRANSITION_FPS) {
326         state->num_frames = tl->transition * TIMELINE_TRANSITION_FPS;
327         state->frame = 0;
328         state->transition = 0.0;
329         g_timeout_add (1000 / TIMELINE_TRANSITION_FPS, transition_advance, state);
330     } else {
331         state->num_frames = 1;
332         state->frame = 0;
333         state->transition = 1.0;
334     }
335
336     return TRUE;
337 }
338
339 int
340 main (int argc, char *argv[])
341 {
342     state_t state;
343
344     gtk_init (&argc, &argv);
345
346     state.drawing_area = create_window (&state);
347     state.progress = 0.0;
348     state.anim_stage = 0;
349     state.num_frames = 1;
350     state.frame = 0;
351     state.transition = 1.0;
352     state.stage_ticks_remaining = (timeline[state.anim_stage].duration * 1000
353                                    / TIMELINE_ADVANCE_TIMER_PERIOD_MS);
354
355     gtk_widget_show_all (gtk_widget_get_toplevel (state.drawing_area));
356
357     g_timeout_add (TIMELINE_ADVANCE_TIMER_PERIOD_MS,
358                    timeline_advance, &state);
359
360     gtk_main ();
361
362     return 0;
363 }