/* splitflap, Copyright (c) 2015-2018 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * Draws a split-flap text display. */ #define FLAP_FONT "-*-helvetica-bold-r-normal-*-*-1440-*-*-*-*-*-*" #define DEFAULTS "*delay: 20000 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ "*flapFont: " FLAP_FONT "\n" \ "*frameColor: #444444" "\n" \ "*caseColor: #666666" "\n" \ "*discColor: #888888" "\n" \ "*finColor: #222222" "\n" \ "*textColor: #FFFF00" "\n" \ "*multiSample: True \n" \ "*program: xscreensaver-text\n" \ "*usePty: False\n" # define release_splitflap 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #define DEF_SPEED "1.0" #define DEF_WIDTH "22" #define DEF_HEIGHT "8" #define DEF_SPIN "XYZ" #define DEF_WANDER "True" #define DEF_FACE_FRONT "True" #define DEF_MODE "Text" #include "xlockmore.h" #include #ifdef USE_GL /* whole file */ #include "gltrackball.h" #include "rotator.h" #include "ximage-loader.h" #include "utf8wc.h" #include "textclient.h" #include "texfont.h" #include "gllist.h" extern const struct gllist *splitflap_obj_box_quarter_frame, *splitflap_obj_disc_quarter, *splitflap_obj_fin_edge_half, *splitflap_obj_fin_face_half; static struct gllist *splitflap_obj_outer_frame = 0; static const struct gllist * const *all_objs[] = { &splitflap_obj_box_quarter_frame, &splitflap_obj_disc_quarter, &splitflap_obj_fin_edge_half, &splitflap_obj_fin_face_half, (const struct gllist * const *) &splitflap_obj_outer_frame }; #define SPLITFLAP_QUARTER_FRAME 0 #define SPLITFLAP_DISC_QUARTER 1 #define SPLITFLAP_FIN_EDGE_HALF 2 #define SPLITFLAP_FIN_FACE_HALF 3 #define SPLITFLAP_OUTER_FRAME 4 #define COLON_WIDTH 0.5 typedef struct { int target_index; /* desired character */ double current_index; /* currently displayed, fractional */ GLfloat sticky; /* bottom fin doesn't fall all the way */ int missing; /* which fin has snapped off, or -1 */ const char * const *spool; /* chars available for display */ int spool_size; /* how many fins on the spool */ } flapper; typedef struct { const char *text; GLuint texid; XCharStruct metrics; int tex_width, tex_height; } texinfo; typedef struct { GLXContext *glx_context; rotator *rot, *rot2; trackball_state *trackball; Bool button_down_p; Bool spinx, spiny, spinz; texinfo *texinfo; int texinfo_size; GLuint *dlists; GLfloat component_colors[countof(all_objs)][4]; GLfloat text_color[4]; flapper *flappers; /* grid_width * grid_height */ texture_font_data *font_data; int ascent, descent; text_data *tc; unsigned char text[5]; int linger; int clock_p; Bool first_time_p; } splitflap_configuration; static const char * const digit_s1_spool[] = { " ", "1" }; static const char * const digit_01_spool[] = { "0", "1" }; static const char * const ap_spool[] = { "A", "P" }; static const char * const m_spool[] = { "M" }; static const char * const digit_05_spool[] = { "0", "1", "2", "3", "4", "5" }; static const char * const digit_spool[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" }; static const char * const ascii_spool[] = { " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~", }; /* If we include these, the flappers just take too long. It's boring. */ static const char * const latin1_spool[] = { " ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "{", "|", "}", "~", "\302\241", "\302\242", "\302\243", "\302\245", "\302\247", "\302\251", "\302\265", "\302\266", "\303\200", "\303\201", "\303\202", "\303\203", "\303\204", "\303\205", "\303\206", "\303\207", "\303\210", "\303\211", "\303\212", "\303\213", "\303\214", "\303\215", "\303\216", "\303\217", "\303\220", "\303\221", "\303\222", "\303\223", "\303\224", "\303\225", "\303\226", "\303\230", "\303\231", "\303\232", "\303\233", "\303\234", "\303\235", "\303\236", "\303\237", "\303\267", }; static splitflap_configuration *bps = NULL; static GLfloat speed; static int grid_width, grid_height; static char *do_spin; static Bool do_wander; static Bool face_front_p; static char *mode_str; static XrmOptionDescRec opts[] = { { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-width", ".width", XrmoptionSepArg, 0 }, { "-height", ".height", XrmoptionSepArg, 0 }, { "-spin", ".spin", XrmoptionSepArg, 0 }, { "+spin", ".spin", XrmoptionNoArg, "" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, { "-front", ".faceFront", XrmoptionNoArg, "True" }, { "+front", ".faceFront", XrmoptionNoArg, "False" }, { "-mode", ".mode", XrmoptionSepArg, 0 }, }; static argtype vars[] = { {&speed, "speed", "Speed", DEF_SPEED, t_Float}, {&grid_width, "width", "Width", DEF_WIDTH, t_Int}, {&grid_height, "height", "Height", DEF_HEIGHT, t_Int}, {&do_spin, "spin", "Spin", DEF_SPIN, t_String}, {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool}, {&mode_str, "mode", "Mode", DEF_MODE, t_String}, }; ENTRYPOINT ModeSpecOpt splitflap_opts = { countof(opts), opts, countof(vars), vars, NULL}; /* Window management, etc */ ENTRYPOINT void reshape_splitflap (ModeInfo *mi, int width, int height) { GLfloat h = (GLfloat) height / (GLfloat) width; glViewport (0, 0, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (40.0, 1/h, 0.5, 25); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0, 0, 3, /* 10x lower than traditional, for better depth rez */ 0, 0, 0, 0, 1, 0); # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */ { int o = (int) current_device_rotation(); if (o != 0 && o != 180 && o != -180) glScalef (h, h, h); } # endif glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT Bool splitflap_handle_event (ModeInfo *mi, XEvent *event) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; if (gltrackball_event_handler (event, bp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &bp->button_down_p)) return True; return False; } static void init_textures (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; int i; const char * const *spool = latin1_spool; int max = countof(latin1_spool); bp->texinfo = (texinfo *) calloc (max+1, sizeof(*bp->texinfo)); texture_string_metrics (bp->font_data, "", 0, &bp->ascent, &bp->descent); for (i = 0; i < max; i++) { texinfo *ti = &bp->texinfo[i]; glGenTextures (1, &ti->texid); glBindTexture (GL_TEXTURE_2D, ti->texid); ti->text = spool[i]; /* fprintf(stderr, "%d \\%03o\\%03o %s\n", i, (unsigned char) ti->text[0], (unsigned char) ti->text[1], ti->text); */ string_to_texture (bp->font_data, ti->text, &ti->metrics, &ti->tex_width, &ti->tex_height); } bp->texinfo_size = i; glBindTexture (GL_TEXTURE_2D, 0); } static void parse_color (ModeInfo *mi, char *key, GLfloat color[4]) { XColor xcolor; char *string = get_string_resource (mi->dpy, key, "Color"); if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor)) { fprintf (stderr, "%s: unparsable color in %s: %s\n", progname, key, string); exit (1); } free (string); color[0] = xcolor.red / 65536.0; color[1] = xcolor.green / 65536.0; color[2] = xcolor.blue / 65536.0; color[3] = 1; } static int draw_outer_frame (ModeInfo *mi); ENTRYPOINT void init_splitflap (ModeInfo *mi) { splitflap_configuration *bp; int wire = MI_IS_WIREFRAME(mi); int i; MI_INIT (mi, bps); bp = &bps[MI_SCREEN(mi)]; bp->glx_context = init_GL(mi); reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); bp->first_time_p = True; if (!mode_str || !*mode_str || !strcasecmp(mode_str, "text")) { bp->clock_p = 0; } else if (!strcasecmp (mode_str, "clock") || !strcasecmp (mode_str, "clock12")) { bp->clock_p = 12; grid_width = 8; grid_height = 1; } else if (!strcasecmp (mode_str, "clock24")) { bp->clock_p = 24; grid_width = 6; grid_height = 1; } else { fprintf (stderr, "%s: `mode' must be text, clock12 or clock24: not `%s'\n", progname, mode_str); exit (1); } if (! bp->clock_p) { bp->tc = textclient_open (MI_DISPLAY (mi)); bp->text[0] = 0; if (grid_width > 10) textclient_reshape (bp->tc, grid_width, grid_height, grid_width, grid_height, 0); } if (bp->clock_p) speed /= 4; glShadeModel(GL_SMOOTH); glEnable(GL_DEPTH_TEST); glEnable(GL_NORMALIZE); glEnable(GL_CULL_FACE); if (!wire) { GLfloat pos[4] = {0.4, 0.2, 0.4, 0.0}; /* GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};*/ GLfloat amb[4] = {0.2, 0.2, 0.2, 1.0}; GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0}; GLfloat spc[4] = {1.0, 1.0, 1.0, 1.0}; glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glLightfv(GL_LIGHT0, GL_POSITION, pos); glLightfv(GL_LIGHT0, GL_AMBIENT, amb); glLightfv(GL_LIGHT0, GL_DIFFUSE, dif); glLightfv(GL_LIGHT0, GL_SPECULAR, spc); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } { double spin_speed = 0.5; double wander_speed = 0.005; double tilt_speed = 0.001; double spin_accel = 0.5; char *s = do_spin; while (*s) { if (*s == 'x' || *s == 'X') bp->spinx = True; else if (*s == 'y' || *s == 'Y') bp->spiny = True; else if (*s == 'z' || *s == 'Z') bp->spinz = True; else if (*s == '0') ; else { fprintf (stderr, "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n", progname, do_spin); exit (1); } s++; } bp->rot = make_rotator (bp->spinx ? spin_speed : 0, bp->spiny ? spin_speed : 0, bp->spinz ? spin_speed : 0, spin_accel, do_wander ? wander_speed : 0, False); bp->rot2 = (face_front_p ? make_rotator (0, 0, 0, 0, tilt_speed, True) : 0); bp->trackball = gltrackball_init (False); } bp->dlists = (GLuint *) calloc (countof(all_objs)+1, sizeof(GLuint)); for (i = 0; i < countof(all_objs); i++) bp->dlists[i] = glGenLists (1); parse_color (mi, "textColor", bp->text_color); for (i = 0; i < countof(all_objs); i++) { const struct gllist *gll = *all_objs[i]; char *key = 0; GLfloat spec[4] = {0.4, 0.4, 0.4, 1.0}; GLfloat shiny = 80; /* 0-128 */ glNewList (bp->dlists[i], GL_COMPILE); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glMatrixMode(GL_TEXTURE); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glRotatef (-90, 1, 0, 0); glBindTexture (GL_TEXTURE_2D, 0); switch (i) { case SPLITFLAP_QUARTER_FRAME: key = "frameColor"; break; case SPLITFLAP_OUTER_FRAME: key = "caseColor"; break; case SPLITFLAP_DISC_QUARTER: key = (wire ? "frameColor" : "discColor"); break; case SPLITFLAP_FIN_EDGE_HALF: case SPLITFLAP_FIN_FACE_HALF: key = "finColor"; break; default: abort(); } parse_color (mi, key, bp->component_colors[i]); if (wire && i == SPLITFLAP_FIN_EDGE_HALF) bp->component_colors[i][0] = bp->component_colors[i][1] = bp->component_colors[i][2] = 0.7; glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, spec); glMaterialf (GL_FRONT_AND_BACK, GL_SHININESS, shiny); switch (i) { case SPLITFLAP_OUTER_FRAME: if (! splitflap_obj_outer_frame) splitflap_obj_outer_frame = (struct gllist *) calloc (1, sizeof(*splitflap_obj_outer_frame)); splitflap_obj_outer_frame->points = draw_outer_frame(mi); break; default: renderList (gll, wire); break; } glMatrixMode(GL_TEXTURE); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glEndList (); } if (grid_width < 1) grid_width = 1; if (grid_height < 1) grid_height = 1; bp->flappers = (flapper *) calloc (grid_width * grid_height, sizeof (flapper)); for (i = 0; i < grid_width * grid_height; i++) { flapper *f = &bp->flappers[i]; if (!bp->clock_p) { f->spool = ascii_spool; f->spool_size = countof (ascii_spool); } else { switch (i) { case 0: if (bp->clock_p == 12) { f->spool = digit_s1_spool; f->spool_size = countof (digit_s1_spool); } else { f->spool = digit_01_spool; f->spool_size = countof (digit_01_spool); } break; case 1: case 3: case 5: f->spool = digit_spool; f->spool_size = countof (digit_spool); break; case 2: case 4: f->spool = digit_05_spool; f->spool_size = countof (digit_05_spool); break; case 6: f->spool = ap_spool; f->spool_size = countof (ap_spool); break; case 7: f->spool = m_spool; f->spool_size = countof (m_spool); break; default: abort(); } } f->target_index = random() % f->spool_size; /* f->target_index = 0; */ f->current_index = f->target_index; f->missing = (((random() % 10) == 0) ? (random() % f->spool_size) : -1); } bp->font_data = load_texture_font (mi->dpy, "flapFont"); init_textures (mi); reshape_splitflap (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); } static int draw_component (ModeInfo *mi, int i) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bp->component_colors[i]); glCallList (bp->dlists[i]); return (*all_objs[i])->points / 3; } static int draw_frame_quarter (ModeInfo *mi, flapper *f) { int count = 0; glPushMatrix(); count += draw_component (mi, SPLITFLAP_QUARTER_FRAME); glPopMatrix(); return count; } static int draw_disc_quarter (ModeInfo *mi, flapper *f) { int count = 0; glPushMatrix(); count += draw_component (mi, SPLITFLAP_DISC_QUARTER); glPopMatrix(); return count; } static int draw_fin_edge_half (ModeInfo *mi, flapper *f) { int count = 0; glPushMatrix(); count += draw_component (mi, SPLITFLAP_FIN_EDGE_HALF); glPopMatrix(); return count; } static int draw_fin_face_half (ModeInfo *mi, flapper *f) { int count = 0; if (MI_IS_WIREFRAME(mi)) return 0; glPushMatrix(); count += draw_component (mi, SPLITFLAP_FIN_FACE_HALF); glPopMatrix(); return count; } static int draw_frame (ModeInfo *mi, flapper *f) { int count = 0; glPushMatrix(); glFrontFace (GL_CCW); count += draw_frame_quarter (mi, f); count += draw_disc_quarter (mi, f); glScalef (-1, 1, 1); glFrontFace (GL_CW); count += draw_frame_quarter (mi, f); count += draw_disc_quarter (mi, f); glScalef ( 1, -1, 1); glFrontFace (GL_CCW); count += draw_frame_quarter (mi, f); count += draw_disc_quarter (mi, f); glScalef (-1, 1, 1); glFrontFace (GL_CW); count += draw_frame_quarter (mi, f); count += draw_disc_quarter (mi, f); glPopMatrix(); return count; } static void draw_fin_text_quad (ModeInfo *mi, flapper *f, int index, Bool top_p) { int wire = MI_IS_WIREFRAME(mi); splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; /* 15 / is weird 27 ; descends too far 32 @ is too wide 59 [ descends too far 79 A^ is taller than the font 89 I` is weird */ GLfloat z = 0.035; /* Lifted off the surface by this distance */ GLfloat bot = 0.013; /* Distance away from the mid gutter */ GLfloat scale = 1.8; /* Scale to fill the panel */ int lh = bp->ascent + bp->descent; texinfo *ti; GLfloat qx0, qy0, qx1, qy1; GLfloat tx0, ty0, tx1, ty1; XCharStruct overall; int tex_width, tex_height; int i; for (i = 0; i < bp->texinfo_size; i++) { ti = &bp->texinfo[i]; if (!strcmp (f->spool[index], ti->text)) break; } if (i >= bp->texinfo_size) abort(); overall = ti->metrics; tex_width = ti->tex_width; tex_height = ti->tex_height; if (bp->ascent < overall.ascent) /* WTF! Á has a higher ascent than the font itself! Scale it down so that it doesn't overlap the fin. */ scale *= bp->ascent / (GLfloat) overall.ascent * 0.98; glPushMatrix(); glNormal3f (0, 0, 1); glFrontFace (top_p ? GL_CCW : GL_CW); if (! wire) { glBindTexture (GL_TEXTURE_2D, ti->texid); enable_texture_string_parameters(); } glTranslatef (0, 0, z); /* Move to just above the surface */ glScalef (1.0 / lh, 1.0 / lh, 1); /* Scale to font pixel coordinates */ glScalef (scale, scale, 1); /* Fill the panel with the font */ if (!top_p) { glRotatef (180, 0, 0, 1); } /* Position the XCharStruct origin at 0,0 in the scene. */ qx0 = -overall.lbearing; qy0 = -overall.descent; qx1 = overall.rbearing; qy1 = overall.ascent; /* Center horizontally. */ qx0 -= (overall.rbearing - overall.lbearing) / 2.0; qx1 -= (overall.rbearing - overall.lbearing) / 2.0; /* Move origin to below font descenders. */ qy0 += bp->descent; qy1 += bp->descent; /* Center vertically. */ qy0 -= (bp->ascent + bp->descent) / 2.0; qy1 -= (bp->ascent + bp->descent) / 2.0; /* Move the descenders down a bit, if there's room. This means that weirdos like [ and $ might not be on the baseline. #### This looks good with X11 fonts but bad with MacOS fonts. WTF? */ #ifndef HAVE_COCOA { GLfloat off = bp->descent / 3.0; GLfloat max = bp->descent - off; if (overall.descent > max) off = max - overall.descent; if (off < 0) off = 0; qy0 -= off; qy1 -= off; } # endif /* !HAVE_COCOA */ /* Attach the texture to the quad. */ tx0 = 0; ty1 = 0; tx1 = (overall.rbearing - overall.lbearing) / (GLfloat) tex_width; ty0 = (overall.ascent + overall.descent) / (GLfloat) tex_height; /* Convert from font ascent/descent to character ascent/descent. */ /* Flip texture horizontally on bottom panel. */ if (!top_p) { GLfloat s = tx0; tx0 = tx1; tx1 = s; } /* Cut the character in half, truncating just above the split line. */ { GLfloat oqy0 = qy0; GLfloat oqy1 = qy1; GLfloat r0, r1; bot *= lh * scale; if (top_p) { if (qy0 < bot) qy0 = bot; } else { if (qy1 > -bot) qy1 = -bot; } r0 = (qy0 - oqy0) / (oqy1 - oqy0); r1 = (qy1 - oqy1) / (oqy1 - oqy0); ty0 -= r0 * (ty0 - ty1); ty1 -= r1 * (ty0 - ty1); } glColor4fv (bp->text_color); glBegin (wire ? GL_LINE_LOOP : GL_QUADS); glTexCoord2f (tx0, ty0); glVertex3f (qx0, qy0, 0); glTexCoord2f (tx1, ty0); glVertex3f (qx1, qy0, 0); glTexCoord2f (tx1, ty1); glVertex3f (qx1, qy1, 0); glTexCoord2f (tx0, ty1); glVertex3f (qx0, qy1, 0); glEnd(); glPopMatrix(); if (! wire) { glDisable (GL_BLEND); glEnable (GL_LIGHTING); glDisable (GL_TEXTURE_2D); } } static int draw_fin (ModeInfo *mi, flapper *f, int front_index, int back_index, Bool text_p) { int count = 0; glPushMatrix(); glFrontFace (GL_CCW); if (! text_p) count += draw_fin_edge_half (mi, f); if (front_index >= 0) { if (text_p) { draw_fin_text_quad (mi, f, front_index, True); count++; } else count += draw_fin_face_half (mi, f); } glScalef (-1, 1, 1); if (! text_p) { glFrontFace (GL_CW); count += draw_fin_edge_half (mi, f); if (front_index >= 0) count += draw_fin_face_half (mi, f); } if (back_index >= 0) { glRotatef (180, 0, 1, 0); if (text_p) { draw_fin_text_quad (mi, f, back_index, False); count++; } else { count += draw_fin_face_half (mi, f); glScalef (-1, 1, 1); glFrontFace (GL_CCW); count += draw_fin_face_half (mi, f); } } glPopMatrix(); return count; } /* The case holding the grid of flappers. */ static int draw_outer_frame (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; int count = 0; GLfloat w = grid_width; GLfloat h = grid_height; GLfloat d = 1; if (bp->clock_p == 12) w += COLON_WIDTH * 3; else if (bp->clock_p == 24) w += COLON_WIDTH * 2; w += 0.2; h += 0.2; if (bp->clock_p) w += 0.25; if (w > 3) w += 0.5; if (h > 3) h += 0.5; if (MI_IS_WIREFRAME(mi)) return 0; glFrontFace (GL_CCW); glPushMatrix(); glTranslatef (0, 1.03, 0); glBegin (GL_QUADS); glNormal3f ( 0, 1, 0); /* back */ glVertex3f (-w, d, h); glVertex3f ( w, d, h); glVertex3f ( w, d, -h); glVertex3f (-w, d, -h); count++; glNormal3f ( 0, -1, 0); /* front */ glVertex3f (-w, -d, -h); glVertex3f ( w, -d, -h); glVertex3f ( w, -d, h); glVertex3f (-w, -d, h); count++; glNormal3f ( 0, 0, 1); /* top */ glVertex3f (-w, -d, h); glVertex3f ( w, -d, h); glVertex3f ( w, d, h); glVertex3f (-w, d, h); count++; glNormal3f ( 0, 0, -1); /* bottom */ glVertex3f (-w, d, -h); glVertex3f ( w, d, -h); glVertex3f ( w, -d, -h); glVertex3f (-w, -d, -h); count++; glNormal3f ( 1, 0, 0); /* left */ glVertex3f ( w, -d, h); glVertex3f ( w, -d, -h); glVertex3f ( w, d, -h); glVertex3f ( w, d, h); count++; glNormal3f (-1, 0, 0); /* right */ glVertex3f (-w, -d, -h); glVertex3f (-w, -d, h); glVertex3f (-w, d, h); glVertex3f (-w, d, -h); count++; glEnd(); glPopMatrix(); return count; } static void tick_flapper (ModeInfo *mi, flapper *f) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; double prev = f->current_index; Bool wrapped_p = False; if (bp->button_down_p) return; if (f->current_index == f->target_index) return; f->current_index += speed * 0.35; /* turn the crank */ while (f->current_index > f->spool_size) { f->current_index -= f->spool_size; wrapped_p = True; } if (f->current_index < 0) abort(); if ((prev < f->target_index || wrapped_p) && f->current_index > f->target_index) /* just overshot */ f->current_index = f->target_index; } #define MOD(M,N) (((M)+(N)) % (N)) /* Works with negatives */ static int draw_flapper (ModeInfo *mi, flapper *f, Bool text_p) { int prev_index = floor (f->current_index); int next_index = MOD (prev_index+1, f->spool_size); int count = 0; GLfloat epsilon = 0.02; GLfloat r = f->current_index - prev_index; Bool moving_p = (r > 0 && r < 1); GLfloat sticky = f->sticky; if (f->missing >= 0) sticky = 0; if (f->missing >= 0 && MOD (prev_index, f->spool_size) == f->missing) { moving_p = False; sticky = 0; } if (!moving_p) next_index = prev_index; if (! text_p) count += draw_frame (mi, f); /* Top flap, flat: top half of target char */ if (!moving_p || !text_p || r > epsilon) { int p2 = next_index; if (p2 == f->missing) p2 = MOD (p2+1, f->spool_size); count += draw_fin (mi, f, p2, -1, text_p); } /* Bottom flap, flat: bottom half of prev char */ if (!moving_p || !text_p || r < 1 - epsilon) { int p2 = prev_index; if (!moving_p && sticky) p2 = MOD (p2-1, f->spool_size); if (f->missing >= 0 && p2 == MOD (f->missing+1, f->spool_size)) p2 = MOD (p2-1, f->spool_size); glPushMatrix(); glRotatef (180, 1, 0, 0); count += draw_fin (mi, f, -1, p2, text_p); glPopMatrix(); } /* Moving flap, front: top half of prev char */ /* Moving flap, back: bottom half of target char */ if (moving_p || sticky) { if (!moving_p) r = 1.0; if (sticky && r > 1 - sticky) r = 1 - sticky; glPushMatrix(); glRotatef (r * 180, 1, 0, 0); count += draw_fin (mi, f, prev_index, next_index, text_p); glPopMatrix(); } return count; } static int draw_colon (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; GLfloat s = 1.0 / (bp->ascent + bp->descent); GLfloat z = 0.01; int count = 0; XCharStruct m; texture_string_metrics (bp->font_data, ":", &m, 0, 0); s *= 2; glPushMatrix(); glTranslatef (-(1 + COLON_WIDTH), 0, 0); glScalef (s, s, 1); glTranslatef (-m.lbearing - (m.rbearing - m.lbearing)/2, -(m.ascent + m.descent) / 2, 0); glEnable (GL_TEXTURE_2D); /* draw the text five times, to give it a border. */ { const XPoint offsets[] = {{ -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 }, { 0, 0 }}; int i; GLfloat n = 1.5; glColor3f (0, 0, 0); for (i = 0; i < countof(offsets); i++) { glPushMatrix(); if (offsets[i].x == 0) { glColor4fv (bp->text_color); glTranslatef (0, 0, z * 2); } glTranslatef (n * offsets[i].x, n * offsets[i].y, 0); print_texture_string (bp->font_data, ":"); count++; glPopMatrix(); } } glPopMatrix(); return count; } /* Reads and returns a single Unicode character from the text client. */ static unsigned long read_unicode (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; const unsigned char *end = bp->text + sizeof(bp->text) - 1; /* 4 bytes */ unsigned long uc = 0; long L; int i; if (bp->clock_p || !bp->tc) abort(); /* Fill the buffer with available input. */ i = strlen ((char *) bp->text); while (i < (end - bp->text)) { int c = textclient_getc (bp->tc); if (c <= 0) break; bp->text[i++] = (char) c; bp->text[i] = 0; } /* Pop 1-4 bytes from the front of the buffer and extract a UTF8 character. */ L = utf8_decode (bp->text, i, &uc); if (L) { int j = end - bp->text - L; memmove (bp->text, bp->text + L, j); bp->text[j] = 0; } else uc = 0; return uc; } /* Given a Unicode character, finds the corresponding index on the spool, if any. Returns 0 if not found. */ static int find_index (ModeInfo *mi, flapper *f, long uc) { char string[5]; int L = utf8_encode (uc, string, sizeof(string) - 1); int i; if (L <= 0) return 0; string[L] = 0; for (i = 0; i < f->spool_size; i++) { if (!strcmp (string, f->spool[i])) return i; } return 0; } /* Read input from the text client and populate the spool with it. */ static void fill_targets (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; int x, y; Bool cls_p = False; if (bp->clock_p) { char buf[80]; time_t now = time ((time_t *) 0); struct tm *tm = localtime (&now); const char *fmt = (bp->clock_p == 24 ? "%H%M%S" : "%I%M%S%p"); int i; strftime (buf, sizeof(buf)-1, fmt, tm); if (bp->clock_p == 12 && buf[0] == '0') buf[0] = ' '; for (i = 0; i < strlen(buf); i++) { flapper *f = &bp->flappers[i]; f->target_index = find_index (mi, f, buf[i]); } for (; i < grid_width * grid_height; i++) { flapper *f = &bp->flappers[i]; f->target_index = find_index (mi, f, ' '); } return; } for (y = 0; y < grid_height; y++) { Bool nl_p = False; for (x = 0; x < grid_width; x++) { int i = y * grid_width + x; flapper *f = &bp->flappers[i]; unsigned long uc = ((nl_p || cls_p) ? ' ' : read_unicode (mi)); if (uc == '\r' || uc == '\n') nl_p = True; else if (uc == 12) /* ^L */ cls_p = True; /* Convert Unicode to the closest Latin1 equivalent. */ if (uc > 127) { Bool ascii_p = (f->spool != latin1_spool); unsigned char s[5], *s2; int L = utf8_encode (uc, (char *) s, sizeof(s)); s[L] = 0; s2 = (unsigned char *) utf8_to_latin1 ((char *) s, ascii_p); if (s2[0] < 128) /* ASCII */ uc = s2[0]; else /* Latin1 -> UTF8 -> Unicode */ { s[0] = (s2[0] > 0xBF ? 0xC3 : 0xC2); s[1] = s2[0] & (s2[0] > 0xBF ? 0xBF : 0xFF); s[2] = 0; utf8_decode (s, 2, &uc); } free (s2); } /* Upcase ASCII. Upcasing Unicrud would be rocket surgery. */ if (uc >= 'a' && uc <= 'z') uc += ('A'-'a'); f->target_index = find_index (mi, f, uc); f->sticky = (((random() % 20) == 0) ? 0.05 + frand(0.1) + frand(0.1) : 0); } } # if 0 for (y = 0; y < grid_height; y++) { fprintf (stderr, "# "); for (x = 0; x < grid_width; x++) { int i = y * grid_width + x; flapper *f = &bp->flappers[i]; fprintf(stderr, "%s", bp->spool[f->target_index]); } fprintf (stderr, " #\n"); } fprintf (stderr, "\n"); # endif } static void draw_flappers (ModeInfo *mi, Bool text_p) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; int x, y; int running = 0; for (y = 0; y < grid_height; y++) for (x = 0; x < grid_width; x++) { int i = (grid_height - y - 1) * grid_width + x; flapper *f = &bp->flappers[i]; GLfloat xx = x; GLfloat yy = y; if (bp->clock_p) { if (x >= 2) xx += COLON_WIDTH; if (x >= 4) xx += COLON_WIDTH; if (x >= 6) xx += COLON_WIDTH; } xx *= 2.01; yy *= 1.98; glPushMatrix(); glTranslatef (xx, yy, 0); mi->polygon_count += draw_flapper (mi, f, text_p); if (text_p && bp->clock_p && (x == 2 || x == 4)) mi->polygon_count += draw_colon (mi); glPopMatrix(); if (text_p) { tick_flapper (mi, f); if (f->current_index != f->target_index) running++; } } if (text_p && !running) { if (bp->clock_p) fill_targets (mi); else if (bp->linger) { bp->linger--; if (!bp->linger) fill_targets (mi); } else { /* Base of 1 second, plus 1 second for every 25 characters. Also multiply by speed? */ bp->linger = 30; if (!bp->first_time_p) bp->linger += (grid_width * grid_height * 1.2); bp->first_time_p = False; } } } ENTRYPOINT void draw_splitflap (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix (); glRotatef(current_device_rotation(), 0, 0, 1); glScalef (0.1, 0.1, 0.1); /* because of gluLookAt */ { double x, y, z; get_position (bp->rot, &x, &y, &z, !bp->button_down_p); glTranslatef((x - 0.5) * 8, (y - 0.5) * 8, (z - 0.5) * 8); gltrackball_rotate (bp->trackball); if (face_front_p) { double maxx = 120; double maxy = 60; double maxz = 45; get_position (bp->rot2, &x, &y, &z, !bp->button_down_p); if (bp->spinx) glRotatef (maxy/2 - x*maxy, 1, 0, 0); if (bp->spiny) glRotatef (maxx/2 - y*maxx, 0, 1, 0); if (bp->spinz) glRotatef (maxz/2 - z*maxz, 0, 0, 1); } else { get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p); glRotatef (x * 360, 1, 0, 0); glRotatef (y * 360, 0, 1, 0); glRotatef (z * 360, 0, 0, 1); } } /* Fit the whole grid on the screen */ { GLfloat r = MI_HEIGHT(mi) / (GLfloat) MI_WIDTH(mi); int cells = (grid_width > grid_height ? grid_width * r : grid_height); GLfloat s = 8; # ifdef HAVE_MOBILE s *= 2; /* #### What. Why is this necessary? */ #endif s /= cells; glScalef (s, s, s); } mi->polygon_count = 0; mi->polygon_count += draw_component (mi, SPLITFLAP_OUTER_FRAME); { GLfloat xoff = (bp->clock_p == 12 ? COLON_WIDTH * 3 : bp->clock_p == 24 ? COLON_WIDTH * 2 : 0); glTranslatef (1 - (grid_width + xoff), 1 - grid_height, 0); } /* We must render all text after all polygons, or alpha blending doesn't work right. */ draw_flappers (mi, False); draw_flappers (mi, True); glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_splitflap (ModeInfo *mi) { splitflap_configuration *bp = &bps[MI_SCREEN(mi)]; int i; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); if (bp->flappers) free (bp->flappers); if (bp->tc) textclient_close (bp->tc); if (bp->trackball) gltrackball_free (bp->trackball); if (bp->rot) free_rotator (bp->rot); if (bp->rot2) free_rotator (bp->rot2); if (bp->font_data) free_texture_font (bp->font_data); if (bp->dlists) { for (i = 0; i < countof(all_objs); i++) if (glIsList(bp->dlists[i])) glDeleteLists(bp->dlists[i], 1); free (bp->dlists); } if (bp->texinfo) { for (i = 0; i < bp->texinfo_size; i++) if (bp->texinfo[i].texid) glDeleteTextures (1, &bp->texinfo[i].texid); free (bp->texinfo); } } XSCREENSAVER_MODULE ("SplitFlap", splitflap) #endif /* USE_GL */