/* * 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. */ /* * Single-window graphics driver framework. Under this framework, the driver * creates a single "native" display context and Agar emulates a window * manager internally. */ #include #include #include #include #include #include #include AG_DriverSw *agDriverSw = NULL; /* Root driver instance */ static void (*_Nullable agVideoResizeCallback)(Uint w, Uint h) = NULL; static void Init(void *_Nonnull obj) { AG_DriverSw *dsw = obj; dsw->w = 0; dsw->h = 0; dsw->depth = 0; dsw->flags = 0; dsw->winop = AG_WINOP_NONE; dsw->winSelected = NULL; dsw->winLastKeydown = NULL; dsw->rNom = 1000/60; dsw->rCur = 0; dsw->rLast = 0; dsw->windowIconWidth = 32; dsw->windowIconHeight = 32; dsw->bgPopup = NULL; AG_SetString(dsw, "bgColor", "rgb(0,0,0)"); } /* * Generic background popup menu. */ static void UnminimizeWindow(AG_Event *_Nonnull event) { AG_Window *win = AG_WINDOW_PTR(1); AG_WindowUnminimize(win); } #if defined(AG_DEBUG) static void OpenDebugger(AG_Event *_Nonnull event) { AG_Window *win; if ((win = AG_GuiDebugger(agWindowFocused)) != NULL) AG_WindowShow(win); } #endif /* AG_DEBUG */ static void OpenStyleEditor(AG_Event *_Nonnull event) { AG_Window *win; if ((win = AG_StyleEditor(agWindowFocused)) != NULL) AG_WindowShow(win); } static void ExitApplication(AG_Event *_Nonnull event) { AG_Quit(); } /* Display a generic popup menu for the display background. */ void AG_WM_BackgroundPopupMenu(AG_DriverSw *dsw) { AG_Menu *me; AG_MenuItem *mi; AG_Window *win; int nWindows = 0; if (dsw->bgPopup) { AG_MenuCollapseAll(dsw->bgPopup); } me = dsw->bgPopup = AG_MenuNew(NULL, 0); mi = me->itemSel = AG_MenuNode(me->root, NULL, NULL); AG_FOREACH_WINDOW_REVERSE(win, dsw) { switch (win->wmType) { case AG_WINDOW_WM_DND: case AG_WINDOW_WM_DOCK: case AG_WINDOW_WM_COMBO: case AG_WINDOW_WM_POPUP_MENU: case AG_WINDOW_WM_DROPDOWN_MENU: continue; default: break; } AG_MenuAction(mi, (win->caption[0] != '\0') ? win->caption : _("Untitled"), agIconWinMaximize.s, UnminimizeWindow, "%p", win); nWindows++; } if (nWindows > 0) { AG_MenuSeparator(mi); } AG_MenuAction(mi, _("Style editor"), agIconGear.s, OpenStyleEditor, NULL); #ifdef AG_DEBUG AG_MenuAction(mi, _("GUI debugger"), agIconMagnifier.s, OpenDebugger, NULL); #else AG_MenuDisable(AG_MenuNode(mi, _("GUI debugger"), agIconMagnifier.s)); #endif AG_MenuSeparator(mi); AG_MenuAction(mi, _("Exit application"), agIconWinClose.s, ExitApplication, NULL); win = AG_MenuExpand(NULL, mi, AGDRIVER(dsw)->mouse->x + 4, AGDRIVER(dsw)->mouse->y + 4); if (win == NULL) { Verbose("%s; ignoring\n", AG_GetError()); AG_ObjectDestroy(dsw->bgPopup); /* Undo */ dsw->bgPopup = NULL; } } /* * Update Agar window geometries following a display resize (for * single-window drivers). */ void AG_PostResizeDisplay(AG_DriverSw *dsw) { const int clampOnResize = AG_Defined(dsw, "clampOnResize"); const int w = dsw->w; const int h = dsw->h; AG_Window *win; AG_FOREACH_WINDOW(win, dsw) { AG_SizeAlloc a; a.x = WIDGET(win)->x; a.y = WIDGET(win)->y; a.w = WIDGET(win)->w; a.h = WIDGET(win)->h; AG_ObjectLock(win); if (win->flags & AG_WINDOW_MAXIMIZED) { AG_WindowSetGeometryMax(win); } else { if (win->flags & AG_WINDOW_HMAXIMIZE) { a.x = 0; a.w = w; } else if (clampOnResize) { if (a.x+a.w > w) { a.x = w - a.w; if (a.x < 0) { a.x = 0; a.w = w; } } } if (win->flags & AG_WINDOW_VMAXIMIZE) { a.y = 0; a.h = h; } else if (clampOnResize) { if (a.y+a.h > h) { a.y = h - a.h; if (a.y < 0) { a.y = 0; a.h = h; } } } AG_WidgetSizeAlloc(win, &a); AG_WindowUpdate(win); } win->dirty = 1; AG_ObjectUnlock(win); } if (agVideoResizeCallback != NULL) agVideoResizeCallback(w,h); } /* Resize a video display to the specified dimensions. */ int AG_ResizeDisplay(int w, int h) { if (agDriverSw == NULL) { AG_SetError("AG_ResizeDisplay() is only applicable to " "single-window graphics drivers"); return (-1); } if (AGDRIVER_SW_CLASS(agDriverSw)->videoResize(agDriverSw, (Uint)w, (Uint)h) == -1) { return (-1); } AG_PostResizeDisplay(agDriverSw); return (0); } /* Configure a video resize callback routine. */ void AG_SetVideoResizeCallback(void (*fn)(Uint w, Uint h)) { agVideoResizeCallback = fn; } /* Handle a window displacement initiated by the WM. */ static void WM_Move(AG_Window *_Nonnull win, int xRel, int yRel) { AG_DriverSw *dsw = (AG_DriverSw *)WIDGET(win)->drv; AG_DriverClass *dc = AGDRIVER_CLASS(dsw); AG_Rect rPrev, rNew; AG_Rect a, b; rPrev.x = WIDGET(win)->x; rPrev.y = WIDGET(win)->y; rPrev.w = WIDTH(win); rPrev.h = HEIGHT(win); WIDGET(win)->x += xRel; WIDGET(win)->y += yRel; AG_WidgetUpdateCoords(win, WIDGET(win)->x, WIDGET(win)->y); if (dc->type == AG_FRAMEBUFFER) { /* Update the background. */ rNew.x = WIDGET(win)->x; rNew.y = WIDGET(win)->y; rNew.w = WIDTH(win); rNew.h = HEIGHT(win); a.w = 0; b.w = 0; if (rNew.x > rPrev.x) { /* Right */ a.x = rPrev.x - 2; a.y = rPrev.y; a.w = rNew.x - rPrev.x + 1; a.h = rNew.h + 1; } else if (rNew.x < rPrev.x) { /* Left */ a.x = rNew.x + rNew.w - 1; a.y = rNew.y - 1; a.w = rPrev.x - rNew.x + 2; a.h = rPrev.h + 2; } if (rNew.y > rPrev.y) { /* Down */ b.x = rPrev.x - 1; b.y = rPrev.y; b.w = rNew.w + 2; b.h = rNew.y - rPrev.y; } else if (rNew.y < rPrev.y) { /* Up */ b.x = rPrev.x - 1; b.y = rNew.y + rNew.h; b.w = rPrev.w + 2; b.h = rPrev.y - rNew.y + 1; } if (a.w > 0) { AGDRIVER_CLASS(dsw)->fillRect(dsw, &a, &dsw->bgColor); if (AGDRIVER_CLASS(dsw)->updateRegion != NULL) AGDRIVER_CLASS(dsw)->updateRegion(dsw, &a); } if (b.w > 0) { AGDRIVER_CLASS(dsw)->fillRect(dsw, &b, &dsw->bgColor); if (AGDRIVER_CLASS(dsw)->updateRegion != NULL) AGDRIVER_CLASS(dsw)->updateRegion(dsw, &b); } } win->dirty = 1; if (agWindowPinnedCount > 0) AG_WindowMovePinned(win, xRel, yRel); } /* Handle a window resize operation initiated by the WM. */ static void WM_Resize(int op, AG_Window *_Nonnull win, int xRel, int yRel) { int x = WIDGET(win)->x; int y = WIDGET(win)->y; int w = WIDTH(win); int h = HEIGHT(win); switch (op) { case AG_WINOP_LRESIZE: if (!(win->flags & AG_WINDOW_NOHRESIZE)) { if (xRel < 0) { w -= xRel; x += xRel; } else if (xRel > 0) { w -= xRel; x += xRel; } } if (!(win->flags & AG_WINDOW_NOVRESIZE)) { if (yRel < 0 || yRel > 0) h += yRel; } break; case AG_WINOP_RRESIZE: if (!(win->flags & AG_WINDOW_NOHRESIZE)) { if (xRel < 0 || xRel > 0) w += xRel; } if (!(win->flags & AG_WINDOW_NOVRESIZE)) { if (yRel < 0 || yRel > 0) h += yRel; } break; case AG_WINOP_HRESIZE: if (!(win->flags & AG_WINDOW_NOHRESIZE)) { if (yRel < 0 || yRel > 0) h += yRel; } break; } AG_WindowSetGeometry(win, x, y, w, h); } /* Start window move operation. */ void AG_WM_MoveBegin(AG_Window *win) { AG_DriverSw *dsw = (AG_DriverSw *)WIDGET(win)->drv; if (!(win->flags & AG_WINDOW_DENYFOCUS)) { agWindowToFocus = win; } dsw->winSelected = win; if (!(win->flags & AG_WINDOW_NOMOVE)) dsw->winop = AG_WINOP_MOVE; } /* Terminate window move operation. */ void AG_WM_MoveEnd(AG_Window *win) { AG_DriverSw *dsw = (AG_DriverSw *)WIDGET(win)->drv; dsw->winop = AG_WINOP_NONE; dsw->winSelected = NULL; } /* * Window-manager specific processing of mouse-motion events. This is * called by low-level event processing code whenever a WM operation is * in effect. */ void AG_WM_MouseMotion(AG_DriverSw *dsw, AG_Window *win, int xRel, int yRel) { switch (dsw->winop) { case AG_WINOP_MOVE: WM_Move(win, xRel, yRel); break; case AG_WINOP_LRESIZE: WM_Resize(AG_WINOP_LRESIZE, win, xRel, yRel); break; case AG_WINOP_RRESIZE: WM_Resize(AG_WINOP_RRESIZE, win, xRel, yRel); break; case AG_WINOP_HRESIZE: WM_Resize(AG_WINOP_HRESIZE, win, xRel, yRel); break; default: break; } } /* * Reorder the window list and post the appropriate Window events following * a change in focus (single-display drivers only). */ void AG_WM_CommitWindowFocus(AG_Window *win) { #ifdef AG_DEBUG if (win->flags & AG_WINDOW_DENYFOCUS) AG_FatalError("Window is not focusable"); #endif if (agWindowFocused) { if (win && win == agWindowFocused) { /* Nothing to do */ return; } AG_PostEvent(agWindowFocused, "window-lostfocus", NULL); } if (win) { AG_ObjectLock(win); if (!(win->flags & AG_WINDOW_KEEPBELOW)) { AG_ObjectMoveToTail(win); } agWindowFocused = win; AG_PostEvent(win, "window-gainfocus", NULL); win->dirty = 1; AG_ObjectUnlock(win); } else { agWindowFocused = NULL; } } /* Limit window size to display size. */ void AG_WM_LimitWindowToDisplaySize(AG_Driver *drv, AG_SizeAlloc *a) { Uint wMax = 0, hMax = 0; AG_GetDisplaySize(drv, &wMax, &hMax); if (a->x+a->w > wMax) { a->w = wMax - a->x; } if (a->y+a->h > hMax) { a->h = hMax - a->y; } if (a->w < 0) { a->x = 0; a->w = wMax; } if (a->h < 0) { a->y = 0; a->h = hMax; } } /* Compute default positions for AG_WINDOW_TILING windows */ /* TODO optimize this */ static void GetTilingPosition(AG_Window *_Nonnull win, int *_Nonnull xDst, int *_Nonnull yDst, int w, int h) { AG_DriverSw *dsw = AGDRIVERSW(WIDGET(win)->drv); AG_Window *wOther; const int maxTests = 10000, dx = 16; int nTest = 0; int dw = dsw->w; int dh = dsw->h; int x,y, xd, yd; switch (win->alignment) { case AG_WINDOW_TL: xd = 0; yd = 0; break; case AG_WINDOW_TC: xd = (dw>>1)-(w>>1); yd = 0; break; case AG_WINDOW_TR: xd = dw-w; yd = 0; break; case AG_WINDOW_ML: xd = 0; yd = (dh>>1)-(h>>1); break; case AG_WINDOW_MR: xd = dw-w; yd = (dh>>1)-(h>>1); break; case AG_WINDOW_BL: xd = 0; yd = dh - h; break; case AG_WINDOW_BC: xd = (dw>>1)-(w>>1); yd = dh - h; break; case AG_WINDOW_BR: xd = dw-w; yd = dh - h; break; default: case AG_WINDOW_ALIGNMENT_NONE: case AG_WINDOW_MC: xd = (dw>>1)-(w>>1); yd = (dh>>1)-(h>>1); break; } x = xd; y = yd; for (;;) { OBJECT_FOREACH_CHILD(wOther, dsw, ag_window) { int xo,yo, wo,ho; if (wOther == win || (wOther->flags & AG_WINDOW_TILING) == 0) { continue; } xo = WIDGET(wOther)->x; yo = WIDGET(wOther)->y; wo = WIDGET(wOther)->w; ho = WIDGET(wOther)->h; if (x <= xo+wo && x+w >= xo && y <= yo+ho && y+h >= yo) { switch (win->alignment) { case AG_WINDOW_TL: case AG_WINDOW_TC: case AG_WINDOW_ML: case AG_WINDOW_MC: default: x += dx; if (x+w > dw) { x = 0; y += dx; if (y > dh) goto fail; } break; case AG_WINDOW_TR: case AG_WINDOW_MR: x -= dx; if (x < 0) { x = dw - w; y += dx; if (y > dh) goto fail; } break; case AG_WINDOW_BL: case AG_WINDOW_BC: x += dx; if (x+w > dw) { x = 0; y -= dx; if (y < 0) goto fail; } break; case AG_WINDOW_BR: x -= dx; if (x < 0) { x = dw - w; y -= dx; if (y < 0) goto fail; } break; } break; } } if (wOther == NULL) { goto out; } if (++nTest > maxTests) goto fail; } out: *xDst = x; *yDst = y; return; fail: Verbose("Window tiling failed\n"); *xDst = xd; *yDst = yd; } /* Compute default window coordinates from requested alignment settings. */ void AG_WM_GetPrefPosition(AG_Window *win, int *x, int *y, int w, int h) { AG_DriverSw *dsw = AGDRIVERSW(WIDGET(win)->drv); int xOffs = 0, yOffs = 0; if (win->flags & AG_WINDOW_TILING) { GetTilingPosition(win, x, y, w, h); return; } switch (win->alignment) { case AG_WINDOW_TL: *x = xOffs; *y = yOffs; break; case AG_WINDOW_TC: *x = (dsw->w >> 1) - (w >> 1) + xOffs; *y = 0; break; case AG_WINDOW_TR: *x = dsw->w - w - xOffs; *y = yOffs; break; case AG_WINDOW_ML: *x = xOffs; *y = (dsw->h >> 1) - (h >> 1) + yOffs; break; default: case AG_WINDOW_ALIGNMENT_NONE: case AG_WINDOW_MC: *x = (dsw->w >> 1) - (w >> 1) + xOffs; *y = (dsw->h >> 1) - (h >> 1) + yOffs; break; case AG_WINDOW_MR: *x = dsw->w - w - xOffs; *y = (dsw->h >> 1) - (h >> 1) + yOffs; break; case AG_WINDOW_BL: *x = xOffs; *y = dsw->h - h - yOffs; break; case AG_WINDOW_BC: *x = (dsw->w >> 1) - (w >> 1) + xOffs; *y = dsw->h - h; break; case AG_WINDOW_BR: *x = dsw->w - w - xOffs; *y = dsw->h - h - yOffs; break; } } /* Blank the display background. */ void AG_ClearBackground(void) { AG_DriverSw *dsw; if ((dsw = agDriverSw) == NULL) { return; } AGDRIVER_SW_CLASS(dsw)->videoClear(dsw, &dsw->bgColor); } /* Configure the display refresh rate (driver-dependent). */ int AG_SetRefreshRate(int fps) { if (agDriverOps->setRefreshRate == NULL) { AG_SetError("Refresh rate not applicable to graphics driver"); return (-1); } return agDriverOps->setRefreshRate(agDriverSw, fps); } /* Evaluate whether there are pending events to be processed. */ int AG_PendingEvents(AG_Driver *drv) { if (drv) { return AGDRIVER_CLASS(drv)->pendingEvents(drv); } else { return agDriverOps->pendingEvents(agDriverSw); } } /* Retrieve the next pending event, translated to generic AG_DriverEvent form. */ int AG_GetNextEvent(AG_Driver *drv, AG_DriverEvent *dev) { if (drv) { return AGDRIVER_CLASS(drv)->getNextEvent(drv, dev); } else { return agDriverOps->getNextEvent(agDriverSw, dev); } } /* Process the next pending event in generic manner. */ int AG_ProcessEvent(AG_Driver *drv, AG_DriverEvent *dev) { if (drv) { return AGDRIVER_CLASS(drv)->processEvent(drv, dev); } else { return agDriverOps->processEvent(agDriverSw, dev); } } AG_ObjectClass agDriverSwClass = { "AG_Driver:AG_DriverSw", sizeof(AG_DriverSw), { 1,7, AGC_DRIVER_SW, 0xE024 }, Init, NULL, /* reset */ NULL, /* destroy */ NULL, /* load */ NULL, /* save */ NULL /* edit */ };