http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / boxfit.c
1 /* xscreensaver, Copyright (c) 2005 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 <X11/Xutil.h>
21
22 #define ALIVE   1
23 #define CHANGED 2
24
25 typedef struct {
26   unsigned long fill_color;
27   short x, y, w, h;
28   char flags;
29 } box;
30
31 typedef struct {
32   Display *dpy;
33   Window window;
34   XWindowAttributes xgwa;
35   GC gc;
36   unsigned long bg_color;
37   int border_size;
38   int spacing;
39   int inc;
40
41   Bool circles_p;
42   Bool growing_p;
43   Bool color_horiz_p;
44
45   int box_count;
46   int boxes_size;
47   int nboxes;
48   box *boxes;
49
50   int ncolors;
51   XColor *colors;
52 } state;
53
54
55 static void
56 reset_boxes (state *st)
57 {
58   static Bool once = False;
59   int mode = -1;
60
61   st->nboxes = 0;
62   st->growing_p = True;
63   st->color_horiz_p = random() & 1;
64
65   if (once)
66     free_colors (st->dpy, st->xgwa.colormap, st->colors, st->ncolors);
67
68   if (!once)
69     {
70       char *s = get_string_resource ("mode", "Mode");
71       if (!s || !*s || !strcasecmp (s, "random"))
72         mode = -1;
73       else if (!strcasecmp (s, "squares") || !strcasecmp (s, "square"))
74         mode = 0;
75       else if (!strcasecmp (s, "circles") || !strcasecmp (s, "circle"))
76         mode = 1;
77       else
78         {
79           fprintf (stderr,
80                    "%s: mode must be random, squares, or circles, not '%s'\n",
81                    progname, s);
82           exit (1);
83         }
84     }
85
86   if (mode == -1)
87     st->circles_p = random() & 1;
88   else
89     st->circles_p = (mode == 1);
90
91   once = True;
92
93   st->ncolors = get_integer_resource ("colors", "Colors");  /* re-get this */
94   make_smooth_colormap (st->dpy, st->xgwa.visual, st->xgwa.colormap,
95                         st->colors, &st->ncolors, True, 0, False);
96   XClearWindow (st->dpy, st->window);
97 }
98
99
100 state *
101 init_boxes (Display *dpy, Window window)
102 {
103   XGCValues gcv;
104   state *st = (state *) calloc (1, sizeof (*st));
105
106   st->dpy = dpy;
107   st->window = window;
108
109   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
110   XSelectInput (dpy, window, st->xgwa.your_event_mask | ExposureMask);
111
112   st->ncolors = get_integer_resource ("colors", "Colors");
113   if (st->ncolors < 1) st->ncolors = 1;
114   st->colors = (XColor *) malloc (sizeof(XColor) * st->ncolors);
115
116   st->inc = get_integer_resource ("growBy", "GrowBy");
117   st->spacing = get_integer_resource ("spacing", "Spacing");
118   st->border_size = get_integer_resource ("borderSize", "BorderSize");
119   st->bg_color = get_pixel_resource ("background", "Background",
120                                      st->dpy, st->xgwa.colormap);
121   if (st->inc < 1) st->inc = 1;
122   if (st->border_size < 0) st->border_size = 0;
123
124   gcv.line_width = st->border_size;
125   gcv.background = st->bg_color;
126   st->gc = XCreateGC (st->dpy, st->window, GCBackground|GCLineWidth, &gcv);
127
128   st->box_count = get_integer_resource ("boxCount", "BoxCount");
129   if (st->box_count < 1) st->box_count = 1;
130
131   st->nboxes = 0;
132   st->boxes_size = st->box_count * 2;
133   st->boxes = (box *) calloc (st->boxes_size, sizeof(*st->boxes));
134
135   reset_boxes (st);
136
137   return st;
138 }
139
140 static void
141 reshape_boxes (state *st)
142 {
143   int i;
144   XGetWindowAttributes (st->dpy, st->window, &st->xgwa);
145   for (i = 0; i < st->nboxes; i++)
146     {
147       box *b = &st->boxes[i];
148       b->flags |= CHANGED;
149     }
150 }
151
152
153 static Bool
154 boxes_overlap_p (box *a, box *b, int pad)
155 {
156   /* Two rectangles overlap if the max of the tops is less than the
157      min of the bottoms and the max of the lefts is less than the min
158      of the rights.
159    */
160 # undef MAX
161 # undef MIN
162 # define MAX(A,B) ((A)>(B)?(A):(B))
163 # define MIN(A,B) ((A)<(B)?(A):(B))
164
165   int maxleft  = MAX(a->x - pad, b->x);
166   int maxtop   = MAX(a->y - pad, b->y);
167   int minright = MIN(a->x + a->w + pad + pad - 1, b->x + b->w);
168   int minbot   = MIN(a->y + a->h + pad + pad - 1, b->y + b->h);
169   return (maxtop < minbot && maxleft < minright);
170 }
171
172
173 static Bool
174 circles_overlap_p (box *a, box *b, int pad)
175 {
176   int ar = a->w/2;      /* radius */
177   int br = b->w/2;
178   int ax = a->x + ar;   /* center */
179   int ay = a->y + ar;
180   int bx = b->x + br;
181   int by = b->y + br;
182   int d2 = (((bx - ax) * (bx - ax)) +   /* distance between centers squared */
183             ((by - ay) * (by - ay)));
184   int r2 = ((ar + br + pad) *           /* sum of radii squared */
185             (ar + br + pad));
186   return (d2 < r2);
187 }
188
189
190 static Bool
191 box_collides_p (state *st, box *a, int pad)
192 {
193   int i;
194
195   /* collide with wall */
196   if (a->x - pad < 0 ||
197       a->y - pad < 0 ||
198       a->x + a->w + pad + pad >= st->xgwa.width ||
199       a->y + a->h + pad + pad >= st->xgwa.height)
200     return True;
201
202   /* collide with another box */
203   for (i = 0; i < st->nboxes; i++)
204     {
205       box *b = &st->boxes[i];
206       if (a != b &&
207           (st->circles_p
208            ? circles_overlap_p (a, b, pad)
209            : boxes_overlap_p (a, b, pad)))
210         return True;
211     }
212
213   return False;
214 }
215
216
217 static void
218 grow_boxes (state *st)
219 {
220   int inc2 = st->inc + st->spacing + st->border_size;
221   int i;
222   int live_count = 0;
223
224   /* check box collisions, and grow if none.
225    */
226   for (i = 0; i < st->nboxes; i++)
227     {
228       box *a = &st->boxes[i];
229       if (!(a->flags & ALIVE)) continue;
230
231       if (box_collides_p (st, a, inc2))
232         {
233           a->flags &= ~ALIVE;
234           continue;
235         }
236       
237       live_count++;
238       a->x -= st->inc;
239       a->y -= st->inc;
240       a->w += st->inc + st->inc;
241       a->h += st->inc + st->inc;
242       a->flags |= CHANGED;
243     }
244
245   /* Add more boxes.
246    */
247   while (live_count < st->box_count)
248     {
249       box *a;
250       st->nboxes++;
251       if (st->boxes_size <= st->nboxes)
252         {
253           st->boxes_size = (st->boxes_size * 1.2) + st->nboxes;
254           st->boxes = (box *)
255             realloc (st->boxes, st->boxes_size * sizeof(*st->boxes));
256           if (! st->boxes)
257             {
258               fprintf (stderr, "%s: out of memory (%d boxes)\n",
259                        progname, st->boxes_size);
260               exit (1);
261             }
262         }
263
264       a = &st->boxes[st->nboxes-1];
265       a->flags |= CHANGED;
266
267       for (i = 0; i < 10000; i++)
268         {
269           a->x = inc2 + (random() % (st->xgwa.width  - inc2));
270           a->y = inc2 + (random() % (st->xgwa.height - inc2));
271           a->w = 0;
272           a->h = 0;
273
274           if (! box_collides_p (st, a, inc2))
275             {
276               a->flags |= ALIVE;
277               live_count++;
278               break;
279             }
280         }
281
282       if (! (a->flags & ALIVE) ||       /* too many retries; */
283           st->nboxes > 65535)           /* that's about 1MB of box structs. */
284         {
285           st->nboxes--;                 /* go into "fade out" mode now. */
286           st->growing_p = False;
287
288           XSync (st->dpy, False);
289           sleep (1);
290
291           break;
292         }
293
294       /* Pick colors for this box */
295       {
296         int n = (st->color_horiz_p
297                  ? (a->x * st->ncolors / st->xgwa.width)
298                  : (a->y * st->ncolors / st->xgwa.height));
299         a->fill_color   = st->colors [n % st->ncolors].pixel;
300       }
301     }
302 }
303
304
305 static void
306 shrink_boxes (state *st)
307 {
308   int i;
309   int remaining = 0;
310
311   for (i = 0; i < st->nboxes; i++)
312     {
313       box *a = &st->boxes[i];
314
315       if (a->w <= 0 || a->h <= 0) continue;
316
317       a->x += st->inc;
318       a->y += st->inc;
319       a->w -= st->inc + st->inc;
320       a->h -= st->inc + st->inc;
321       a->flags |= CHANGED;
322       if (a->w < 0) a->w = 0;
323       if (a->h < 0) a->h = 0;
324
325       if (a->w > 0 && a->h > 0)
326         remaining++;
327     }
328
329   if (remaining == 0)
330     reset_boxes (st);
331 }
332
333
334 static void
335 draw_boxes (state *st)
336 {
337   int i;
338   for (i = 0; i < st->nboxes; i++)
339     {
340       box *b = &st->boxes[i];
341
342       if (!st->growing_p)
343         {
344           /* When shrinking, black out an area outside of the border
345              before re-drawing the box.
346            */
347           XSetForeground (st->dpy, st->gc, st->bg_color);
348           XSetLineAttributes (st->dpy, st->gc,
349                               (st->inc + st->border_size) * 2,
350                               LineSolid, CapButt, JoinMiter);
351
352           if (st->circles_p)
353             XDrawArc (st->dpy, st->window, st->gc,
354                       b->x, b->y,
355                       (b->w > 0 ? b->w : 1),
356                       (b->h > 0 ? b->h : 1),
357                       0, 360*64);
358           else
359             XDrawRectangle (st->dpy, st->window, st->gc,
360                             b->x, b->y,
361                             (b->w > 0 ? b->w : 1),
362                             (b->h > 0 ? b->h : 1));
363           XSetLineAttributes (st->dpy, st->gc, st->border_size,
364                               LineSolid, CapButt, JoinMiter);
365         }
366
367       if (b->w <= 0 || b->h <= 0) continue;
368       if (! (b->flags & CHANGED)) continue;
369       b->flags &= ~CHANGED;
370
371       XSetForeground (st->dpy, st->gc, b->fill_color);
372
373       if (st->circles_p)
374         XFillArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
375                   0, 360*64);
376       else
377         XFillRectangle (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h);
378
379       if (st->border_size > 0)
380         {
381           unsigned long bd = st->colors [(b->fill_color + st->ncolors/2)
382                                          % st->ncolors].pixel;
383           XSetForeground (st->dpy, st->gc, bd);
384           if (st->circles_p)
385             XDrawArc (st->dpy, st->window, st->gc, b->x, b->y, b->w, b->h,
386                       0, 360*64);
387           else
388             XDrawRectangle (st->dpy, st->window, st->gc,
389                             b->x, b->y, b->w, b->h);
390         }
391     }
392 }
393
394 static void
395 handle_events (state *st)
396 {
397   XSync (st->dpy, False);
398   while (XPending (st->dpy))
399     {
400       XEvent event;
401       XNextEvent (st->dpy, &event);
402       if (event.xany.type == ConfigureNotify ||
403           event.xany.type == Expose)
404         reshape_boxes (st);
405       else if (event.xany.type == ButtonPress)
406         st->growing_p = !st->growing_p;
407
408       screenhack_handle_event (st->dpy, &event);
409     }
410 }
411
412
413 \f
414 char *progclass = "BoxFit";
415
416 char *defaults [] = {
417   ".background:            black",
418   "*delay:                 20000",
419   "*mode:                  random",
420   "*colors:                64",
421   "*boxCount:              50",
422   "*growBy:                1",
423   "*spacing:               1",
424   "*borderSize:            1",
425   0
426 };
427
428 XrmOptionDescRec options [] = {
429   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
430   { "-colors",          ".colors",              XrmoptionSepArg, 0 },
431   { "-count",           ".boxCount",            XrmoptionSepArg, 0 },
432   { "-growby",          ".growBy",              XrmoptionSepArg, 0 },
433   { "-spacing",         ".spacing",             XrmoptionSepArg, 0 },
434   { "-border",          ".borderSize",          XrmoptionSepArg, 0 },
435   { "-circles",         ".mode",                XrmoptionNoArg, "circles" },
436   { "-squares",         ".mode",                XrmoptionNoArg, "squares" },
437   { "-random",          ".mode",                XrmoptionNoArg, "random"  },
438   { 0, 0, 0, 0 }
439 };
440
441
442 void
443 screenhack (Display *dpy, Window window)
444 {
445   state *st = init_boxes (dpy, window);
446   int delay = get_integer_resource ("delay", "Integer");
447   reshape_boxes (st);
448   while (1)
449     {
450       if (st->growing_p)
451         grow_boxes (st);
452       else
453         shrink_boxes (st);
454
455       draw_boxes (st);
456       handle_events (st);
457       if (delay) usleep (delay);
458     }
459 }