1 /*
2  * Copyright (c) 2009-2012 Hypertriton, Inc. <http://hypertriton.com/>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 /*
27  * Generic interface to mice. Most graphics drivers will register a single
28  * mouse object, but multiple instances are supported.
29  */
30 
31 #include <agar/core/core.h>
32 #include <agar/gui/window.h>
33 #include <agar/gui/cursors.h>
34 
35 AG_Mouse *
AG_MouseNew(void * drv,const char * desc)36 AG_MouseNew(void *drv, const char *desc)
37 {
38 	AG_Mouse *ms;
39 
40 	AG_ASSERT_CLASS(drv, "AG_Driver:*");
41 
42 	if ((ms = TryMalloc(sizeof(AG_Mouse))) == NULL) {
43 		return (NULL);
44 	}
45 	AG_ObjectInit(ms, &agMouseClass);
46 	AGINPUTDEV(ms)->drv = drv;
47 	if ((AGINPUTDEV(ms)->desc = TryStrdup(desc)) == NULL) {
48 		goto fail;
49 	}
50 	AGDRIVER(drv)->mouse = ms;
51 	AG_ObjectAttach(&agInputDevices, ms);
52 	return (ms);
53 fail:
54 	AG_ObjectDestroy(ms);
55 	return (NULL);
56 }
57 
58 static void
Init(void * obj)59 Init(void *obj)
60 {
61 	AG_Mouse *ms = obj;
62 
63 	OBJECT(ms)->flags |= AG_OBJECT_NAME_ONATTACH;
64 	ms->nButtons = 0;
65 	ms->btnState = 0;
66 	ms->x = 0;
67 	ms->y = 0;
68 	ms->xRel = 0;
69 	ms->yRel = 0;
70 }
71 
72 /* Update the mouse state following a cursor motion event. */
73 void
AG_MouseMotionUpdate(AG_Mouse * ms,int x,int y)74 AG_MouseMotionUpdate(AG_Mouse *ms, int x, int y)
75 {
76 	ms->xRel = x - ms->x;
77 	ms->yRel = y - ms->y;
78 
79 	if (ms->xRel != 0 || ms->yRel != 0) {
80 		ms->x = x;
81 		ms->y = y;
82 	}
83 }
84 
85 /*
86  * Change the cursor if overlapping a registered cursor area.
87  * Generally called in response to mousemotion events.
88  */
89 void
AG_MouseCursorUpdate(AG_Window * win,int x,int y)90 AG_MouseCursorUpdate(AG_Window *win, int x, int y)
91 {
92 	AG_Driver *drv = WIDGET(win)->drv;
93 	AG_CursorArea *ca;
94 	AG_Rect r;
95 
96 	TAILQ_FOREACH(ca, &win->cursorAreas, cursorAreas) {
97 		if (!(ca->wid->flags & AG_WIDGET_VISIBLE)) {
98 			continue;
99 		}
100 		r.x = ca->r.x + ca->wid->rView.x1;
101 		r.y = ca->r.y + ca->wid->rView.y1;
102 		r.w = ca->r.w;
103 		r.h = ca->r.h;
104 		if (AG_RectInside(&r, x,y))
105 			break;
106 	}
107 	if (ca == NULL) {
108 		if (drv->activeCursor != TAILQ_FIRST(&drv->cursors)) {
109 			AGDRIVER_CLASS(drv)->unsetCursor(drv);
110 		}
111 	} else if (ca->c != drv->activeCursor) {
112 		AGDRIVER_CLASS(drv)->setCursor(drv, ca->c);
113 	}
114 }
115 
116 /* Update the mouse state following a button press/release event. */
117 void
AG_MouseButtonUpdate(AG_Mouse * ms,AG_MouseButtonAction a,int which)118 AG_MouseButtonUpdate(AG_Mouse *ms, AG_MouseButtonAction a, int which)
119 {
120 	if (a == AG_BUTTON_PRESSED) {
121 		ms->btnState |= AG_MOUSE_BUTTON(which);
122 	} else {
123 		ms->btnState &= ~(AG_MOUSE_BUTTON(which));
124 	}
125 }
126 
127 /*
128  * Deliver a `mouse-motion' event to all active widgets which are
129  * either focused or have UNFOCUSED_MOTION set.
130  *
131  * Also, deliver `mouse-over' event (and update MOUSEOVER flag) to
132  * all widgets with USE_MOUSEOVER enabled.
133  */
134 static void
PostMouseMotion(AG_Window * win,AG_Widget * wid,int x,int y,int xRel,int yRel,Uint state)135 PostMouseMotion(AG_Window *win, AG_Widget *wid, int x, int y, int xRel,
136     int yRel, Uint state)
137 {
138 	AG_Widget *chld;
139 
140 	AG_ObjectLock(wid);
141 	if ((wid->flags & AG_WIDGET_VISIBLE) &&
142 	   !(wid->flags & AG_WIDGET_DISABLED)) {
143 		if (wid->flags & AG_WIDGET_USE_MOUSEOVER) {
144 			if (AG_WidgetArea(wid, x,y)) {
145 				if ((wid->flags & AG_WIDGET_MOUSEOVER) == 0) {
146 					wid->flags |= AG_WIDGET_MOUSEOVER;
147 					AG_PostEvent(NULL, wid, "mouse-over",
148 					    NULL);
149 					AG_Redraw(wid);
150 				}
151 			} else {
152 				if (wid->flags & AG_WIDGET_MOUSEOVER) {
153 					wid->flags &= ~(AG_WIDGET_MOUSEOVER);
154 					AG_PostEvent(NULL, wid, "mouse-over",
155 					    NULL);
156 					AG_Redraw(wid);
157 				}
158 			}
159 		}
160 		if ((wid->flags & AG_WIDGET_FOCUSED) ||
161 		    (wid->flags & AG_WIDGET_UNFOCUSED_MOTION)) {
162 			AG_PostEvent(NULL, wid, "mouse-motion",
163 			    "%i(x),%i(y),%i(xRel),%i(yRel),%i(buttons)",
164 			    x - wid->rView.x1,
165 			    y - wid->rView.y1,
166 			    xRel,
167 			    yRel,
168 			    (int)state);
169 
170 			if (wid == win->widExclMotion)
171 				goto out;		/* Skip other widgets */
172 		}
173 	}
174 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget)
175 		PostMouseMotion(win, chld, x, y, xRel, yRel, state);
176 out:
177 	AG_ObjectUnlock(wid);
178 }
179 
180 /*
181  * Deliver a `mouse-button-up' event to all active widgets which are
182  * either focused or have UNFOCUSED_BUTTONUP set.
183  */
184 static void
PostMouseButtonUp(AG_Window * win,AG_Widget * wid,int x,int y,AG_MouseButton button)185 PostMouseButtonUp(AG_Window *win, AG_Widget *wid, int x, int y,
186     AG_MouseButton button)
187 {
188 	AG_Widget *chld;
189 
190 	AG_ObjectLock(wid);
191 	if ((wid->flags & AG_WIDGET_VISIBLE) &&
192 	   !(wid->flags & AG_WIDGET_DISABLED)) {
193 		if ((wid->flags & AG_WIDGET_FOCUSED) ||
194 		    (wid->flags & AG_WIDGET_UNFOCUSED_BUTTONUP)) {
195 			AG_PostEvent(NULL, wid, "mouse-button-up",
196 			    "%i(button),%i(x),%i(y)",
197 			    (int)button,
198 			    x - wid->rView.x1,
199 			    y - wid->rView.y1);
200 		}
201 	}
202 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
203 		PostMouseButtonUp(win, chld, x, y, button);
204 	}
205 	AG_ObjectUnlock(wid);
206 }
207 
208 /*
209  * Deliver a `mouse-button-down' event to the active widget at specified
210  * window coordinates (if multiple widgets overlap, deliver to the topmost
211  * widget which has a `mouse-button-down' handler defined).
212  */
213 static int
PostMouseButtonDown(AG_Window * win,AG_Widget * wid,int x,int y,AG_MouseButton button)214 PostMouseButtonDown(AG_Window *win, AG_Widget *wid, int x, int y,
215     AG_MouseButton button)
216 {
217 	AG_Widget *chld;
218 	AG_Event *ev;
219 
220 	AG_ObjectLock(wid);
221 
222 	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
223 		if (PostMouseButtonDown(win, chld, x, y, button))
224 			goto match;
225 	}
226 	if ((wid->flags & AG_WIDGET_VISIBLE) &&
227 	   !(wid->flags & AG_WIDGET_DISABLED) &&
228 	    AG_WidgetSensitive(wid, x, y)) {
229 		TAILQ_FOREACH(ev, &OBJECT(wid)->events, events) {
230 			if (strcmp(ev->name, "mouse-button-down") == 0)
231 				break;
232 		}
233 		if (ev != NULL) {
234 			AG_PostEvent(NULL, wid, "mouse-button-down",
235 			    "%i(button),%i(x),%i(y)",
236 			    (int)button,
237 			    x - wid->rView.x1,
238 			    y - wid->rView.y1);
239 			goto match;
240 		}
241 	}
242 	AG_ObjectUnlock(wid);
243 	return (0);
244 match:
245 	AG_ObjectUnlock(wid);
246 	return (1);
247 }
248 
249 /*
250  * Process a `mouse-motion' event relative to the given window.
251  *
252  * This is generally called from the event-handling section of low-level
253  * driver code. The agDrivers VFS must be locked.
254  */
255 void
AG_ProcessMouseMotion(AG_Window * win,int x,int y,int xRel,int yRel,Uint state)256 AG_ProcessMouseMotion(AG_Window *win, int x, int y, int xRel, int yRel,
257     Uint state)
258 {
259 	AG_Widget *wid;
260 
261 	/*
262 	 * If needed, we give a particular widget exclusivity over all
263 	 * `mouse-motion' events. This is used notably by AG_Pane(3).
264 	 */
265 	if ((wid = win->widExclMotion) != NULL) {
266 		AG_ObjectLock(wid);
267 		PostMouseMotion(win, wid, x, y, xRel, yRel, state);
268 		AG_ObjectUnlock(wid);
269 		return;
270 	}
271 
272 	OBJECT_FOREACH_CHILD(wid, win, ag_widget)
273 		PostMouseMotion(win, wid, x, y, xRel, yRel, state);
274 }
275 
276 /*
277  * Process a mouse-button event relative to the given window.
278  *
279  * This is generally called from the event-handling section of low-level
280  * driver code. The agDrivers VFS must be locked.
281  */
282 void
AG_ProcessMouseButtonUp(AG_Window * win,int x,int y,AG_MouseButton button)283 AG_ProcessMouseButtonUp(AG_Window *win, int x, int y, AG_MouseButton button)
284 {
285 	AG_Widget *wid;
286 
287 	OBJECT_FOREACH_CHILD(wid, win, ag_widget)
288 		PostMouseButtonUp(win, wid, x, y, button);
289 }
290 
291 /*
292  * Process a mouse-button event relative to the given window.
293  *
294  * This is generally called from the event-handling section of low-level
295  * driver code. The agDrivers VFS must be locked.
296  */
297 void
AG_ProcessMouseButtonDown(AG_Window * win,int x,int y,AG_MouseButton button)298 AG_ProcessMouseButtonDown(AG_Window *win, int x, int y, AG_MouseButton button)
299 {
300 	AG_Widget *wid;
301 	AG_Driver *drv;
302 	AG_Window *winOther;
303 
304 	/* Handle modal windows. */
305 	AGOBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) {
306 		AG_FOREACH_WINDOW(winOther, drv) {
307 			if (winOther == win) {
308 				continue;
309 			}
310 			if ((winOther->flags & AG_WINDOW_MODAL) &&
311 			    (winOther->transientFor == NULL ||	/* App modal */
312 			     winOther->transientFor == win)) {	/* Parent modal */
313 				AG_PostEvent(NULL, winOther,
314 				    "window-modal-close", "%i,%i", x, y);
315 			}
316 		}
317 	}
318 
319 	OBJECT_FOREACH_CHILD(wid, win, ag_widget)
320 		PostMouseButtonDown(win, wid, x, y, button);
321 }
322 
323 AG_ObjectClass agMouseClass = {
324 	"Agar(InputDevice:Mouse)",
325 	sizeof(AG_Mouse),
326 	{ 0,0 },
327 	Init,
328 	NULL,		/* reinit */
329 	NULL,		/* destroy */
330 	NULL,		/* load */
331 	NULL,		/* save */
332 	NULL		/* edit */
333 };
334