/* * Copyright (c) 2009-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Interface to mice. */ #include #include #include #if defined(AG_WIDGETS) && defined(AG_DEBUG) #include #include #include #endif static void PostMouseMotion(AG_Window *_Nonnull, AG_Widget *_Nonnull, int,int, int,int); static void PostMouseButtonUp(AG_Window *_Nonnull, AG_Widget *_Nonnull, int,int, AG_MouseButton); static void PostMouseButtonDown(AG_Window *_Nonnull, AG_Widget *_Nonnull, int,int, AG_MouseButton); AG_Mouse * AG_MouseNew(void *drv, const char *desc) { AG_Mouse *ms; AG_OBJECT_ISA(drv, "AG_Driver:*"); if ((ms = TryMalloc(sizeof(AG_Mouse))) == NULL) { return (NULL); } AG_ObjectInit(ms, &agMouseClass); AGINPUTDEVICE(ms)->drv = drv; if ((AGINPUTDEVICE(ms)->desc = TryStrdup(desc)) == NULL) { goto fail; } AGDRIVER(drv)->mouse = ms; AG_ObjectAttach(&agInputDevices, ms); return (ms); fail: AG_ObjectDestroy(ms); return (NULL); } static void Init(void *_Nonnull obj) { AG_Mouse *ms = obj; OBJECT(ms)->flags |= AG_OBJECT_NAME_ONATTACH; ms->nButtons = 0; ms->btnState = 0; ms->x = 0; ms->y = 0; ms->xRel = 0; ms->yRel = 0; } /* * Change the cursor if overlapping a registered cursor area. * Generally called in response to mousemotion events. */ void AG_MouseCursorUpdate(AG_Window *win, int x, int y) { AG_Driver *drv = WIDGET(win)->drv; AG_CursorArea *ca; AG_Rect r; TAILQ_FOREACH(ca, &win->pvt.cursorAreas, cursorAreas) { if (!(ca->wid->flags & AG_WIDGET_VISIBLE)) { continue; } r.x = ca->r.x + ca->wid->rView.x1; r.y = ca->r.y + ca->wid->rView.y1; r.w = ca->r.w; r.h = ca->r.h; if (AG_RectInside(&r, x,y)) break; } if (ca == NULL) { if (drv->activeCursor != TAILQ_FIRST(&drv->cursors)) { AGDRIVER_CLASS(drv)->unsetCursor(drv); } } else if (ca->c != drv->activeCursor) { AGDRIVER_CLASS(drv)->setCursor(drv, ca->c); } } /* * Process a Mouse Motion event. * * Called from AG_EventLoop(3) or from a user-defined AG_CustomEventLoop(3). * The agDrivers VFS must be locked. */ void AG_ProcessMouseMotion(AG_Window *win, int x, int y, int xRel, int yRel) { AG_Widget *wid; /* * If requested, give a particular widget exclusivity over all * "mouse-motion" events. This is used notably by AG_Pane(3). */ if ((wid = win->widExclMotion) != NULL) { PostMouseMotion(win, wid, x,y, xRel,yRel); return; } OBJECT_FOREACH_CHILD(wid, win, ag_widget) PostMouseMotion(win, wid, x,y, xRel,yRel); } static void PostMouseMotion(AG_Window *_Nonnull win, AG_Widget *_Nonnull wid, int x, int y, int xRel, int yRel) { AG_Widget *chld; Uint flags; AG_ObjectLock(wid); flags = wid->flags; if ((flags & AG_WIDGET_VISIBLE) == 0) { /* * If a widget is hidden, it is implied that its children are * hidden as well. TODO: Lockless visibility determination. */ goto out; } if (flags & AG_WIDGET_USE_MOUSEOVER) { /* Update MOUSEOVER */ if (AG_WidgetArea(wid, x,y)) { if ((flags & AG_WIDGET_MOUSEOVER) == 0) { wid->flags |= AG_WIDGET_MOUSEOVER; AG_PostEvent(wid, "mouse-over", NULL); AG_Redraw(wid); } } else { if (flags & AG_WIDGET_MOUSEOVER) { wid->flags &= ~(AG_WIDGET_MOUSEOVER); AG_PostEvent(wid, "mouse-over", NULL); AG_Redraw(wid); } } } if ((flags & AG_WIDGET_FOCUSED) || (flags & AG_WIDGET_UNFOCUSED_MOTION)) { if (WIDGET_OPS(wid)->mouse_motion != NULL) { WIDGET_OPS(wid)->mouse_motion(wid, x - wid->rView.x1, y - wid->rView.y1, xRel, yRel); } AG_PostEvent(wid, "mouse-motion", "%i(x),%i(y),%i(xRel),%i(yRel),%i(buttons)", x - wid->rView.x1, y - wid->rView.y1, xRel, yRel, (int)wid->drv->mouse->btnState); if (wid == win->widExclMotion) { /* Receives mouse-motion exclusively. */ goto out; } } OBJECT_FOREACH_CHILD(chld, wid, ag_widget) PostMouseMotion(win, chld, x, y, xRel, yRel); out: AG_ObjectUnlock(wid); } /* * Process a Mouse Button Up (Button Released) event. * * Called from AG_EventLoop(3) or from a user-defined AG_CustomEventLoop(3). * The agDrivers VFS must be locked. */ void AG_ProcessMouseButtonUp(AG_Window *win, int x, int y, AG_MouseButton button) { AG_Widget *wid; OBJECT_FOREACH_CHILD(wid, win, ag_widget) PostMouseButtonUp(win, wid, x, y, button); } static void PostMouseButtonUp(AG_Window *_Nonnull win, AG_Widget *_Nonnull wid, int x, int y, AG_MouseButton button) { AG_Widget *chld; Uint flags; AG_ObjectLock(wid); flags = wid->flags; if ((flags & AG_WIDGET_VISIBLE) == 0) { /* * If a widget is hidden, it is implied that its children are * hidden as well. TODO: Lockless visibility determination. */ goto out; } if ((flags & AG_WIDGET_FOCUSED) || (flags & AG_WIDGET_UNFOCUSED_BUTTONUP)) { if (WIDGET_OPS(wid)->mouse_button_up != NULL) { WIDGET_OPS(wid)->mouse_button_up(wid, button, x - wid->rView.x1, y - wid->rView.y1); } else { AG_PostEvent(wid, "mouse-button-up", "%i(button),%i(x),%i(y)", (int)button, x - wid->rView.x1, y - wid->rView.y1); } } OBJECT_FOREACH_CHILD(chld, wid, ag_widget) PostMouseButtonUp(win, chld, x, y, button); out: AG_ObjectUnlock(wid); } /* * Process a Mouse Button Down (Button Pressed) event. * * Called from AG_EventLoop(3) or from a user-defined AG_CustomEventLoop(3). * The agDrivers VFS must be locked. */ void AG_ProcessMouseButtonDown(AG_Window *win, int x, int y, AG_MouseButton button) { AG_Driver *drv; AG_Window *winModal; AG_Widget *wid; if ((win->flags & AG_WINDOW_MODAL) == 0) { OBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) { AG_OBJECT_ISA(drv, "AG_Driver:*"); OBJECT_FOREACH_CHILD(winModal, drv, ag_window) { if (winModal == win || !winModal->visible || (winModal->flags & AG_WINDOW_MODAL) == 0) { continue; } AG_OBJECT_ISA(winModal, "AG_Widget:AG_Window:*"); AG_PostEvent(winModal, "window-close", NULL); } } } if ((WIDGET(win)->drv->kbd->modState & AG_KEYMOD_CTRL) && agCtrlMouseWheelAction == 1) { if (button == AG_MOUSE_WHEELUP) { AG_ZoomIn(); return; } else if (button == AG_MOUSE_WHEELDOWN) { AG_ZoomOut(); return; } } OBJECT_FOREACH_CHILD(wid, win, ag_widget) PostMouseButtonDown(win, wid, x, y, button); } static void PostMouseButtonDown(AG_Window *_Nonnull win, AG_Widget *_Nonnull wid, int x, int y, AG_MouseButton button) { AG_Widget *chld; AG_Event *ev; AG_ObjectLock(wid); if ((wid->flags & AG_WIDGET_VISIBLE) == 0) goto out; OBJECT_FOREACH_CHILD(chld, wid, ag_widget) PostMouseButtonDown(win, chld, x, y, button); if ((wid->flags & AG_WIDGET_UNFOCUSED_BUTTONDOWN) == 0) { if (!AG_WidgetSensitive(wid, x,y)) goto out; } if (WIDGET_OPS(wid)->mouse_button_down != NULL) { WIDGET_OPS(wid)->mouse_button_down(wid, button, x - wid->rView.x1, y - wid->rView.y1); } else { TAILQ_FOREACH(ev, &OBJECT(wid)->events, events) { if (strcmp(ev->name, "mouse-button-down") == 0) break; } if (ev != NULL) AG_PostEvent(wid, "mouse-button-down", "%i(button),%i(x),%i(y)", (int)button, x - wid->rView.x1, y - wid->rView.y1); } out: AG_ObjectUnlock(wid); } #if defined(AG_WIDGETS) && defined(AG_DEBUG) static void * Edit(void *obj) { AG_Mouse *mouse = obj; AG_Box *box; AG_Label *lbl; box = AG_BoxNewVert(NULL, AG_BOX_HFILL); lbl = AG_LabelNewS(box, 0, AGINPUTDEVICE(mouse)->desc); AG_SetFontFamily(lbl, "league-spartan"); AG_SetFontSize(lbl, "130%"); lbl = AG_LabelNewPolledMT(box, AG_LABEL_HFILL, &OBJECT(mouse)->lock, _("Button Count: %u\n" "Button State: 0x%x\n" "Cursor Position: %d,%d [%d %d]"), &mouse->nButtons, &mouse->btnState, &mouse->x, &mouse->y, &mouse->xRel, &mouse->yRel); AG_RedrawOnTick(lbl, 100); return (box); } #endif /* AG_WIDGETS and AG_DEBUG */ AG_ObjectClass agMouseClass = { "Agar(InputDevice:Mouse)", sizeof(AG_Mouse), { 1,0, AGC_MOUSE, 0x1F5B0 }, Init, NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ #if defined(AG_WIDGETS) && defined(AG_DEBUG) Edit #else NULL /* edit */ #endif };