1 /* splitflap, Copyright (c) 2015-2018 Jamie Zawinski <jwz@jwz.org>
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
11 * Draws a split-flap text display.
14 #define FLAP_FONT "-*-helvetica-bold-r-normal-*-*-1440-*-*-*-*-*-*"
16 #define DEFAULTS "*delay: 20000 \n" \
17 "*showFPS: False \n" \
18 "*wireframe: False \n" \
19 "*flapFont: " FLAP_FONT "\n" \
20 "*frameColor: #444444" "\n" \
21 "*caseColor: #666666" "\n" \
22 "*discColor: #888888" "\n" \
23 "*finColor: #222222" "\n" \
24 "*textColor: #FFFF00" "\n" \
25 "*multiSample: True \n" \
26 "*program: xscreensaver-text\n" \
29 # define release_splitflap 0
31 #define countof(x) (sizeof((x))/sizeof((*x)))
33 #define DEF_SPEED "1.0"
34 #define DEF_WIDTH "22"
35 #define DEF_HEIGHT "8"
36 #define DEF_SPIN "XYZ"
37 #define DEF_WANDER "True"
38 #define DEF_FACE_FRONT "True"
39 #define DEF_MODE "Text"
41 #include "xlockmore.h"
45 #ifdef USE_GL /* whole file */
47 #include "gltrackball.h"
49 #include "ximage-loader.h"
51 #include "textclient.h"
55 extern const struct gllist
56 *splitflap_obj_box_quarter_frame, *splitflap_obj_disc_quarter,
57 *splitflap_obj_fin_edge_half, *splitflap_obj_fin_face_half;
58 static struct gllist *splitflap_obj_outer_frame = 0;
60 static const struct gllist * const *all_objs[] = {
61 &splitflap_obj_box_quarter_frame, &splitflap_obj_disc_quarter,
62 &splitflap_obj_fin_edge_half, &splitflap_obj_fin_face_half,
63 (const struct gllist * const *) &splitflap_obj_outer_frame
66 #define SPLITFLAP_QUARTER_FRAME 0
67 #define SPLITFLAP_DISC_QUARTER 1
68 #define SPLITFLAP_FIN_EDGE_HALF 2
69 #define SPLITFLAP_FIN_FACE_HALF 3
70 #define SPLITFLAP_OUTER_FRAME 4
72 #define COLON_WIDTH 0.5
75 int target_index; /* desired character */
76 double current_index; /* currently displayed, fractional */
77 GLfloat sticky; /* bottom fin doesn't fall all the way */
78 int missing; /* which fin has snapped off, or -1 */
79 const char * const *spool; /* chars available for display */
80 int spool_size; /* how many fins on the spool */
87 int tex_width, tex_height;
91 GLXContext *glx_context;
93 trackball_state *trackball;
95 Bool spinx, spiny, spinz;
101 GLfloat component_colors[countof(all_objs)][4];
102 GLfloat text_color[4];
104 flapper *flappers; /* grid_width * grid_height */
106 texture_font_data *font_data;
110 unsigned char text[5];
115 } splitflap_configuration;
117 static const char * const digit_s1_spool[] = { " ", "1" };
118 static const char * const digit_01_spool[] = { "0", "1" };
119 static const char * const ap_spool[] = { "A", "P" };
120 static const char * const m_spool[] = { "M" };
121 static const char * const digit_05_spool[] = { "0", "1", "2", "3", "4", "5" };
122 static const char * const digit_spool[] = {
123 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
126 static const char * const ascii_spool[] = {
127 " ", "!", "\"", "#", "$", "%", "&", "'",
128 "(", ")", "*", "+", ",", "-", ".", "/",
129 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
130 ":", ";", "<", "=", ">", "?", "@",
131 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
132 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
133 "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~",
137 /* If we include these, the flappers just take too long. It's boring. */
138 static const char * const latin1_spool[] = {
139 " ", "!", "\"", "#", "$", "%", "&", "'",
140 "(", ")", "*", "+", ",", "-", ".", "/",
141 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
142 ":", ";", "<", "=", ">", "?", "@",
143 "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
144 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
145 "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~",
147 "\302\241", "\302\242", "\302\243", "\302\245",
148 "\302\247", "\302\251", "\302\265", "\302\266",
150 "\303\200", "\303\201", "\303\202", "\303\203",
151 "\303\204", "\303\205", "\303\206", "\303\207",
152 "\303\210", "\303\211", "\303\212", "\303\213",
153 "\303\214", "\303\215", "\303\216", "\303\217",
154 "\303\220", "\303\221", "\303\222", "\303\223",
155 "\303\224", "\303\225", "\303\226", "\303\230",
156 "\303\231", "\303\232", "\303\233", "\303\234",
157 "\303\235", "\303\236", "\303\237", "\303\267",
161 static splitflap_configuration *bps = NULL;
163 static GLfloat speed;
164 static int grid_width, grid_height;
165 static char *do_spin;
166 static Bool do_wander;
167 static Bool face_front_p;
168 static char *mode_str;
170 static XrmOptionDescRec opts[] = {
171 { "-speed", ".speed", XrmoptionSepArg, 0 },
172 { "-width", ".width", XrmoptionSepArg, 0 },
173 { "-height", ".height", XrmoptionSepArg, 0 },
174 { "-spin", ".spin", XrmoptionSepArg, 0 },
175 { "+spin", ".spin", XrmoptionNoArg, "" },
176 { "-wander", ".wander", XrmoptionNoArg, "True" },
177 { "+wander", ".wander", XrmoptionNoArg, "False" },
178 { "-front", ".faceFront", XrmoptionNoArg, "True" },
179 { "+front", ".faceFront", XrmoptionNoArg, "False" },
180 { "-mode", ".mode", XrmoptionSepArg, 0 },
183 static argtype vars[] = {
184 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
185 {&grid_width, "width", "Width", DEF_WIDTH, t_Int},
186 {&grid_height, "height", "Height", DEF_HEIGHT, t_Int},
187 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
188 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
189 {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool},
190 {&mode_str, "mode", "Mode", DEF_MODE, t_String},
193 ENTRYPOINT ModeSpecOpt splitflap_opts = {
194 countof(opts), opts, countof(vars), vars, NULL};
197 /* Window management, etc
200 reshape_splitflap (ModeInfo *mi, int width, int height)
202 GLfloat h = (GLfloat) height / (GLfloat) width;
204 glViewport (0, 0, (GLint) width, (GLint) height);
206 glMatrixMode(GL_PROJECTION);
208 gluPerspective (40.0, 1/h, 0.5, 25);
210 glMatrixMode(GL_MODELVIEW);
212 gluLookAt( 0, 0, 3, /* 10x lower than traditional, for better depth rez */
216 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
218 int o = (int) current_device_rotation();
219 if (o != 0 && o != 180 && o != -180)
224 glClear(GL_COLOR_BUFFER_BIT);
229 splitflap_handle_event (ModeInfo *mi, XEvent *event)
231 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
233 if (gltrackball_event_handler (event, bp->trackball,
234 MI_WIDTH (mi), MI_HEIGHT (mi),
243 init_textures (ModeInfo *mi)
245 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
247 const char * const *spool = latin1_spool;
248 int max = countof(latin1_spool);
250 bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo));
251 texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent);
253 for (i = 0; i < max; i++)
255 texinfo *ti = &bp->texinfo[i];
256 glGenTextures (1, &ti->texid);
257 glBindTexture (GL_TEXTURE_2D, ti->texid);
261 /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i,
262 (unsigned char) ti->text[0],
263 (unsigned char) ti->text[1],
266 string_to_texture (bp->font_data, ti->text, &ti->metrics,
267 &ti->tex_width, &ti->tex_height);
269 bp->texinfo_size = i;
271 glBindTexture (GL_TEXTURE_2D, 0);
276 parse_color (ModeInfo *mi, char *key, GLfloat color[4])
279 char *string = get_string_resource (mi->dpy, key, "Color");
280 if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
282 fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
287 color[0] = xcolor.red / 65536.0;
288 color[1] = xcolor.green / 65536.0;
289 color[2] = xcolor.blue / 65536.0;
294 static int draw_outer_frame (ModeInfo *mi);
297 init_splitflap (ModeInfo *mi)
299 splitflap_configuration *bp;
300 int wire = MI_IS_WIREFRAME(mi);
304 bp = &bps[MI_SCREEN(mi)];
305 bp->glx_context = init_GL(mi);
306 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
308 bp->first_time_p = True;
310 if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text"))
314 else if (!strcasecmp (mode_str, "clock") ||
315 !strcasecmp (mode_str, "clock12"))
321 else if (!strcasecmp (mode_str, "clock24"))
330 "%s: `mode' must be text, clock12 or clock24: not `%s'\n",
337 bp->tc = textclient_open (MI_DISPLAY (mi));
341 textclient_reshape (bp->tc,
342 grid_width, grid_height,
343 grid_width, grid_height,
350 glShadeModel(GL_SMOOTH);
352 glEnable(GL_DEPTH_TEST);
353 glEnable(GL_NORMALIZE);
354 glEnable(GL_CULL_FACE);
358 GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0};
359 /* GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/
360 GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0};
361 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
362 GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0};
364 glEnable(GL_LIGHTING);
366 glEnable(GL_DEPTH_TEST);
367 glEnable(GL_CULL_FACE);
369 glLightfv(GL_LIGHT0, GL_POSITION, pos);
370 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
371 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
372 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
374 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
379 double spin_speed = 0.5;
380 double wander_speed = 0.005;
381 double tilt_speed = 0.001;
382 double spin_accel = 0.5;
387 if (*s == 'x' || *s == 'X') bp->spinx = True;
388 else if (*s == 'y' || *s == 'Y') bp->spiny = True;
389 else if (*s == 'z' || *s == 'Z') bp->spinz = True;
390 else if (*s == '0') ;
394 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
401 bp->rot = make_rotator (bp->spinx ? spin_speed : 0,
402 bp->spiny ? spin_speed : 0,
403 bp->spinz ? spin_speed : 0,
405 do_wander ? wander_speed : 0,
407 bp->rot2 = (face_front_p
408 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
410 bp->trackball = gltrackball_init (False);
413 bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint));
414 for (i = 0; i < countof(all_objs); i++)
415 bp->dlists[i] = glGenLists (1);
417 parse_color (mi, "textColor", bp->text_color);
418 for (i = 0; i < countof(all_objs); i++)
420 const struct gllist *gll = *all_objs[i];
422 GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0};
423 GLfloat shiny = 80; /* 0-128 */
425 glNewList (bp->dlists[i], GL_COMPILE);
427 glMatrixMode(GL_MODELVIEW);
429 glMatrixMode(GL_TEXTURE);
431 glMatrixMode(GL_MODELVIEW);
433 glRotatef (-90, 1, 0, 0);
435 glBindTexture (GL_TEXTURE_2D, 0);
438 case SPLITFLAP_QUARTER_FRAME:
441 case SPLITFLAP_OUTER_FRAME:
444 case SPLITFLAP_DISC_QUARTER:
445 key = (wire ? "frameColor" : "discColor");
447 case SPLITFLAP_FIN_EDGE_HALF:
448 case SPLITFLAP_FIN_FACE_HALF:
455 parse_color (mi, key, bp->component_colors[i]);
457 if (wire && i == SPLITFLAP_FIN_EDGE_HALF)
458 bp->component_colors[i][0] =
459 bp->component_colors[i][1] =
460 bp->component_colors[i][2] = 0.7;
462 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec);
463 glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
466 case SPLITFLAP_OUTER_FRAME:
467 if (! splitflap_obj_outer_frame)
468 splitflap_obj_outer_frame =
469 (struct gllist *) calloc (1, sizeof(*splitflap_obj_outer_frame));
470 splitflap_obj_outer_frame->points = draw_outer_frame(mi);
473 renderList (gll, wire);
477 glMatrixMode(GL_TEXTURE);
479 glMatrixMode(GL_MODELVIEW);
485 if (grid_width < 1) grid_width = 1;
486 if (grid_height < 1) grid_height = 1;
487 bp->flappers = (flapper *) calloc (grid_width * grid_height,
490 for (i = 0; i < grid_width * grid_height; i++)
492 flapper *f = &bp->flappers[i];
496 f->spool = ascii_spool;
497 f->spool_size = countof (ascii_spool);
503 if (bp->clock_p == 12)
505 f->spool = digit_s1_spool;
506 f->spool_size = countof (digit_s1_spool);
510 f->spool = digit_01_spool;
511 f->spool_size = countof (digit_01_spool);
514 case 1: case 3: case 5:
515 f->spool = digit_spool;
516 f->spool_size = countof (digit_spool);
519 f->spool = digit_05_spool;
520 f->spool_size = countof (digit_05_spool);
524 f->spool_size = countof (ap_spool);
528 f->spool_size = countof (m_spool);
535 f->target_index = random() % f->spool_size;
536 /* f->target_index = 0; */
537 f->current_index = f->target_index;
538 f->missing = (((random() % 10) == 0)
539 ? (random() % f->spool_size)
543 bp->font_data = load_texture_font (mi->dpy, "flapFont");
546 reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
551 draw_component (ModeInfo *mi, int i)
553 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
554 glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE,
555 bp->component_colors[i]);
556 glCallList (bp->dlists[i]);
557 return (*all_objs[i])->points / 3;
562 draw_frame_quarter (ModeInfo *mi, flapper *f)
566 count += draw_component (mi, SPLITFLAP_QUARTER_FRAME);
572 draw_disc_quarter (ModeInfo *mi, flapper *f)
576 count += draw_component (mi, SPLITFLAP_DISC_QUARTER);
582 draw_fin_edge_half (ModeInfo *mi, flapper *f)
586 count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF);
592 draw_fin_face_half (ModeInfo *mi, flapper *f)
595 if (MI_IS_WIREFRAME(mi)) return 0;
597 count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF);
604 draw_frame (ModeInfo *mi, flapper *f)
610 glFrontFace (GL_CCW);
611 count += draw_frame_quarter (mi, f);
612 count += draw_disc_quarter (mi, f);
616 count += draw_frame_quarter (mi, f);
617 count += draw_disc_quarter (mi, f);
619 glScalef ( 1, -1, 1);
620 glFrontFace (GL_CCW);
621 count += draw_frame_quarter (mi, f);
622 count += draw_disc_quarter (mi, f);
626 count += draw_frame_quarter (mi, f);
627 count += draw_disc_quarter (mi, f);
635 draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p)
637 int wire = MI_IS_WIREFRAME(mi);
638 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
641 27 ; descends too far
643 59 [ descends too far
644 79 A^ is taller than the font
648 GLfloat z = 0.035; /* Lifted off the surface by this distance */
649 GLfloat bot = 0.013; /* Distance away from the mid gutter */
650 GLfloat scale = 1.8; /* Scale to fill the panel */
652 int lh = bp->ascent + bp->descent;
654 GLfloat qx0, qy0, qx1, qy1;
655 GLfloat tx0, ty0, tx1, ty1;
657 int tex_width, tex_height;
660 for (i = 0; i < bp->texinfo_size; i++)
662 ti = &bp->texinfo[i];
663 if (!strcmp (f->spool[index], ti->text))
666 if (i >= bp->texinfo_size) abort();
668 overall = ti->metrics;
669 tex_width = ti->tex_width;
670 tex_height = ti->tex_height;
672 if (bp->ascent < overall.ascent)
673 /* WTF! Á has a higher ascent than the font itself!
674 Scale it down so that it doesn't overlap the fin. */
675 scale *= bp->ascent / (GLfloat) overall.ascent * 0.98;
679 glNormal3f (0, 0, 1);
680 glFrontFace (top_p ? GL_CCW : GL_CW);
684 glBindTexture (GL_TEXTURE_2D, ti->texid);
685 enable_texture_string_parameters();
688 glTranslatef (0, 0, z); /* Move to just above the surface */
689 glScalef (1.0 / lh, 1.0 / lh, 1); /* Scale to font pixel coordinates */
690 glScalef (scale, scale, 1); /* Fill the panel with the font */
694 glRotatef (180, 0, 0, 1);
697 /* Position the XCharStruct origin at 0,0 in the scene. */
698 qx0 = -overall.lbearing;
699 qy0 = -overall.descent;
700 qx1 = overall.rbearing;
701 qy1 = overall.ascent;
703 /* Center horizontally. */
704 qx0 -= (overall.rbearing - overall.lbearing) / 2.0;
705 qx1 -= (overall.rbearing - overall.lbearing) / 2.0;
708 /* Move origin to below font descenders. */
712 /* Center vertically. */
713 qy0 -= (bp->ascent + bp->descent) / 2.0;
714 qy1 -= (bp->ascent + bp->descent) / 2.0;
716 /* Move the descenders down a bit, if there's room.
717 This means that weirdos like [ and $ might not be on the baseline.
718 #### This looks good with X11 fonts but bad with MacOS fonts. WTF?
722 GLfloat off = bp->descent / 3.0;
723 GLfloat max = bp->descent - off;
724 if (overall.descent > max)
725 off = max - overall.descent;
731 # endif /* !HAVE_COCOA */
733 /* Attach the texture to the quad. */
736 tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width;
737 ty0 = (overall.ascent + overall.descent) / (GLfloat) tex_height;
739 /* Convert from font ascent/descent to character ascent/descent. */
741 /* Flip texture horizontally on bottom panel. */
750 /* Cut the character in half, truncating just above the split line. */
769 r0 = (qy0 - oqy0) / (oqy1 - oqy0);
770 r1 = (qy1 - oqy1) / (oqy1 - oqy0);
771 ty0 -= r0 * (ty0 - ty1);
772 ty1 -= r1 * (ty0 - ty1);
775 glColor4fv (bp->text_color);
776 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
777 glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0);
778 glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0);
779 glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0);
780 glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0);
787 glDisable (GL_BLEND);
788 glEnable (GL_LIGHTING);
789 glDisable (GL_TEXTURE_2D);
795 draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index,
802 glFrontFace (GL_CCW);
805 count += draw_fin_edge_half (mi, f);
807 if (front_index >= 0)
811 draw_fin_text_quad (mi, f, front_index, True);
815 count += draw_fin_face_half (mi, f);
822 count += draw_fin_edge_half (mi, f);
823 if (front_index >= 0)
824 count += draw_fin_face_half (mi, f);
829 glRotatef (180, 0, 1, 0);
832 draw_fin_text_quad (mi, f, back_index, False);
837 count += draw_fin_face_half (mi, f);
839 glFrontFace (GL_CCW);
840 count += draw_fin_face_half (mi, f);
849 /* The case holding the grid of flappers.
852 draw_outer_frame (ModeInfo *mi)
854 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
856 GLfloat w = grid_width;
857 GLfloat h = grid_height;
860 if (bp->clock_p == 12)
861 w += COLON_WIDTH * 3;
862 else if (bp->clock_p == 24)
863 w += COLON_WIDTH * 2;
868 if (bp->clock_p) w += 0.25;
872 if (MI_IS_WIREFRAME(mi))
875 glFrontFace (GL_CCW);
877 glTranslatef (0, 1.03, 0);
881 glNormal3f ( 0, 1, 0); /* back */
882 glVertex3f (-w, d, h);
883 glVertex3f ( w, d, h);
884 glVertex3f ( w, d, -h);
885 glVertex3f (-w, d, -h);
888 glNormal3f ( 0, -1, 0); /* front */
889 glVertex3f (-w, -d, -h);
890 glVertex3f ( w, -d, -h);
891 glVertex3f ( w, -d, h);
892 glVertex3f (-w, -d, h);
895 glNormal3f ( 0, 0, 1); /* top */
896 glVertex3f (-w, -d, h);
897 glVertex3f ( w, -d, h);
898 glVertex3f ( w, d, h);
899 glVertex3f (-w, d, h);
902 glNormal3f ( 0, 0, -1); /* bottom */
903 glVertex3f (-w, d, -h);
904 glVertex3f ( w, d, -h);
905 glVertex3f ( w, -d, -h);
906 glVertex3f (-w, -d, -h);
909 glNormal3f ( 1, 0, 0); /* left */
910 glVertex3f ( w, -d, h);
911 glVertex3f ( w, -d, -h);
912 glVertex3f ( w, d, -h);
913 glVertex3f ( w, d, h);
916 glNormal3f (-1, 0, 0); /* right */
917 glVertex3f (-w, -d, -h);
918 glVertex3f (-w, -d, h);
919 glVertex3f (-w, d, h);
920 glVertex3f (-w, d, -h);
931 tick_flapper (ModeInfo *mi, flapper *f)
933 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
934 double prev = f->current_index;
935 Bool wrapped_p = False;
937 if (bp->button_down_p) return;
938 if (f->current_index == f->target_index)
941 f->current_index += speed * 0.35; /* turn the crank */
943 while (f->current_index > f->spool_size)
945 f->current_index -= f->spool_size;
949 if (f->current_index < 0) abort();
951 if ((prev < f->target_index || wrapped_p) &&
952 f->current_index > f->target_index) /* just overshot */
953 f->current_index = f->target_index;
957 #define MOD(M,N) (((M)+(N)) % (N)) /* Works with negatives */
960 draw_flapper (ModeInfo *mi, flapper *f, Bool text_p)
962 int prev_index = floor (f->current_index);
963 int next_index = MOD (prev_index+1, f->spool_size);
965 GLfloat epsilon = 0.02;
966 GLfloat r = f->current_index - prev_index;
967 Bool moving_p = (r > 0 && r < 1);
968 GLfloat sticky = f->sticky;
973 if (f->missing >= 0 &&
974 MOD (prev_index, f->spool_size) == f->missing)
981 next_index = prev_index;
984 count += draw_frame (mi, f);
986 /* Top flap, flat: top half of target char */
987 if (!moving_p || !text_p || r > epsilon)
991 if (p2 == f->missing)
992 p2 = MOD (p2+1, f->spool_size);
994 count += draw_fin (mi, f, p2, -1, text_p);
997 /* Bottom flap, flat: bottom half of prev char */
998 if (!moving_p || !text_p || r < 1 - epsilon)
1000 int p2 = prev_index;
1002 if (!moving_p && sticky)
1003 p2 = MOD (p2-1, f->spool_size);
1005 if (f->missing >= 0 &&
1006 p2 == MOD (f->missing+1, f->spool_size))
1007 p2 = MOD (p2-1, f->spool_size);
1010 glRotatef (180, 1, 0, 0);
1011 count += draw_fin (mi, f, -1, p2, text_p);
1015 /* Moving flap, front: top half of prev char */
1016 /* Moving flap, back: bottom half of target char */
1017 if (moving_p || sticky)
1021 if (sticky && r > 1 - sticky)
1024 glRotatef (r * 180, 1, 0, 0);
1025 count += draw_fin (mi, f, prev_index, next_index, text_p);
1034 draw_colon (ModeInfo *mi)
1036 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1037 GLfloat s = 1.0 / (bp->ascent + bp->descent);
1042 texture_string_metrics (bp->font_data, ":", &m, 0, 0);
1048 glTranslatef (-(1 + COLON_WIDTH), 0, 0);
1051 glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2,
1052 -(m.ascent + m.descent) / 2,
1055 glEnable (GL_TEXTURE_2D);
1057 /* draw the text five times, to give it a border. */
1059 const XPoint offsets[] = {{ -1, -1 },
1067 glColor3f (0, 0, 0);
1068 for (i = 0; i < countof(offsets); i++)
1071 if (offsets[i].x == 0)
1073 glColor4fv (bp->text_color);
1074 glTranslatef (0, 0, z * 2);
1076 glTranslatef (n * offsets[i].x, n * offsets[i].y, 0);
1077 print_texture_string (bp->font_data, ":");
1089 /* Reads and returns a single Unicode character from the text client.
1091 static unsigned long
1092 read_unicode (ModeInfo *mi)
1094 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1095 const unsigned char *end = bp->text + sizeof(bp->text) - 1; /* 4 bytes */
1096 unsigned long uc = 0;
1100 if (bp->clock_p || !bp->tc) abort();
1102 /* Fill the buffer with available input.
1104 i = strlen ((char *) bp->text);
1105 while (i < (end - bp->text))
1107 int c = textclient_getc (bp->tc);
1109 bp->text[i++] = (char) c;
1113 /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character.
1115 L = utf8_decode (bp->text, i, &uc);
1118 int j = end - bp->text - L;
1119 memmove (bp->text, bp->text + L, j);
1129 /* Given a Unicode character, finds the corresponding index on the spool,
1130 if any. Returns 0 if not found.
1133 find_index (ModeInfo *mi, flapper *f, long uc)
1136 int L = utf8_encode (uc, string, sizeof(string) - 1);
1138 if (L <= 0) return 0;
1140 for (i = 0; i < f->spool_size; i++)
1142 if (!strcmp (string, f->spool[i]))
1149 /* Read input from the text client and populate the spool with it.
1152 fill_targets (ModeInfo *mi)
1154 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1161 time_t now = time ((time_t *) 0);
1162 struct tm *tm = localtime (&now);
1163 const char *fmt = (bp->clock_p == 24
1167 strftime (buf, sizeof(buf)-1, fmt, tm);
1168 if (bp->clock_p == 12 && buf[0] == '0')
1171 for (i = 0; i < strlen(buf); i++)
1173 flapper *f = &bp->flappers[i];
1174 f->target_index = find_index (mi, f, buf[i]);
1176 for (; i < grid_width * grid_height; i++)
1178 flapper *f = &bp->flappers[i];
1179 f->target_index = find_index (mi, f, ' ');
1184 for (y = 0; y < grid_height; y++)
1187 for (x = 0; x < grid_width; x++)
1189 int i = y * grid_width + x;
1190 flapper *f = &bp->flappers[i];
1191 unsigned long uc = ((nl_p || cls_p) ? ' ' : read_unicode (mi));
1192 if (uc == '\r' || uc == '\n')
1194 else if (uc == 12) /* ^L */
1197 /* Convert Unicode to the closest Latin1 equivalent. */
1200 Bool ascii_p = (f->spool != latin1_spool);
1201 unsigned char s[5], *s2;
1202 int L = utf8_encode (uc, (char *) s, sizeof(s));
1204 s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p);
1206 if (s2[0] < 128) /* ASCII */
1208 else /* Latin1 -> UTF8 -> Unicode */
1210 s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2);
1211 s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF);
1213 utf8_decode (s, 2, &uc);
1219 /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */
1220 if (uc >= 'a' && uc <= 'z') uc += ('A'-'a');
1222 f->target_index = find_index (mi, f, uc);
1224 f->sticky = (((random() % 20) == 0)
1225 ? 0.05 + frand(0.1) + frand(0.1)
1231 for (y = 0; y < grid_height; y++)
1233 fprintf (stderr, "# ");
1234 for (x = 0; x < grid_width; x++)
1236 int i = y * grid_width + x;
1237 flapper *f = &bp->flappers[i];
1238 fprintf(stderr, "%s", bp->spool[f->target_index]);
1240 fprintf (stderr, " #\n");
1242 fprintf (stderr, "\n");
1248 draw_flappers (ModeInfo *mi, Bool text_p)
1250 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1254 for (y = 0; y < grid_height; y++)
1255 for (x = 0; x < grid_width; x++)
1257 int i = (grid_height - y - 1) * grid_width + x;
1258 flapper *f = &bp->flappers[i];
1264 if (x >= 2) xx += COLON_WIDTH;
1265 if (x >= 4) xx += COLON_WIDTH;
1266 if (x >= 6) xx += COLON_WIDTH;
1273 glTranslatef (xx, yy, 0);
1274 mi->polygon_count += draw_flapper (mi, f, text_p);
1276 if (text_p && bp->clock_p && (x == 2 || x == 4))
1277 mi->polygon_count += draw_colon (mi);
1283 tick_flapper (mi, f);
1284 if (f->current_index != f->target_index)
1289 if (text_p && !running)
1293 else if (bp->linger)
1301 /* Base of 1 second, plus 1 second for every 25 characters.
1302 Also multiply by speed? */
1304 if (!bp->first_time_p)
1305 bp->linger += (grid_width * grid_height * 1.2);
1306 bp->first_time_p = False;
1313 draw_splitflap (ModeInfo *mi)
1315 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1316 Display *dpy = MI_DISPLAY(mi);
1317 Window window = MI_WINDOW(mi);
1319 if (!bp->glx_context)
1322 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1324 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1327 glRotatef(current_device_rotation(), 0, 0, 1);
1329 glScalef (0.1, 0.1, 0.1); /* because of gluLookAt */
1333 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
1334 glTranslatef((x - 0.5) * 8,
1338 gltrackball_rotate (bp->trackball);
1345 get_position (bp->rot2, &x, &y, &z, !bp->button_down_p);
1346 if (bp->spinx) glRotatef (maxy/2 - x*maxy, 1, 0, 0);
1347 if (bp->spiny) glRotatef (maxx/2 - y*maxx, 0, 1, 0);
1348 if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1);
1352 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
1353 glRotatef (x * 360, 1, 0, 0);
1354 glRotatef (y * 360, 0, 1, 0);
1355 glRotatef (z * 360, 0, 0, 1);
1359 /* Fit the whole grid on the screen */
1361 GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi);
1362 int cells = (grid_width > grid_height
1367 s *= 2; /* #### What. Why is this necessary? */
1373 mi->polygon_count = 0;
1374 mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME);
1377 GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 :
1378 bp->clock_p == 24 ? COLON_WIDTH * 2 :
1380 glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0);
1383 /* We must render all text after all polygons, or alpha blending
1384 doesn't work right. */
1385 draw_flappers (mi, False);
1386 draw_flappers (mi, True);
1390 if (mi->fps_p) do_fps (mi);
1393 glXSwapBuffers(dpy, window);
1397 free_splitflap (ModeInfo *mi)
1399 splitflap_configuration *bp = &bps[MI_SCREEN(mi)];
1401 textclient_close (bp->tc);
1403 /* #### bp->texinfo */
1406 XSCREENSAVER_MODULE ("SplitFlap", splitflap)