Eyes.c revision 55074dd0
1/* 2 3Copyright (c) 1991 X Consortium 4 5Permission is hereby granted, free of charge, to any person obtaining 6a copy of this software and associated documentation files (the 7"Software"), to deal in the Software without restriction, including 8without limitation the rights to use, copy, modify, merge, publish, 9distribute, sublicense, and/or sell copies of the Software, and to 10permit persons to whom the Software is furnished to do so, subject to 11the following conditions: 12 13The above copyright notice and this permission notice shall be included 14in all copies or substantial portions of the Software. 15 16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR 20OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22OTHER DEALINGS IN THE SOFTWARE. 23 24Except as contained in this notice, the name of the X Consortium shall 25not be used in advertising or otherwise to promote the sale, use or 26other dealings in this Software without prior written authorization 27from the X Consortium. 28 29*/ 30 31/* 32 * Eyes.c 33 * 34 * a widget which follows the mouse around 35 */ 36 37#ifdef HAVE_CONFIG_H 38# include "config.h" 39#endif 40 41# include <X11/Xos.h> 42# include <stdio.h> 43# include <X11/IntrinsicP.h> 44# include <X11/StringDefs.h> 45# include <X11/Xmu/Converters.h> 46# include "EyesP.h" 47# include <math.h> 48# include <X11/extensions/shape.h> 49# include <X11/Xlibint.h> 50# include <stdlib.h> 51 52#if (defined(SVR4) || defined(SYSV) && defined(i386)) 53extern double hypot(double, double); 54#endif 55 56#define offset(field) XtOffsetOf(EyesRec, eyes.field) 57#define goffset(field) XtOffsetOf(WidgetRec, core.field) 58 59static XtResource resources[] = { 60 {XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension), 61 goffset(width), XtRImmediate, (XtPointer) 150}, 62 {XtNheight, XtCHeight, XtRDimension, sizeof(Dimension), 63 goffset(height), XtRImmediate, (XtPointer) 100}, 64 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), 65 offset(pixel[PART_PUPIL]), XtRString, XtDefaultForeground}, 66 {XtNoutline, XtCForeground, XtRPixel, sizeof(Pixel), 67 offset(pixel[PART_OUTLINE]), XtRString, XtDefaultForeground}, 68 {XtNcenterColor, XtCBackground, XtRPixel, sizeof (Pixel), 69 offset(pixel[PART_CENTER]), XtRString, XtDefaultBackground}, 70 {XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean), 71 offset (reverse_video), XtRImmediate, (XtPointer) FALSE}, 72 {XtNbackingStore, XtCBackingStore, XtRBackingStore, sizeof (int), 73 offset (backing_store), XtRString, "default"}, 74 {XtNshapeWindow, XtCShapeWindow, XtRBoolean, sizeof (Boolean), 75 offset (shape_window), XtRImmediate, (XtPointer) TRUE}, 76#ifdef XRENDER 77 {XtNrender, XtCBoolean, XtRBoolean, sizeof(Boolean), 78 offset(render), XtRImmediate, (XtPointer) TRUE }, 79#endif 80 {XtNdistance, XtCBoolean, XtRBoolean, sizeof(Boolean), 81 offset(distance), XtRImmediate, (XtPointer) FALSE }, 82}; 83 84#undef offset 85#undef goffset 86 87# define EYE_X(n) ((n) * 2.0) 88# define EYE_Y(n) (0.0) 89# define EYE_OFFSET (0.1) /* padding between eyes */ 90# define EYE_THICK (0.175) /* thickness of eye rim */ 91# define BALL_DIAM (0.3) 92# define BALL_PAD (0.175) 93# define EYE_DIAM (2.0 - (EYE_THICK + EYE_OFFSET) * 2) 94# define BALL_DIST ((EYE_DIAM - BALL_DIAM) / 2.0 - BALL_PAD) 95# define W_MIN_X (-1.0 + EYE_OFFSET) 96# define W_MAX_X (3.0 - EYE_OFFSET) 97# define W_MIN_Y (-1.0 + EYE_OFFSET) 98# define W_MAX_Y (1.0 - EYE_OFFSET) 99 100# define TPOINT_NONE (-1000) /* special value meaning "not yet set" */ 101# define TPointEqual(a, b) ((a).x == (b).x && (a).y == (b).y) 102# define XPointEqual(a, b) ((a).x == (b).x && (a).y == (b).y) 103# define AngleBetween(A, A0, A1) (A0 <= A1 ? A0 <= A && A <= A1 : \ 104 A0 <= A || A <= A1) 105 106static int delays[] = { 50, 100, 200, 400, 0 }; 107 108static void ClassInitialize(void) 109{ 110 XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore, 111 NULL, 0 ); 112} 113 114WidgetClass eyesWidgetClass = (WidgetClass) &eyesClassRec; 115 116/* ARGSUSED */ 117static void Initialize ( 118 Widget greq, 119 Widget gnew, 120 ArgList args, 121 Cardinal *num_args) 122{ 123 EyesWidget w = (EyesWidget)gnew; 124 XtGCMask valuemask; 125 XGCValues myXGCV; 126 int shape_event_base, shape_error_base; 127#ifdef XRENDER 128 enum EyesPart i; 129#endif 130 131 /* 132 * set the colors if reverse video; these are the colors used: 133 * 134 * background - paper white 135 * foreground - text, ticks black 136 * border - border black (foreground) 137 * 138 * This doesn't completely work since the parent has already made up a 139 * border. Sigh. 140 */ 141 if (w->eyes.reverse_video) { 142 Pixel fg = w->eyes.pixel[PART_PUPIL]; 143 Pixel bg = w->core.background_pixel; 144 145 if (w->core.border_pixel == fg) 146 w->core.border_pixel = bg; 147 if (w->eyes.pixel[PART_OUTLINE] == fg) 148 w->eyes.pixel[PART_OUTLINE] = bg; 149 if (w->eyes.pixel[PART_CENTER] == bg) 150 w->eyes.pixel[PART_CENTER] = fg; 151 w->eyes.pixel[PART_PUPIL] = bg; 152 w->core.background_pixel = fg; 153 } 154 155 myXGCV.foreground = w->eyes.pixel[PART_PUPIL]; 156 myXGCV.background = w->core.background_pixel; 157 valuemask = GCForeground | GCBackground; 158 w->eyes.gc[PART_PUPIL] = XtGetGC(gnew, valuemask, &myXGCV); 159 160 myXGCV.foreground = w->eyes.pixel[PART_OUTLINE]; 161 valuemask = GCForeground | GCBackground; 162 w->eyes.gc[PART_OUTLINE] = XtGetGC(gnew, valuemask, &myXGCV); 163 164 myXGCV.foreground = w->eyes.pixel[PART_CENTER]; 165 myXGCV.background = w->eyes.pixel[PART_PUPIL]; 166 valuemask = GCForeground | GCBackground; 167 w->eyes.gc[PART_CENTER] = XtGetGC(gnew, valuemask, &myXGCV); 168 169 w->eyes.update = 0; 170 /* wait for Realize to add the timeout */ 171 w->eyes.interval_id = 0; 172 173 w->eyes.pupil[0].x = w->eyes.pupil[1].x = TPOINT_NONE; 174 w->eyes.pupil[0].y = w->eyes.pupil[1].y = TPOINT_NONE; 175 176 w->eyes.mouse.x = w->eyes.mouse.y = TPOINT_NONE; 177 178 if (w->eyes.shape_window && !XShapeQueryExtension (XtDisplay (w), 179 &shape_event_base, 180 &shape_error_base)) 181 w->eyes.shape_window = False; 182 w->eyes.shape_mask = 0; 183 w->eyes.gc[PART_SHAPE] = NULL; 184 185#ifdef XRENDER 186 for (i = 0; i < PART_SHAPE; i ++) { 187 XColor c; 188 XRenderColor rc; 189 190 c.pixel = w->eyes.pixel[i]; 191 XQueryColor(XtDisplay (w), w->core.colormap, &c); 192 193 rc.red = c.red; 194 rc.green = c.green; 195 rc.blue = c.blue; 196 rc.alpha = -1; 197 w->eyes.fill[i] = XRenderCreateSolidFill(XtDisplay (w), &rc); 198 } 199#endif 200} 201 202static void 203drawEllipse(EyesWidget w, enum EyesPart part, 204 double centerx, double centery, 205 double oldx, double oldy, 206 double diam) 207{ 208 const TRectangle tpos = { 209 centerx - diam/2.0, 210 centery - diam/2.0, 211 diam, diam }; 212 TRectangle pos; 213 Trectangle(&w->eyes.t, &tpos, &pos); 214 215 if (part == PART_CLEAR) { 216 XFillRectangle(XtDisplay(w), XtWindow(w), 217 w->eyes.gc[PART_CENTER], 218 (int)pos.x, (int)pos.y, 219 (int)pos.width+2, (int)pos.height+2); 220 return; 221 } 222#ifdef XRENDER 223 if (w->eyes.render && part != PART_SHAPE && (!w->eyes.shape_window || 224 part != PART_OUTLINE) && 225 w->eyes.picture) { 226 int n, i; 227 double hd, c, s, sx, sy, x, y, px, py; 228 XPointDouble *p; 229 230 pos.x = pos.x + pos.width/2.0; 231 pos.y = pos.y + pos.height/2.0; 232 233 /* determine number of segments to draw */ 234 hd = hypot(pos.width, pos.height)/2; 235 n = (M_PI / acos(hd/(hd+1.0))) + 0.5; 236 if (n < 2) n = 2; 237 238 c = cos(M_PI/n); 239 s = sin(M_PI/n); 240 sx = -(pos.width*s)/pos.height; 241 sy = (pos.height*s)/pos.width; 242 243 n *= 2; 244 p = Xmalloc(sizeof(*p)*n); 245 if (!p) 246 return; 247 x = 0; 248 y = pos.height/2.0; 249 for (i = 0; i < n; i ++) 250 { 251 p[i].x = x + pos.x; 252 p[i].y = y + pos.y; 253 px = x; 254 py = y; 255 x = c*px + sx*py; 256 y = c*py + sy*px; 257 } 258 259 if (oldx != TPOINT_NONE || oldy != TPOINT_NONE) 260 drawEllipse(w, PART_CLEAR, oldx, oldy, 261 TPOINT_NONE, TPOINT_NONE, diam); 262 263 XRenderCompositeDoublePoly(XtDisplay(w), PictOpOver, 264 w->eyes.fill[part], w->eyes.picture, 265 XRenderFindStandardFormat(XtDisplay(w), 266 PictStandardA8), 267 0, 0, 0, 0, p, n, 0); 268 269 Xfree(p); 270 return; 271 } 272#endif 273 if (oldx != TPOINT_NONE || oldy != TPOINT_NONE) 274 drawEllipse(w, PART_CLEAR, oldx, oldy, 275 TPOINT_NONE, TPOINT_NONE, diam); 276 277 XFillArc(XtDisplay(w), 278 part == PART_SHAPE ? w->eyes.shape_mask : XtWindow(w), 279 w->eyes.gc[part], 280 (int)(pos.x + 0.5), (int)(pos.y + 0.5), 281 (int)(pos.width + 0.0), (int)(pos.height + 0.0), 282 90*64, 360*64); 283} 284 285 286static void 287eyeLiner(EyesWidget w, 288 Boolean draw, 289 int num) 290{ 291 drawEllipse(w, draw ? PART_OUTLINE : PART_SHAPE, 292 EYE_X(num), EYE_Y(num), 293 TPOINT_NONE, TPOINT_NONE, 294 EYE_DIAM + 2.0*EYE_THICK); 295 if (draw) { 296 drawEllipse(w, PART_CENTER, EYE_X(num), EYE_Y(num), 297 TPOINT_NONE, TPOINT_NONE, 298 EYE_DIAM); 299 } 300} 301 302static TPoint computePupil ( 303 int num, 304 TPoint mouse, 305 const TRectangle *screen) 306{ 307 double cx, cy; 308 double dist; 309 double angle; 310 double dx, dy; 311 double cosa, sina; 312 TPoint ret; 313 314 cx = EYE_X(num); dx = mouse.x - cx; 315 cy = EYE_Y(num); dy = mouse.y - cy; 316 if (dx == 0 && dy == 0); 317 else { 318 angle = atan2 ((double) dy, (double) dx); 319 cosa = cos (angle); 320 sina = sin (angle); 321 dist = BALL_DIST; 322 if (screen) 323 { 324 /* use distance mapping */ 325 double x0, y0, x1, y1; 326 double a[4]; 327 x0 = screen->x - cx; 328 y0 = screen->y - cy; 329 x1 = x0 + screen->width; 330 y1 = y0 + screen->height; 331 a[0] = atan2(y0, x0); 332 a[1] = atan2(y1, x0); 333 a[2] = atan2(y1, x1); 334 a[3] = atan2(y0, x1); 335 if (AngleBetween(angle, a[0], a[1])) 336 { 337 /* left */ 338 dist *= dx / x0; 339 } 340 else if (AngleBetween(angle, a[1], a[2])) 341 { 342 /* bottom */ 343 dist *= dy / y1; 344 } 345 else if (AngleBetween(angle, a[2], a[3])) 346 { 347 /* right */ 348 dist *= dx / x1; 349 } 350 else if (AngleBetween(angle, a[3], a[0])) 351 { 352 /* top */ 353 dist *= dy / y0; 354 } 355 if (dist > BALL_DIST) 356 dist = BALL_DIST; 357 } 358 if (dist > hypot ((double) dx, (double) dy)) { 359 cx += dx; 360 cy += dy; 361 } else { 362 cx += dist * cosa; 363 cy += dist * sina; 364 } 365 } 366 ret.x = cx; 367 ret.y = cy; 368 return ret; 369} 370 371static void computePupils ( 372 EyesWidget w, 373 TPoint mouse, 374 TPoint pupils[2]) 375{ 376 TRectangle screen, *sp = NULL; 377 if (w->eyes.distance) { 378 Window r, cw; 379 int x, y; 380 r = RootWindowOfScreen(w->core.screen); 381 XTranslateCoordinates(XtDisplay(w), XtWindow(w), r, 0, 0, &x, &y, &cw); 382 screen.x = Tx(-x, -y, &w->eyes.t); 383 screen.y = Ty(-x, -y, &w->eyes.t); 384 screen.width = Twidth (w->core.screen->width, w->core.screen->height, 385 &w->eyes.t); 386 screen.height = Theight(w->core.screen->width, w->core.screen->height, 387 &w->eyes.t); 388 sp = &screen; 389 } 390 pupils[0] = computePupil (0, mouse, sp); 391 pupils[1] = computePupil (1, mouse, sp); 392} 393 394static void 395eyeBall(EyesWidget w, 396 Boolean draw, 397 TPoint *old, 398 int num) 399{ 400 drawEllipse(w, draw ? PART_PUPIL : PART_CLEAR, 401 w->eyes.pupil[num].x, w->eyes.pupil[num].y, 402 old ? old->x : TPOINT_NONE, old ? old->y : TPOINT_NONE, 403 BALL_DIAM); 404} 405 406static void repaint_window (EyesWidget w) 407{ 408 if (XtIsRealized ((Widget) w)) { 409 eyeLiner (w, TRUE, 0); 410 eyeLiner (w, TRUE, 1); 411 computePupils (w, w->eyes.mouse, w->eyes.pupil); 412 eyeBall (w, TRUE, NULL, 0); 413 eyeBall (w, TRUE, NULL, 1); 414 } 415} 416 417static void 418drawEye(EyesWidget w, TPoint newpupil, int num) 419{ 420 XPoint xnewpupil, xpupil; 421 422 xpupil.x = Xx(w->eyes.pupil[num].x, w->eyes.pupil[num].y, &w->eyes.t); 423 xpupil.y = Xy(w->eyes.pupil[num].x, w->eyes.pupil[num].y, &w->eyes.t); 424 xnewpupil.x = Xx(newpupil.x, newpupil.y, &w->eyes.t); 425 xnewpupil.y = Xy(newpupil.x, newpupil.y, &w->eyes.t); 426 if ( 427#ifdef XRENDER 428 w->eyes.picture ? !TPointEqual(w->eyes.pupil[num], newpupil) : 429#endif 430 !XPointEqual(xpupil, xnewpupil)) { 431 TPoint oldpupil = w->eyes.pupil[num]; 432 w->eyes.pupil[num] = newpupil; 433 eyeBall (w, TRUE, &oldpupil, num); 434 } 435} 436 437static void 438drawEyes(EyesWidget w, TPoint mouse) 439{ 440 TPoint newpupil[2]; 441 int num; 442 443 if (TPointEqual (mouse, w->eyes.mouse)) { 444 if (delays[w->eyes.update + 1] != 0) 445 ++w->eyes.update; 446 return; 447 } 448 computePupils (w, mouse, newpupil); 449 for (num = 0; num < 2; num ++) { 450 drawEye(w, newpupil[num], num); 451 } 452 453 w->eyes.mouse = mouse; 454 w->eyes.update = 0; 455} 456 457static void draw_it_core(EyesWidget w) 458{ 459 Window rep_root, rep_child; 460 int rep_rootx, rep_rooty; 461 unsigned int rep_mask; 462 int dx, dy; 463 TPoint mouse; 464 Display *dpy = XtDisplay (w); 465 Window win = XtWindow (w); 466 467 XQueryPointer (dpy, win, &rep_root, &rep_child, 468 &rep_rootx, &rep_rooty, &dx, &dy, &rep_mask); 469 mouse.x = Tx(dx, dy, &w->eyes.t); 470 mouse.y = Ty(dx, dy, &w->eyes.t); 471 472 drawEyes(w, mouse); 473} 474 475/* ARGSUSED */ 476static void draw_it ( 477 XtPointer client_data, 478 XtIntervalId *id) /* unused */ 479{ 480 EyesWidget w = (EyesWidget)client_data; 481 482 if (XtIsRealized((Widget)w)) { 483 draw_it_core(w); 484 } 485 w->eyes.interval_id = 486 XtAppAddTimeOut(XtWidgetToApplicationContext((Widget) w), 487 delays[w->eyes.update], draw_it, (XtPointer)w); 488} /* draw_it */ 489 490static void Resize (Widget gw) 491{ 492 EyesWidget w = (EyesWidget) gw; 493 XGCValues xgcv; 494 Widget parent; 495 Display *dpy = XtDisplay (w); 496 int x, y; 497 498 if (XtIsRealized (gw)) 499 { 500 XClearWindow (dpy, XtWindow (w)); 501 SetTransform (&w->eyes.t, 502 0, w->core.width, 503 w->core.height, 0, 504 W_MIN_X, W_MAX_X, 505 W_MIN_Y, W_MAX_Y); 506#ifdef XRENDER 507 if (w->eyes.picture) { 508 XRenderFreePicture(dpy, w->eyes.picture); 509 w->eyes.picture = 0; 510 } 511#endif 512 if (w->eyes.shape_window) { 513 w->eyes.shape_mask = XCreatePixmap (dpy, XtWindow (w), 514 w->core.width, w->core.height, 1); 515 if (!w->eyes.gc[PART_SHAPE]) 516 w->eyes.gc[PART_SHAPE] = XCreateGC (dpy, w->eyes.shape_mask, 517 0, &xgcv); 518 XSetForeground (dpy, w->eyes.gc[PART_SHAPE], 0); 519 XFillRectangle (dpy, w->eyes.shape_mask, w->eyes.gc[PART_SHAPE], 520 0, 0, w->core.width, w->core.height); 521 XSetForeground (dpy, w->eyes.gc[PART_SHAPE], 1); 522 eyeLiner (w, FALSE, 0); 523 eyeLiner (w, FALSE, 1); 524 x = y = 0; 525 for (parent = (Widget) w; XtParent (parent); parent = XtParent (parent)) { 526 x += parent->core.x + parent->core.border_width; 527 x += parent->core.y + parent->core.border_width; 528 } 529 XShapeCombineMask (XtDisplay (parent), XtWindow (parent), ShapeBounding, 530 x, y, w->eyes.shape_mask, ShapeSet); 531 XFreePixmap (dpy, w->eyes.shape_mask); 532 } 533#ifdef XRENDER 534 if (w->eyes.render) { 535 XRenderPictureAttributes pa; 536 XRenderPictFormat *pf; 537 pf = XRenderFindVisualFormat(dpy, 538 DefaultVisualOfScreen(w->core.screen)); 539 if (pf) 540 w->eyes.picture = XRenderCreatePicture(dpy, XtWindow (w), 541 pf, 0, &pa); 542 } 543#endif 544 } 545} 546 547static void Realize ( 548 Widget gw, 549 XtValueMask *valueMask, 550 XSetWindowAttributes *attrs) 551{ 552 EyesWidget w = (EyesWidget)gw; 553 554 if (w->eyes.backing_store != Always + WhenMapped + NotUseful) { 555 attrs->backing_store = w->eyes.backing_store; 556 *valueMask |= CWBackingStore; 557 } 558 XtCreateWindow( gw, (unsigned)InputOutput, (Visual *)CopyFromParent, 559 *valueMask, attrs ); 560 Resize (gw); 561 w->eyes.interval_id = 562 XtAppAddTimeOut(XtWidgetToApplicationContext(gw), 563 delays[w->eyes.update], draw_it, (XtPointer)gw); 564} 565 566static void Destroy (Widget gw) 567{ 568 EyesWidget w = (EyesWidget)gw; 569 int i; 570 571 if (w->eyes.interval_id) 572 XtRemoveTimeOut (w->eyes.interval_id); 573 for (i = 0; i < PART_MAX; i ++) 574 XtReleaseGC(gw, w->eyes.gc[i]); 575#ifdef XRENDER 576 if (w->eyes.picture) 577 XRenderFreePicture (XtDisplay(w), w->eyes.picture); 578#endif 579} 580 581/* ARGSUSED */ 582static void Redisplay( 583 Widget gw, 584 XEvent *event, 585 Region region) 586{ 587 EyesWidget w; 588 589 w = (EyesWidget) gw; 590 w->eyes.pupil[0].x = TPOINT_NONE; 591 w->eyes.pupil[0].y = TPOINT_NONE; 592 w->eyes.pupil[1].x = TPOINT_NONE; 593 w->eyes.pupil[1].y = TPOINT_NONE; 594 (void) repaint_window ((EyesWidget)gw); 595} 596 597/* ARGSUSED */ 598static Boolean SetValues ( 599 Widget current, 600 Widget request, 601 Widget new, 602 ArgList args, 603 Cardinal *num_args) 604{ 605 return( FALSE ); 606} 607 608EyesClassRec eyesClassRec = { 609 { /* core fields */ 610 /* superclass */ &widgetClassRec, 611 /* class_name */ "Eyes", 612 /* size */ sizeof(EyesRec), 613 /* class_initialize */ ClassInitialize, 614 /* class_part_initialize */ NULL, 615 /* class_inited */ FALSE, 616 /* initialize */ Initialize, 617 /* initialize_hook */ NULL, 618 /* realize */ Realize, 619 /* actions */ NULL, 620 /* num_actions */ 0, 621 /* resources */ resources, 622 /* num_resources */ XtNumber(resources), 623 /* xrm_class */ NULLQUARK, 624 /* compress_motion */ TRUE, 625 /* compress_exposure */ TRUE, 626 /* compress_enterleave */ TRUE, 627 /* visible_interest */ FALSE, 628 /* destroy */ Destroy, 629 /* resize */ Resize, 630 /* expose */ Redisplay, 631 /* set_values */ SetValues, 632 /* set_values_hook */ NULL, 633 /* set_values_almost */ NULL, 634 /* get_values_hook */ NULL, 635 /* accept_focus */ NULL, 636 /* version */ XtVersion, 637 /* callback_private */ NULL, 638 /* tm_table */ NULL, 639 /* query_geometry */ XtInheritQueryGeometry, 640 } 641}; 642