/* * Copyright (c) 2001-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. */ /* * The base class for Agar widgets. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * Agar style attributes. */ const char *agStyleAttributes[] = { /* * Color Scheme */ "color", /* Foreground primary */ "background-color", /* Background primary */ "text-color", /* Text and vector icons */ "line-color", /* Lines and filled shapes */ "high-color", /* Left and top borders */ "low-color", /* Right and bottom borders */ "selection-color", /* Selection primary */ "unused-color", /* * Typography */ "font-family", /* Font face (or filename) */ "font-size", /* Font size (in pt, px or %) */ "font-weight", /* Boldness (thin | extralight | light | normal | semibold | bold | extrabold | black | !parent) */ "font-style", /* Style (normal | oblique | italic | !parent) */ "font-stretch", /* Width variant (normal | ultracondensed | condensed | semicondensed | semiexpanded | expanded | ultraexpanded | !parent) */ /* * Box Model */ "margin", /* Margin (space between border & outer bounding box) */ "padding", /* Padding (space between content & border) */ /* * Element Spacing */ "spacing", /* Spacing between elements (px) */ NULL }; /* Style-effecting State Names */ const char *agWidgetStateNames[] = { "", /* Unfocused (default) */ "#disabled", /* Disabled */ "#focused", /* Focused */ "#hover", /* Mouseover */ NULL }; /* Per-Widget Color Palette */ AG_WidgetPalette agDefaultPalette = {{ #if AG_MODEL == AG_MEDIUM /* --- TrueColor --- */ { /* unfocused */ { 70, 70, 70,255}, /* color */ { 0, 0, 0, 0}, /* background-color */ {200,200,200,255}, /* text-color */ { 50, 50, 50,255}, /* line-color */ {100,100,100,255}, /* high-color */ { 60, 60, 60,255}, /* low-color */ { 0, 0, 80,255}, /* selection-color */ { 0, 0, 0, 0} }, { /* #disabled */ {160,160,160,255}, /* color */ { 0, 0, 0, 0}, /* background-color */ {240,240,240,255}, /* text-color */ {150,150,150,255}, /* line-color */ {120,120,120,255}, /* high-color */ { 90, 90, 90,255}, /* low-color */ { 80, 80,120,255}, /* selection-color */ { 0, 0, 0, 0} }, { /* #focused */ {100,100,100,255}, /* color */ { 0, 0, 0, 0}, /* background-color */ {240,240,240,255}, /* text-color */ { 50, 50, 70,255}, /* line-color */ {110,110,110,255}, /* high-color */ { 60, 60, 60,255}, /* low-color */ { 0, 0,120,255}, /* selection-color */ { 0, 0, 0, 0} }, { /* #hover */ {130,130,130,255}, /* color */ { 0, 0, 0, 0}, /* background-color */ {240,240,240,255}, /* text-color */ { 50, 50, 50,255}, /* line-color */ {120,120,120,255}, /* high-color */ { 60, 60, 60,255}, /* low-color */ { 0, 0,120,255}, /* selection-color */ { 0, 0, 0, 0} }, #elif AG_MODEL == AG_LARGE /* --- DeepColor --- */ { /* unfocused */ {0x4646,0x4646,0x4646,0xffff}, /* color */ {0x0000,0x0000,0x0000,0x0000}, /* background-color */ {0xc8c8,0xc8c8,0xc8c8,0xffff}, /* text-color */ {0x3232,0x3232,0x3232,0xffff}, /* line-color */ {0x7878,0x7878,0x7878,0xffff}, /* high-color */ {0x3c3c,0x3c3c,0x3c3c,0xffff}, /* low-color */ {0x0000,0x0000,0x7878,0xffff}, /* selection-color */ {0x0000,0x0000,0x0000,0x0000} }, { /* #disabled */ {0xa0a0,0xa0a0,0xa0a0,0xffff}, /* color */ {0x0000,0x0000,0x0000,0x0000}, /* background-color */ {0xf0f0,0xf0f0,0xf0f0,0xffff}, /* text-color */ {0x4646,0x4646,0x4646,0xffff}, /* line-color */ {0x7878,0x7878,0x7878,0xffff}, /* high-color */ {0x5a5a,0x5a5a,0x5a5a,0xffff}, /* low-color */ {0x5050,0x5050,0x7878,0xffff}, /* selection-color */ {0x0000,0x0000,0x0000,0x0000} }, { /* #focused */ {0x6464,0x6464,0x6464,0xffff}, /* color */ {0x0000,0x0000,0x0000,0x0000}, /* background-color */ {0xf0f0,0xf0f0,0xf0f0,0xffff}, /* text-color */ {0x3232,0x3232,0x4646,0xffff}, /* line-color */ {0x6e6e,0x6e6e,0x6e6e,0xffff}, /* high-color */ {0x3c3c,0x3c3c,0x3c3c,0xffff}, /* low-color */ {0x0000,0x0000,0x7878,0xffff}, /* selection-color */ {0x0000,0x0000,0x0000,0x0000} }, { /* #hover */ {0x8282,0x8282,0x8282,0xffff}, /* color */ {0x0000,0x0000,0x0000,0x0000}, /* background-color */ {0xf0f0,0xf0f0,0xf0f0,0xffff}, /* text-color */ {0x3232,0x3232,0x3232,0xffff}, /* line-color */ {0x7878,0x7878,0x7878,0xffff}, /* high-color */ {0x3c3c,0x3c3c,0x3c3c,0xffff}, /* low-color */ {0x0000,0x0000,0x7878,0xffff}, /* selection-color */ {0x0000,0x0000,0x0000,0x0000} } #endif /* AG_LARGE */ }}; #if defined(AG_DEBUG) && defined(AG_WIDGETS) AG_Widget *_Nullable agDebuggerTgt = NULL; #endif #if defined(AG_WIDGETS) AG_Widget *_Nullable agStyleEditorTgt = NULL; #endif /* Import inlinables */ #undef AG_INLINE_HEADER #include "inline_widget.h" static void FocusWidget(AG_Widget *_Nonnull); static void UnfocusWidget(AG_Widget *_Nonnull); static void Apply_Font_Size(float *_Nonnull, float, const char *_Nonnull); static void Apply_Font_Weight(Uint *_Nonnull, Uint, const char *_Nonnull); static void Apply_Font_Style(Uint *_Nonnull, Uint, const char *_Nonnull); static void Apply_Font_Stretch(Uint *_Nonnull, Uint, const char *_Nonnull); static void Apply_Padding(AG_Widget *_Nonnull, const char *_Nonnull); static void Inherit_Padding(AG_Widget *_Nonnull, char *_Nonnull, AG_Size); static void Inherit_Margin(AG_Widget *_Nonnull, char *_Nonnull, AG_Size); static void Apply_Margin(AG_Widget *_Nonnull, const char *_Nonnull); static void Apply_Spacing(AG_Widget *_Nonnull, const char *_Nonnull); /* Set the parent window/driver pointers on a widget and its children. */ static void SetParentWindow(AG_Widget *_Nonnull wid, AG_Window *_Nullable win) { AG_Widget *chld; AG_CursorArea *ca, *caNext; wid->window = win; if (win) { wid->drv = AGDRIVER( OBJECT(win)->parent ); wid->drvOps = AGDRIVER_CLASS(wid->drv); if (wid->flags & AG_WIDGET_USE_TEXT) win->flags |= AG_WINDOW_USE_TEXT; /* * Commit any previously deferred AG_MapStockCursor() * operation. */ for (ca = TAILQ_FIRST(&wid->pvt.cursorAreas); ca != TAILQ_END(&wid->pvt.cursorAreas); ca = caNext) { caNext = TAILQ_NEXT(ca, cursorAreas); if (ca->stock >= 0 && ca->stock < wid->drv->nCursors) { AG_Cursor *ac; int i = 0; TAILQ_FOREACH(ac, &wid->drv->cursors, cursors) { if (i++ == ca->stock) break; } if (ac) { ca->c = ac; TAILQ_INSERT_TAIL(&win->pvt.cursorAreas, ca, cursorAreas); } else { free(ca); } } else { free(ca); } } TAILQ_INIT(&wid->pvt.cursorAreas); } else { wid->drv = NULL; wid->drvOps = NULL; } OBJECT_FOREACH_CHILD(chld, wid, ag_widget) SetParentWindow(chld, win); } /* Set the parent driver pointers on a widget and its children. */ static void SetParentDriver(AG_Widget *_Nonnull wid, AG_Driver *_Nullable drv) { AG_Widget *chld; if (drv) { wid->drv = AGDRIVER(drv); wid->drvOps = AGDRIVER_CLASS(drv); } else { wid->drv = NULL; wid->drvOps = NULL; } OBJECT_FOREACH_CHILD(chld, wid, ag_widget) SetParentDriver(chld, drv); } static void OnAttach(AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); const void *parent = AG_PTR(1); if (AG_WINDOW_ISA(parent) && /* Widget attaching to a Window */ AG_WIDGET_ISA(wid)) { AG_Widget *wParent = WIDGET(parent); SetParentWindow(wid, AGWINDOW(wParent)); if (AGWINDOW(wParent)->visible) { wid->flags |= AG_WIDGET_UPDATE_WINDOW; AG_PostEvent(wid, "widget-shown", NULL); } if (wParent->flags & AG_WIDGET_DISABLE_ON_ATTACH) { wid->flags |= AG_WIDGET_DISABLED; } if (wid->nSurfaces > 0) { /* * The widget may have been detached from a different * driver and re-attached to this one, so invalidate * all texture handles just to be safe. */ memset(wid->textures, 0, wid->nSurfaces * sizeof(Uint)); } } else if (AG_WIDGET_ISA(parent) && /* Widget attaching to another */ AG_WIDGET_ISA(wid)) { AG_Widget *wParent = WIDGET(parent); AG_Window *window = wParent->window; SetParentWindow(wid, window); if (window != NULL) { AG_OBJECT_ISA(window, "AG_Widget:AG_Window:*"); if (window->visible) AG_PostEvent(wid, "widget-shown", NULL); } if (wParent->flags & AG_WIDGET_DISABLE_ON_ATTACH) { wid->flags |= AG_WIDGET_DISABLED; } } else if (AG_DRIVER_ISA(parent) && /* Window attaching to a Driver */ AG_WINDOW_ISA(wid)) { SetParentDriver(wid, AGDRIVER(parent)); } else { #ifdef AG_VERBOSITY AG_FatalErrorF("Cannot attach " "(" AGSI_CYAN "%s *" AGSI_RST ") %s to " "(" AGSI_CYAN "%s *" AGSI_RST ") %s", OBJECT_CLASS(wid)->name, OBJECT(wid)->name, OBJECT_CLASS(parent)->name, OBJECT(parent)->name); #else AG_FatalError("Widget cannot attach to parent"); #endif } } static void OnDetach(AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); const void *parent = AG_PTR(1); AG_Widget *chld; AG_InputDevice *id; /* * Forward the "detached" event to child widgets. */ OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { event->argv[1].data.p = chld; /* SENDER */ AG_ForwardEvent(chld, event); } #if defined(AG_DEBUG) && defined(AG_WIDGETS) if (wid == agDebuggerTgt) AG_GuiDebuggerDetachTarget(); #endif #if defined(AG_WIDGETS) if (wid == agStyleEditorTgt) AG_StyleEditorDetachTarget(); #endif if (wid->drv != NULL) { /* Unmap textures */ Uint id; int tex; for (id = 0; id < wid->nSurfaces; id++) { if ((tex = wid->textures[id]) == 0) { continue; } wid->drvOps->deleteTexture(wid->drv, tex); wid->textures[id] = 0; } } AG_LockVFS(&agInputDevices); detach_input_devs: AGOBJECT_FOREACH_CHILD(id, &agInputDevices, ag_input_device) { int i; for (i = 0; i < id->nWidGrab; i++) { if (id->widGrab[i] == wid) { Debug(wid, "Ungrabbing %s on detach\n", OBJECT(id)->name); AG_UngrabInputDevice(wid, id); goto detach_input_devs; } } } AG_UnlockVFS(&agInputDevices); if (AG_WIDGET_ISA(parent) && AG_WIDGET_ISA(wid)) { if (wid->window) { if (wid->window->visible) { AG_PostEvent(wid, "widget-hidden", NULL); } AG_UnmapAllCursors(wid->window, wid); } SetParentWindow(wid, NULL); } else if (AG_DRIVER_ISA(parent) && AG_WINDOW_ISA(wid)) { SetParentDriver(wid, NULL); } else { #ifdef AG_VERBOSITY AG_FatalErrorF("Unexpected parent on detach. " "Why is %s attached to %s?", OBJECT(wid)->name, OBJECT(parent)->name); #else AG_FatalError("Unexpected parent on detach"); #endif } } static Uint32 RedrawOnTickTimeout(AG_Timer *_Nonnull to, AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); if (wid->window) { AG_OBJECT_ISA(wid->window, "AG_Widget:AG_Window:*"); wid->window->dirty = 1; } return (to->ival); } static Uint32 RedrawOnChangeTimeout(AG_Timer *_Nonnull to, AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); AG_RedrawTie *rt = AG_PTR(1); AG_Variable *V, Vd; void *p; V = AG_GetVariable(wid, rt->name, &p); AG_DerefVariable(&Vd, V); if (!rt->VlastInited || AG_CompareVariables(&Vd, &rt->Vlast) != 0) { if (wid->window) { AG_OBJECT_ISA(wid->window, "AG_Widget:AG_Window:*"); wid->window->dirty = 1; } AG_CopyVariable(&rt->Vlast, &Vd); rt->VlastInited = 1; } AG_UnlockVariable(V); return (to->ival); } static void OnShow(AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); AG_Widget *chld; AG_RedrawTie *rt; #ifdef AG_DEBUG if (wid->font == NULL) { AG_FatalError("!font"); } AG_OBJECT_ISA(wid->font, "AG_Font:*"); #endif OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { AG_ForwardEvent(chld, event); } wid->flags |= AG_WIDGET_VISIBLE; TAILQ_FOREACH(rt, &wid->pvt.redrawTies, redrawTies) { switch (rt->type) { case AG_REDRAW_ON_TICK: AG_AddTimer(wid, &rt->to, rt->ival, RedrawOnTickTimeout, NULL); break; case AG_REDRAW_ON_CHANGE: AG_AddTimer(wid, &rt->to, rt->ival, RedrawOnChangeTimeout, "%p", rt); break; } } } static void OnHide(AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); AG_Widget *chld; AG_RedrawTie *rt; OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { AG_ForwardEvent(chld, event); } wid->flags &= ~(AG_WIDGET_VISIBLE); TAILQ_FOREACH(rt, &wid->pvt.redrawTies, redrawTies) { switch (rt->type) { case AG_REDRAW_ON_TICK: case AG_REDRAW_ON_CHANGE: AG_DelTimer(wid, &rt->to); break; } } if (wid->drv != NULL) { /* Unmap textures */ Uint id; int tex; for (id = 0; id < wid->nSurfaces; id++) { if ((tex = wid->textures[id]) == 0) { continue; } wid->drvOps->deleteTexture(wid->drv, tex); wid->textures[id] = 0; } } } static void Init(void *_Nonnull obj) { AG_Widget *wid = obj; OBJECT(wid)->flags |= AG_OBJECT_NAME_ONATTACH; wid->flags = 0; memset(&wid->x, 0xff, sizeof(int) + /* (-1) -> x */ sizeof(int) + /* y */ sizeof(int) + /* w */ sizeof(int) + /* h */ sizeof(AG_Rect) + /* r */ sizeof(AG_Rect2) + /* rView */ sizeof(AG_Rect2)); /* rSens */ memset(&wid->nSurfaces, 0, sizeof(Uint) + /* nSurfaces */ sizeof(AG_Surface **) + /* surfaces */ sizeof(Uint *) + /* surfaceFlags */ sizeof(Uint *) + /* textures */ sizeof(AG_TexCoord *) + /* texcoords */ sizeof(AG_Widget *) + /* focusFwd */ sizeof(AG_Window *) + /* window */ sizeof(AG_Driver *) + /* drv */ sizeof(AG_DriverClass *) + /* drvOps */ sizeof(AG_StyleSheet *) + /* css */ sizeof(enum ag_widget_state) + /* state */ sizeof(int)*4 + /* margin */ sizeof(int)*4 + /* padding */ sizeof(Uint)*2); /* spacing */ wid->font = agDefaultFont; wid->pal = agDefaultPalette; #ifdef HAVE_OPENGL wid->gl = NULL; #endif AG_VEC_INIT(&wid->actions); TAILQ_INIT(&wid->pvt.mouseActions); TAILQ_INIT(&wid->pvt.keyActions); TAILQ_INIT(&wid->pvt.redrawTies); TAILQ_INIT(&wid->pvt.cursorAreas); AG_SetEvent(wid, "attached", OnAttach, NULL); AG_SetEvent(wid, "detached", OnDetach, NULL); AG_SetEvent(wid, "widget-shown", OnShow, NULL); AG_SetEvent(wid, "widget-hidden", OnHide, NULL); } /* Arrange for a redraw whenever a given binding value changes. */ void AG_RedrawOnChange(void *obj, int refresh_ms, const char *name) { AG_Widget *wid = obj; AG_RedrawTie *rt; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); TAILQ_FOREACH(rt, &wid->pvt.redrawTies, redrawTies) { if (rt->type == AG_REDRAW_ON_CHANGE && strcmp(rt->name, name) == 0 && rt->ival == refresh_ms) break; } if (rt) { AG_ResetTimer(wid, &rt->to, refresh_ms); goto out; } rt = Malloc(sizeof(AG_RedrawTie)); rt->type = AG_REDRAW_ON_CHANGE; rt->ival = refresh_ms; rt->VlastInited = 0; Strlcpy(rt->name, name, sizeof(rt->name)); AG_InitTimer(&rt->to, "redrawTie-", 0); #ifdef AG_DEBUG Strlcat(rt->to.name, name, sizeof(rt->to.name)); #endif TAILQ_INSERT_TAIL(&wid->pvt.redrawTies, rt, redrawTies); if (wid->flags & AG_WIDGET_VISIBLE) { AG_AddTimer(wid, &rt->to, rt->ival, RedrawOnChangeTimeout, "%p", rt); } else { /* Fire from OnShow() */ } out: AG_ObjectUnlock(wid); } /* * Enable/disable auto-refresh. Enabling auto-refresh forces the widget to be * redrawn unconditionally every refresh_ms (or -1 disables auto-refresh). * Return the previous setting in ms (or -1 if auto-refresh was not set). */ int AG_RedrawOnTick(void *obj, int refresh_ms) { AG_Widget *wid = obj; AG_RedrawTie *rt; int rv = -1; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); if (refresh_ms == -1) { TAILQ_FOREACH(rt, &wid->pvt.redrawTies, redrawTies) { if (rt->type == AG_REDRAW_ON_TICK) break; } if (rt) { rv = rt->ival; TAILQ_REMOVE(&wid->pvt.redrawTies, rt, redrawTies); AG_DelTimer(wid, &rt->to); free(rt); } goto out; } rt = Malloc(sizeof(AG_RedrawTie)); rt->type = AG_REDRAW_ON_TICK; rt->ival = refresh_ms; rt->name[0] = '\0'; AG_InitTimer(&rt->to, "redrawTick", 0); TAILQ_INSERT_TAIL(&wid->pvt.redrawTies, rt, redrawTies); if (wid->flags & AG_WIDGET_VISIBLE) { AG_AddTimer(wid, &rt->to, rt->ival, RedrawOnTickTimeout, NULL); } else { /* Fire from OnShow() */ } out: AG_ObjectUnlock(wid); return (rv); } /* Default event handler for "key-down" (for widgets using Actions). */ void AG_WidgetStdKeyDown(AG_Event *event) { AG_Widget *wid = AG_WIDGET_SELF(); const int sym = AG_INT(1); const int mod = AG_INT(2); AG_ExecKeyAction(wid, AG_ACTION_ON_KEYDOWN, sym, mod); } /* Default event handler for "key-up" (for widgets using Actions). */ void AG_WidgetStdKeyUp(AG_Event *event) { AG_Widget *wid = AG_WIDGET_SELF(); const int sym = AG_INT(1); const int mod = AG_INT(2); AG_ExecKeyAction(wid, AG_ACTION_ON_KEYUP, sym, mod); } /* Default event handler for "mouse-button-down" (for widgets using Actions). */ void AG_WidgetStdMouseButtonDown(AG_Event *event) { AG_Widget *wid = AG_WIDGET_SELF(); const int btn = AG_INT(1); const int x = AG_INT(2); const int y = AG_INT(3); if (!AG_WidgetIsFocused(wid)) { AG_WidgetFocus(wid); } AG_ExecMouseAction(wid, AG_ACTION_ON_BUTTONDOWN, btn, x,y); } /* Default event handler for "mouse-button-up" (for widgets using Actions). */ void AG_WidgetStdMouseButtonUp(AG_Event *event) { AG_Widget *wid = AG_WIDGET_SELF(); const int btn = AG_INT(1); const int x = AG_INT(2); const int y = AG_INT(3); AG_ExecMouseAction(wid, AG_ACTION_ON_BUTTONUP, btn, x,y); } /* Tie an action to a mouse-button-down event. */ void AG_ActionOnButtonDown(void *obj, int button, const char *action) { AG_Widget *wid = obj; AG_ActionTie *at; AG_OBJECT_ISA(wid, "AG_Widget:*"); at = Malloc(sizeof(AG_ActionTie)); at->type = AG_ACTION_ON_BUTTONDOWN; Strlcpy(at->action, action, sizeof(at->action)); at->data.button = (AG_MouseButton)button; AG_ObjectLock(wid); TAILQ_INSERT_TAIL(&wid->pvt.mouseActions, at, ties); if (AG_FindEventHandler(wid, "mouse-button-down") == NULL) { AG_SetEvent(wid, "mouse-button-down", AG_WidgetStdMouseButtonDown, NULL); } AG_ObjectUnlock(wid); } void AG_ActionOnButton(void *obj, int button, const char *action) { AG_ActionOnButtonDown(obj, button, action); } /* Tie an action to a mouse-button-up event. */ void AG_ActionOnButtonUp(void *obj, int button, const char *action) { AG_Widget *wid = obj; AG_ActionTie *at; AG_OBJECT_ISA(wid, "AG_Widget:*"); at = Malloc(sizeof(AG_ActionTie)); at->type = AG_ACTION_ON_BUTTONUP; Strlcpy(at->action, action, sizeof(at->action)); at->data.button = (AG_MouseButton)button; AG_ObjectLock(wid); TAILQ_INSERT_TAIL(&wid->pvt.mouseActions, at, ties); if (AG_FindEventHandler(wid, "mouse-button-up") == NULL) { AG_SetEvent(wid, "mouse-button-up", AG_WidgetStdMouseButtonUp, NULL); } AG_ObjectUnlock(wid); } /* Tie an action to a key-down event. */ void AG_ActionOnKeyDown(void *obj, AG_KeySym sym, AG_KeyMod mod, const char *action) { AG_Widget *wid = obj; AG_ActionTie *at; AG_OBJECT_ISA(wid, "AG_Widget:*"); at = Malloc(sizeof(AG_ActionTie)); at->type = AG_ACTION_ON_KEYDOWN; Strlcpy(at->action, action, sizeof(at->action)); at->data.key.sym = sym; at->data.key.mod = mod; AG_ObjectLock(wid); TAILQ_INSERT_TAIL(&wid->pvt.keyActions, at, ties); if (AG_FindEventHandler(wid, "key-down") == NULL) { AG_SetEvent(wid, "key-down", AG_WidgetStdKeyDown, NULL); } AG_ObjectUnlock(wid); } /* Tie an action to a key-up event. */ void AG_ActionOnKeyUp(void *obj, AG_KeySym sym, AG_KeyMod mod, const char *action) { AG_Widget *wid = obj; AG_ActionTie *at; AG_OBJECT_ISA(wid, "AG_Widget:*"); at = Malloc(sizeof(AG_ActionTie)); at->type = AG_ACTION_ON_KEYUP; Strlcpy(at->action, action, sizeof(at->action)); at->data.key.sym = sym; at->data.key.mod = mod; AG_ObjectLock(wid); TAILQ_INSERT_TAIL(&wid->pvt.keyActions, at, ties); if (AG_FindEventHandler(wid, "key-up") == NULL) { AG_SetEvent(wid, "key-up", AG_WidgetStdKeyUp, NULL); } AG_ObjectUnlock(wid); } /* Timer callback for AG_ACTION_ON_KEYREPEAT actions. */ static Uint32 ActionKeyRepeatTimeout(AG_Timer *_Nonnull to, AG_Event *_Nonnull event) { AG_Widget *wid = AG_WIDGET_SELF(); AG_ActionTie *at = AG_PTR(1); int i; for (i = 0; i < wid->actions.length; i++) { AG_Action *a = wid->actions.data[i]; if (strcmp(at->action, a->name) == 0) { AG_ExecAction(wid, a); return (agKbdRepeat); } } return (0); } /* Tie an action to a key-down event, with key repeat. */ void AG_ActionOnKey(void *obj, AG_KeySym sym, AG_KeyMod mod, const char *action) { AG_Widget *wid = obj; AG_ActionTie *at; AG_OBJECT_ISA(wid, "AG_Widget:*"); at = Malloc(sizeof(AG_ActionTie)); at->type = AG_ACTION_ON_KEYREPEAT; Strlcpy(at->action, action, sizeof(at->action)); at->data.key.sym = sym; at->data.key.mod = mod; AG_InitTimer(&at->data.key.toRepeat, "actKey-", 0); #ifdef AG_DEBUG Strlcat(at->data.key.toRepeat.name, action, sizeof(at->data.key.toRepeat.name)); #endif AG_ObjectLock(wid); TAILQ_INSERT_TAIL(&wid->pvt.keyActions, at, ties); if (AG_FindEventHandler(wid, "key-up") == NULL && AG_FindEventHandler(wid, "key-down") == NULL) { AG_SetEvent(wid, "key-up", AG_WidgetStdKeyUp, NULL); AG_SetEvent(wid, "key-down", AG_WidgetStdKeyDown, NULL); } AG_ObjectUnlock(wid); } /* Configure a widget action. */ AG_Action * AG_ActionFn(void *obj, const char *name, AG_EventFn fn, const char *fnArgs,...) { AG_Widget *wid = obj; AG_Action *a; AG_OBJECT_ISA(wid, "AG_Widget:*"); a = Malloc(sizeof(AG_Action)); a->type = AG_ACTION_FN; Strlcpy(a->name, name, sizeof(a->name)); AG_ObjectLock(wid); a->fn = AG_SetEvent(wid, NULL, fn, NULL); if (fnArgs) { va_list ap; va_start(ap, fnArgs); AG_EventGetArgs(a->fn, fnArgs, ap); va_end(ap); } AG_VEC_PUSH(&wid->actions, a); AG_ObjectUnlock(wid); return (a); } /* Configure a widget action for setting an integer flag. */ AG_Action * AG_ActionSetInt(void *obj, const char *name, int *p, int val) { AG_Widget *wid = obj; AG_Action *a; AG_OBJECT_ISA(wid, "AG_Widget:*"); a = Malloc(sizeof(AG_Action)); a->type = AG_ACTION_SET_INT; Strlcpy(a->name, name, sizeof(a->name)); a->fn = NULL; a->p = (void *)p; a->val = val; AG_ObjectLock(wid); AG_VEC_PUSH(&wid->actions, a); AG_ObjectUnlock(wid); return (a); } /* Configure a widget action for toggling an integer flag. */ AG_Action * AG_ActionToggleInt(void *obj, const char *name, int *p) { AG_Widget *wid = obj; AG_Action *a; AG_OBJECT_ISA(wid, "AG_Widget:*"); a = Malloc(sizeof(AG_Action)); a->type = AG_ACTION_TOGGLE_INT; Strlcpy(a->name, name, sizeof(a->name)); a->fn = NULL; a->p = (void *)p; AG_ObjectLock(wid); AG_VEC_PUSH(&wid->actions, a); AG_ObjectUnlock(wid); return (a); } /* Configure a widget action for setting bitwise flags. */ AG_Action * AG_ActionSetFlag(void *obj, const char *name, Uint *p, Uint bitmask, int val) { AG_Widget *wid = obj; AG_Action *a; AG_OBJECT_ISA(wid, "AG_Widget:*"); a = Malloc(sizeof(AG_Action)); a->type = AG_ACTION_SET_INT; Strlcpy(a->name, name, sizeof(a->name)); a->fn = NULL; a->p = (void *)p; a->bitmask = bitmask; a->val = val; AG_ObjectLock(wid); AG_VEC_PUSH(&wid->actions, a); AG_ObjectUnlock(wid); return (a); } /* Configure a widget action for toggling bitwise flags. */ AG_Action * AG_ActionToggleFlag(void *obj, const char *name, Uint *p, Uint bitmask) { AG_Widget *wid = obj; AG_Action *a; AG_OBJECT_ISA(wid, "AG_Widget:*"); a = Malloc(sizeof(AG_Action)); a->type = AG_ACTION_TOGGLE_FLAG; Strlcpy(a->name, name, sizeof(a->name)); a->fn = NULL; a->p = (void *)p; a->bitmask = bitmask; AG_ObjectLock(wid); AG_VEC_PUSH(&wid->actions, a); AG_ObjectUnlock(wid); return (a); } /* Execute an action (usually called internally from AG_ExecFooAction()) */ int AG_ExecAction(void *obj, const AG_Action *a) { AG_OBJECT_ISA(obj, "AG_Widget:*"); switch (a->type) { case AG_ACTION_FN: if (a->fn) { a->fn->fn(a->fn); return (1); } return (0); case AG_ACTION_SET_INT: *(int *)a->p = a->val; return (1); case AG_ACTION_TOGGLE_INT: *(int *)a->p = !(*(int *)a->p); return (1); case AG_ACTION_SET_FLAG: if (a->val) { *(Uint *)a->p |= a->bitmask; } else { *(Uint *)a->p &= ~(a->bitmask); } return (1); case AG_ACTION_TOGGLE_FLAG: if (*(Uint *)a->p & a->bitmask) { *(Uint *)a->p &= ~(a->bitmask); } else { *(Uint *)a->p |= a->bitmask; } return (1); } return (0); } /* * Run any action tied to a mouse-button event. Exceptionally, we pass * mouse event arguments to the function. */ int AG_ExecMouseAction(void *obj, AG_ActionEventType et, int button, int x, int y) { AG_Widget *wid = obj; AG_ActionTie *at; int i; AG_OBJECT_ISA(wid, "AG_Widget:*"); #ifdef AG_DEBUG if (et != AG_ACTION_ON_BUTTONDOWN && et != AG_ACTION_ON_BUTTONUP) AG_FatalError("Invalid type argument"); #endif TAILQ_FOREACH(at, &wid->pvt.mouseActions, ties) { if (at->type == et && ((button == at->data.button) || (at->data.button == AG_MOUSE_ANY))) break; } if (at == NULL) { return (0); } for (i = 0; i < wid->actions.length; i++) { const AG_Action *a = wid->actions.data[i]; if (strcmp(a->name, at->action) == 0 && a->fn) { AG_PostEventByPtr(wid, a->fn, "%i,%i,%i", button, x,y); return (1); } } return (0); } /* Run any action tied to a key-down event. */ int AG_ExecKeyAction(void *obj, AG_ActionEventType et, AG_KeySym sym, AG_KeyMod mod) { AG_Widget *wid = obj; AG_ActionTie *at; int i, rv = 0; AG_OBJECT_ISA(wid, "AG_Widget:*"); #ifdef AG_DEBUG if (et != AG_ACTION_ON_KEYDOWN && et != AG_ACTION_ON_KEYUP) AG_FatalError("Invalid type argument"); #endif TAILQ_FOREACH(at, &wid->pvt.keyActions, ties) { if (at->type != et && at->type != AG_ACTION_ON_KEYREPEAT) continue; if ((at->data.key.mod == AG_KEYMOD_ANY || at->data.key.mod & mod) && (at->data.key.sym == AG_KEY_ANY || at->data.key.sym == sym)) break; } if (at == NULL) return (0); for (i = 0; i < wid->actions.length; i++) { const AG_Action *a = wid->actions.data[i]; if (strcmp(a->name, at->action) == 0 && a->fn) { rv = AG_ExecAction(wid, a); break; } } if (i == wid->actions.length) return (0); if (at->type == AG_ACTION_ON_KEYREPEAT) { if (et == AG_ACTION_ON_KEYDOWN) { AG_AddTimer(wid, &at->data.key.toRepeat, agKbdDelay, ActionKeyRepeatTimeout, "%p", at); } else { AG_DelTimer(wid, &at->data.key.toRepeat); } } return (rv); } /* Set the FOCUSABLE flag on a widget. */ int AG_WidgetSetFocusable(void *obj, int enable) { AG_Widget *wid = obj; int prev; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); prev = (wid->flags & AG_WIDGET_FOCUSABLE); AG_SETFLAGS(wid->flags, AG_WIDGET_FOCUSABLE, enable); AG_ObjectUnlock(wid); return (prev); } /* Set widget to "enabled" state for input. */ void AG_WidgetEnable(void *obj) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); /* TODO make the "disabled" flag a (locklessly settable) int. */ if (wid->flags & AG_WIDGET_DISABLED) { wid->flags &= ~(AG_WIDGET_DISABLED); AG_PostEvent(wid, "widget-enabled", NULL); AG_Redraw(wid); } AG_ObjectUnlock(wid); } /* Set widget to "disabled" state for input. */ void AG_WidgetDisable(void *obj) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); /* TODO make the "disabled" flag a (locklessly settable) int. */ if (!(wid->flags & AG_WIDGET_DISABLED)) { wid->flags |= AG_WIDGET_DISABLED; AG_PostEvent(wid, "widget-disabled", NULL); AG_Redraw(wid); } AG_ObjectUnlock(wid); } /* * Arrange for any child widget subsequently attached to the given container * widget to start in the DISABLED state. The flag will be set on attach, * and no "widget-disabled" event will be generated. */ void AG_PushDisabledState(void *obj) { AG_Widget *wid = AGWIDGET(obj); AG_ObjectLock(wid); wid->flags |= AG_WIDGET_DISABLE_ON_ATTACH; AG_ObjectUnlock(wid); } /* Cancel the effect of a previous AG_PushDisabledState. */ void AG_PopDisabledState(void *obj) { AG_Widget *wid = AGWIDGET(obj); AG_ObjectLock(wid); wid->flags &= ~(AG_WIDGET_DISABLE_ON_ATTACH); AG_ObjectUnlock(wid); } /* Arrange for a widget to automatically forward focus to another widget. */ void AG_WidgetForwardFocus(void *obj, void *objFwd) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); if (objFwd) { AG_OBJECT_ISA(objFwd, "AG_Widget:*"); wid->flags |= AG_WIDGET_FOCUSABLE; wid->focusFwd = WIDGET(objFwd); } else { wid->flags &= ~(AG_WIDGET_FOCUSABLE); wid->focusFwd = NULL; } AG_ObjectUnlock(wid); } static void Destroy(void *_Nonnull obj) { AG_Widget *wid = obj; AG_CursorArea *ca, *caNext; AG_RedrawTie *rt, *rtNext; AG_ActionTie *at, *atNext; Uint i; for (ca = TAILQ_FIRST(&wid->pvt.cursorAreas); ca != TAILQ_END(&wid->pvt.cursorAreas); ca = caNext) { caNext = TAILQ_NEXT(ca, cursorAreas); free(ca); } for (rt = TAILQ_FIRST(&wid->pvt.redrawTies); rt != TAILQ_END(&wid->pvt.redrawTies); rt = rtNext) { rtNext = TAILQ_NEXT(rt, redrawTies); free(rt); } for (at = TAILQ_FIRST(&wid->pvt.mouseActions); at != TAILQ_END(&wid->pvt.mouseActions); at = atNext) { atNext = TAILQ_NEXT(at, ties); free(at); } for (at = TAILQ_FIRST(&wid->pvt.keyActions); at != TAILQ_END(&wid->pvt.keyActions); at = atNext) { atNext = TAILQ_NEXT(at, ties); free(at); } for (i = 0; i < wid->actions.length; i++) { Free(wid->actions.data[i]); } AG_VEC_DESTROY(&wid->actions); /* * Free surfaces. We can assume that drivers have already deleted * any associated resources. */ for (i = 0; i < wid->nSurfaces; i++) { AG_Surface *S; if ((S = wid->surfaces[i]) != NULL && !(wid->surfaceFlags[i] & AG_WIDGET_SURFACE_NODUP)) { #ifdef AG_DEBUG S->flags &= ~(AG_SURFACE_MAPPED); #endif AG_SurfaceFree(S); } } Free(wid->surfaces); Free(wid->surfaceFlags); Free(wid->textures); Free(wid->texcoords); } #ifdef HAVE_OPENGL /* * Coordinate-free variants of AG_WidgetBlit*() for OpenGL-only widgets. * Rely on GL transformations instead of coordinates. */ void AG_WidgetBlitGL(void *obj, AG_Surface *S, float w, float h) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); wid->drvOps->blitSurfaceGL(wid->drv, wid, S, w,h); } void AG_WidgetBlitSurfaceGL(void *obj, int name, float w, float h) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); wid->drvOps->blitSurfaceFromGL(wid->drv, wid, name, w,h); } void AG_WidgetBlitSurfaceFlippedGL(void *obj, int name, float w, float h) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); wid->drvOps->blitSurfaceFlippedGL(wid->drv, wid, name, w,h); } /* * Backup and regenerate all GL resources associated with a widget * (and any of its children). * * If some textures exist without a corresponding surface, allocate a * software surface and copy their contents to be later restored. These * routines are necessary for dealing with GL context loss. */ void AG_WidgetFreeResourcesGL(void *obj) { AG_Widget *wid = obj, *cwid; AG_OBJECT_ISA(wid, "AG_Widget:*"); if (wid->drvOps->backupSurfaces != NULL) { wid->drvOps->backupSurfaces(wid->drv, wid); } OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) AG_WidgetFreeResourcesGL(cwid); } void AG_WidgetRegenResourcesGL(void *obj) { AG_Widget *wid = obj, *cwid; AG_OBJECT_ISA(wid, "AG_Widget:*"); if (wid->drvOps->restoreSurfaces != NULL) { wid->drvOps->restoreSurfaces(wid->drv, wid); } OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) AG_WidgetRegenResourcesGL(cwid); } #endif /* HAVE_OPENGL */ /* Remove focus from a widget and its children. */ void AG_WidgetUnfocus(void *p) { AG_Widget *wid = p, *cwid, *focusFwd; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); if ((focusFwd = wid->focusFwd) != NULL) { AG_OBJECT_ISA(focusFwd, "AG_Widget:*"); AG_ObjectLock(focusFwd); if (focusFwd->flags & AG_WIDGET_FOCUSED) { UnfocusWidget(focusFwd); } AG_ObjectUnlock(focusFwd); } if (wid->flags & AG_WIDGET_FOCUSED) { UnfocusWidget(wid); } OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) AG_WidgetUnfocus(cwid); AG_ObjectUnlock(wid); } static void UnfocusWidget(AG_Widget *_Nonnull wid) { AG_Window *win; AG_OBJECT_ISA(wid, "AG_Widget:*"); wid->flags &= ~(AG_WIDGET_FOCUSED); if ((win = wid->window)) { AG_OBJECT_ISA(win, "AG_Widget:AG_Window:*"); AG_PostEvent(wid, "widget-lostfocus", NULL); win->nFocused--; win->dirty = 1; } } /* Move the focus over a widget (and its parents). */ int AG_WidgetFocus(void *obj) { AG_Widget *wid = obj, *wParent = wid, *focusFwd; AG_Window *win; AG_OBJECT_ISA(wid, "AG_Widget:*"); win = wid->window; AG_OBJECT_ISA(win, "AG_Widget:AG_Window:*"); AG_LockVFS(wid); AG_ObjectLock(wid); if (AG_WidgetIsFocused(wid)) { goto out; } if (!(wid->flags & AG_WIDGET_FOCUSABLE)) { if ((focusFwd = wid->focusFwd) && !(focusFwd->flags & AG_WIDGET_FOCUSED)) { AG_OBJECT_ISA(focusFwd, "AG_Widget:*"); AG_ObjectLock(focusFwd); FocusWidget(focusFwd); AG_ObjectUnlock(focusFwd); goto out; } goto fail; } /* Remove any existing focus. XXX inefficient */ if (win && win->nFocused > 0) AG_WidgetUnfocus(win); /* * Set the focus flag on the widget and its parents, up * to the parent window. */ do { if (AG_WINDOW_ISA(wParent)) { AG_WindowFocus(AGWINDOW(wParent)); break; } AG_ObjectLock(wParent); if ((wParent->flags & AG_WIDGET_FOCUSED) == 0) { if ((focusFwd = wParent->focusFwd) && !(focusFwd->flags & AG_WIDGET_FOCUSED)) { FocusWidget(focusFwd); } FocusWidget(wParent); } AG_ObjectUnlock(wParent); } while ((wParent = OBJECT(wParent)->parent) != NULL); out: AG_ObjectUnlock(wid); AG_UnlockVFS(wid); return (1); fail: AG_ObjectUnlock(wid); AG_UnlockVFS(wid); return (0); } /* Acquire widget focus */ static void FocusWidget(AG_Widget *_Nonnull wid) { AG_Window *win; AG_OBJECT_ISA(wid, "AG_Widget:*"); wid->flags |= AG_WIDGET_FOCUSED; if ((win = wid->window) != NULL) { AG_OBJECT_ISA(win, "AG_Widget:AG_Window:*"); AG_PostEvent(wid, "widget-gainfocus", NULL); win->nFocused++; win->dirty = 1; } } /* Size requisition function */ static void SizeRequest(void *_Nonnull p, AG_SizeReq *_Nonnull r) { r->w = 0; r->h = 0; } /* Size allocation callback */ static int SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a) { AG_Widget *wid = obj; if (a->w < wid->paddingLeft + wid->paddingRight || a->h < wid->paddingTop + wid->paddingBottom) { return (-1); } return (0); } /* * Size Requisition: Invoke the size_request() of a widget and return * the requested width and height (in pixels) into r. * * If the widget class defines a NULL "size_request" field then inherit the * size_request() operation of the parent class. */ void AG_WidgetSizeReq(void *obj, AG_SizeReq *r) { AG_Widget *wid = obj; int useText; r->w = 0; r->h = 0; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); useText = (wid->flags & AG_WIDGET_USE_TEXT); if (useText) { AG_PushTextState(); AG_TextFont(wid->font); } if (WIDGET_OPS(wid)->size_request != NULL) { WIDGET_OPS(wid)->size_request(wid, r); } else { AG_WidgetClass *Csup; while ((Csup = AGWIDGET_SUPER_OPS(wid)) != (void *)&agObjectClass) { if (Csup->size_request != NULL) { Csup->size_request(wid, r); break; } } } if (useText) { AG_PopTextState(); } AG_ObjectUnlock(wid); } /* * Size allocation callback. Invoke the size_allocate() of a widget, * passing it the final allocated coordinates (x, y) and size (w, h). * * If the size is negative, normalize it to (0, 0) and set UNDERSIZE flag. * * If the widget class defines a NULL "size_allocate" field then inherit the * size_allocate() operation of the parent class. */ void AG_WidgetSizeAlloc(void *obj, AG_SizeAlloc *a) { AG_Widget *wid = obj; int useText; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); useText = (wid->flags & AG_WIDGET_USE_TEXT); if (useText) { AG_PushTextState(); AG_TextFont(wid->font); } if (a->w <= 0 || a->h <= 0) { a->w = 0; a->h = 0; wid->flags |= AG_WIDGET_UNDERSIZE; } else { wid->flags &= ~(AG_WIDGET_UNDERSIZE); } wid->x = a->x; wid->y = a->y; wid->w = a->w; wid->h = a->h; wid->r.x = 0; wid->r.y = 0; wid->r.w = wid->w; wid->r.h = wid->h; if (WIDGET_OPS(wid)->size_allocate != NULL) { if (WIDGET_OPS(wid)->size_allocate(wid, a) == -1) { wid->flags |= AG_WIDGET_UNDERSIZE; } else { wid->flags &= ~(AG_WIDGET_UNDERSIZE); } } else { AG_WidgetClass *Csup; while ((Csup = AGWIDGET_SUPER_OPS(wid)) != (void *)&agObjectClass) { if (Csup->size_allocate != NULL) { if (Csup->size_allocate(wid, a) == -1) { wid->flags |= AG_WIDGET_UNDERSIZE; } else { wid->flags &= ~(AG_WIDGET_UNDERSIZE); } break; } } } if (useText) { AG_PopTextState(); } #ifdef HAVE_OPENGL wid->flags |= AG_WIDGET_GL_RESHAPE; #endif AG_ObjectUnlock(wid); } /* * Test whether view coordinates x,y lie in widget's sensitivity rectangle * (intersected against those of all parent widgets). */ int AG_WidgetSensitive(void *obj, int x, int y) { AG_Widget *wid = WIDGET(obj); AG_Window *winParent; AG_Rect2 rx; AG_OBJECT_ISA(wid, "AG_Widget:*"); if ((winParent = wid->window) == NULL) return (0); AG_RectIntersect2(&rx, &wid->rSens, &WIDGET(winParent)->rSens); return AG_RectInside2(&rx, x,y); } /* * Search for a focused widget inside a window. Return value is only valid * as long as the Driver VFS is locked. */ AG_Widget * AG_WidgetFindFocused(void *p) { AG_Widget *wid = p; AG_Widget *cwid, *fwid; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_LockVFS(wid); AG_ObjectLock(wid); if (!AG_WINDOW_ISA(wid)) { if ((wid->flags & AG_WIDGET_FOCUSED) == 0 || (wid->flags & AG_WIDGET_VISIBLE) == 0) { goto fail; } } /* Search for a better match. */ OBJECT_FOREACH_CHILD(cwid, wid, ag_widget) { if ((fwid = AG_WidgetFindFocused(cwid)) != NULL) { AG_ObjectUnlock(wid); AG_UnlockVFS(wid); return (fwid); } } AG_ObjectUnlock(wid); AG_UnlockVFS(wid); return (wid); fail: AG_ObjectUnlock(wid); AG_UnlockVFS(wid); return (NULL); } /* * Compute the absolute view coordinates of a widget and its descendents. * * The Widget and its parent VFS must be locked. */ void AG_WidgetUpdateCoords(void *obj, int x, int y) { AG_Widget *wid = obj, *chld; #ifdef HAVE_OPENGL const AG_Rect2 rPrev = wid->rView; #endif wid->flags &= ~(AG_WIDGET_UPDATE_WINDOW); if (AG_WINDOW_ISA(wid) && wid->drv != NULL && AGDRIVER_MULTIPLE(wid->drv)) { x = 0; y = 0; } wid->rView.x1 = x; wid->rView.y1 = y; wid->rView.w = wid->w; wid->rView.h = wid->h; wid->rView.x2 = x + wid->w; wid->rView.y2 = y + wid->h; wid->rSens.x1 = x; wid->rSens.y1 = y; wid->rSens.w = wid->w; wid->rSens.h = wid->h; wid->rSens.x2 = x + wid->w; wid->rSens.y2 = y + wid->h; #ifdef HAVE_OPENGL if (AG_RectCompare2(&wid->rView, &rPrev) != 0) wid->flags |= AG_WIDGET_GL_RESHAPE; #endif OBJECT_FOREACH_CHILD(chld, wid, ag_widget) /* Recurse */ AG_WidgetUpdateCoords(chld, wid->rView.x1 + chld->x, wid->rView.y1 + chld->y); } /* Parse a generic size specification. */ enum ag_widget_sizespec AG_WidgetParseSizeSpec(const char *s, int *w) { const char *p; size_t len; len = strlen(s); if (len == 0) { goto syntax; } p = &s[len-1]; switch (*p) { case '-': *w = 0; return (AG_WIDGET_FILL); case '%': *w = (int)strtol(s, NULL, 10); return (AG_WIDGET_PERCENT); case '>': if (s[0] != '<') { goto syntax; } AG_TextSize(&s[1], w, NULL); return (AG_WIDGET_STRINGLEN); case 'x': if (p > &s[0] && p[-1] != 'p') { goto syntax; } *w = (int)strtol(s, NULL, 10); return (AG_WIDGET_PIXELS); } syntax: *w = 0; return (AG_WIDGET_BAD_SPEC); } /* Show a widget */ void AG_WidgetShow(void *obj) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); if (wid->flags & AG_WIDGET_HIDE) { wid->flags &= ~(AG_WIDGET_HIDE); AG_PostEvent(wid, "widget-shown", NULL); if (wid->window) AG_WindowUpdate(wid->window); } AG_ObjectUnlock(wid); } /* Hide a widget */ void AG_WidgetHide(void *obj) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); if ((wid->flags & AG_WIDGET_HIDE) == 0) { wid->flags |= AG_WIDGET_HIDE; AG_PostEvent(wid, "widget-hidden", NULL); if (wid->window) AG_WindowUpdate(wid->window); } AG_ObjectUnlock(wid); } /* Make a widget and all of its children visible. */ void AG_WidgetShowAll(void *p) { AG_Widget *wid = p; AG_Widget *chld; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_LockVFS(wid); AG_ObjectLock(wid); OBJECT_FOREACH_CHILD(chld, wid, ag_widget) AG_WidgetShowAll(chld); AG_PostEvent(wid, "widget-shown", NULL); AG_ObjectUnlock(wid); AG_UnlockVFS(wid); } /* Make a widget and all of its children invisible. */ void AG_WidgetHideAll(void *p) { AG_Widget *wid = p; AG_Widget *chld; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_LockVFS(wid); AG_ObjectLock(wid); OBJECT_FOREACH_CHILD(chld, wid, ag_widget) AG_WidgetHideAll(chld); AG_PostEvent(wid, "widget-hidden", NULL); AG_ObjectUnlock(wid); AG_UnlockVFS(wid); } static void *_Nullable FindAtPoint(AG_Widget *_Nonnull parent, const char *_Nonnull type, int x, int y) { AG_Widget *chld; void *p; OBJECT_FOREACH_CHILD(chld, parent, ag_widget) { if ((p = FindAtPoint(chld, type, x,y)) != NULL) return (p); } if ((parent->flags & AG_WIDGET_VISIBLE) && AG_OfClass(parent, type) && AG_WidgetArea(parent, x,y)) { return (parent); } return (NULL); } /* Search for widgets of the specified class enclosing the given point. */ void *_Nullable AG_WidgetFindPoint(const char *_Nonnull type, int x, int y) { AG_Driver *drv; AG_Window *win; void *p; AG_LockVFS(&agDrivers); OBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) { AG_FOREACH_WINDOW_REVERSE(win, drv) { if ((p = FindAtPoint(WIDGET(win), type, x,y)) != NULL) { AG_UnlockVFS(&agDrivers); return (p); } } } AG_UnlockVFS(&agDrivers); return (NULL); } static void *_Nullable FindRectOverlap(AG_Widget *_Nonnull parent, const char *_Nonnull type, int x, int y, int w, int h) { AG_Widget *chld; void *p; OBJECT_FOREACH_CHILD(chld, parent, ag_widget) { if ((p = FindRectOverlap(chld, type, x,y,w,h)) != NULL) return (p); } if (AG_OfClass(parent, type) && !(x+w < parent->rView.x1 || x > parent->rView.x2 || y+w < parent->rView.y1 || y > parent->rView.y2)) { return (parent); } return (NULL); } /* * Search for widgets of the specified class enclosing the given rectangle. * Result is only accurate as long as the Driver VFS is locked. */ void * AG_WidgetFindRect(const char *type, int x, int y, int w, int h) { AG_Driver *drv; AG_Window *win; void *p; AG_LockVFS(&agDrivers); OBJECT_FOREACH_CHILD(drv, &agDrivers, ag_driver) { AG_FOREACH_WINDOW_REVERSE(win, drv) { if ((p = FindRectOverlap(WIDGET(win), type, x,y,w,h)) != NULL) { AG_UnlockVFS(&agDrivers); return (p); } } } AG_UnlockVFS(&agDrivers); return (NULL); } /* Render a widget to an AG_Surface(3). */ AG_Surface * AG_WidgetSurface(void *obj) { AG_Widget *wid = obj; AG_Surface *S; int rv; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_LockVFS(wid); if (wid->drvOps->renderToSurface == NULL) { AG_SetErrorS(_("Render-to-surface is not supported")); rv = -1; } else { rv = wid->drvOps->renderToSurface(wid->drv, wid, &S); } AG_UnlockVFS(wid); return (rv == 0) ? S : NULL; } #ifdef HAVE_OPENGL static void DrawPrologueGL_Reshape(AG_Widget *_Nonnull wid) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); AG_PostEvent(wid, "widget-reshape", NULL); wid->flags &= ~(AG_WIDGET_GL_RESHAPE); if (wid->gl == NULL) { wid->gl = Malloc(sizeof(AG_WidgetGL)); } glGetFloatv(GL_PROJECTION, wid->gl->mProjection); glGetFloatv(GL_MODELVIEW, wid->gl->mModelview); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } static void DrawPrologueGL(AG_Widget *_Nonnull wid) { Uint hView; AG_PostEvent(wid, "widget-underlay", NULL); glPushAttrib(GL_TRANSFORM_BIT | GL_VIEWPORT_BIT | GL_TEXTURE_BIT); if (wid->flags & AG_WIDGET_GL_RESHAPE) DrawPrologueGL_Reshape(wid); hView = AGDRIVER_SINGLE(wid->drv) ? AGDRIVERSW(wid->drv)->h : HEIGHT(wid->window); glViewport(wid->rView.x1, (hView - wid->rView.y2), WIDTH(wid), HEIGHT(wid)); glMatrixMode(GL_TEXTURE); glPushMatrix(); glLoadIdentity(); #ifdef AG_DEBUG if (wid->gl == NULL) { AG_FatalError("!wid->gl"); } #endif glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadMatrixf(wid->gl->mProjection); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(wid->gl->mModelview); glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); } static void DrawEpilogueGL(AG_Widget *_Nonnull wid) { glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_TEXTURE); glPopMatrix(); glPopAttrib(); /* GL_TRANSFORM_BIT | GL_VIEWPORT_BIT | GL_TEXTURE_BIT */ AG_PostEvent(wid, "widget-overlay", NULL); } #endif /* HAVE_OPENGL */ /* * Render a widget to the display. Invoked from GUI rendering context * (typically the draw() operation of a container widget). */ void AG_WidgetDraw(void *p) { AG_Widget *wid = p; Uint flags; int useText; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); flags = wid->flags; if ((flags & AG_WIDGET_VISIBLE) == 0 || (flags & (AG_WIDGET_HIDE | AG_WIDGET_UNDERSIZE))) goto out; if (flags & AG_WIDGET_DISABLED) { wid->state = AG_DISABLED_STATE; } else if (flags & AG_WIDGET_MOUSEOVER) { wid->state = AG_HOVER_STATE; } else if (flags & AG_WIDGET_FOCUSED) { wid->state = AG_FOCUSED_STATE; } else { wid->state = AG_DEFAULT_STATE; } useText = (flags & AG_WIDGET_USE_TEXT); if (useText) { AG_PushTextState(); AG_TextFont(wid->font); AG_TextColor(&wid->pal.c[wid->state][AG_TEXT_COLOR]); } #ifdef HAVE_OPENGL if (flags & AG_WIDGET_USE_OPENGL) DrawPrologueGL(wid); #endif if (WIDGET_OPS(wid)->draw != NULL) { WIDGET_OPS(wid)->draw(wid); } else { AG_WidgetClass *Csuper; while ((Csuper = AGWIDGET_SUPER_OPS(wid)) != (void *)&agObjectClass) { if (Csuper->draw != NULL) { Csuper->draw(wid); break; } } } #ifdef HAVE_OPENGL if (flags & AG_WIDGET_USE_OPENGL) DrawEpilogueGL(wid); #endif if (useText) { AG_PopTextState(); } out: AG_ObjectUnlock(wid); } /* * Attach a surface to a Widget and return an integer surface handle. * * The returned handle is unique to the Widget, and is also an index into its * surfaces[] and surfaceFlags[]. If textures are supported, it is also an * index into textures[] and texcoords[]. * * The surface will be freed automatically when the widget is destroyed * (unless the NODUP bit is set in surfaceFlags). */ int AG_WidgetMapSurface(void *obj, AG_Surface *S) { AG_Widget *wid = obj; int i, n, id = -1; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); n = wid->nSurfaces; for (i = 0; i < n; i++) { if (wid->surfaces[i] == NULL) { id = i; break; } } if (i == n) { ++n; wid->surfaces = Realloc(wid->surfaces, n * sizeof(AG_Surface *)); wid->surfaceFlags = Realloc(wid->surfaceFlags, n * sizeof(Uint)); wid->textures = Realloc(wid->textures, n * sizeof(Uint)); wid->texcoords = Realloc(wid->texcoords, n * sizeof(AG_TexCoord)); id = wid->nSurfaces++; } wid->surfaces[id] = S; wid->surfaceFlags[id] = 0; wid->textures[id] = 0; #ifdef AG_DEBUG if (S != NULL) S->flags |= AG_SURFACE_MAPPED; #endif AG_ObjectUnlock(wid); #ifdef DEBUG_SURFACE if (S->flags & AG_SURFACE_TRACE) { Debug(NULL, "SURFACE(%dx%dx%d): Mapped to %s widget " AGSI_BOLD "%s" AGSI_RST "'s surface #" AGSI_BOLD "%d" AGSI_RST ".\n", S->w, S->h, S->format.BitsPerPixel, wid->drv ? OBJECT(wid->drv)->name : "unattached", OBJECT(wid)->name, id); } #endif return (id); } /* * Request update of any hardware copy of a given mapped surface. * If hardware textures are not used, this is a no-op. */ void AG_WidgetUpdateSurface(void *obj, int id) { #ifdef HAVE_OPENGL AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); # ifdef AG_DEBUG if (id < 0 || id >= wid->nSurfaces) AG_FatalError("No such surface"); # endif wid->surfaceFlags[id] |= AG_WIDGET_SURFACE_REGEN; #endif } /* * Delete and free all resources associated with a mapped surface. * If a hardware texture was created, it is deleted asynchronously. */ void AG_WidgetUnmapSurface(void *obj, int id) { AG_OBJECT_ISA(obj, "AG_Widget:*"); AG_WidgetReplaceSurface(obj, id, NULL); } /* * Perform an image transfer from a mapped surface (by surface ID) to specified * target coordinates x,y (relative to the widget). If a texture unit is * available, this is done entirely in hardware. * * This must be called from rendering context (the Widget draw() operation). */ void AG_WidgetBlitSurface(void *obj, int id, int x, int y) { AG_OBJECT_ISA(obj, "AG_Widget:*"); AG_WidgetBlitFrom(obj, id, NULL, x,y); } /* * Replace the contents of a mapped surface. Passing S => NULL is equivalent * to calling AG_WidgetUnmapSurface(). */ void AG_WidgetReplaceSurface(void *obj, int id, AG_Surface *S) { AG_Widget *wid = obj; AG_Surface *Sprev; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_ObjectLock(wid); #ifdef AG_DEBUG if (id < 0 || id >= wid->nSurfaces) AG_FatalError("No such surface"); #endif if ((Sprev = wid->surfaces[id]) && /* Existing surface? */ !(wid->surfaceFlags[id] & AG_WIDGET_SURFACE_NODUP)) { #ifdef AG_DEBUG # ifdef DEBUG_SURFACE if (Sprev->flags & AG_SURFACE_TRACE) { Debug(NULL, "SURFACE(%dx%dx%d): Unmapped from %s widget " AGSI_BOLD "%s" AGSI_RST "'s surface #" AGSI_BOLD "%d" AGSI_RST ".\n", Sprev->w, Sprev->h, Sprev->format.BitsPerPixel, wid->drv ? OBJECT(wid->drv)->name : "unattached", OBJECT(wid)->name, id); } # endif Sprev->flags &= ~(AG_SURFACE_MAPPED); #endif AG_SurfaceFree(Sprev); } #ifdef AG_DEBUG if (S != NULL) { # ifdef DEBUG_SURFACE if (S->flags & AG_SURFACE_TRACE) { Debug(NULL, "SURFACE(%dx%dx%d): Mapped to %s widget " AGSI_BOLD "%s" AGSI_RST "'s surface #" AGSI_BOLD "%d" AGSI_RST ".\n", S->w, S->h, S->format.BitsPerPixel, wid->drv ? OBJECT(wid->drv)->name : "unattached", OBJECT(wid)->name, id); } # endif S->flags |= AG_SURFACE_MAPPED; } #endif wid->surfaces[id] = S; wid->surfaceFlags[id] &= ~(AG_WIDGET_SURFACE_NODUP); /* * Queue the previous texture for deletion and set the texture handle * to 0 so the texture will be regenerated at the next blit. */ if (wid->textures[id] != 0 && wid->drv) { AG_OBJECT_ISA(wid->drv, "AG_Driver:*"); if (wid->drvOps->deleteTexture != NULL) { wid->drvOps->deleteTexture(wid->drv, wid->textures[id]); wid->textures[id] = 0; } } AG_ObjectUnlock(wid); } static void CompileStyleRecursive(AG_Widget *_Nonnull wid, const char *_Nonnull parentFace, float parentFontSize, Uint parentFontFlags, const AG_WidgetPalette *parentPalette) { AG_StyleSheet *css = &agDefaultCSS; char *fontFace, *cssData; AG_Widget *chld; AG_Variable *V; AG_Object *po; float fontSize; Uint fontFlags = parentFontFlags; int i, j, paletteChanged=0; AG_OBJECT_ISA(wid, "AG_Widget:*"); /* TODO make alt stylesheet a per-window attribute */ for (po = OBJECT(wid); po->parent != NULL && AG_WIDGET_ISA(po->parent); po = po->parent) { if (WIDGET(po)->css != NULL) { /* alternate stylesheet? */ css = WIDGET(po)->css; break; } } /* * Font face (fontconfig name or specific filename under font-path). */ if ((V = AG_AccessVariable(wid, "font-family")) != NULL) { fontFace = Strdup(V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "font-family", &cssData)) { fontFace = Strdup(cssData); } else { fontFace = Strdup(parentFace); } /* * Font size (points, pixels, or % relative to parent). * Fractional point sizes (e.g., "10.5" are allowed). */ if ((V = AG_AccessVariable(wid, "font-size")) != NULL) { Apply_Font_Size(&fontSize, parentFontSize, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "font-size", &cssData)) { Apply_Font_Size(&fontSize, parentFontSize, cssData); } else { fontSize = parentFontSize; } /* * Font weight (thin, extralight, light, semibold, bold, extrabold, * black or !parent). */ if ((V = AG_AccessVariable(wid, "font-weight")) != NULL) { Apply_Font_Weight(&fontFlags, parentFontFlags, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "font-weight", &cssData)) { Apply_Font_Weight(&fontFlags, parentFontFlags, cssData); } else { fontFlags &= ~(AG_FONT_WEIGHTS); fontFlags |= (parentFontFlags & AG_FONT_WEIGHTS); } /* * Font style (normal, oblique, italic or !parent). */ if ((V = AG_AccessVariable(wid, "font-style")) != NULL) { Apply_Font_Style(&fontFlags, parentFontFlags, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "font-style", &cssData)) { Apply_Font_Style(&fontFlags, parentFontFlags, cssData); } else { fontFlags &= ~(AG_FONT_STYLES); fontFlags |= (parentFontFlags & AG_FONT_STYLES); } /* * Width variant (normal, ultracondensed, condensed, semicondensed, * semiexpanded, expanded, ultraexpanded or !parent). */ if ((V = AG_AccessVariable(wid, "font-stretch")) != NULL) { Apply_Font_Stretch(&fontFlags, parentFontFlags, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "font-stretch", &cssData)) { Apply_Font_Stretch(&fontFlags, parentFontFlags, cssData); } else { fontFlags &= ~(AG_FONT_WD_VARIANTS); fontFlags |= (parentFontFlags & AG_FONT_WD_VARIANTS); } /* * Padding and margin (in pixels) for box model. * * The margin is applied by the size_allocate() routine of container * widgets. The padding is widget-specific in its implementation. */ if ((V = AG_AccessVariable(wid, "padding")) != NULL) { Apply_Padding(wid, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "padding", &cssData)) { Apply_Padding(wid, cssData); } if ((V = AG_AccessVariable(wid, "margin")) != NULL) { Apply_Margin(wid, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "margin", &cssData)) { Apply_Margin(wid, cssData); } /* * Spacing between widget-specific elements (in pixels). */ if ((V = AG_AccessVariable(wid, "spacing")) != NULL) { Apply_Spacing(wid, V->data.s); AG_UnlockVariable(V); } else if (AG_LookupStyleSheet(css, wid, "spacing", &cssData)) { Apply_Spacing(wid, cssData); } /* * Color palette. */ for (i = 0; i < AG_WIDGET_NSTATES; i++) { for (j = 0; j < AG_WIDGET_NCOLORS; j++) { char nameFull[AG_VARIABLE_NAME_MAX]; const AG_Color *cParent = &parentPalette->c[i][j]; const char *name = agStyleAttributes[j]; AG_Color cNew; Strlcpy(nameFull, name, sizeof(nameFull)); if (i != 0) Strlcat(nameFull, agWidgetStateNames[i], sizeof(nameFull)); if (((V = AG_AccessVariable(wid, nameFull)) != NULL || (V = AG_AccessVariable(wid, name)) != NULL) && V->data.s[0] != '\0') { AG_ColorFromString(&cNew, V->data.s, cParent); AG_UnlockVariable(V); } else if ((AG_LookupStyleSheet(css, wid, nameFull, &cssData) || AG_LookupStyleSheet(css, wid, name, &cssData)) && cssData[0] != '\0') { AG_ColorFromString(&cNew, cssData, cParent); } else { cNew = *cParent; } if (AG_ColorCompare(&cNew, &wid->pal.c[i][j]) != 0) { wid->pal.c[i][j] = cNew; paletteChanged = 1; } } } if (paletteChanged) AG_PostEvent(wid, "palette-changed", NULL); if (wid->flags & AG_WIDGET_USE_TEXT) { /* Load any fonts required */ AG_Font *fontNew; fontNew = AG_FetchFontFromList(fontFace, fontSize, fontFlags); if (wid->font != fontNew) { wid->font = fontNew; AG_PushTextState(); AG_TextFont(wid->font); AG_PostEvent(wid, "font-changed", NULL); AG_PopTextState(); AG_Redraw(wid); } } OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { CompileStyleRecursive(chld, fontFace, fontSize, fontFlags, &wid->pal); } free(fontFace); } static void Apply_Font_Size(float *fontSize, float parentFontSize, const char *spec) { char *ep; float v, pts; v = (float)strtod(spec, &ep); pts = ((*ep == '%') ? parentFontSize * (v / 100.0f) : v); *fontSize = AG_FontGetStandardSize(pts); } static void Apply_Font_Weight(Uint *fontFlags, Uint parentFontFlags, const char *weight) { Uint flags; if ((flags = AG_FontGetStyleByName(weight)) == 0) { if (AG_Strcasecmp(weight, "!parent") == 0) { if ((parentFontFlags & AG_FONT_WEIGHTS) == 0) flags = AG_FONT_BOLD; } } *fontFlags &= ~(AG_FONT_WEIGHTS); *fontFlags |= flags; } static void Apply_Font_Style(Uint *fontFlags, Uint parentFontFlags, const char *style) { Uint flags; if ((flags = AG_FontGetStyleByName(style)) == 0) { if (AG_Strcasecmp(style, "!parent") == 0) { if ((parentFontFlags & AG_FONT_STYLES) == 0) flags = AG_FONT_ITALIC; } } *fontFlags &= ~(AG_FONT_STYLES); *fontFlags |= flags; } static void Apply_Font_Stretch(Uint *fontFlags, Uint parentFontFlags, const char *wdVariant) { Uint flags; if ((flags = AG_FontGetStyleByName(wdVariant)) == 0) { if (AG_Strcasecmp(wdVariant, "!parent") == 0) { if ((parentFontFlags & AG_FONT_WD_VARIANTS) == 0) flags = AG_FONT_CONDENSED; } } *fontFlags &= ~(AG_FONT_WD_VARIANTS); *fontFlags |= flags; } static void Apply_Padding(AG_Widget *wid, const char *spec) { char buf[16], *s=&buf[0], *sTop, *sRight; int nChanges=0; if (Strcasecmp(spec, "inherit") == 0) { Inherit_Padding(OBJECT(wid)->parent, buf, sizeof(buf)); } else { Strlcpy(buf, spec, sizeof(buf)); } if ((sTop = Strsep(&s, " ")) == NULL) return; if ((sRight = Strsep(&s, " ")) == NULL) { /* "padding: X" */ const int val = atoi(sTop); if (wid->paddingTop != val) { wid->paddingTop = val; nChanges++; } if (wid->paddingRight != val) { wid->paddingRight = val; nChanges++; } if (wid->paddingBottom != val) { wid->paddingBottom = val; nChanges++; } if (wid->paddingLeft != val) { wid->paddingLeft = val; nChanges++; } } else { /* "padding: T R B L" */ const char *sBottom = Strsep(&s, " "); const char *sLeft = Strsep(&s, " "); const int padTop = atoi(sTop); const int padRight = atoi(sRight); const int padBottom = (sBottom) ? atoi(sBottom) : 0; const int padLeft = (sLeft) ? atoi(sLeft) : 0; if (wid->paddingTop != padTop) { wid->paddingTop = padTop; nChanges++; } if (wid->paddingRight != padRight) { wid->paddingRight = padRight; nChanges++; } if (wid->paddingBottom != padBottom) { wid->paddingBottom = padBottom; nChanges++; } if (wid->paddingLeft != padLeft) { wid->paddingLeft = padLeft; nChanges++; } } if (nChanges > 0) AG_PostEvent(wid, "padding-changed", NULL); } static void Inherit_Padding(AG_Widget *wid, char *buf, AG_Size bufSize) { if (AG_Defined(wid, "padding")) { AG_GetString(wid, "padding", buf, sizeof(buf)); if (Strcasecmp(buf, "inherit") == 0) Inherit_Padding(OBJECT(wid)->parent, buf, sizeof(buf)); } else { if (bufSize > 0) buf[0] = '\0'; } } static void Apply_Margin(AG_Widget *wid, const char *spec) { char buf[16], *s=&buf[0], *sTop, *sRight; if (Strcasecmp(spec, "inherit") == 0) { Inherit_Margin(OBJECT(wid)->parent, buf, sizeof(buf)); } else { Strlcpy(buf, spec, sizeof(buf)); } if ((sTop = Strsep(&s, " ")) == NULL) return; if ((sRight = Strsep(&s, " ")) == NULL) { /* "margin: X" */ const int val = atoi(sTop); wid->marginTop = val; wid->marginRight = val; wid->marginBottom = val; wid->marginLeft = val; } else { /* "margin: T R [B L]" */ const char *sBottom = Strsep(&s, " "); const char *sLeft = Strsep(&s, " "); wid->marginTop = atoi(sTop); wid->marginRight = atoi(sRight); wid->marginBottom = (sBottom) ? atoi(sBottom) : 0; wid->marginLeft = (sLeft) ? atoi(sLeft) : 0; } } static void Inherit_Margin(AG_Widget *wid, char *buf, AG_Size bufSize) { if (AG_Defined(wid, "margin")) { AG_GetString(wid, "margin", buf, sizeof(buf)); if (Strcasecmp(buf, "inherit") == 0) Inherit_Margin(OBJECT(wid)->parent, buf, sizeof(buf)); } else { if (bufSize > 0) buf[0] = '\0'; } } static void Apply_Spacing(AG_Widget *wid, const char *spec) { char buf[8], *s=&buf[0], *sHoriz, *sVert; Strlcpy(buf, spec, sizeof(buf)); if ((sHoriz = Strsep(&s, " ")) == NULL) { return; } if ((sVert = Strsep(&s, " ")) != NULL) { wid->spacingHoriz = atoi(sHoriz); wid->spacingVert = atoi(sVert); } else { wid->spacingHoriz = atoi(sHoriz); wid->spacingVert = wid->spacingHoriz; } } /* * Update run-time style information for a widget (and its children). Use * both stylesheet definitions and per-widget attributes to update the color * palette, fonts, margin, padding and spacing data. This data can then be * used efficiently by draw(), size_allocate() or other widget methods. */ void AG_WidgetCompileStyle(void *obj) { AG_Widget *wid = obj; AG_Widget *parent; AG_Font *parentFont; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_LockVFS(wid); AG_MutexLock(&agTextLock); if ((parent = OBJECT(wid)->parent) != NULL && AG_WIDGET_ISA(parent) && (parentFont = parent->font) != NULL) { CompileStyleRecursive(wid, /* Inheritable attributes: */ OBJECT(parentFont)->name, /* "font-family" */ parentFont->spec.size, /* "font-size" */ parentFont->flags, /* "font-{style,weight,stretch}" */ &parent->pal); /* and the color palette */ } else { CompileStyleRecursive(wid, OBJECT(agDefaultFont)->name, agDefaultFont->spec.size, agDefaultFont->flags, &agDefaultPalette); } AG_MutexUnlock(&agTextLock); AG_UnlockVFS(wid); } /* * Clear the style parameters of a widget and its descendants. Called * on detach. The widget's VFS must be locked. */ void AG_WidgetFreeStyle(void *obj) { AG_Widget *wid = obj; AG_Widget *chld; AG_OBJECT_ISA(wid, "AG_Widget:*"); if (wid->font) { wid->font = NULL; } OBJECT_FOREACH_CHILD(chld, wid, ag_widget) AG_WidgetFreeStyle(chld); } /* Copy all style properties from one widget to another. */ void AG_WidgetCopyStyle(void *objDst, void *objSrc) { AG_Widget *widSrc = objSrc; AG_Widget *widDst = objDst; AG_Variable *V; const char **s; #ifdef AG_DEBUG int debugLvlSave; #endif AG_OBJECT_ISA(widSrc, "AG_Widget:*"); AG_OBJECT_ISA(widDst, "AG_Widget:*"); AG_ObjectLock(widSrc); AG_ObjectLock(widDst); Debug_Mute(debugLvlSave); for (s = &agStyleAttributes[0]; *s != NULL; s++) { if ((V = AG_AccessVariable(widSrc, *s)) != NULL) { AG_SetString(widDst, *s, V->data.s); AG_UnlockVariable(V); } } Debug_Unmute(debugLvlSave); AG_ObjectUnlock(widDst); AG_ObjectUnlock(widSrc); AG_WidgetCompileStyle(widDst); AG_Redraw(widDst); } /* * Generate per-widget style attributes from the style attributes of the * given font (font-family, font-weight and font-stretch). */ void AG_SetFont(void *obj, const AG_Font *font) { char buf[64]; AG_Widget *wid = obj; Uint flags; AG_OBJECT_ISA(wid, "AG_Widget:*"); AG_OBJECT_ISA(font, "Font:*"); AG_SetString(wid, "font-family", font->name); if ((flags = (font->flags & AG_FONT_WEIGHTS)) != 0) { AG_FontGetStyleName(buf, sizeof(buf), flags); AG_SetString(wid, "font-weight", buf); } if ((flags = (font->flags & AG_FONT_STYLES)) != 0) { AG_FontGetStyleName(buf, sizeof(buf), flags); AG_SetString(wid, "font-style", buf); } if ((flags = (font->flags & AG_FONT_WD_VARIANTS)) != 0) { AG_FontGetStyleName(buf, sizeof(buf), flags); AG_SetString(wid, "font-stretch", buf); } AG_WidgetCompileStyle(wid); AG_Redraw(wid); } /* Set the value of a CSS parameter to the given formatted string. */ void AG_SetStyleF(void *obj, const char *which, const char *fmt, ...) { AG_Widget *wid = obj; va_list ap; char *val; va_start(ap, fmt); Vasprintf(&val, fmt, ap); va_end(ap); AG_SetStyle(wid, which, val); free(val); } /* Set the value of a CSS parameter to the given string (or NULL = unset it). */ void AG_SetStyle(void *obj, const char *which, const char *value) { AG_Widget *wid = obj; AG_OBJECT_ISA(wid, "AG_Widget:*"); if (value) { AG_SetString(wid, which, value); } else { AG_Unset(wid, which); /* inherit */ } AG_WidgetCompileStyle(wid); AG_Redraw(wid); } #ifdef AG_LEGACY /* * Lookup a widget by pathname (equivalent to AG_ObjectFind() on &agDrivers). */ void * AG_WidgetFind(void *root, const char *pathname) { return AG_ObjectFindS(&agDrivers, pathname); } /* Generic inherited draw() routine (now equivalent to a NULL "draw" op). */ void AG_WidgetInheritDraw(void *obj) { WIDGET_SUPER_OPS(obj)->draw(obj); } #endif /* AG_LEGACY */ AG_WidgetClass agWidgetClass = { { "Agar(Widget)", sizeof(AG_Widget), { 1,0, AGC_WIDGET, 0xE018 }, Init, NULL, /* reset */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, NULL, /* draw */ SizeRequest, SizeAllocate, NULL, /* mouse_button_down */ NULL, /* mouse_button_up */ NULL, /* mouse_motion */ NULL, /* key_down */ NULL, /* key_up */ NULL, /* touch */ NULL, /* ctrl */ NULL /* joy */ };