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