41878c0b5f52f52746b02d904975b19fb1ff6959
[xscreensaver] / hacks / boxfit.c
1 /* xscreensaver, Copyright (c) 2005, 2006 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Boxfit -- fills space with a gradient of growing boxes or circles.
12  *
13  * Written by jwz, 21-Feb-2005.
14  *
15  * Inspired by http://www.levitated.net/daily/levBoxFitting.html
16  */
17
18 #include "screenhack.h"
19 #include <stdio.h>
20 #include "xpm-pixmap.h"
21
22 #define ALIVE   1
23 #define CHANGED 2
24 #define UNDEAD  4
25
26 typedef struct {
27   unsigned long fill_color;
28   short x, y, w, h;
29   char flags;
30 } box;
31
32 typedef struct {
33   Display *dpy;
34   Window window;
35   XWindowAttributes xgwa;
36   GC gc;
37   unsigned long fg_color, bg_color;
38   int border_size;
39   int spacing;
40   int inc;
41
42   Bool circles_p;
43   Bool growing_p;
44   Bool color_horiz_p;
45
46   int box_count;
47   int boxes_size;
48   int nboxes;
49   box *boxes;
50
51   XImage *image;
52   int ncolors;
53   XColor *colors;
54   int delay;
55   int countdown;
56
57   Bool done_once;
58   async_load_state *img_loader;
59   Pixmap loading_pixmap;
60
61 } state;
62
63
64 static void
65 reset_boxes (state *st)
66 {
67   int mode = -1;
68
69   st->nboxes = 0;
70   st->growing_p = True;
71   st->color_horiz_p = random() & 1;
72
73   if (st->done_once && st->colors)
74     free_colors (st->dpy, st->xgwa.colormap, st->colors, st->ncolors);
75
76   if (!st->done_once)
77     {
78       char *s = get_string_resource (st->dpy, "mode", "Mode");
79       if (!s || !*s || !strcasecmp (s, "random"))
80         mode = -1;
81       else if (!strcasecmp (s, "squares") || !strcasecmp (s, "square"))
82         mode = 0;
83       else if (!strcasecmp (s, "circles") || !strcasecmp (s, "circle"))
84         mode = 1;
85       else
86         {
87           fprintf (stderr,
88                    "%s: mode must be random, squares, or circles, not '%s'\n",
89                    progname, s);
90           exit (1);
91         }
92     }
93
94   if (mode == -1)
95     st->circles_p = random() & 1;
96   else
97     st->circles_p = (mode == 1);
98
99   st->done_once = True;
100
101   if (st->image || get_boolean_resource (st->dpy, "grab", "Boolean"))
102     {
103       if (st->image) XDestroyImage (st->image);
104       st->image = 0;
105
106       if (st->loading_pixmap) abort();
107       if (st->img_loader) abort();
108       if (!get_boolean_resource (st->dpy, "peek", "Boolean"))
109         st->loading_pixmap = XCreatePixmap (st->dpy, st->window,
110                                             st->xgwa.width, st->xgwa.height,
111                                             st->xgwa.depth);
112
113       XClearWindow (st->dpy, st->window);
114       st->img_loader = load_image_async_simple (0, st->xgwa.screen, 
115                                                 st->window,
116                                                 (st->loading_pixmap
117                                                  ? st->loading_pixmap
118                                                  : st->window), 
119                                                 0, 0);
120     }
121   else
122     {
123       st->ncolors = get_integer_resource (st->dpy, "colors", "Colors");  /* re-get */
124       make_smooth_colormap (st->dpy, st->xgwa.visual, st->xgwa.colormap,
125                             st->colors, &st->ncolors, True, 0, False);
126       XClearWindow (st->dpy, st->window);
127     }
128 }
129
130
131 static void
132 reshape_boxes (state *st)
133 {
134   int i;
135   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
136   for (i = 0; i < st->nboxes; i++)
137     {
138       box *b = &st->boxes[i];
139       b->flags |= CHANGED;
140     }
141 }
142
143 static void *
144 boxfit_init (Display *dpy, Window window)
145 {
146   XGCValues gcv;
147   state *st = (state *) calloc (1, sizeof (*st));
148
149   st->dpy = dpy;
150   st->window = window;
151   st->delay = get_integer_resource (dpy, "delay", "Integer");
152
153   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
154 /*  XSelectInput (dpy, window, st->xgwa.your_event_mask | ExposureMask);*/
155
156   if (! get_boolean_resource (dpy, "grab", "Boolean"))
157     {
158       st->ncolors = get_integer_resource (dpy, "colors", "Colors");
159       if (st->ncolors < 1) st->ncolors = 1;
160       st->colors = (XColor *) malloc (sizeof(XColor) * st->ncolors);
161     }
162
163   st->inc = get_integer_resource (dpy, "growBy", "GrowBy");
164   st->spacing = get_integer_resource (dpy, "spacing", "Spacing");
165   st->border_size = get_integer_resource (dpy, "borderSize", "BorderSize");
166   st->fg_color = get_pixel_resource (st->dpy, st->xgwa.colormap, 
167                                      "foreground", "Foreground");
168   st->bg_color = get_pixel_resource (st->dpy, st->xgwa.colormap,
169                                      "background", "Background");
170   if (st->inc < 1) st->inc = 1;
171   if (st->border_size < 0) st->border_size = 0;
172
173   gcv.line_width = st->border_size;
174   gcv.background = st->bg_color;
175   st->gc = XCreateGC (st->dpy, st->window, GCBackground|GCLineWidth, &gcv);
176
177   st->box_count = get_integer_resource (dpy, "boxCount", "BoxCount");
178   if (st->box_count < 1) st->box_count = 1;
179
180   st->nboxes = 0;
181   st->boxes_size = st->box_count * 2;
182   st->boxes = (box *) calloc (st->boxes_size, sizeof(*st->boxes));
183
184   reset_boxes (st);
185
186   reshape_boxes (st);
187   return st;
188 }
189
190
191
192 static Bool
193 boxes_overlap_p (box *a, box *b, int pad)
194 {
195   /* Two rectangles overlap if the max of the tops is less than the
196      min of the bottoms and the max of the lefts is less than the min
197      of the rights.
198    */
199 # undef MAX
200 # undef MIN
201 # define MAX(A,B) ((A)>(B)?(A):(B))
202 # define MIN(A,B) ((A)<(B)?(A):(B))
203
204   int maxleft  = MAX(a->x - pad, b->x);
205   int maxtop   = MAX(a->y - pad, b->y);
206   int minright = MIN(a->x + a->w + pad + pad - 1, b->x + b->w);
207   int minbot   = MIN(a->y + a->h + pad + pad - 1, b->y + b->h);
208   return (maxtop < minbot && maxleft < minright);
209 }
210
211
212 static Bool
213 circles_overlap_p (box *a, box *b, int pad)
214 {
215   int ar = a->w/2;      /* radius */
216   int br = b->w/2;
217   int ax = a->x + ar;   /* center */
218   int ay = a->y + ar;
219   int bx = b->x + br;
220   int by = b->y + br;
221   int d2 = (((bx - ax) * (bx - ax)) +   /* distance between centers squared */
222             ((by - ay) * (by - ay)));
223   int r2 = ((ar + br + pad) *           /* sum of radii squared */
224             (ar + br + pad));
225   return (d2 < r2);
226 }
227
228
229 static Bool
230 box_collides_p (state *st, box *a, int pad)
231 {
232   int i;
233
234   /* collide with wall */
235   if (a->x - pad < 0 ||
236       a->y - pad < 0 ||
237       a->x + a->w + pad + pad >= st->xgwa.width ||
238       a->y + a->h + pad + pad >= st->xgwa.height)
239     return True;
240
241   /* collide with another box */
242   for (i = 0; i < st->nboxes; i++)
243     {
244       box *b = &st->boxes[i];
245       if (a != b &&
246           (st->circles_p
247            ? circles_overlap_p (a, b, pad)
248            : boxes_overlap_p (a, b, pad)))
249         return True;
250     }
251
252   return False;
253 }
254
255
256 static unsigned int
257 grow_boxes (state *st)
258 {
259   int inc2 = st->inc + st->spacing + st->border_size;
260   int i;
261   int live_count = 0;
262
263   /* check box collisions, and grow if none.
264    */
265   for (i = 0; i < st->nboxes; i++)
266     {
267       box *a = &st->boxes[i];
268       if (!(a->flags & ALIVE)) continue;
269
270       if (box_collides_p (st, a, inc2))
271         {
272           a->flags &= ~ALIVE;
273           continue;
274         }
275       
276       live_count++;
277       a->x -= st->inc;
278       a->y -= st->inc;
279       a->w += st->inc + st->inc;
280       a->h += st->inc + st->inc;
281       a->flags |= CHANGED;
282     }
283
284   /* Add more boxes.
285    */
286   while (live_count < st->box_count)
287     {
288       box *a;
289       st->nboxes++;
290       if (st->boxes_size <= st->nboxes)
291         {
292           st->boxes_size = (st->boxes_size * 1.2) + st->nboxes;
293           st->boxes = (box *)
294             realloc (st->boxes, st->boxes_size * sizeof(*st->boxes));
295           if (! st->boxes)
296             {
297               fprintf (stderr, "%s: out of memory (%d boxes)\n",
298                        progname, st->boxes_size);
299               exit (1);
300             }
301         }
302
303       a = &st->boxes[st->nboxes-1];
304       a->flags = CHANGED;
305
306       for (i = 0; i < 100; i++)
307         {
308           a->x = inc2 + (random() % (st->xgwa.width  - inc2));
309           a->y = inc2 + (random() % (st->xgwa.height - inc2));
310           a->w = 0;
311           a->h = 0;
312
313           if (! box_collides_p (st, a, inc2))
314             {
315               a->flags |= ALIVE;
316               live_count++;
317               break;
318             }
319         }
320
321       if (! (a->flags & ALIVE) ||       /* too many retries; */
322           st->nboxes > 65535)           /* that's about 1MB of box structs. */
323         {
324           st->nboxes--;                 /* go into "fade out" mode now. */
325           st->growing_p = False;
326           return 2000000; /* make customizable... */
327         }
328
329       /* Pick colors for this box */
330       if (st->image)
331         {
332           int w = st->image->width;
333           int h = st->image->height;
334           a->fill_color = XGetPixel (st->image, a->x % w, a->y % h);
335         }
336       else
337         {
338           int n = (st->color_horiz_p
339                    ? (a->x * st->ncolors / st->xgwa.width)
340                    : (a->y * st->ncolors / st->xgwa.height));
341           a->fill_color   = st->colors [n % st->ncolors].pixel;
342         }
343     }
344
345   return st->delay;
346 }
347
348
349 static unsigned int
350 shrink_boxes (state *st)
351 {
352   int i;
353   int remaining = 0;
354
355   for (i = 0; i < st->nboxes; i++)
356     {
357       box *a = &st->boxes[i];
358
359       if (a->w <= 0 || a->h <= 0) continue;
360
361       a->x += st->inc;
362       a->y += st->inc;
363       a->w -= st->inc + st->inc;
364       a->h -= st->inc + st->inc;
365       a->flags |= CHANGED;
366       if (a->w < 0) a->w = 0;
367       if (a->h < 0) a->h = 0;
368
369       if (a->w > 0 && a->h > 0)
370         remaining++;
371     }
372
373   if (remaining == 0) {
374     reset_boxes (st);
375     return 1000000;
376   } else {
377     return st->delay;
378   }
379 }
380
381
382 static void
383 draw_boxes (state *st)
384 {
385   int i;
386   for (i = 0; i < st->nboxes; i++)
387     {
388       box *b = &st->boxes[i];
389
390       if (b->flags & UNDEAD) continue;
391       if (! (b->flags & CHANGED)) continue;
392       b->flags &= ~CHANGED;
393
394       if (!st->growing_p)
395         {
396           /* When shrinking, black out an area outside of the border
397              before re-drawing the box.
398            */
399           int margin = st->inc + st->border_size;
400
401           XSetForeground (st->dpy, st->gc, st->bg_color);
402           if (st->circles_p)
403             XFillArc (st->dpy, st->window, st->gc,
404                       b->x - margin, b->y - margin,
405                       b->w + (margin*2), b->h + (margin*2),
406                       0, 360*64);
407           else
408             XFillRectangle (st->dpy, st->window, st->gc,
409                       b->x - margin, b->y - margin,
410                       b->w + (margin*2), b->h + (margin*2));
411
412           if (b->w <= 0 || b->h <= 0)
413             b->flags |= UNDEAD;   /* really very dead now */
414         }
415
416       if (b->w <= 0 || b->h <= 0) continue;
417
418       XSetForeground (st->dpy, st->gc, b->fill_color);
419
420       if (st->circles_p)
421         XFillArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
422                   0, 360*64);
423       else
424         XFillRectangle (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h);
425
426       if (st->border_size > 0)
427         {
428           unsigned int bd = (st->image
429                              ? st->fg_color
430                              : st->colors [(b->fill_color + st->ncolors/2)
431                                            % st->ncolors].pixel);
432           XSetForeground (st->dpy, st->gc, bd);
433           if (st->circles_p)
434             XDrawArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
435                       0, 360*64);
436           else
437             XDrawRectangle (st->dpy, st->window, st->gc,
438                             b->x, b->y, b->w, b->h);
439         }
440     }
441 }
442
443
444 static unsigned long
445 boxfit_draw (Display *dpy, Window window, void *closure)
446 {
447   state *st = (state *) closure;
448   int delay;
449
450   if (st->img_loader)   /* still loading */
451     {
452       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
453       if (! st->img_loader)  /* just finished */
454         {
455           st->image = XGetImage (st->dpy,
456                                  (st->loading_pixmap ? st->loading_pixmap :
457                                   st->window),
458                                  0, 0,
459                                  st->xgwa.width, st->xgwa.height, ~0L, 
460                                  ZPixmap);
461           if (st->loading_pixmap) XFreePixmap (st->dpy, st->loading_pixmap);
462           XSetWindowBackground (st->dpy, st->window, st->bg_color);
463           if (st->loading_pixmap)
464             XClearWindow (st->dpy, st->window);
465           else
466             st->countdown = 2000000;
467           st->loading_pixmap = 0;
468         }
469       return st->delay;
470     }
471
472   if (st->countdown > 0)
473     {
474       st->countdown -= st->delay;
475       if (st->countdown <= 0)
476         {
477           st->countdown = 0;
478           XClearWindow (st->dpy, st->window);
479         }
480       return st->delay;
481     }
482
483   if (st->growing_p) {
484     draw_boxes (st);
485     delay = grow_boxes (st);
486   } else {
487     delay = shrink_boxes (st);
488     draw_boxes (st);
489   }
490   return delay;
491 }
492
493 static void
494 boxfit_reshape (Display *dpy, Window window, void *closure, 
495                  unsigned int w, unsigned int h)
496 {
497   state *st = (state *) closure;
498   reshape_boxes (st);
499 }
500
501 static Bool
502 boxfit_event (Display *dpy, Window window, void *closure, XEvent *event)
503 {
504   state *st = (state *) closure;
505   if (event->xany.type == ButtonPress) {
506     st->growing_p = !st->growing_p;
507     return True;
508   }
509   return False;
510 }
511
512 static void
513 boxfit_free (Display *dpy, Window window, void *closure)
514 {
515 }
516
517
518 static const char *boxfit_defaults [] = {
519   ".background:            black",
520   ".foreground:            #444444",
521   "*delay:                 20000",
522   "*mode:                  random",
523   "*colors:                64",
524   "*boxCount:              50",
525   "*growBy:                1",
526   "*spacing:               1",
527   "*borderSize:            1",
528   "*grab:                  False",
529   "*peek:                  False",
530   "*grabDesktopImages:     False",   /* HAVE_COCOA */
531   "*chooseRandomImages:    True",    /* HAVE_COCOA */
532   0
533 };
534
535 static XrmOptionDescRec boxfit_options [] = {
536   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
537   { "-colors",          ".colors",              XrmoptionSepArg, 0 },
538   { "-count",           ".boxCount",            XrmoptionSepArg, 0 },
539   { "-growby",          ".growBy",              XrmoptionSepArg, 0 },
540   { "-spacing",         ".spacing",             XrmoptionSepArg, 0 },
541   { "-border",          ".borderSize",          XrmoptionSepArg, 0 },
542   { "-mode",            ".mode",                XrmoptionSepArg, 0 },
543   { "-circles",         ".mode",                XrmoptionNoArg, "circles" },
544   { "-squares",         ".mode",                XrmoptionNoArg, "squares" },
545   { "-random",          ".mode",                XrmoptionNoArg, "random"  },
546   { "-grab",            ".grab",                XrmoptionNoArg, "True"    },
547   { "-no-grab",         ".grab",                XrmoptionNoArg, "False"   },
548   { "-peek",            ".peek",                XrmoptionNoArg, "True"    },
549   { "-no-peek",         ".peek",                XrmoptionNoArg, "False"   },
550   { 0, 0, 0, 0 }
551 };
552
553
554 XSCREENSAVER_MODULE ("Boxfit", boxfit)