From 6f6c975d2cdc1f615f83576c9d1f828e1cdabda3 Mon Sep 17 00:00:00 2001 From: Serge Bazanski Date: Sun, 28 May 2023 14:21:56 +0200 Subject: Implement multi-ocular support, add biblical example This removes the assumption that an xeyes instance displays just a pair of eyes, and instead allows future developers to implement different kinds of ocular layouts. Currently, the ocular layout system only allows for specifying offsets, but a future change might also make different parts of the eye geometry configurable: size of different elements, padding, etc. Signed-off-by: Serge Bazanski --- Eyes.c | 157 +++++++++++++++++++++++++++++++++++++++++++--------------- Eyes.h | 1 + EyesP.h | 21 +++++++- man/xeyes.man | 3 ++ xeyes.c | 2 + 5 files changed, 143 insertions(+), 41 deletions(-) diff --git a/Eyes.c b/Eyes.c index aa54b1c..11a1f8e 100644 --- a/Eyes.c +++ b/Eyes.c @@ -1,6 +1,7 @@ /* Copyright (c) 1991 X Consortium +Copyright (c) 2023 q3k Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -49,6 +50,7 @@ from the X Consortium. # include # include # include +# include #define offset(field) XtOffsetOf(EyesRec, eyes.field) #define goffset(field) XtOffsetOf(WidgetRec, core.field) @@ -83,23 +85,19 @@ static XtResource resources[] = { #endif {(char *) XtNdistance, (char *) XtCBoolean, XtRBoolean, sizeof(Boolean), offset(distance), XtRImmediate, (XtPointer) FALSE }, + {(char *) XtNbiblicallyAccurate, (char *) XtCBoolean, XtRBoolean, sizeof(Boolean), + offset(biblically_accurate), XtRImmediate, (XtPointer) FALSE }, }; #undef offset #undef goffset -# define EYE_X(n) ((n) * 2.0) -# define EYE_Y(n) (0.0) # define EYE_OFFSET (0.1) /* padding between eyes */ # define EYE_THICK (0.175) /* thickness of eye rim */ # define BALL_DIAM (0.3) # define BALL_PAD (0.175) # define EYE_DIAM (2.0 - (EYE_THICK + EYE_OFFSET) * 2) # define BALL_DIST ((EYE_DIAM - BALL_DIAM) / 2.0 - BALL_PAD) -# define W_MIN_X (-1.0 + EYE_OFFSET) -# define W_MAX_X (3.0 - EYE_OFFSET) -# define W_MIN_Y (-1.0 + EYE_OFFSET) -# define W_MAX_Y (1.0 - EYE_OFFSET) # define TPOINT_NONE (-1000) /* special value meaning "not yet set" */ # define TPointEqual(a, b) ((a).x == (b).x && (a).y == (b).y) @@ -109,6 +107,69 @@ static XtResource resources[] = { static int delays[] = { 50, 100, 200, 400, 0 }; +static EyeLayout layout_standard[] = { + { .x = 0.0, .y = 0.0, }, + { .x = 2.0, .y = 0.0, }, +}; + +static EyeLayout layout_biblical[] = { + { .x = 0.0+0.75, .y = 0.0, }, + { .x = 1.5+0.75, .y = 0.0, }, + { .x = 3.0+0.75, .y = 0.0, }, + + { .x = 0.0+0.00, .y = 1.4, }, + { .x = 1.5+0.00, .y = 1.4, }, + { .x = 3.0+0.00, .y = 1.4, }, + { .x = 4.5+0.00, .y = 1.4, }, + + { .x = 0.0+0.75, .y = 2.8, }, + { .x = 1.5+0.75, .y = 2.8, }, + { .x = 3.0+0.75, .y = 2.8, }, +}; + +static EyeConfiguration *EyesConfigure(Boolean biblically_accurate) +{ + EyeConfiguration *c = calloc(sizeof(EyeConfiguration), 1); + assert(c != NULL); + + if (biblically_accurate) { + c->eyes = layout_biblical; + c->count = sizeof(layout_biblical) / sizeof(EyeLayout); + } else { + c->eyes = layout_standard; + c->count = sizeof(layout_standard) / sizeof(EyeLayout); + } + + // Calculate the bounding box of the eyes. + c->w_min_x = c->eyes[0].x; + c->w_max_x = c->eyes[0].x; + c->w_min_y = c->eyes[0].y; + c->w_max_y = c->eyes[0].y; + + for (int i = 0; i < c->count; i++) { + EyeLayout *l = &c->eyes[i]; + if (l->x > c->w_max_x) { + c->w_max_x = l->x; + } + if (l->x < c->w_min_x) { + c->w_min_x = l->x; + } + if (l->y > c->w_max_y) { + c->w_max_y = l->y; + } + if (l->y < c->w_min_y) { + c->w_min_y = l->y; + } + } + + // Add half size of eye (2.0) minus padding to each edge. + c->w_min_x -= (1.0 - EYE_OFFSET); + c->w_max_x += (1.0 - EYE_OFFSET); + c->w_min_y -= (1.0 - EYE_OFFSET); + c->w_max_y += (1.0 - EYE_OFFSET); + return c; +} + static void ClassInitialize(void) { XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore, @@ -344,6 +405,17 @@ static void Initialize ( enum EyesPart i; #endif + EyeConfiguration *config = EyesConfigure(w->eyes.biblically_accurate); + TPoint *pupils = calloc(sizeof(TPoint), config->count); + assert(pupils != NULL); + for (int j = 0; j < config->count; j++) { + pupils[j].x = TPOINT_NONE; + pupils[j].y = TPOINT_NONE; + } + w->eyes.configuration = config; + w->eyes.pupils = pupils; + + /* * set the colors if reverse video; these are the colors used: * @@ -386,9 +458,6 @@ static void Initialize ( /* wait for Realize to add the timeout */ w->eyes.interval_id = 0; - w->eyes.pupil[0].x = w->eyes.pupil[1].x = TPOINT_NONE; - w->eyes.pupil[0].y = w->eyes.pupil[1].y = TPOINT_NONE; - w->eyes.mouse.x = w->eyes.mouse.y = TPOINT_NONE; if (w->eyes.shape_window && !XShapeQueryExtension (XtDisplay (w), @@ -511,19 +580,20 @@ eyeLiner(EyesWidget w, Boolean draw, int num) { + EyeLayout *l = &w->eyes.configuration->eyes[num]; drawEllipse(w, draw ? PART_OUTLINE : PART_SHAPE, - EYE_X(num), EYE_Y(num), + l->x, l->y, TPOINT_NONE, TPOINT_NONE, EYE_DIAM + 2.0*EYE_THICK); if (draw) { - drawEllipse(w, PART_CENTER, EYE_X(num), EYE_Y(num), + drawEllipse(w, PART_CENTER, l->x, l->y, TPOINT_NONE, TPOINT_NONE, EYE_DIAM); } } static TPoint computePupil ( - int num, + EyeLayout *layout, TPoint mouse, const TRectangle *screen) { @@ -534,8 +604,8 @@ static TPoint computePupil ( double cosa, sina; TPoint ret; - cx = EYE_X(num); dx = mouse.x - cx; - cy = EYE_Y(num); dy = mouse.y - cy; + cx = layout->x; dx = mouse.x - cx; + cy = layout->y; dy = mouse.y - cy; if (dx == 0 && dy == 0); else { angle = atan2 ((double) dy, (double) dx); @@ -594,7 +664,7 @@ static TPoint computePupil ( static void computePupils ( EyesWidget w, TPoint mouse, - TPoint pupils[2]) + TPoint *pupils) { TRectangle screen, *sp = NULL; if (w->eyes.distance) { @@ -610,8 +680,9 @@ static void computePupils ( &w->eyes.t); sp = &screen; } - pupils[0] = computePupil (0, mouse, sp); - pupils[1] = computePupil (1, mouse, sp); + for (int i = 0; i < w->eyes.configuration->count; i++) { + pupils[i] = computePupil(&w->eyes.configuration->eyes[i], mouse, sp); + } } static void @@ -620,8 +691,9 @@ eyeBall(EyesWidget w, TPoint *old, int num) { + //printf("eyeBall(_, %d, %p, %d)\n", draw, old, num); drawEllipse(w, draw ? PART_PUPIL : PART_CLEAR, - w->eyes.pupil[num].x, w->eyes.pupil[num].y, + w->eyes.pupils[num].x, w->eyes.pupils[num].y, old ? old->x : TPOINT_NONE, old ? old->y : TPOINT_NONE, BALL_DIAM); } @@ -632,11 +704,13 @@ static void repaint_window (EyesWidget w) #ifdef PRESENT MakePresentData(w); #endif - eyeLiner (w, TRUE, 0); - eyeLiner (w, TRUE, 1); - computePupils (w, w->eyes.mouse, w->eyes.pupil); - eyeBall (w, TRUE, NULL, 0); - eyeBall (w, TRUE, NULL, 1); + for (int i = 0; i < w->eyes.configuration->count; i++) { + eyeLiner (w, TRUE, i); + } + computePupils (w, w->eyes.mouse, w->eyes.pupils); + for (int i = 0; i < w->eyes.configuration->count; i++) { + eyeBall (w, TRUE, NULL, i); + } #ifdef PRESENT UpdatePresent(w); #endif @@ -648,17 +722,17 @@ drawEye(EyesWidget w, TPoint newpupil, int num) { XPoint xnewpupil, xpupil; - xpupil.x = Xx(w->eyes.pupil[num].x, w->eyes.pupil[num].y, &w->eyes.t); - xpupil.y = Xy(w->eyes.pupil[num].x, w->eyes.pupil[num].y, &w->eyes.t); + xpupil.x = Xx(w->eyes.pupils[num].x, w->eyes.pupils[num].y, &w->eyes.t); + xpupil.y = Xy(w->eyes.pupils[num].x, w->eyes.pupils[num].y, &w->eyes.t); xnewpupil.x = Xx(newpupil.x, newpupil.y, &w->eyes.t); xnewpupil.y = Xy(newpupil.x, newpupil.y, &w->eyes.t); if ( #ifdef XRENDER - w->eyes.picture ? !TPointEqual(w->eyes.pupil[num], newpupil) : + w->eyes.picture ? !TPointEqual(w->eyes.pupils[num], newpupil) : #endif !XPointEqual(xpupil, xnewpupil)) { - TPoint oldpupil = w->eyes.pupil[num]; - w->eyes.pupil[num] = newpupil; + TPoint oldpupil = w->eyes.pupils[num]; + w->eyes.pupils[num] = newpupil; eyeBall (w, TRUE, &oldpupil, num); } } @@ -666,8 +740,8 @@ drawEye(EyesWidget w, TPoint newpupil, int num) static void drawEyes(EyesWidget w, TPoint mouse) { - TPoint newpupil[2]; int num; + TPoint newpupils[w->eyes.configuration->count]; #ifdef PRESENT MakePresentData(w); @@ -677,9 +751,9 @@ drawEyes(EyesWidget w, TPoint mouse) ++w->eyes.update; return; } - computePupils (w, mouse, newpupil); - for (num = 0; num < 2; num ++) { - drawEye(w, newpupil[num], num); + computePupils (w, mouse, newpupils); + for (num = 0; num < w->eyes.configuration->count; num++) { + drawEye(w, newpupils[num], num); } w->eyes.mouse = mouse; @@ -737,8 +811,10 @@ static void Resize (Widget gw) SetTransform (&w->eyes.t, 0, w->core.width, w->core.height, 0, - W_MIN_X, W_MAX_X, - W_MIN_Y, W_MAX_Y); + w->eyes.configuration->w_min_x, + w->eyes.configuration->w_max_x, + w->eyes.configuration->w_min_y, + w->eyes.configuration->w_max_y); #ifdef PRESENT if (w->eyes.back_buffer) { xcb_free_pixmap(xt_xcb(w), @@ -769,8 +845,9 @@ static void Resize (Widget gw) XFillRectangle (dpy, w->eyes.shape_mask, w->eyes.gc[PART_SHAPE], 0, 0, w->core.width, w->core.height); XSetForeground (dpy, w->eyes.gc[PART_SHAPE], 1); - eyeLiner (w, FALSE, 0); - eyeLiner (w, FALSE, 1); + for (int i = 0; i < w->eyes.configuration->count; i++) { + eyeLiner (w, FALSE, i); + } x = y = 0; for (parent = (Widget) w; XtParent (parent); parent = XtParent (parent)) { x += parent->core.x + parent->core.border_width; @@ -842,10 +919,10 @@ static void Redisplay( EyesWidget w; w = (EyesWidget) gw; - w->eyes.pupil[0].x = TPOINT_NONE; - w->eyes.pupil[0].y = TPOINT_NONE; - w->eyes.pupil[1].x = TPOINT_NONE; - w->eyes.pupil[1].y = TPOINT_NONE; + for (int i = 0; i < w->eyes.configuration->count; i++) { + w->eyes.pupils[i].x = TPOINT_NONE; + w->eyes.pupils[i].y = TPOINT_NONE; + } (void) repaint_window ((EyesWidget)gw); } diff --git a/Eyes.h b/Eyes.h index 6bac782..66b3880 100644 --- a/Eyes.h +++ b/Eyes.h @@ -34,6 +34,7 @@ #define XtNrender "render" #define XtNdistance "distance" +#define XtNbiblicallyAccurate "biblicallyAccurate" #define XtNpresent "present" diff --git a/EyesP.h b/EyesP.h index ecd565b..fa7e940 100644 --- a/EyesP.h +++ b/EyesP.h @@ -18,6 +18,23 @@ #define SEG_BUFF_SIZE 128 +typedef struct { + // X offset + double x; + // Y offset + double y; +} EyeLayout; + +typedef struct { + EyeLayout *eyes; + int count; + + double w_min_x; + double w_max_x; + double w_min_y; +double w_max_y; +} EyeConfiguration; + /* New fields for the eyes widget instance record */ typedef struct { Pixel pixel[PART_SHAPE]; @@ -28,7 +45,9 @@ typedef struct { Boolean shape_window; /* use SetWindowShapeMask */ int update; /* current timeout index */ TPoint mouse; /* old mouse position */ - TPoint pupil[2]; /* pupil position */ + Boolean biblically_accurate; + EyeConfiguration *configuration; + TPoint *pupils; Transform t; Transform maskt; XtIntervalId interval_id; diff --git a/man/xeyes.man b/man/xeyes.man index 298e152..6696237 100644 --- a/man/xeyes.man +++ b/man/xeyes.man @@ -52,6 +52,9 @@ disables Xrender and draws traditional eyes. .B \-distance uses an alternative mapping, as if the eyes were set back from the screen, thus following the mouse more precisely. .TP 8 +.B \-biblicallyAccurate +renders the eyes as if they belonged to a biblically accurate angel. +.TP 8 .B \-help print a usage message and exit. .TP 8 diff --git a/xeyes.c b/xeyes.c index c5484a0..a9d484f 100644 --- a/xeyes.c +++ b/xeyes.c @@ -52,6 +52,7 @@ usage(int exitval) " [-fg {color}] [-bg {color}] [-bd {color}] [-bw {pixels}]\n" " [-shape | +shape] [-outline {color}] [-center {color}]\n" " [-backing {backing-store}] [-distance]\n" + " [-biblicallyAccurate]\n" #ifdef XRENDER " [-render | +render]\n" #endif @@ -81,6 +82,7 @@ static XrmOptionDescRec options[] = { {(char *)"+present", (char *)"*eyes.present", XrmoptionNoArg, (char *)"FALSE"}, #endif {(char *)"-distance", (char *)"*eyes.distance", XrmoptionNoArg, (char *)"TRUE"}, +{(char *)"-biblicallyAccurate", (char *)"*eyes.biblicallyAccurate", XrmoptionNoArg, (char *)"TRUE"}, }; static Atom wm_delete_window; -- cgit v1.2.3