1 /* carousel, Copyright (c) 2005-2018 Jamie Zawinski <jwz@jwz.org>
2 * Loads a sequence of images and rotates them around.
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
10 * implied warranty.
11 *
12 * Created: 21-Feb-2005
13 */
14
15 #if defined(HAVE_COCOA) || defined(HAVE_ANDROID)
16 # define DEF_FONT "OCR A Std 48, Lucida Console 48, Monaco 48"
17 #elif 0 /* real X11, XQueryFont() */
18 # define DEF_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*"
19 #else /* real X11, load_font_retry() */
20 # define DEF_FONT "-*-ocr a std-medium-r-*-*-*-480-*-*-m-*-*-*"
21 #endif
22
23 #define DEF_TITLE_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*"
24
25 #define DEFAULTS "*count: 7 \n" \
26 "*delay: 10000 \n" \
27 "*wireframe: False \n" \
28 "*showFPS: False \n" \
29 "*fpsSolid: True \n" \
30 "*useSHM: True \n" \
31 "*font: " DEF_FONT "\n" \
32 "*titleFont: " DEF_TITLE_FONT "\n" \
33 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
34 "*grabDesktopImages: False \n" \
35 "*chooseRandomImages: True \n"
36
37 # define release_carousel 0
38 # include "xlockmore.h"
39
40 #undef countof
41 #define countof(x) (sizeof((x))/sizeof((*x)))
42
43 #ifdef USE_GL
44
45 # define DEF_SPEED "1.0"
46 # define DEF_DURATION "20"
47 # define DEF_TITLES "True"
48 # define DEF_ZOOM "True"
49 # define DEF_TILT "XY"
50 # define DEF_MIPMAP "True"
51 # define DEF_DEBUG "False"
52
53 #include "rotator.h"
54 #include "gltrackball.h"
55 #include "grab-ximage.h"
56 #include "texfont.h"
57
58 # ifndef HAVE_JWXYZ
59 # include <X11/Intrinsic.h> /* for XrmDatabase in -debug mode */
60 # endif
61
62 /* Should be in <GL/glext.h> */
63 # ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
64 # define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
65 # endif
66 # ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
67 # define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
68 # endif
69
70 typedef struct {
71 double x, y, w, h;
72 } rect;
73
74 typedef enum { EARLY, NORMAL, LOADING, OUT, IN, DEAD } fade_mode;
75 static int fade_ticks = 60;
76
77 typedef struct {
78 char *title; /* the filename of this image */
79 int w, h; /* size in pixels of the image */
80 int tw, th; /* size in pixels of the texture */
81 XRectangle geom; /* where in the image the bits are */
82 GLuint texid;
83 } image;
84
85 typedef struct {
86 ModeInfo *mi;
87 image current, loading;
88 GLfloat r, theta; /* radius and rotation on the tube */
89 rotator *rot; /* for zoomery */
90 Bool from_top_p; /* whether this image drops in or rises up */
91 time_t expires; /* when this image should be replaced */
92 fade_mode mode; /* in/out animation state */
93 int mode_tick;
94 Bool loaded_p; /* whether background load is done */
95 } image_frame;
96
97
98 typedef struct {
99 GLXContext *glx_context;
100 GLfloat anisotropic;
101 rotator *rot;
102 trackball_state *trackball;
103 Bool button_down_p;
104 time_t button_down_time;
105
106 int nframes; /* how many frames are loaded */
107 int frames_size;
108 image_frame **frames; /* pointers to the frames */
109
110 Bool awaiting_first_images_p;
111 int loads_in_progress;
112
113 texture_font_data *texfont, *titlefont;
114
115 fade_mode mode;
116 int mode_tick;
117
118 int loading_sw, loading_sh;
119
120 time_t last_time, now;
121 int draw_tick;
122
123 } carousel_state;
124
125 static carousel_state *sss = NULL;
126
127
128 /* Command-line arguments
129 */
130 static GLfloat speed; /* animation speed scale factor */
131 static int duration; /* reload images after this long */
132 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
133 static Bool titles_p; /* Display image titles. */
134 static Bool zoom_p; /* Throb the images in and out as they spin. */
135 static char *tilt_str;
136 static Bool tilt_x_p; /* Tilt axis towards the viewer */
137 static Bool tilt_y_p; /* Tilt axis side to side */
138 static Bool debug_p; /* Be loud and do weird things. */
139
140
141 static XrmOptionDescRec opts[] = {
142 {"-zoom", ".zoom", XrmoptionNoArg, "True" },
143 {"-no-zoom", ".zoom", XrmoptionNoArg, "False" },
144 {"-tilt", ".tilt", XrmoptionSepArg, 0 },
145 {"-no-tilt", ".tilt", XrmoptionNoArg, "" },
146 {"-titles", ".titles", XrmoptionNoArg, "True" },
147 {"-no-titles", ".titles", XrmoptionNoArg, "False" },
148 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
149 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
150 {"-duration", ".duration", XrmoptionSepArg, 0 },
151 {"-debug", ".debug", XrmoptionNoArg, "True" },
152 {"-font", ".font", XrmoptionSepArg, 0 },
153 {"-speed", ".speed", XrmoptionSepArg, 0 },
154 };
155
156 static argtype vars[] = {
157 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
158 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
159 { &titles_p, "titles", "Titles", DEF_TITLES, t_Bool},
160 { &zoom_p, "zoom", "Zoom", DEF_ZOOM, t_Bool},
161 { &tilt_str, "tilt", "Tilt", DEF_TILT, t_String},
162 { &speed, "speed", "Speed", DEF_SPEED, t_Float},
163 { &duration, "duration", "Duration", DEF_DURATION, t_Int},
164 };
165
166 ENTRYPOINT ModeSpecOpt carousel_opts = {countof(opts), opts, countof(vars), vars, NULL};
167
168
169 /* Allocates a frame structure and stores it in the list.
170 */
171 static image_frame *
alloc_frame(ModeInfo * mi)172 alloc_frame (ModeInfo *mi)
173 {
174 carousel_state *ss = &sss[MI_SCREEN(mi)];
175 image_frame *frame = (image_frame *) calloc (1, sizeof (*frame));
176
177 frame->mi = mi;
178 frame->mode = EARLY;
179 frame->rot = make_rotator (0, 0, 0, 0, 0.04 * frand(1.0) * speed, False);
180
181 glGenTextures (1, &frame->current.texid);
182 glGenTextures (1, &frame->loading.texid);
183 if (frame->current.texid <= 0) abort();
184 if (frame->loading.texid <= 0) abort();
185
186 if (ss->frames_size <= ss->nframes)
187 {
188 ss->frames_size = (ss->frames_size * 1.2) + ss->nframes;
189 ss->frames = (image_frame **)
190 realloc (ss->frames, ss->frames_size * sizeof(*ss->frames));
191 if (! ss->frames)
192 {
193 fprintf (stderr, "%s: out of memory (%d images)\n",
194 progname, ss->frames_size);
195 exit (1);
196 }
197 }
198
199 ss->frames[ss->nframes++] = frame;
200
201 return frame;
202 }
203
204
205 static void image_loaded_cb (const char *filename, XRectangle *geom,
206 int image_width, int image_height,
207 int texture_width, int texture_height,
208 void *closure);
209
210
211 /* Load a new file into the given image struct.
212 */
213 static void
load_image(ModeInfo * mi,image_frame * frame)214 load_image (ModeInfo *mi, image_frame *frame)
215 {
216 carousel_state *ss = &sss[MI_SCREEN(mi)];
217 int wire = MI_IS_WIREFRAME(mi);
218
219 if (debug_p && !wire && frame->current.w != 0)
220 fprintf (stderr, "%s: dropped %4d x %-4d %4d x %-4d \"%s\"\n",
221 progname,
222 frame->current.geom.width,
223 frame->current.geom.height,
224 frame->current.tw, frame->current.th,
225 (frame->current.title ? frame->current.title : "(null)"));
226
227 switch (frame->mode)
228 {
229 case EARLY: break;
230 case NORMAL: frame->mode = LOADING; break;
231 default: abort();
232 }
233
234 ss->loads_in_progress++;
235
236 if (wire)
237 image_loaded_cb (0, 0, 0, 0, 0, 0, frame);
238 else
239 {
240 int w = (MI_WIDTH(mi) / 2) - 1;
241 int h = (MI_HEIGHT(mi) / 2) - 1;
242 if (w <= 10) w = 10;
243 if (h <= 10) h = 10;
244
245 if (w > h * 5) { /* tiny window: use 16:9 boxes */
246 h = w * 9/16;
247 }
248
249 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context, w, h,
250 mipmap_p, frame->loading.texid,
251 image_loaded_cb, frame);
252 }
253 }
254
255
256 /* Callback that tells us that the texture has been loaded.
257 */
258 static void
image_loaded_cb(const char * filename,XRectangle * geom,int image_width,int image_height,int texture_width,int texture_height,void * closure)259 image_loaded_cb (const char *filename, XRectangle *geom,
260 int image_width, int image_height,
261 int texture_width, int texture_height,
262 void *closure)
263 {
264 image_frame *frame = (image_frame *) closure;
265 ModeInfo *mi = frame->mi;
266 carousel_state *ss = &sss[MI_SCREEN(mi)];
267 int wire = MI_IS_WIREFRAME(mi);
268
269 if (wire)
270 {
271 frame->loading.w = MI_WIDTH (mi) * (0.5 + frand (1.0));
272 frame->loading.h = MI_HEIGHT (mi);
273 frame->loading.geom.width = frame->loading.w;
274 frame->loading.geom.height = frame->loading.h;
275 goto DONE;
276 }
277
278 if (image_width == 0 || image_height == 0)
279 exit (1);
280
281 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
282 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
283 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
284
285 if (ss->anisotropic >= 1.0)
286 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
287 ss->anisotropic);
288
289 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
290 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
291
292 frame->loading.w = image_width;
293 frame->loading.h = image_height;
294 frame->loading.tw = texture_width;
295 frame->loading.th = texture_height;
296 frame->loading.geom = *geom;
297
298 if (frame->loading.title)
299 free (frame->loading.title);
300 frame->loading.title = (filename ? strdup (filename) : 0);
301
302 /* xscreensaver-getimage returns paths relative to the image directory
303 now, so leave the sub-directory part in. Unless it's an absolute path.
304 */
305 if (frame->loading.title && frame->loading.title[0] == '/')
306 { /* strip filename to part after last /. */
307 char *s = strrchr (frame->loading.title, '/');
308 if (s) strcpy (frame->loading.title, s+1);
309 }
310
311 if (debug_p)
312 fprintf (stderr, "%s: loaded %4d x %-4d %4d x %-4d \"%s\"\n",
313 progname,
314 frame->loading.geom.width,
315 frame->loading.geom.height,
316 frame->loading.tw, frame->loading.th,
317 (frame->loading.title ? frame->loading.title : "(null)"));
318
319 DONE:
320
321 frame->loaded_p = True;
322
323 if (ss->loads_in_progress <= 0) abort();
324 ss->loads_in_progress--;
325
326 /* This image expires N seconds after it finished loading. */
327 frame->expires = time((time_t *) 0) + (duration * MI_COUNT(mi));
328
329 switch (frame->mode)
330 {
331 case EARLY: /* part of the initial batch of images */
332 {
333 image swap = frame->current;
334 frame->current = frame->loading;
335 frame->loading = swap;
336 }
337 break;
338 case LOADING: /* start dropping the old image out */
339 {
340 frame->mode = OUT;
341 frame->mode_tick = fade_ticks / speed;
342 frame->from_top_p = random() & 1;
343 }
344 break;
345 default:
346 abort();
347 }
348 }
349
350
351 static void loading_msg (ModeInfo *mi, int n);
352
353 static Bool
load_initial_images(ModeInfo * mi)354 load_initial_images (ModeInfo *mi)
355 {
356 carousel_state *ss = &sss[MI_SCREEN(mi)];
357 int i;
358 Bool all_loaded_p = True;
359 for (i = 0; i < ss->nframes; i++)
360 if (! ss->frames[i]->loaded_p)
361 all_loaded_p = False;
362
363 if (all_loaded_p)
364 {
365 if (ss->nframes < MI_COUNT (mi))
366 {
367 /* The frames currently on the list are fully loaded.
368 Start the next one loading. (We run the image loader
369 asynchronously, but we load them one at a time.)
370 */
371 load_image (mi, alloc_frame (mi));
372 }
373 else
374 {
375 /* The first batch of images are now all loaded!
376 Stagger the expire times so that they don't all drop out at once.
377 */
378 time_t now = time((time_t *) 0);
379 int i;
380
381 for (i = 0; i < ss->nframes; i++)
382 {
383 image_frame *frame = ss->frames[i];
384 frame->r = 1.0;
385 frame->theta = i * 360.0 / ss->nframes;
386 frame->expires = now + (duration * (i + 1));
387 frame->mode = NORMAL;
388 }
389
390 /* Instead of always going clockwise, shuffle the expire times
391 of the frames so that they drop out in a random order.
392 */
393 for (i = 0; i < ss->nframes; i++)
394 {
395 image_frame *frame1 = ss->frames[i];
396 image_frame *frame2 = ss->frames[random() % ss->nframes];
397 time_t swap = frame1->expires;
398 frame1->expires = frame2->expires;
399 frame2->expires = swap;
400 }
401
402 ss->awaiting_first_images_p = False;
403 }
404 }
405
406 loading_msg (mi, ss->nframes-1);
407
408 return !ss->awaiting_first_images_p;
409 }
410
411
412 ENTRYPOINT void
reshape_carousel(ModeInfo * mi,int width,int height)413 reshape_carousel (ModeInfo *mi, int width, int height)
414 {
415 GLfloat h = (GLfloat) height / (GLfloat) width;
416 int y = 0;
417
418 if (width > height * 5) { /* tiny window: show middle */
419 height = width * 9/16;
420 y = -height/2;
421 h = height / (GLfloat) width;
422 }
423
424 glViewport (0, y, (GLint) width, (GLint) height);
425
426 glMatrixMode(GL_PROJECTION);
427 glLoadIdentity();
428 gluPerspective (60.0, 1/h, 1.0, 8.0);
429
430 glMatrixMode(GL_MODELVIEW);
431 glLoadIdentity();
432 gluLookAt( 0.0, 0.0, 2.6,
433 0.0, 0.0, 0.0,
434 0.0, 1.0, 0.0);
435
436 glClear(GL_COLOR_BUFFER_BIT);
437 }
438
439
440 ENTRYPOINT Bool
carousel_handle_event(ModeInfo * mi,XEvent * event)441 carousel_handle_event (ModeInfo *mi, XEvent *event)
442 {
443 carousel_state *ss = &sss[MI_SCREEN(mi)];
444
445 if (event->xany.type == ButtonPress &&
446 event->xbutton.button == Button1)
447 {
448 if (! ss->button_down_p)
449 ss->button_down_time = time((time_t *) 0);
450 }
451 else if (event->xany.type == ButtonRelease &&
452 event->xbutton.button == Button1)
453 {
454 if (ss->button_down_p)
455 {
456 /* Add the time the mouse was held to the expire times of all
457 frames, so that mouse-dragging doesn't count against
458 image expiration.
459 */
460 int secs = time((time_t *) 0) - ss->button_down_time;
461 int i;
462 for (i = 0; i < ss->nframes; i++)
463 ss->frames[i]->expires += secs;
464 }
465 }
466
467 if (gltrackball_event_handler (event, ss->trackball,
468 MI_WIDTH (mi), MI_HEIGHT (mi),
469 &ss->button_down_p))
470 return True;
471 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
472 {
473 int i = random() % ss->nframes;
474 ss->frames[i]->expires = 0;
475 return True;
476 }
477
478 return False;
479 }
480
481
482 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
483 */
484 static void
hack_resources(Display * dpy)485 hack_resources (Display *dpy)
486 {
487 # ifndef HAVE_JWXYZ
488 char *res = "desktopGrabber";
489 char *val = get_string_resource (dpy, res, "DesktopGrabber");
490 char buf1[255];
491 char buf2[255];
492 XrmValue value;
493 XrmDatabase db = XtDatabase (dpy);
494 sprintf (buf1, "%.100s.%.100s", progname, res);
495 sprintf (buf2, "%.200s -v", val);
496 value.addr = buf2;
497 value.size = strlen(buf2);
498 XrmPutResource (&db, buf1, "String", &value);
499 free (val);
500 # endif /* !HAVE_JWXYZ */
501 }
502
503
504 static void
loading_msg(ModeInfo * mi,int n)505 loading_msg (ModeInfo *mi, int n)
506 {
507 carousel_state *ss = &sss[MI_SCREEN(mi)];
508 int wire = MI_IS_WIREFRAME(mi);
509 char text[100];
510
511 if (wire) return;
512
513 if (n == 0)
514 sprintf (text, "Loading images...");
515 else
516 sprintf (text, "Loading images... (%d%%)",
517 (int) (n * 100 / MI_COUNT(mi)));
518
519 if (ss->loading_sw == 0)
520 {
521 /* only do this once, so that the string doesn't move. */
522 XCharStruct e;
523 texture_string_metrics (ss->titlefont, text, &e, 0, 0);
524 ss->loading_sw = e.width;
525 ss->loading_sh = e.ascent + e.descent;
526 }
527
528 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
529
530 glMatrixMode(GL_PROJECTION);
531 glPushMatrix();
532 glLoadIdentity();
533
534 glMatrixMode(GL_MODELVIEW);
535 glPushMatrix();
536 glLoadIdentity();
537
538 /*
539 {
540 double rot = current_device_rotation();
541 glRotatef(rot, 0, 0, 1);
542 if ((rot > 45 && rot < 135) ||
543 (rot < -45 && rot > -135))
544 {
545 GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
546 glScalef (s, 1/s, 1);
547 }
548 }
549 */
550
551 # ifdef HAVE_MOBILE
552 if (MI_WIDTH(mi) < MI_HEIGHT(mi)) /* portrait orientation */
553 {
554 GLfloat s = (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi));
555 glScalef (s, s, s);
556 glTranslatef(-s/2, 0, 0);
557 }
558 # endif
559
560 glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
561 glTranslatef ((MI_WIDTH(mi) - ss->loading_sw) / 2,
562 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
563 0);
564 glColor3f (1, 1, 0);
565 glEnable (GL_TEXTURE_2D);
566 glDisable (GL_DEPTH_TEST);
567 print_texture_string (ss->titlefont, text);
568 glEnable (GL_DEPTH_TEST);
569 glPopMatrix();
570
571 glMatrixMode(GL_PROJECTION);
572 glPopMatrix();
573
574 glMatrixMode(GL_MODELVIEW);
575
576 glFinish();
577 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
578 }
579
580
581 ENTRYPOINT void
init_carousel(ModeInfo * mi)582 init_carousel (ModeInfo *mi)
583 {
584 int screen = MI_SCREEN(mi);
585 carousel_state *ss;
586 int wire = MI_IS_WIREFRAME(mi);
587
588 MI_INIT (mi, sss);
589 ss = &sss[screen];
590
591 if ((ss->glx_context = init_GL(mi)) != NULL) {
592 reshape_carousel (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
593 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
594 } else {
595 MI_CLEARWINDOW(mi);
596 }
597
598 if (!tilt_str || !*tilt_str)
599 ;
600 else if (!strcasecmp (tilt_str, "0"))
601 ;
602 else if (!strcasecmp (tilt_str, "X"))
603 tilt_x_p = 1;
604 else if (!strcasecmp (tilt_str, "Y"))
605 tilt_y_p = 1;
606 else if (!strcasecmp (tilt_str, "XY"))
607 tilt_x_p = tilt_y_p = 1;
608 else
609 {
610 fprintf (stderr, "%s: tilt must be 'X', 'Y', 'XY' or '', not '%s'\n",
611 progname, tilt_str);
612 exit (1);
613 }
614
615 {
616 double spin_speed = speed * 0.2; /* rotation of tube around axis */
617 double spin_accel = speed * 0.1;
618 double wander_speed = speed * 0.001; /* tilting of axis */
619
620 spin_speed *= 0.9 + frand(0.2);
621 wander_speed *= 0.9 + frand(0.2);
622
623 ss->rot = make_rotator (spin_speed, spin_speed, spin_speed,
624 spin_accel, wander_speed, True);
625
626 ss->trackball = gltrackball_init (False);
627 }
628
629 if (strstr ((char *) glGetString(GL_EXTENSIONS),
630 "GL_EXT_texture_filter_anisotropic"))
631 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &ss->anisotropic);
632 else
633 ss->anisotropic = 0.0;
634
635 glDisable (GL_LIGHTING);
636 glEnable (GL_DEPTH_TEST);
637 glDisable (GL_CULL_FACE);
638
639 if (! wire)
640 {
641 glShadeModel (GL_SMOOTH);
642 glEnable (GL_LINE_SMOOTH);
643 /* This gives us a transparent diagonal slice through each image! */
644 /* glEnable (GL_POLYGON_SMOOTH); */
645 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
646 glEnable (GL_BLEND);
647 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
648 glEnable (GL_ALPHA_TEST);
649
650 glEnable (GL_POLYGON_OFFSET_FILL);
651 glPolygonOffset (1.0, 1.0);
652
653 }
654
655 ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
656 ss->titlefont = load_texture_font (MI_DISPLAY(mi), "titleFont");
657
658 if (debug_p)
659 hack_resources (MI_DISPLAY (mi));
660
661 ss->nframes = 0;
662 ss->frames_size = 10;
663 ss->frames = (image_frame **)
664 calloc (1, ss->frames_size * sizeof(*ss->frames));
665
666 ss->mode = IN;
667 ss->mode_tick = fade_ticks / speed;
668
669 ss->awaiting_first_images_p = True;
670 }
671
672
673 static void
draw_frame(ModeInfo * mi,image_frame * frame,time_t now,Bool body_p)674 draw_frame (ModeInfo *mi, image_frame *frame, time_t now, Bool body_p)
675 {
676 carousel_state *ss = &sss[MI_SCREEN(mi)];
677 int wire = MI_IS_WIREFRAME(mi);
678
679 GLfloat texw = frame->current.geom.width / (GLfloat) frame->current.tw;
680 GLfloat texh = frame->current.geom.height / (GLfloat) frame->current.th;
681 GLfloat texx1 = frame->current.geom.x / (GLfloat) frame->current.tw;
682 GLfloat texy1 = frame->current.geom.y / (GLfloat) frame->current.th;
683 GLfloat texx2 = texx1 + texw;
684 GLfloat texy2 = texy1 + texh;
685 GLfloat aspect = ((GLfloat) frame->current.geom.height /
686 (GLfloat) frame->current.geom.width);
687
688 glBindTexture (GL_TEXTURE_2D, frame->current.texid);
689
690 glPushMatrix();
691
692 /* Position this image on the wheel.
693 */
694 glRotatef (frame->theta, 0, 1, 0);
695 glTranslatef (0, 0, frame->r);
696
697 /* Scale down the image so that all N frames fit on the wheel
698 without bumping in to each other.
699 */
700 {
701 GLfloat t, s;
702 switch (ss->nframes)
703 {
704 case 1: t = -1.0; s = 1.7; break;
705 case 2: t = -0.8; s = 1.6; break;
706 case 3: t = -0.4; s = 1.5; break;
707 case 4: t = -0.2; s = 1.3; break;
708 default: t = 0.0; s = 6.0 / ss->nframes; break;
709 }
710 glTranslatef (0, 0, t);
711 glScalef (s, s, s);
712 }
713
714 /* Center this image on the wheel plane.
715 */
716 glTranslatef (-0.5, -(aspect/2), 0);
717
718 /* Move as per the "zoom in and out" setting.
719 */
720 if (zoom_p)
721 {
722 double x, y, z;
723 /* Only use the Z component of the rotator for in/out position. */
724 get_position (frame->rot, &x, &y, &z, !ss->button_down_p);
725 glTranslatef (0, 0, z/2);
726 }
727
728 /* Compute the "drop in and out" state.
729 */
730 switch (frame->mode)
731 {
732 case EARLY:
733 abort();
734 break;
735 case NORMAL:
736 if (!ss->button_down_p &&
737 now >= frame->expires &&
738 ss->loads_in_progress == 0) /* only load one at a time */
739 load_image (mi, frame);
740 break;
741 case LOADING:
742 break;
743 case OUT:
744 if (--frame->mode_tick <= 0) {
745 image swap = frame->current;
746 frame->current = frame->loading;
747 frame->loading = swap;
748
749 frame->mode = IN;
750 frame->mode_tick = fade_ticks / speed;
751 }
752 break;
753 case IN:
754 if (--frame->mode_tick <= 0)
755 frame->mode = NORMAL;
756 break;
757 default:
758 abort();
759 }
760
761 /* Now translate for current in/out state.
762 */
763 if (frame->mode == OUT || frame->mode == IN)
764 {
765 GLfloat t = (frame->mode == OUT
766 ? frame->mode_tick / (fade_ticks / speed)
767 : (((fade_ticks / speed) - frame->mode_tick + 1) /
768 (fade_ticks / speed)));
769 t = 5 * (1 - t);
770 if (frame->from_top_p) t = -t;
771 glTranslatef (0, t, 0);
772 }
773
774 if (body_p) /* Draw the image quad. */
775 {
776 if (! wire)
777 {
778 glColor3f (1, 1, 1);
779 glNormal3f (0, 0, 1);
780 glEnable (GL_TEXTURE_2D);
781 glBegin (GL_QUADS);
782 glNormal3f (0, 0, 1);
783 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
784 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
785 glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0);
786 glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0);
787 glEnd();
788 }
789
790 /* Draw a box around it.
791 */
792 glLineWidth (2.0);
793 glColor3f (0.5, 0.5, 0.5);
794 glDisable (GL_TEXTURE_2D);
795 glBegin (GL_LINE_LOOP);
796 glVertex3f (0, 0, 0);
797 glVertex3f (1, 0, 0);
798 glVertex3f (1, aspect, 0);
799 glVertex3f (0, aspect, 0);
800 glEnd();
801
802 }
803 else /* Draw a title under the image. */
804 {
805 XCharStruct e;
806 int sw, sh;
807 GLfloat scale = 0.05;
808 char *title = frame->current.title ? frame->current.title : "(untitled)";
809 texture_string_metrics (ss->texfont, title, &e, 0, 0);
810 sw = e.width;
811 sh = e.ascent + e.descent;
812
813 glTranslatef (0, -scale, 0);
814
815 scale /= sh;
816 glScalef (scale, scale, scale);
817
818 glTranslatef (((1/scale) - sw) / 2, 0, 0);
819 glColor3f (1, 1, 1);
820
821 if (!wire)
822 {
823 glEnable (GL_TEXTURE_2D);
824 print_texture_string (ss->texfont, title);
825 }
826 else
827 {
828 glBegin (GL_LINE_LOOP);
829 glVertex3f (0, 0, 0);
830 glVertex3f (sw, 0, 0);
831 glVertex3f (sw, sh, 0);
832 glVertex3f (0, sh, 0);
833 glEnd();
834 }
835 }
836
837 glPopMatrix();
838 }
839
840
841 ENTRYPOINT void
draw_carousel(ModeInfo * mi)842 draw_carousel (ModeInfo *mi)
843 {
844 carousel_state *ss = &sss[MI_SCREEN(mi)];
845 int i;
846
847 if (!ss->glx_context)
848 return;
849
850 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *ss->glx_context);
851
852 if (ss->awaiting_first_images_p)
853 if (!load_initial_images (mi))
854 return;
855
856 /* Only check the wall clock every 10 frames */
857 {
858 if (ss->now == 0 || ss->draw_tick++ > 10)
859 {
860 ss->now = time((time_t *) 0);
861 if (ss->last_time == 0) ss->last_time = ss->now;
862 ss->draw_tick = 0;
863 }
864 }
865
866
867 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
868
869 glPushMatrix();
870
871 glRotatef(current_device_rotation(), 0, 0, 1);
872
873
874 /* Run the startup "un-shrink" animation.
875 */
876 switch (ss->mode)
877 {
878 case IN:
879 if (--ss->mode_tick <= 0)
880 {
881 ss->mode = NORMAL;
882 ss->last_time = time((time_t *) 0);
883 }
884 break;
885 case NORMAL:
886 break;
887 default:
888 abort();
889 }
890
891
892 /* Scale as per the startup "un-shrink" animation.
893 */
894 if (ss->mode != NORMAL)
895 {
896 GLfloat s = (ss->mode == OUT
897 ? ss->mode_tick / (fade_ticks / speed)
898 : (((fade_ticks / speed) - ss->mode_tick + 1) /
899 (fade_ticks / speed)));
900 glScalef (s, s, s);
901 }
902
903 /* Rotate and tilt as per the user, and the motion modeller.
904 */
905 {
906 double x, y, z;
907 gltrackball_rotate (ss->trackball);
908
909 /* Tilt the tube up or down by up to 30 degrees */
910 get_position (ss->rot, &x, &y, &z, !ss->button_down_p);
911 if (tilt_x_p)
912 glRotatef (15 - (x * 30), 1, 0, 0);
913 if (tilt_y_p)
914 glRotatef (7 - (y * 14), 0, 0, 1);
915
916 /* Only use the Y component of the rotator. */
917 get_rotation (ss->rot, &x, &y, &z, !ss->button_down_p);
918 glRotatef (y * 360, 0, 1, 0);
919 }
920
921 /* First draw each image, then draw the titles. GL insists that you
922 draw back-to-front in order to make alpha blending work properly,
923 so we need to draw all of the 100% opaque images before drawing
924 any of the not-100%-opaque titles.
925 */
926 for (i = 0; i < ss->nframes; i++)
927 draw_frame (mi, ss->frames[i], ss->now, True);
928 if (titles_p)
929 for (i = 0; i < ss->nframes; i++)
930 draw_frame (mi, ss->frames[i], ss->now, False);
931
932 glPopMatrix();
933
934 if (mi->fps_p) do_fps (mi);
935 glFinish();
936 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
937 }
938
939
940 ENTRYPOINT void
free_carousel(ModeInfo * mi)941 free_carousel (ModeInfo *mi)
942 {
943 carousel_state *ss = &sss[MI_SCREEN(mi)];
944 int i;
945 if (!ss->glx_context) return;
946 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *ss->glx_context);
947 if (ss->rot) free_rotator (ss->rot);
948 if (ss->trackball) gltrackball_free (ss->trackball);
949 if (ss->texfont) free_texture_font (ss->texfont);
950 if (ss->titlefont) free_texture_font (ss->titlefont);
951 for (i = 0; i < ss->nframes; i++) {
952 if (ss->frames[i]->current.title) free (ss->frames[i]->current.title);
953 if (ss->frames[i]->loading.title) free (ss->frames[i]->loading.title);
954 if (ss->frames[i]->rot) free_rotator (ss->frames[i]->rot);
955 if (ss->frames[i]->current.texid)
956 glDeleteTextures (1, &ss->frames[i]->current.texid);
957 if (ss->frames[i]->loading.texid)
958 glDeleteTextures (1, &ss->frames[i]->loading.texid);
959 }
960 }
961
962 XSCREENSAVER_MODULE ("Carousel", carousel)
963
964 #endif /* USE_GL */
965