]> git.cworth.org Git - akamaru/blob - main.c
Add new Spacer constraint and a 'dock' model.
[akamaru] / main.c
1 /*                                           -*- mode: c; c-basic-offset: 2 -*-
2  * To compile:
3  *
4  *     gcc -Wall -g $(pkg-config --cflags --libs gtk+-2.0 cairo) \
5  *             akamaru.c main.c -o akamaru
6  */
7
8 #include <gtk/gtk.h>
9 #include <cairo.h>
10 #include <cairo-xlib.h>
11 #include <gdk/gdkx.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/time.h>
15 #include <math.h>
16
17 #include "akamaru.h"
18
19 static void
20 model_init_polygons (Model *model)
21 {
22   const int num_polygons = 5;
23   const double ground_level = 500;
24
25   model->polygons = g_new (Polygon, num_polygons);
26   polygon_init_diamond (&model->polygons[0], 250, 300);
27   polygon_init_diamond (&model->polygons[1], 400, 150);
28   polygon_init_rectangle (&model->polygons[2], -100, 200, 200, 250);
29   polygon_init_rectangle (&model->polygons[3], -200, ground_level,
30                           1200, ground_level + 400);
31
32   polygon_init_rectangle (&model->polygons[4], 300, 320, 400, 350);
33
34   model->num_polygons = num_polygons;
35 }
36
37 static void
38 model_init_snake (Model *model)
39 {
40   const int num_objects = 20;
41   const int num_sticks = num_objects * 2 - 3;
42   int i;
43
44   memset (model, 0, sizeof *model);
45   model->objects = g_new (Object, num_objects);
46   model->num_objects = num_objects;
47   model->sticks = g_new (Stick, num_sticks);
48   model->num_sticks = num_sticks;
49   model_init_polygons (model);
50
51   for (i = 0; i < num_objects; i++) {
52     object_init (&model->objects[i],
53                  random() % 200 + 20, random() % 200 + 20, 1);
54
55     if (i + 1 < num_objects) {
56       model->sticks[i * 2].a = &model->objects[i];
57       model->sticks[i * 2].b = &model->objects[i + 1];
58       model->sticks[i * 2].length = random() % 20 + 20;
59     }
60     if (i + 2 < num_objects) {
61       model->sticks[i * 2 + 1].a = &model->objects[i];
62       model->sticks[i * 2 + 1].b = &model->objects[i + 2];
63       model->sticks[i * 2 + 1].length = random() % 20 + 20;
64     }
65   }
66
67   model->anchor_object = NULL;
68 }
69
70 static void
71 model_init_rope (Model *model)
72 {
73   const int num_objects = 20;
74   const int num_sticks = num_objects - 1;
75   const int stick_length = 10;
76   int i;
77
78   memset (model, 0, sizeof *model);
79   model->objects = g_new (Object, num_objects);
80   model->num_objects = num_objects;
81   model->sticks = g_new (Stick, num_sticks);
82   model->num_sticks = num_sticks;
83   model_init_polygons (model);
84
85   for (i = 0; i < num_objects; i++) {
86     object_init (&model->objects[i], 200, 40 + i * stick_length, 1);
87
88     if (i + 1 < num_objects) {
89       model->sticks[i].a = &model->objects[i];
90       model->sticks[i].b = &model->objects[i + 1];
91       model->sticks[i].length = stick_length;
92     }
93   }
94
95   model->anchor_object = NULL;
96 }
97
98 static void
99 model_init_curtain (Model *model)
100 {
101   const int num_ropes = 5;
102   const int num_rope_objects = 15;
103   const int num_objects = num_ropes * num_rope_objects;
104   const int num_sticks = num_ropes * (num_rope_objects - 1);
105   const int stick_length = 10;
106   const int rope_offset = 30;
107   double x, y;
108   int i, j, index, stick_index;
109
110   memset (model, 0, sizeof *model);
111   model->objects = g_new (Object, num_objects);
112   model->num_objects = num_objects;
113   model->sticks = g_new (Stick, num_sticks);
114   model->num_sticks = num_sticks;
115   model->offsets = g_new (Offset, 1);
116   model->num_offsets = 1;
117   model_init_polygons (model);
118
119   model->offsets[0].num_objects = num_ropes;
120   model->offsets[0].objects = g_new (Object *, num_ropes);
121   model->offsets[0].dx = rope_offset;
122   model->offsets[0].dy = 0;
123
124   for (i = 0; i < num_ropes; i++) {
125     for (j = 0; j < num_rope_objects; j++) {
126       x = 200 + i * rope_offset;
127       y = 40 + j * stick_length;
128       index = i * num_rope_objects + j;
129       object_init (&model->objects[index], x, y, 1);
130
131       if (j + 1 < num_rope_objects) {
132         stick_index = i * (num_rope_objects - 1) + j;
133         model->sticks[stick_index].a = &model->objects[index];
134         model->sticks[stick_index].b = &model->objects[index + 1];
135         model->sticks[stick_index].length = stick_length;
136       }
137     }
138
139     model->offsets[0].objects[i] = &model->objects[i * num_rope_objects];
140   }
141
142   model->anchor_object = NULL;
143 }
144
145 static void
146 model_init_grid (Model *model)
147 {
148   const int num_ropes = 4;
149   const int num_rope_objects = 4;
150   const int num_objects = num_ropes * num_rope_objects;
151   const int num_strings = num_ropes * (num_rope_objects - 1) +
152     (num_ropes - 1) * num_rope_objects;
153   const int string_length = 20;
154   const int rope_offset = 20;
155   Object *object;
156   String *string;
157   int i, j;
158
159   memset (model, 0, sizeof *model);
160   model->objects = g_new (Object, num_objects);
161   model->num_objects = num_objects;
162   model->strings = g_new (String, num_strings);
163   model->num_strings = num_strings;
164   model->offsets = g_new (Offset, 1);
165   model->num_offsets = 1;
166   model_init_polygons (model);
167
168   model->offsets[0].num_objects = num_ropes;
169   model->offsets[0].objects = g_new (Object *, num_ropes);
170   model->offsets[0].dx = rope_offset;
171   model->offsets[0].dy = 0;
172
173   object = model->objects;
174   string = model->strings;
175   for (i = 0; i < num_ropes; i++) {
176     for (j = 0; j < num_rope_objects; j++) {
177       object_init (object, 200 + i * rope_offset, 40 + j * string_length, 1);
178
179       if (i + 1 < num_ropes)
180         string_init (string++,
181                      object, object + num_rope_objects, string_length);
182       if (j + 1 < num_rope_objects)
183         string_init (string++, object, object + 1, string_length);
184       object++;
185     }
186
187     model->offsets[0].objects[i] = &model->objects[i * num_rope_objects];
188   }
189
190   model->anchor_object = NULL;
191 }
192
193 static void
194 model_init_molecule (Model *model)
195 {
196   const int num_objects = 8;
197   const int num_springs = num_objects * 2;
198   const int spring_length = 50;
199   int i;
200   Spring *spring;
201
202   memset (model, 0, sizeof *model);
203   model->objects = g_new (Object, num_objects);
204   model->num_objects = num_objects;
205   model->springs = g_new (Spring, num_springs);
206   model->num_springs = num_springs;
207   model->k = 2;
208
209   for (i = 0; i < num_objects; i++)
210     object_init (&model->objects[i], 200 + i * 20, 200, 0);
211
212   spring = model->springs;
213   for (i = 0; i < num_objects; i++) {
214     spring_init (spring++, &model->objects[i],
215                  &model->objects[(i + 1) % num_objects],
216                  spring_length);
217     spring_init (spring++, &model->objects[i],
218                  &model->objects[(i + 2) % num_objects],
219                  spring_length);
220   }
221 }
222
223
224 static void
225 model_init_wobbly (Model *model)
226 {
227   const int width = 8, height = 8;
228   const int num_objects = width * height;
229   const int num_offset_springs = (width - 1) * height + width * (height - 1);
230   const int distance = 30;
231   double x, y;
232   int i, j;
233   Object *object;
234   OffsetSpring *spring;
235
236   memset (model, 0, sizeof *model);
237   model->objects = g_new (Object, num_objects);
238   model->num_objects = num_objects;
239   model->offset_springs = g_new (OffsetSpring, num_offset_springs);
240   model->num_offset_springs = num_offset_springs;
241   model->k = 4.5;
242
243   model_init_polygons (model);
244
245   object = model->objects;
246   spring = model->offset_springs;
247   for (i = 0; i < width; i++) {
248     for (j = 0; j < height; j++) {
249       x = 200 + i * distance;
250       y = 40 + j * distance;
251       object_init (object, x, y, 0);
252
253       if (i + 1 < width)
254         offset_spring_init (spring++, object, object + height, distance, 0);
255       
256       if (j + 1 < height)
257         offset_spring_init (spring++, object, object + 1, 0, distance);
258
259       object++;
260     }
261   }
262 }
263
264 static void
265 model_init_dock (Model *model)
266 {
267   const int num_objects = 8;
268   const int num_spacers = (num_objects - 1) * (num_objects - 2) / 2;
269   const int num_springs = num_objects - 1;
270   const int distance = 30;
271   double x, y;
272   int i, j;
273   Object *object;
274   Spring *spring;
275   Spacer *spacer;
276
277   memset (model, 0, sizeof *model);
278   model->objects = g_new (Object, num_objects);
279   model->num_objects = num_objects;
280   model->springs = g_new (Spring, num_springs);
281   model->num_springs = num_springs;
282   model->spacers = g_new (Spacer, num_spacers);
283   model->num_spacers = num_spacers;
284   model->k = 0.1;
285
286   model_init_polygons (model);
287   model->polygons = g_new (Polygon, 1);
288   polygon_init_rectangle (&model->polygons[0], -400, 300, 1400, 350);
289   model->num_polygons = 1;
290
291
292   object = model->objects;
293   spring = model->springs;
294   spacer = model->spacers;
295   for (i = 0; i < num_objects; i++, object++) {
296     x = 200 + i * distance / 3;
297     y = 40;
298     object_init (object, x, y, 1);
299     if (i == 0)
300       continue;
301     spring_init (spring++,
302                  &model->objects[0],
303                  &model->objects[i],
304                  distance);
305     for (j = 1; j < num_objects - i; j++) {
306       spacer_init (spacer++, object, object + j, distance);
307     }
308   }
309 }
310
311 typedef struct _Color Color;
312 struct _Color {
313   double red, green, blue;
314 };
315
316 static void
317 draw_sticks (cairo_t *cr,
318              Model   *model,
319              Color   *color)
320 {
321   int i;
322
323   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
324   cairo_new_path (cr);
325   cairo_set_line_width (cr, 2);
326   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
327   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
328
329   for (i = 0; i < model->num_sticks; i++) {
330     cairo_move_to (cr,
331                    model->sticks[i].a->position.x,
332                    model->sticks[i].a->position.y);
333     cairo_line_to (cr,
334                    model->sticks[i].b->position.x,
335                    model->sticks[i].b->position.y);
336   }
337
338   cairo_stroke (cr);
339 }
340
341 static void
342 draw_strings (cairo_t *cr,
343               Model   *model,
344               Color   *color)
345 {
346   int i;
347
348   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 1);
349   cairo_new_path (cr);
350   cairo_set_line_width (cr, 1);
351   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
352   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
353
354   for (i = 0; i < model->num_strings; i++) {
355     cairo_move_to (cr,
356                    model->strings[i].a->position.x,
357                    model->strings[i].a->position.y);
358     cairo_line_to (cr,
359                    model->strings[i].b->position.x,
360                    model->strings[i].b->position.y);
361   }
362
363   cairo_stroke (cr);
364 }
365
366 static void
367 draw_offsets (cairo_t *cr,
368               Model   *model,
369               Color   *color)
370 {
371   int i, j;
372
373   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.5);
374   cairo_new_path (cr);
375   cairo_set_line_width (cr, 4);
376   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
377   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
378
379   for (i = 0; i < model->num_offsets; i++) {
380     for (j = 0; j < model->offsets[i].num_objects; j++) {
381       cairo_line_to (cr,
382                      model->offsets[i].objects[j]->position.x,
383                      model->offsets[i].objects[j]->position.y);
384     }
385     cairo_stroke (cr);
386   }
387
388 }
389
390 static void
391 draw_springs (cairo_t *cr,
392               Model   *model,
393               Color   *color)
394 {
395   int i;
396
397   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
398   cairo_new_path (cr);
399   cairo_set_line_width (cr, 2);
400   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
401   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
402
403   for (i = 0; i < model->num_springs; i++) {
404     cairo_move_to (cr,
405                    model->springs[i].a->position.x,
406                    model->springs[i].a->position.y);
407     cairo_line_to (cr,
408                    model->springs[i].b->position.x,
409                    model->springs[i].b->position.y);
410   }
411
412   cairo_stroke (cr);
413 }
414
415 static void
416 draw_offset_springs (cairo_t *cr,
417                      Model   *model,
418                      Color   *color)
419 {
420   int i;
421
422   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
423   cairo_new_path (cr);
424   cairo_set_line_width (cr, 2);
425   cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
426   cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
427
428   for (i = 0; i < model->num_offset_springs; i++) {
429     cairo_move_to (cr,
430                    model->offset_springs[i].a->position.x,
431                    model->offset_springs[i].a->position.y);
432     cairo_line_to (cr,
433                    model->offset_springs[i].b->position.x,
434                    model->offset_springs[i].b->position.y);
435   }
436
437   cairo_stroke (cr);
438 }
439
440 static void
441 draw_polygons (cairo_t *cr, Model *model, Color *color)
442 {
443   Polygon *p;
444   int i, j;
445
446   for (i = 0; i < model->num_polygons; i++) {
447     p = &model->polygons[i];
448     cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
449
450     
451     for (j = 0; j < p->num_points; j++)
452       cairo_line_to (cr, p->points[j].x, p->points[j].y);
453     cairo_close_path (cr);
454   }
455   cairo_fill (cr);
456
457 }
458
459 static void
460 draw_objects (cairo_t *cr, Model *model, Color *color)
461 {
462   int i;
463
464   cairo_set_source_rgba (cr, color->red, color->green, color->blue, 0.4);
465   for (i = 0; i < model->num_objects; i++) {
466     cairo_arc (cr, model->objects[i].position.x,
467                model->objects[i].position.y,
468                3, 0, 2*M_PI);
469     cairo_fill (cr);
470   }
471 }
472
473 static Color blue = { 0, 0, 1 };
474 static Color green = { 0, 1, 0 };
475 static Color red = { 1, 0, 0 };
476 static Color black = { 0, 0, 0 };
477
478 typedef struct _Closure Closure;
479 struct _Closure {
480   GtkWidget *drawing_area;
481   GtkWidget *fps_label;
482   Model *model;
483   int frame_count;
484   int i;
485   struct timeval start;
486 };
487
488 static void
489 draw_model (GtkWidget *widget, Model *model)
490 {
491   cairo_t *cr;
492
493   cr = gdk_cairo_create (widget->window);
494
495   cairo_set_source_rgb (cr, 1, 1, 1);
496   cairo_paint (cr);
497
498   draw_polygons (cr, model, &blue);
499   draw_sticks (cr, model, &black);
500   draw_strings (cr, model, &green);
501   draw_springs (cr, model, &black);
502   draw_offsets (cr, model, &blue);
503   draw_offset_springs (cr, model, &blue);
504   draw_objects (cr, model, &red);
505
506   cairo_destroy (cr);
507 }
508
509 static gboolean
510 expose_event (GtkWidget      *widget,
511               GdkEventExpose *event,
512               gpointer        data)
513 {
514   Closure *closure = data;
515
516   draw_model (widget, closure->model);
517
518   return TRUE;
519 }
520
521 static gboolean
522 button_press_event (GtkWidget      *widget,
523                     GdkEventButton *event,
524                     gpointer        data)
525 {
526   Closure *closure = data;
527
528   if (event->button != 1)
529     return TRUE;
530
531   closure->model->anchor_position.x = event->x;
532   closure->model->anchor_position.y = event->y;
533   closure->model->anchor_object = model_find_nearest (closure->model,
534                                                       event->x, event->y);
535
536   return TRUE;
537 }
538
539 static gboolean
540 button_release_event (GtkWidget      *widget,
541                       GdkEventButton *event,
542                       gpointer        data)
543 {
544   Closure *closure = data;
545
546   if ((event->state & GDK_BUTTON1_MASK) == 0)
547     return TRUE;
548
549   closure->model->anchor_object = NULL;
550
551   return TRUE;
552 }
553
554 static gboolean
555 motion_notify_event (GtkWidget      *widget,
556                      GdkEventMotion *event,
557                      gpointer        data)
558 {
559   Closure *closure = data;
560   int x, y;
561   GdkModifierType state;
562
563   gdk_window_get_pointer (event->window, &x, &y, &state);
564   
565   closure->model->anchor_position.x = x + 0.5;
566   closure->model->anchor_position.y = y + 0.5;
567
568   return TRUE;
569 }
570
571 typedef void (*ModelInitFunc) (Model *model);
572
573 static void
574 model_changed (GtkComboBox *combo, gpointer user_data)
575 {
576   Closure *closure = user_data;
577   GtkTreeIter iter;
578   GtkTreeModel *tree_model;
579   ModelInitFunc init;
580   char *name;
581
582   tree_model = gtk_combo_box_get_model (combo);
583   if (!gtk_combo_box_get_active_iter (combo, &iter))
584     return;
585
586   gtk_tree_model_get (tree_model, &iter, 0, &name, 1, &init, -1);
587
588   model_fini (closure->model);
589   (*init) (closure->model);
590 }
591
592 static GtkTreeModel *
593 create_model_store (void)
594 {
595   static struct {
596     const char *name;
597     ModelInitFunc init;
598   } models[] = {
599     { "Rope", model_init_rope },
600     { "Snake", model_init_snake },
601     { "Curtain", model_init_curtain },
602     { "Grid", model_init_grid },
603     { "Molecule", model_init_molecule },
604     { "Wobbly", model_init_wobbly },
605     { "Dock", model_init_dock }
606   };
607
608   GtkTreeIter iter;
609   GtkTreeStore *store;
610   gint i;
611
612   store = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
613
614   for (i = 0; i < G_N_ELEMENTS(models); i++) {
615     gtk_tree_store_append (store, &iter, NULL);
616     gtk_tree_store_set (store, &iter,
617                         0, models[i].name, 1, models[i].init, -1);
618  }
619   
620   return GTK_TREE_MODEL (store);
621
622 }
623
624 static GtkWidget *
625 create_model_combo (Closure *closure)
626 {
627   GtkWidget *hbox;
628   GtkWidget *combo, *label;
629   GtkTreeModel *store;
630   GtkCellRenderer *renderer;
631
632   hbox = gtk_hbox_new (FALSE, 8);
633
634   label = gtk_label_new_with_mnemonic ("_Model:");
635   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
636
637   store = create_model_store ();
638   combo = gtk_combo_box_new_with_model (store);
639   gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
640   g_object_unref (store);
641
642   renderer = gtk_cell_renderer_text_new ();
643   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
644   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
645                                   "text", 0,
646                                   NULL);
647
648   gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
649   gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0);
650   g_signal_connect (combo, "changed",
651                     G_CALLBACK (model_changed), closure);
652
653   label = gtk_label_new ("Frames per second: 0");
654   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
655
656   closure->fps_label = label;
657
658   return hbox;
659 }
660
661 static void
662 create_window (Closure *closure)
663 {
664   GtkWidget *window;
665   GtkWidget *frame;
666   GtkWidget *vbox;
667   GtkWidget *da;
668   GtkWidget *model_combo;
669
670   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
671   gtk_window_set_title (GTK_WINDOW (window), "Akamaru");
672
673   g_signal_connect (window, "destroy",
674                     G_CALLBACK (gtk_main_quit), &window);
675
676   gtk_container_set_border_width (GTK_CONTAINER (window), 8);
677
678   vbox = gtk_vbox_new (FALSE, 8);
679   gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
680   gtk_container_add (GTK_CONTAINER (window), vbox);
681
682   /*
683    * Create the drawing area
684    */
685       
686   frame = gtk_frame_new (NULL);
687   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
688   gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
689       
690   da = gtk_drawing_area_new ();
691   /* set a minimum size */
692   gtk_widget_set_size_request (da, 200, 200);
693
694   gtk_container_add (GTK_CONTAINER (frame), da);
695
696   /* Signals used to handle backing pixmap */
697       
698   g_signal_connect (da, "expose_event",
699                     G_CALLBACK (expose_event), closure);
700       
701   /* Event signals */
702       
703   g_signal_connect (da, "motion_notify_event",
704                     G_CALLBACK (motion_notify_event), closure);
705   g_signal_connect (da, "button_press_event",
706                     G_CALLBACK (button_press_event), closure);
707   g_signal_connect (da, "button_release_event",
708                     G_CALLBACK (button_release_event), closure);
709
710   /* Ask to receive events the drawing area doesn't normally
711    * subscribe to
712    */
713   gtk_widget_set_events (da, gtk_widget_get_events (da)
714                          | GDK_LEAVE_NOTIFY_MASK
715                          | GDK_BUTTON_PRESS_MASK
716                          | GDK_BUTTON_RELEASE_MASK
717                          | GDK_POINTER_MOTION_MASK
718                          | GDK_POINTER_MOTION_HINT_MASK);
719
720   model_combo = create_model_combo (closure);
721   gtk_box_pack_start (GTK_BOX (vbox), model_combo, FALSE, FALSE, 0);
722
723   closure->drawing_area = da;
724 }
725
726 static gint
727 timeout_callback (gpointer data)
728 {
729   Closure *closure = data;
730
731   model_step (closure->model, 0.2);
732
733   closure->i++;
734   if (closure->i == 1) {
735     gtk_widget_queue_draw (closure->drawing_area);
736     closure->i = 0;
737     closure->frame_count++;
738   }
739
740   if (closure->frame_count == 200) {
741     struct timeval end, elapsed;
742     double total;
743     char text[50];
744
745     closure->frame_count = 0;
746     gettimeofday (&end, NULL);
747     if (closure->start.tv_usec > end.tv_usec) {
748       end.tv_usec += 1000000;
749       end.tv_sec--;
750     }
751
752     elapsed.tv_usec = end.tv_usec - closure->start.tv_usec;
753     elapsed.tv_sec = end.tv_sec - closure->start.tv_sec;
754
755     total = elapsed.tv_sec + ((double) elapsed.tv_usec / 1e6);
756     if (total < 0) {
757       total = 0;
758     }
759     closure->start = end;
760     snprintf (text, sizeof text, "Frames per second: %.2f", 200 / total);
761     gtk_label_set_text (GTK_LABEL (closure->fps_label), text);
762   }
763
764   return TRUE;
765 }
766
767 int
768 main (int argc, char *argv[])
769 {
770   Closure closure;
771   Model model;
772
773   gtk_init (&argc, &argv);
774   model_init_rope (&model);
775   create_window (&closure);
776   closure.i = 0;
777   gtk_widget_show_all (gtk_widget_get_toplevel (closure.drawing_area));
778   closure.model = &model;
779   closure.frame_count = 0;
780   gettimeofday (&closure.start, NULL);
781   g_timeout_add (40, timeout_callback, &closure);
782   gtk_main ();
783
784   return 0;
785 }