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