Eyes.c revision a1d141d5
1/* $XConsortium: Eyes.c,v 1.28 94/04/17 20:45:22 eswu Exp $ */
2/* $XFree86: xc/programs/xeyes/Eyes.c,v 1.3 2001/07/25 15:05:21 dawes Exp $ */
3/*
4
5Copyright (c) 1991  X Consortium
6
7Permission is hereby granted, free of charge, to any person obtaining
8a copy of this software and associated documentation files (the
9"Software"), to deal in the Software without restriction, including
10without limitation the rights to use, copy, modify, merge, publish,
11distribute, sublicense, and/or sell copies of the Software, and to
12permit persons to whom the Software is furnished to do so, subject to
13the following conditions:
14
15The above copyright notice and this permission notice shall be included
16in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
22OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
23ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24OTHER DEALINGS IN THE SOFTWARE.
25
26Except as contained in this notice, the name of the X Consortium shall
27not be used in advertising or otherwise to promote the sale, use or
28other dealings in this Software without prior written authorization
29from the X Consortium.
30
31*/
32
33/*
34 * Eyes.c
35 *
36 * a widget which follows the mouse around
37 */
38
39# include <X11/Xos.h>
40# include <stdio.h>
41# include <X11/IntrinsicP.h>
42# include <X11/StringDefs.h>
43# include <X11/Xmu/Converters.h>
44# include "EyesP.h"
45# include <math.h>
46# include <X11/extensions/shape.h>
47
48#if (defined(SVR4) || defined(SYSV) && defined(i386))
49extern double hypot(double, double);
50#endif
51
52#define offset(field) XtOffsetOf(EyesRec, eyes.field)
53#define goffset(field) XtOffsetOf(WidgetRec, core.field)
54
55static XtResource resources[] = {
56    {XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension),
57	goffset(width), XtRImmediate, (XtPointer) 150},
58    {XtNheight, XtCHeight, XtRDimension, sizeof(Dimension),
59	goffset(height), XtRImmediate, (XtPointer) 100},
60    {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
61        offset(puppixel), XtRString, XtDefaultForeground},
62    {XtNoutline, XtCForeground, XtRPixel, sizeof(Pixel),
63        offset(outline), XtRString, XtDefaultForeground},
64    {XtNcenterColor, XtCBackground, XtRPixel, sizeof (Pixel),
65    	offset(center), XtRString, XtDefaultBackground},
66    {XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
67	offset (reverse_video), XtRImmediate, (XtPointer) FALSE},
68    {XtNbackingStore, XtCBackingStore, XtRBackingStore, sizeof (int),
69    	offset (backing_store), XtRString, "default"},
70    {XtNshapeWindow, XtCShapeWindow, XtRBoolean, sizeof (Boolean),
71	offset (shape_window), XtRImmediate, (XtPointer) TRUE},
72};
73
74#undef offset
75#undef goffset
76
77# define NUM_EYES	2
78# define EYE_X(n)	((n) * 2.0)
79# define EYE_Y(n)	(0.0)
80# define EYE_OFFSET	(0.1)	/* padding between eyes */
81# define EYE_THICK	(0.175)	/* thickness of eye rim */
82# define BALL_WIDTH	(0.3)
83# define BALL_PAD	(0.05)
84# define EYE_WIDTH	(2.0 - (EYE_THICK + EYE_OFFSET) * 2)
85# define EYE_HEIGHT	EYE_WIDTH
86# define EYE_HWIDTH	(EYE_WIDTH / 2.0)
87# define EYE_HHEIGHT	(EYE_HEIGHT / 2.0)
88# define BALL_HEIGHT	BALL_WIDTH
89# define BALL_DIST	((EYE_WIDTH - BALL_WIDTH) / 2.0 - BALL_PAD)
90# define W_MIN_X	(-1.0 + EYE_OFFSET)
91# define W_MAX_X	(3.0 - EYE_OFFSET)
92# define W_MIN_Y	(-1.0 + EYE_OFFSET)
93# define W_MAX_Y	(1.0 - EYE_OFFSET)
94
95# define TPointEqual(a, b)  ((a).x == (b).x && (a).y == (b).y)
96# define XPointEqual(a, b)  ((a).x == (b).x && (a).y == (b).y)
97
98static int delays[] = { 50, 100, 200, 400, 0 };
99
100static void ClassInitialize(void)
101{
102    XtAddConverter( XtRString, XtRBackingStore, XmuCvtStringToBackingStore,
103		    NULL, 0 );
104}
105
106WidgetClass eyesWidgetClass = (WidgetClass) &eyesClassRec;
107
108/* ARGSUSED */
109static void Initialize (
110    Widget greq,
111    Widget gnew,
112    ArgList args,
113    Cardinal *num_args)
114{
115    EyesWidget w = (EyesWidget)gnew;
116    XtGCMask	valuemask;
117    XGCValues	myXGCV;
118    int shape_event_base, shape_error_base;
119
120    /*
121     * set the colors if reverse video; these are the colors used:
122     *
123     *     background - paper		white
124     *     foreground - text, ticks	black
125     *     border - border		black (foreground)
126     *
127     * This doesn't completely work since the parent has already made up a
128     * border.  Sigh.
129     */
130    if (w->eyes.reverse_video) {
131	Pixel fg = w->eyes.puppixel;
132	Pixel bg = w->core.background_pixel;
133
134	if (w->core.border_pixel == fg)
135 	    w->core.border_pixel = bg;
136	if (w->eyes.outline == fg)
137	    w->eyes.outline = bg;
138	if (w->eyes.center == bg)
139	    w->eyes.center = fg;
140	w->eyes.puppixel = bg;
141	w->core.background_pixel = fg;
142    }
143
144    myXGCV.foreground = w->eyes.puppixel;
145    myXGCV.background = w->core.background_pixel;
146    valuemask = GCForeground | GCBackground;
147    w->eyes.pupGC = XtGetGC(gnew, valuemask, &myXGCV);
148
149    myXGCV.foreground = w->eyes.outline;
150    valuemask = GCForeground | GCBackground;
151    w->eyes.outGC = XtGetGC(gnew, valuemask, &myXGCV);
152
153    myXGCV.foreground = w->eyes.center;
154    myXGCV.background = w->eyes.puppixel;
155    valuemask = GCForeground | GCBackground;
156    w->eyes.centerGC = XtGetGC(gnew, valuemask, &myXGCV);
157
158    w->eyes.update = 0;
159    /* wait for Realize to add the timeout */
160    w->eyes.interval_id = 0;
161
162    w->eyes.pupil[0].x = w->eyes.pupil[1].x = -1000;
163    w->eyes.pupil[0].y = w->eyes.pupil[1].y = -1000;
164
165    w->eyes.mouse.x = w->eyes.mouse.y = -1000;
166
167    if (w->eyes.shape_window && !XShapeQueryExtension (XtDisplay (w),
168						       &shape_event_base,
169						       &shape_error_base))
170	w->eyes.shape_window = False;
171    w->eyes.shape_mask = 0;
172    w->eyes.shapeGC = 0;
173}
174
175static void eyeLiner (
176    EyesWidget	w,
177    Drawable	d,
178    GC		outgc,
179    GC		centergc,
180    int		num)
181{
182	Display *dpy = XtDisplay(w);
183
184	TFillArc (dpy, d, outgc, &w->eyes.t,
185		  EYE_X(num) - EYE_HWIDTH - EYE_THICK,
186 		  EYE_Y(num) - EYE_HHEIGHT - EYE_THICK,
187		  EYE_WIDTH + EYE_THICK * 2.0,
188 		  EYE_HEIGHT + EYE_THICK * 2.0,
189 		  90 * 64, 360 * 64);
190	if (centergc) {
191    	    TFillArc (dpy, d, centergc, &w->eyes.t,
192		  EYE_X(num) - EYE_HWIDTH,
193 		  EYE_Y(num) - EYE_HHEIGHT,
194		  EYE_WIDTH, EYE_HEIGHT,
195		  90 * 64, 360 * 64);
196	}
197}
198
199static TPoint computePupil (
200    int		num,
201    TPoint	mouse)
202{
203	double	cx, cy;
204	double	dist;
205	double	angle;
206	double	x, y;
207	double	h;
208	double	dx, dy;
209	double	cosa, sina;
210	TPoint	ret;
211
212	dx = mouse.x - EYE_X(num);
213	dy = mouse.y - EYE_Y(num);
214	if (dx == 0 && dy == 0) {
215		cx = EYE_X(num);
216		cy = EYE_Y(num);
217	} else {
218		angle = atan2 ((double) dy, (double) dx);
219		cosa = cos (angle);
220		sina = sin (angle);
221		h = hypot (EYE_HHEIGHT * cosa, EYE_HWIDTH * sina);
222		x = (EYE_HWIDTH * EYE_HHEIGHT) * cosa / h;
223		y = (EYE_HWIDTH * EYE_HHEIGHT) * sina / h;
224		dist = BALL_DIST * hypot (x, y);
225		if (dist > hypot ((double) dx, (double) dy)) {
226			cx = dx + EYE_X(num);
227			cy = dy + EYE_Y(num);
228		} else {
229			cx = dist * cosa + EYE_X(num);
230			cy = dist * sina + EYE_Y(num);
231		}
232	}
233	ret.x = cx;
234	ret.y = cy;
235	return ret;
236}
237
238static void computePupils (
239    TPoint	mouse,
240    TPoint	pupils[2])
241{
242    pupils[0] = computePupil (0, mouse);
243    pupils[1] = computePupil (1, mouse);
244}
245
246static void eyeBall (
247    EyesWidget	w,
248    GC	gc,
249    int	num)
250{
251	Display *dpy = XtDisplay(w);
252	Window win = XtWindow(w);
253
254	TFillArc (dpy, win, gc, &w->eyes.t,
255		   w->eyes.pupil[num].x - BALL_WIDTH / 2.0,
256		   w->eyes.pupil[num].y - BALL_HEIGHT / 2.0,
257		   BALL_WIDTH, BALL_HEIGHT,
258		  90 * 64, 360 * 64);
259}
260
261static void repaint_window (EyesWidget w)
262{
263	if (XtIsRealized ((Widget) w)) {
264		eyeLiner (w, XtWindow (w), w->eyes.outGC, w->eyes.centerGC, 0);
265		eyeLiner (w, XtWindow (w), w->eyes.outGC, w->eyes.centerGC, 1);
266		computePupils (w->eyes.mouse, w->eyes.pupil);
267		eyeBall (w, w->eyes.pupGC, 0);
268		eyeBall (w, w->eyes.pupGC, 1);
269	}
270}
271
272/* ARGSUSED */
273static void draw_it (
274     XtPointer client_data,
275     XtIntervalId *id)		/* unused */
276{
277        EyesWidget	w = (EyesWidget)client_data;
278	Window		rep_root, rep_child;
279	int		rep_rootx, rep_rooty;
280	unsigned int	rep_mask;
281	int		dx, dy;
282	TPoint		mouse;
283	Display		*dpy = XtDisplay (w);
284	Window		win = XtWindow (w);
285	TPoint		newpupil[2];
286	XPoint		xnewpupil, xpupil;
287
288	if (XtIsRealized((Widget)w)) {
289    		XQueryPointer (dpy, win, &rep_root, &rep_child,
290 			&rep_rootx, &rep_rooty, &dx, &dy, &rep_mask);
291		mouse.x = Tx(dx, dy, &w->eyes.t);
292		mouse.y = Ty(dx, dy, &w->eyes.t);
293		if (!TPointEqual (mouse, w->eyes.mouse)) {
294			computePupils (mouse, newpupil);
295			xpupil.x = Xx(w->eyes.pupil[0].x, w->eyes.pupil[0].y, &w->eyes.t);
296			xpupil.y = Xy(w->eyes.pupil[0].x, w->eyes.pupil[0].y, &w->eyes.t);
297			xnewpupil.x =  Xx(newpupil[0].x, newpupil[0].y, &w->eyes.t);
298			xnewpupil.y =  Xy(newpupil[0].x, newpupil[0].y, &w->eyes.t);
299			if (!XPointEqual (xpupil, xnewpupil)) {
300			    if (w->eyes.pupil[0].x != -1000 || w->eyes.pupil[0].y != -1000)
301				eyeBall (w, w->eyes.centerGC, 0);
302			    w->eyes.pupil[0] = newpupil[0];
303			    eyeBall (w, w->eyes.pupGC, 0);
304			}
305			xpupil.x = Xx(w->eyes.pupil[1].x, w->eyes.pupil[1].y, &w->eyes.t);
306			xpupil.y = Xy(w->eyes.pupil[1].x, w->eyes.pupil[1].y, &w->eyes.t);
307			xnewpupil.x =  Xx(newpupil[1].x, newpupil[1].y, &w->eyes.t);
308			xnewpupil.y =  Xy(newpupil[1].x, newpupil[1].y, &w->eyes.t);
309			if (!XPointEqual (xpupil, xnewpupil)) {
310			    if (w->eyes.pupil[1].x != -1 || w->eyes.pupil[1].y != -1)
311				eyeBall (w, w->eyes.centerGC, 1);
312			    w->eyes.pupil[1] = newpupil[1];
313			    eyeBall (w, w->eyes.pupGC, 1);
314			}
315			w->eyes.mouse = mouse;
316			w->eyes.update = 0;
317		} else {
318			if (delays[w->eyes.update + 1] != 0)
319				++w->eyes.update;
320		}
321	}
322	w->eyes.interval_id =
323		XtAppAddTimeOut(XtWidgetToApplicationContext((Widget) w),
324				delays[w->eyes.update], draw_it, (XtPointer)w);
325} /* draw_it */
326
327static void Resize (Widget gw)
328{
329    EyesWidget	w = (EyesWidget) gw;
330    XGCValues	xgcv;
331    Widget	parent;
332    int		x, y;
333
334    if (XtIsRealized (gw))
335    {
336    	XClearWindow (XtDisplay (w), XtWindow (w));
337    	SetTransform (&w->eyes.t,
338		    	0, w->core.width,
339 		    	w->core.height, 0,
340		    	W_MIN_X, W_MAX_X,
341		    	W_MIN_Y, W_MAX_Y);
342    	if (w->eyes.shape_window) {
343    	    w->eyes.shape_mask = XCreatePixmap (XtDisplay (w), XtWindow (w),
344	    	    w->core.width, w->core.height, 1);
345    	    if (!w->eyes.shapeGC)
346            	w->eyes.shapeGC = XCreateGC (XtDisplay (w), w->eyes.shape_mask, 0, &xgcv);
347    	    XSetForeground (XtDisplay (w), w->eyes.shapeGC, 0);
348    	    XFillRectangle (XtDisplay (w), w->eyes.shape_mask, w->eyes.shapeGC, 0, 0,
349	    	w->core.width, w->core.height);
350    	    XSetForeground (XtDisplay (w), w->eyes.shapeGC, 1);
351    	    eyeLiner (w, w->eyes.shape_mask, w->eyes.shapeGC, (GC) 0, 0);
352    	    eyeLiner (w, w->eyes.shape_mask, w->eyes.shapeGC, (GC) 0, 1);
353	    x = y = 0;
354	    for (parent = (Widget) w; XtParent (parent); parent = XtParent (parent)) {
355	    	x += parent->core.x + parent->core.border_width;
356	    	x += parent->core.y + parent->core.border_width;
357	    }
358    	    XShapeCombineMask (XtDisplay (parent), XtWindow (parent), ShapeBounding,
359		       	       x, y, w->eyes.shape_mask, ShapeSet);
360    	    XFreePixmap (XtDisplay (w), w->eyes.shape_mask);
361    	}
362    }
363}
364
365static void Realize (
366     Widget gw,
367     XtValueMask *valueMask,
368     XSetWindowAttributes *attrs)
369{
370    EyesWidget	w = (EyesWidget)gw;
371
372    if (w->eyes.backing_store != Always + WhenMapped + NotUseful) {
373     	attrs->backing_store = w->eyes.backing_store;
374	*valueMask |= CWBackingStore;
375    }
376    XtCreateWindow( gw, (unsigned)InputOutput, (Visual *)CopyFromParent,
377		     *valueMask, attrs );
378    Resize (gw);
379    w->eyes.interval_id =
380	XtAppAddTimeOut(XtWidgetToApplicationContext(gw),
381			delays[w->eyes.update], draw_it, (XtPointer)gw);
382}
383
384static void Destroy (Widget gw)
385{
386     EyesWidget w = (EyesWidget)gw;
387
388     if (w->eyes.interval_id)
389	XtRemoveTimeOut (w->eyes.interval_id);
390     XtReleaseGC(gw, w->eyes.pupGC);
391     XtReleaseGC(gw, w->eyes.outGC);
392     XtReleaseGC(gw, w->eyes.centerGC);
393}
394
395/* ARGSUSED */
396static void Redisplay(
397     Widget gw,
398     XEvent *event,
399     Region region)
400{
401    EyesWidget	w;
402
403    w = (EyesWidget) gw;
404    w->eyes.pupil[0].x = -1000;
405    w->eyes.pupil[0].y = -1000;
406    w->eyes.pupil[1].x = -1000;
407    w->eyes.pupil[1].y = -1000;
408    (void) repaint_window ((EyesWidget)gw);
409}
410
411/* ARGSUSED */
412static Boolean SetValues (
413    Widget current,
414    Widget request,
415    Widget new,
416    ArgList args,
417    Cardinal *num_args)
418{
419    return( FALSE );
420}
421
422EyesClassRec eyesClassRec = {
423    { /* core fields */
424    /* superclass		*/	&widgetClassRec,
425    /* class_name		*/	"Eyes",
426    /* size			*/	sizeof(EyesRec),
427    /* class_initialize		*/	ClassInitialize,
428    /* class_part_initialize	*/	NULL,
429    /* class_inited		*/	FALSE,
430    /* initialize		*/	Initialize,
431    /* initialize_hook		*/	NULL,
432    /* realize			*/	Realize,
433    /* actions			*/	NULL,
434    /* num_actions		*/	0,
435    /* resources		*/	resources,
436    /* num_resources		*/	XtNumber(resources),
437    /* xrm_class		*/	NULLQUARK,
438    /* compress_motion		*/	TRUE,
439    /* compress_exposure	*/	TRUE,
440    /* compress_enterleave	*/	TRUE,
441    /* visible_interest		*/	FALSE,
442    /* destroy			*/	Destroy,
443    /* resize			*/	Resize,
444    /* expose			*/	Redisplay,
445    /* set_values		*/	SetValues,
446    /* set_values_hook		*/	NULL,
447    /* set_values_almost	*/	NULL,
448    /* get_values_hook		*/	NULL,
449    /* accept_focus		*/	NULL,
450    /* version			*/	XtVersion,
451    /* callback_private		*/	NULL,
452    /* tm_table			*/	NULL,
453    /* query_geometry		*/	XtInheritQueryGeometry,
454    }
455};
456