/* * Copyright (c) 2002-2012 Hypertriton, Inc. * 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. */ #include #include #include #include #include #include #include #include enum { ZOOM_MIN = 20, /* Min zoom factor (%) */ ZOOM_MAX = 500, /* Max zoom factor (%) */ ZOOM_PROPS_MIN = 60, /* Min zoom factor for showing properties (%) */ ZOOM_GRID_MIN = 20 /* Min zoom factor for showing the grid (%) */ }; int mapViewBg = 1; /* Background tiles enable */ int mapViewAnimatedBg = 1; /* Background tiles moving */ int mapViewBgTileSize = 8; /* Background tile size */ int mapViewEditSelOnly = 0; /* Restrict edition to selection */ int mapViewZoomInc = 8; MAP_View * MAP_ViewNew(void *parent, MAP *m, Uint flags, struct ag_toolbar *toolbar, struct ag_statusbar *statbar) { MAP_View *mv; mv = Malloc(sizeof(MAP_View)); AG_ObjectInit(mv, &mapViewClass); mv->flags |= flags; mv->map = m; mv->toolbar = toolbar; mv->statusbar = statbar; AG_ObjectLock(m); mv->mx = m->origin.x; mv->my = m->origin.y; AG_ObjectUnlock(m); if (statbar != NULL) { mv->statusbar = statbar; mv->status = AG_StatusbarAddLabel(statbar, "..."); } AG_ObjectAttach(parent, mv); return (mv); } void MAP_ViewControl(MAP_View *mv, const char *slot, void *obj) { #ifdef SG_DEBUG if (!AG_OfClass(obj, "MAP_Actor:*")) AG_FatalError(NULL); #endif mv->actor = (MAP_Actor *)obj; } void MAP_ViewSelectTool(MAP_View *mv, MAP_Tool *ntool, void *p) { AG_Window *pwin; if (mv->curtool != NULL) { if (mv->curtool->trigger != NULL) { AG_SetBool(mv->curtool->trigger, "state", 0); } if (mv->curtool->win != NULL) { AG_WindowHide(mv->curtool->win); } if (mv->curtool->pane != NULL) { AG_ObjectFreeChildren(mv->curtool->pane); AG_WidgetUpdate(mv->curtool->pane); } mv->curtool->mv = NULL; AG_WidgetReplaceSurface(mv->status, mv->status->surface, AG_TextRender( _("Select a tool or double-click on an element to insert.") )); } mv->curtool = ntool; if (ntool != NULL) { ntool->p = p; ntool->mv = mv; if (ntool->trigger != NULL) { AG_SetBool(ntool->trigger, "state", 1); } if (ntool->win != NULL) { AG_WindowShow(ntool->win); } if (ntool->pane != NULL && ntool->ops->edit_pane != NULL) { ntool->ops->edit_pane(ntool, ntool->pane); AG_WidgetUpdate(mv->curtool->pane); } MAP_ToolUpdateStatus(ntool); } if ((pwin = AG_ParentWindow(mv)) != NULL) { AG_WindowFocus(pwin); AG_WidgetFocus(mv); } } MAP_Tool * MAP_ViewFindTool(MAP_View *mv, const char *name) { MAP_Tool *tool; TAILQ_FOREACH(tool, &mv->tools, tools) { if (strcmp(tool->ops->name, name) == 0) return (tool); } return (NULL); } static void SelectTool(AG_Event *_Nonnull event) { MAP_View *mv = AG_PTR(1); MAP_Tool *tool = AG_PTR(2); void *p = AG_PTR(3); if (mv->curtool == tool) { MAP_ViewSelectTool(mv, NULL, NULL); } else { MAP_ViewSelectTool(mv, tool, p); } } MAP_Tool * MAP_ViewRegTool(MAP_View *mv, const MAP_ToolOps *ops, void *p) { MAP_Tool *t; t = Malloc(ops->len); t->ops = ops; t->mv = mv; t->p = p; MAP_ToolInit(t); if (((ops->flags & TOOL_HIDDEN) == 0) && mv->toolbar != NULL) { t->trigger = AG_ToolbarButtonIcon(mv->toolbar, (ops->icon != NULL) ? ops->icon->s : NULL, 0, SelectTool, "%p, %p, %p", mv, t, p); } TAILQ_INSERT_TAIL(&mv->tools, t, tools); return (t); } void MAP_ViewSetDefaultTool(MAP_View *mv, MAP_Tool *tool) { mv->deftool = tool; } void MAP_ViewRegDrawCb(MAP_View *mv, void (*draw_func)(MAP_View *, void *), void *p) { MAP_ViewDrawCb *dcb; dcb = Malloc(sizeof(MAP_ViewDrawCb)); dcb->func = draw_func; dcb->p = p; SLIST_INSERT_HEAD(&mv->draw_cbs, dcb, draw_cbs); } static void Destroy(void *_Nonnull p) { MAP_View *mv = p; MAP_ViewDrawCb *dcb, *ndcb; for (dcb = SLIST_FIRST(&mv->draw_cbs); dcb != SLIST_END(&mv->draw_cbs); dcb = ndcb) { ndcb = SLIST_NEXT(dcb, draw_cbs); Free(dcb); } } static void OnDetach(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); MAP_Tool *tool, *ntool; for (tool = TAILQ_FIRST(&mv->tools); tool != TAILQ_END(&mv->tools); tool = ntool) { ntool = TAILQ_NEXT(tool, tools); MAP_ToolDestroy(tool); Free(tool); } TAILQ_INIT(&mv->tools); mv->curtool = NULL; } static void ExpireDblClick(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); mv->dblclicked = 0; } static void UpdateCamera(AG_Event *_Nonnull event) { MAP_View *mv = AG_PTR(1); MAP_ViewUpdateCamera(mv); } void MAP_ViewUseScrollbars(MAP_View *mv, AG_Scrollbar *hbar, AG_Scrollbar *vbar) { mv->hbar = hbar; mv->vbar = vbar; if (hbar != NULL) { WIDGET(hbar)->flags &= ~(AG_WIDGET_FOCUSABLE); WIDGET(hbar)->flags |= AG_WIDGET_UNFOCUSED_MOTION; AG_BindInt(mv->hbar, "value", &AGMCAM(mv).x); AG_SetEvent(mv->hbar, "scrollbar-changed", UpdateCamera, "%p", mv); } if (vbar != NULL) { WIDGET(vbar)->flags &= ~(AG_WIDGET_FOCUSABLE); WIDGET(vbar)->flags |= AG_WIDGET_UNFOCUSED_MOTION; AG_BindInt(mv->vbar, "value", &AGMCAM(mv).y); AG_SetEvent(mv->vbar, "scrollbar-changed", UpdateCamera, "%p", mv); } } /* * Translate widget coordinates to node coordinates. * The map must be locked. */ static __inline__ void GetNodeCoords(MAP_View *_Nonnull mv, int *_Nonnull x, int *_Nonnull y) { *x -= mv->xoffs; *y -= mv->yoffs; mv->cxoffs = *x % AGMTILESZ(mv); mv->cyoffs = *y % AGMTILESZ(mv); *x = ((*x) - mv->cxoffs)/AGMTILESZ(mv); *y = ((*y) - mv->cyoffs)/AGMTILESZ(mv); mv->cx = mv->mx + *x; mv->cy = mv->my + *y; if (mv->cx < 0 || mv->cx >= (int)mv->map->mapw || mv->cxoffs < 0) mv->cx = -1; if (mv->cy < 0 || mv->cy >= (int)mv->map->maph || mv->cyoffs < 0) mv->cy = -1; } static void DrawMapCursor(MAP_View *_Nonnull mv) { AG_Rect rd; AG_Color c; rd.w = AGMTILESZ(mv); rd.h = AGMTILESZ(mv); if (mv->msel.set) { #if 0 /* XXX */ AG_MouseGetState(&msx, &msy); if (!agView->opengl) AG_SurfaceBlit(mapIconCursor.s, NULL, agView->v, msx, msy); #endif return; } rd.x = mv->mouse.x*AGMTILESZ(mv) + mv->xoffs; rd.y = mv->mouse.y*AGMTILESZ(mv) + mv->yoffs; if (mv->curtool == NULL) return; if (mv->curtool->ops->cursor != NULL) { if (mv->curtool->ops->cursor(mv->curtool, &rd) == -1) goto defcurs; } return; defcurs: AG_ColorRGB_8(&c, 100,100,100); rd.x++; rd.y++; rd.w = rd.h = AGMTILESZ(mv)-1; AG_DrawRectOutline(mv, &rd, &c); rd.x++; rd.y++; rd.w = rd.h = AGMTILESZ(mv)-3; AG_DrawRectOutline(mv, &rd, &c); } static __inline__ void CenterToOrigin(MAP_View *_Nonnull mv) { AGMCAM(mv).x = mv->map->origin.x*AGMTILESZ(mv) - AGMTILESZ(mv)/2; AGMCAM(mv).y = mv->map->origin.y*AGMTILESZ(mv) - AGMTILESZ(mv)/2; MAP_ViewUpdateCamera(mv); } /* * Return a pointer to the referenced animation frame. * If there are transforms to apply, return a pointer to a matching * entry in the anim transformation cache, or allocate a new one. */ static void RenderAnimItem(MAP_Item *_Nonnull r, RG_Anim *_Nonnull anim, AG_Surface *_Nonnull *_Nonnull pSurface, Uint *_Nullable pTexture) /* TODO */ { RG_Tileset *ts = r->r_anim.obj; RG_AnimVariant *var; RG_AnimFrame *frame; RG_Transform *xf, *xf2; if (TAILQ_EMPTY(&r->transforms)) { frame = RG_ANIM_FRAME(anim, *r->r_anim.curframe); *pSurface = frame->su; return; } SLIST_FOREACH(var, &anim->vars, vars) { for (xf = TAILQ_FIRST(&r->transforms), xf2 = TAILQ_FIRST(&var->transforms); xf != TAILQ_END(&r->transforms) && xf2 != TAILQ_END(&var->transforms); xf = TAILQ_NEXT(xf, transforms), xf2 = TAILQ_NEXT(xf2, transforms)) { if (!RG_TransformCompare(xf, xf2)) break; } if (xf == TAILQ_END(&r->transforms) && xf2 == TAILQ_END(&var->transforms)) break; } if (var == NULL) { RG_AnimVariant *var; Uint32 i; /* * Create a new variant. Inherit the generated frames and * apply the transformations. Ignore the source instructions. */ var = Malloc(sizeof(RG_AnimVariant)); TAILQ_INIT(&var->transforms); RG_TransformChainDup(&r->transforms, &var->transforms); var->anim = Malloc(sizeof(RG_Anim)); RG_AnimInit(var->anim, ts, anim->name, (anim->flags & RG_ANIM_DUPED_FLAGS)); var->anim->w = anim->w; var->anim->h = anim->h; for (i = 0; i < anim->nframes; i++) { RG_AnimFrame *vframe; AG_Surface *Sx, *Sframe; Uint32 frame_no; /* Duplicate the original frame surface. */ frame = &anim->frames[i]; Sframe = frame->su; Sx = AG_SurfaceRGBA(Sframe->w, Sframe->h, Sframe->format.BitsPerPixel, Sframe->flags & (AG_SURFACE_ALPHA|AG_SURFACE_COLORKEY), Sframe->format.Rmask, Sframe->format.Gmask, Sframe->format.Bmask, Sframe->format.Amask); if (Sx == NULL) { AG_FatalError(NULL); } frame_no = RG_AnimInsertFrame(var->anim, Sx); vframe = &var->anim->frames[frame_no]; AG_SurfaceCopy(vframe->su, frame->su); /* Apply the transform chain. */ TAILQ_FOREACH(xf, &r->transforms, transforms) { Sx = xf->func(vframe->su, xf->nargs, xf->args); if (Sx != vframe->su) { AG_SurfaceFree(vframe->su); vframe->su = Sx; } } } SLIST_INSERT_HEAD(&anim->vars, var, vars); } var->last_drawn = AG_GetTicks(); frame = RG_ANIM_FRAME(var->anim, *r->r_anim.curframe); *pSurface = frame->su; } /* * Return a pointer to a tile item surface. If there are transforms to * apply, perform a cache lookup/insert. */ static __inline__ void RenderTileItem(MAP_Item *_Nonnull r, RG_Tile *_Nonnull tile, AG_Surface *_Nonnull *_Nonnull pSurface, Uint *_Nullable pTexture) /* TODO */ { RG_TileVariant *var; RG_Transform *xf, *xf2; if (TAILQ_EMPTY(&r->transforms)) { *pSurface = tile->su; return; } SLIST_FOREACH(var, &tile->vars, vars) { for (xf = TAILQ_FIRST(&r->transforms), xf2 = TAILQ_FIRST(&var->transforms); xf != TAILQ_END(&r->transforms) && xf2 != TAILQ_END(&var->transforms); xf = TAILQ_NEXT(xf, transforms), xf2 = TAILQ_NEXT(xf2, transforms)) { if (!RG_TransformCompare(xf, xf2)) break; } if (xf == TAILQ_END(&r->transforms) && xf2 == TAILQ_END(&var->transforms)) break; } if (var == NULL) { RG_Transform *xf; AG_Surface *Sx, *Stile; var = Malloc(sizeof(RG_TileVariant)); TAILQ_INIT(&var->transforms); RG_TransformChainDup(&r->transforms, &var->transforms); var->last_drawn = AG_GetTicks(); Stile = tile->su; var->su = AG_SurfaceRGBA( Stile->w, Stile->h, Stile->format.BitsPerPixel, Stile->flags & (AG_SURFACE_ALPHA|AG_SURFACE_COLORKEY), Stile->format.Rmask, Stile->format.Gmask, Stile->format.Bmask, Stile->format.Amask); if (var->su == NULL) { AG_FatalError(NULL); } AG_SurfaceCopy(var->su, Stile); TAILQ_FOREACH(xf, &r->transforms, transforms) { Sx = xf->func(var->su, xf->nargs, xf->args); if (Sx != var->su) { AG_SurfaceFree(var->su); var->su = Sx; } } SLIST_INSERT_HEAD(&tile->vars, var, vars); } var->last_drawn = AG_GetTicks(); *pSurface = var->su; } /* * Render a graphical map item to absolute view coordinates rx,ry. * The map must be locked. Must be called from widget draw context only. */ static void DrawItem(MAP_View *_Nonnull mv, MAP *_Nonnull m, MAP_Item *_Nonnull r, int rx, int ry, int cam) { char num[16]; int debug_su = 0; AG_Surface *su; int tilesz = m->cameras[cam].tilesz; RG_Tile *tile; RG_Anim *anim; int x, y; switch (r->type) { case MAP_ITEM_TILE: if (RG_LookupTile(r->r_tile.obj,r->r_tile.id, &tile) == 0) { RenderTileItem(r, tile, &su, NULL); } else { Snprintf(num, sizeof(num), "(s%u)", (Uint)r->r_tile.id); AG_TextColorRGBA(250,250,50,150); su = AG_TextRender(num); debug_su++; goto draw; } break; case MAP_ITEM_ANIM: if (RG_LookupAnim(r->r_anim.obj,r->r_anim.id, &anim) == 0) { RenderAnimItem(r, anim, &su, NULL); } else { Snprintf(num, sizeof(num), "(a%u)", r->r_anim.id); AG_TextColorRGBA(250,250,50,150); su = AG_TextRender(num); debug_su++; goto draw; } break; default: /* Not a drawable */ return; } draw: if (tilesz != MAPTILESZ) { x = rx + r->r_gfx.xcenter*tilesz/MAPTILESZ + r->r_gfx.xmotion*tilesz/MAPTILESZ - r->r_gfx.xorigin*tilesz/MAPTILESZ; y = ry + r->r_gfx.ycenter*tilesz/MAPTILESZ + r->r_gfx.ymotion*tilesz/MAPTILESZ - r->r_gfx.yorigin*tilesz/MAPTILESZ; #if 0 /* XXX 1.4 */ BlitSurfaceScaled(m, su, &r->r_gfx.rs, x,y, cam); #endif } else { x = rx + r->r_gfx.xcenter + r->r_gfx.xmotion - r->r_gfx.xorigin; y = ry + r->r_gfx.ycenter + r->r_gfx.ymotion - r->r_gfx.yorigin; AG_WidgetBlit(mv, su, x,y); #if 0 /* XXX 1.4 */ AG_SurfaceBlit(su, debug_su ? NULL : &r->r_gfx.rs, agView->v, x,y); #endif } if (debug_su) AG_SurfaceFree(su); } static void Draw(void *_Nonnull obj) { MAP_View *mv = obj; MAP_ViewDrawCb *dcb; MAP *m = mv->map; MAP_Node *node; MAP_Item *nref; int mx, my, rx = 0, ry = 0; int layer = 0; AG_Rect r, rSel, mSel, rExtent; AG_Color c, c2; rSel.x = -1; mSel.x = -1; rSel.y = -1; mSel.y = -1; rSel.w = -1; mSel.w = -1; rSel.h = -1; mSel.h = -1; if (WIDTH(mv) < MAPTILESZ || HEIGHT(mv) < MAPTILESZ) return; if (mv->hbar != NULL) { AG_WidgetDraw(mv->hbar); } if (mv->vbar != NULL) { AG_WidgetDraw(mv->vbar); } AG_PushClipRect(mv, &mv->r); if (WIDGET(mv)->flags & AG_WIDGET_FOCUSED) { AG_ColorRGB_8(&c, 150,150,150); AG_DrawRectOutline(mv, &mv->r, &c); } SLIST_FOREACH(dcb, &mv->draw_cbs, draw_cbs) dcb->func(mv, dcb->p); if (mv->flags & MAP_VIEW_CENTER) { mv->flags &= ~(MAP_VIEW_CENTER); CenterToOrigin(mv); } if ((mv->flags & MAP_VIEW_NO_BG) == 0) { r.x = 0; r.y = 0; r.w = WIDTH(mv); r.h = HEIGHT(mv); AG_ColorRGB_8(&c, 50,50,50); AG_ColorRGB_8(&c2, 40,40,40); AG_DrawTiling(mv, &r, mapViewBgTileSize, 0, &c, &c2); } AG_ObjectLock(m); if (m->map == NULL) goto out; draw_layer: if (!m->layers[layer].visible) { goto next_layer; } for (my = mv->my, ry = mv->yoffs; ((my - mv->my) <= (int)mv->mh) && (my < (int)m->maph); my++, ry += AGMTILESZ(mv)) { for (mx = mv->mx, rx = mv->xoffs; ((mx - mv->mx) <= (int)mv->mw) && (mx < (int)m->mapw); mx++, rx += AGMTILESZ(mv)) { node = &m->map[my][mx]; TAILQ_FOREACH(nref, &node->nrefs, nrefs) { if (nref->layer != layer) continue; DrawItem(mv, m, nref, rx, ry, mv->cam); #ifdef SG_DEBUG if (mv->flags & MAP_VIEW_SHOW_OFFSETS) { AG_ColorRGB_8(&c, 60,250,60); AG_DrawLine(mv, rx, ry, (rx+nref->r_gfx.xcenter + nref->r_gfx.xmotion - nref->r_gfx.xorigin), (ry+nref->r_gfx.ycenter + nref->r_gfx.ymotion - nref->r_gfx.yorigin), &c); } #endif /* SG_DEBUG */ if ((nref->layer == m->cur_layer) && (mv->mode == MAP_VIEW_EDIT_ATTRS)) { MAP_ItemAttrColor(mv->edit_attr, (nref->flags & mv->edit_attr), &c); r.x = rx; r.y = ry; r.w = AGMTILESZ(mv); r.h = AGMTILESZ(mv); AG_DrawRectBlended(mv, &r, &c, AG_ALPHA_OVERLAY); } if ((nref->flags & MAP_ITEM_SELECTED) && MAP_ItemExtent(m, nref, &rExtent, mv->cam) == 0) { r.x = rx + rExtent.x - 1; r.y = ry + rExtent.y - 1; r.w = rExtent.w + 1; r.h = rExtent.h + 1; AG_ColorRGB_8(&c, 60,250,60); AG_DrawRectOutline(mv, &r, &c); } } if ((mv->flags & MAP_VIEW_EDIT) == 0) continue; if ((mv->flags & MAP_VIEW_SHOW_ORIGIN) && (mx == m->origin.x && my == m->origin.y)) { int t2 = AGMTILESZ(mv) >> 1; int rx2 = rx+AGMTILESZ(mv); int ry2 = ry+AGMTILESZ(mv); int rxt2 = rx+t2; int ryt2 = ry+t2; AG_ColorRGB_8(&c, 150,150,0); AG_DrawCircle(mv, rxt2, ryt2, t2, &c); AG_DrawLine(mv, rxt2, ry, rxt2, ry2, &c); AG_DrawLine(mv, rx, ryt2, rx2, ryt2, &c); } if (mv->msel.set && mv->msel.x == mx && mv->msel.y == my) { mSel.x = rx + 1; mSel.y = ry + 1; mSel.w = mv->msel.xoffs * AGMTILESZ(mv) - 2; mSel.h = mv->msel.yoffs * AGMTILESZ(mv) - 2; } if (mv->esel.set && mv->esel.x == mx && mv->esel.y == my) { rSel.x = rx; rSel.y = ry; rSel.w = AGMTILESZ(mv) * mv->esel.w; rSel.h = AGMTILESZ(mv) * mv->esel.h; } } } next_layer: if (++layer < (int)m->nlayers) goto draw_layer; /* Draw next layer */ /* Draw the node grid. */ if (mv->flags & MAP_VIEW_GRID) { int rx2 = rx; for (; ry >= mv->yoffs; ry -= AGMTILESZ(mv)) { MAP_ViewHLine(mv, mv->xoffs, rx2, ry); for (; rx >= mv->xoffs; rx -= AGMTILESZ(mv)) MAP_ViewVLine(mv, rx, mv->yoffs, ry); } } if (rSel.x != -1) { /* Active selection */ AG_ColorRGB_8(&c, 180,180,180); AG_DrawRectOutline(mv, &rSel, &c); } if (mSel.x != -1) { /* Mouse selection */ AG_ColorRGB_8(&c, 150,150,150); AG_DrawRectOutline(mv, &mSel, &c); } /* Draw the cursor for the current tool. */ if ((mv->flags & MAP_VIEW_EDIT) && (mv->mode == MAP_VIEW_EDITION) && (mv->flags & MAP_VIEW_NO_CURSOR) == 0 && (mv->cx != -1 && mv->cy != -1)) { DrawMapCursor(mv); } out: AG_ObjectUnlock(m); AG_PopClipRect(mv); } /* * Recalculate the offsets to be used by the rendering routine based on * the current camera coordinates. */ void MAP_ViewUpdateCamera(MAP_View *mv) { MAP *m = mv->map; MAP_Camera *cam; int xcam, ycam, xMax, yMax; AG_ObjectLock(m); cam = &AGMCAM(mv); if (cam->x < 0) { cam->x = 0; } else if (cam->x > (xMax = (int)m->mapw*AGMTILESZ(mv))) { cam->x = xMax; } if (cam->y < 0) { cam->y = 0; } else if (cam->y > (yMax = (int)m->maph*AGMTILESZ(mv))) { cam->y = yMax; } xcam = cam->x; ycam = cam->y; switch (cam->alignment) { case AG_MAP_CENTER: xcam -= WIDTH(mv) >> 1; ycam -= HEIGHT(mv) >> 1; break; case AG_MAP_LOWER_CENTER: xcam -= WIDTH(mv) >> 1; ycam -= HEIGHT(mv); break; case AG_MAP_UPPER_CENTER: xcam -= WIDTH(mv) >> 1; break; case AG_MAP_UPPER_LEFT: break; case AG_MAP_MIDDLE_LEFT: ycam -= HEIGHT(mv) >> 1; break; case AG_MAP_LOWER_LEFT: ycam -= HEIGHT(mv); break; case AG_MAP_UPPER_RIGHT: xcam -= WIDTH(mv); break; case AG_MAP_MIDDLE_RIGHT: xcam -= WIDTH(mv); ycam -= HEIGHT(mv) >> 1; break; case AG_MAP_LOWER_RIGHT: xcam -= WIDTH(mv); ycam -= HEIGHT(mv); break; } mv->mx = xcam / AGMTILESZ(mv); if (mv->mx < 0) { mv->mx = 0; mv->xoffs = -xcam - AGMTILESZ(mv); } else { mv->xoffs = -(xcam % AGMTILESZ(mv)) - AGMTILESZ(mv); } mv->my = ycam / AGMTILESZ(mv); if (mv->my < 0) { mv->my = 0; mv->yoffs = -ycam - AGMTILESZ(mv); } else { mv->yoffs = -(ycam % AGMTILESZ(mv)) - AGMTILESZ(mv); } if (mv->hbar != NULL) { AG_SetInt(mv->hbar, "min", 0); AG_SetInt(mv->hbar, "max", m->mapw * AGMTILESZ(mv)); AG_ScrollbarSetControlLength(mv->hbar, 20); /* XXX */ } if (mv->vbar != NULL) { AG_SetInt(mv->vbar, "min", 0); AG_SetInt(mv->vbar, "max", m->maph * AGMTILESZ(mv)); AG_ScrollbarSetControlLength(mv->vbar, 20); } AG_ObjectUnlock(m); } void MAP_ViewSetScale(MAP_View *mv, Uint zoom, int adj_offs) { MAP *m; int pixw, pixh, old_pixw, old_pixh; AG_ObjectLock(mv); m = mv->map; AG_ObjectLock(m); old_pixw = m->mapw * AGMTILESZ(mv); old_pixh = m->maph * AGMTILESZ(mv); if (zoom < ZOOM_MIN) { zoom = ZOOM_MIN; } else if (zoom > ZOOM_MAX) { zoom = ZOOM_MAX; } AGMZOOM(mv) = zoom; AGMTILESZ(mv) = zoom*MAPTILESZ/100; AGMPIXSZ(mv) = AGMTILESZ(mv)/MAPTILESZ; if (AGMTILESZ(mv) > MAP_TILESZ_MAX) AGMTILESZ(mv) = MAP_TILESZ_MAX; mv->mw = WIDTH(mv)/AGMTILESZ(mv) + 2; mv->mh = HEIGHT(mv)/AGMTILESZ(mv) + 2; pixw = m->mapw * AGMTILESZ(mv); pixh = m->maph * AGMTILESZ(mv); if (adj_offs) { AGMCAM(mv).x = AGMCAM(mv).x * pixw / old_pixw; AGMCAM(mv).y = AGMCAM(mv).y * pixh / old_pixh; } AG_ObjectUnlock(m); AG_ObjectUnlock(mv); MAP_ViewUpdateCamera(mv); } static __inline__ int InsideNodeSelection(MAP_View *_Nonnull mv, int x, int y) { return (!mapViewEditSelOnly || !mv->esel.set || (x >= mv->esel.x && y >= mv->esel.y && x < mv->esel.x + mv->esel.w && y < mv->esel.y + mv->esel.h)); } static void ToggleAttribute(MAP_View *_Nonnull mv) { MAP *m = mv->map; MAP_Item *r; MAP_Node *node; if (mv->attr_x == mv->cx && mv->attr_y == mv->cy) return; MAP_ModBegin(m); MAP_ModNodeChg(m, mv->cx, mv->cy); node = &m->map[mv->cy][mv->cx]; TAILQ_FOREACH(r, &node->nrefs, nrefs) { if (r->layer != m->cur_layer) { continue; } if (r->flags & mv->edit_attr) { r->flags &= ~(mv->edit_attr); } else { r->flags |= mv->edit_attr; } } MAP_ModEnd(m); mv->attr_x = mv->cx; mv->attr_y = mv->cy; } static void MouseMotion(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); MAP *m = mv->map; int x = AG_INT(1); int y = AG_INT(2); int xrel = AG_INT(3); int yrel = AG_INT(4); int state = AG_INT(5); int xmap, ymap; int rv; AG_ObjectLock(m); GetNodeCoords(mv, &x, &y); mv->cxrel = x - mv->mouse.x; mv->cyrel = y - mv->mouse.y; xmap = mv->cx * AGMTILESZ(mv) + mv->cxoffs; ymap = mv->cy * AGMTILESZ(mv) + mv->cyoffs; mv->mouse.xmap_rel += xmap - mv->mouse.xmap; mv->mouse.ymap_rel += ymap - mv->mouse.ymap; mv->mouse.xmap = xmap; mv->mouse.ymap = ymap; if (mv->flags & MAP_VIEW_EDIT) { if ((state & AG_MOUSE_LEFT) && mv->cx != -1 && mv->cy != -1 && (x != mv->mouse.x || y != mv->mouse.y) && (InsideNodeSelection(mv, mv->cx, mv->cy))) { if (mv->flags & MAP_VIEW_SET_ATTRS) { ToggleAttribute(mv); goto out; } if (mv->mode == MAP_VIEW_EDIT_ORIGIN) { m->origin.x = mv->cx; m->origin.y = mv->cy; m->origin.layer = m->cur_layer; goto out; } if (mv->curtool != NULL && mv->curtool->ops->effect != NULL && (rv = mv->curtool->ops->effect(mv->curtool, &m->map[mv->cy][mv->cx])) != -1) { m->nmods += rv; goto out; } } if (mv->curtool != NULL && mv->curtool->ops->mousemotion != NULL && mv->curtool->ops->mousemotion(mv->curtool, mv->mouse.xmap, mv->mouse.ymap, xrel, yrel, state) == 1) { goto out; } if (mv->deftool != NULL && mv->deftool->ops->mousemotion != NULL && mv->deftool->ops->mousemotion(mv->deftool, mv->mouse.xmap, mv->mouse.ymap, xrel, yrel, state) == 1) { goto out; } } if (mv->mouse.scrolling) { AGMCAM(mv).x -= xrel; AGMCAM(mv).y -= yrel; MAP_ViewUpdateCamera(mv); } else if (mv->msel.set) { mv->msel.xoffs += mv->cxrel; mv->msel.yoffs += mv->cyrel; } else if (mv->esel.set && mv->esel.moving) { MAP_NodeselUpdateMove(mv, mv->cxrel, mv->cyrel); } else if (mv->rsel.moving) { if (abs(mv->mouse.xmap_rel) > AGMPIXSZ(mv)) { MAP_UpdateRefSel(mv, mv->mouse.xmap_rel < 0 ? -1 : 1, 0); mv->mouse.xmap_rel = 0; } if (abs(mv->mouse.ymap_rel) > AGMPIXSZ(mv)) { MAP_UpdateRefSel(mv, 0, mv->mouse.ymap_rel < 0 ? -1 : 1); mv->mouse.ymap_rel = 0; } } out: AG_ObjectUnlock(m); mv->mouse.x = x; mv->mouse.y = y; } void MAP_ViewSetMode(MAP_View *mv, enum map_view_mode mode) { AG_ObjectLock(mv); mv->mode = mode; AG_ObjectUnlock(mv); } static void MouseButtonDown(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); MAP *m = mv->map; int button = AG_INT(1); int x = AG_INT(2); int y = AG_INT(3); MAP_Tool *tool; int rv; AG_WidgetFocus(mv); AG_ObjectLock(m); GetNodeCoords(mv, &x, &y); mv->mouse.x = x; mv->mouse.y = y; mv->mouse.xmap = mv->cx*AGMTILESZ(mv) + mv->cxoffs; mv->mouse.ymap = mv->cy*AGMTILESZ(mv) + mv->cyoffs; mv->mouse.xmap_rel = 0; mv->mouse.ymap_rel = 0; if (mv->actor != NULL && MAP_ACTOR_OPS(mv->actor)->mousebuttondown != NULL && MAP_ACTOR_OPS(mv->actor)->mousebuttondown(mv->actor, mv->mouse.xmap, mv->mouse.ymap, button) == -1) goto out; if ((mv->flags & MAP_VIEW_EDIT) && (mv->cx >= 0 && mv->cy >= 0)) { if (mv->mode == MAP_VIEW_EDIT_ATTRS && button == AG_MOUSE_LEFT) { mv->flags |= MAP_VIEW_SET_ATTRS; mv->attr_x = -1; mv->attr_y = -1; ToggleAttribute(mv); goto out; } if (mv->flags & MAP_VIEW_SHOW_ORIGIN && button == AG_MOUSE_LEFT && mv->cx == m->origin.x && mv->cy == m->origin.y) { MAP_ViewSetMode(mv, MAP_VIEW_EDIT_ORIGIN); goto out; } if (mv->curtool != NULL) { if (mv->curtool->ops->mousebuttondown != NULL && mv->curtool->ops->mousebuttondown(mv->curtool, mv->mouse.xmap, mv->mouse.ymap, button) == 1) { goto out; } if (button == AG_MOUSE_LEFT && mv->curtool->ops->effect != NULL && InsideNodeSelection(mv, mv->cx, mv->cy)) { if ((rv = mv->curtool->ops->effect(mv->curtool, &m->map[mv->cy][mv->cx])) != -1) { mv->map->nmods = rv; goto out; } } } /* Mouse bindings allow inactive tools to bind mouse events. */ TAILQ_FOREACH(tool, &mv->tools, tools) { MAP_ToolMouseBinding *mbinding; SLIST_FOREACH(mbinding, &tool->mbindings, mbindings) { if (mbinding->button != button) { continue; } if (mbinding->edit && (OBJECT(m)->flags & AG_OBJECT_READONLY)) { continue; } tool->mv = mv; if (mbinding->func(tool, button, 1, mv->mouse.xmap, mv->mouse.ymap, mbinding->arg) == 1) goto out; } } if (mv->deftool != NULL && mv->deftool->ops->mousebuttondown != NULL && mv->deftool->ops->mousebuttondown(mv->deftool, mv->mouse.xmap, mv->mouse.ymap, button) == 1) { goto out; } } switch (button) { case AG_MOUSE_LEFT: if (mv->esel.set) { if (mv->cx >= mv->esel.x && mv->cy >= mv->esel.y && mv->cx < mv->esel.x+mv->esel.w && mv->cy < mv->esel.y+mv->esel.h) { MAP_NodeselBeginMove(mv); } else { mv->esel.set = 0; } goto out; } else { AG_KeyMod mod = AG_GetModState(mv); MAP_Item *r; int nx, ny; if (mv->curtool != NULL && mv->curtool->ops == &mapNodeselOps && (mv->flags & MAP_VIEW_NO_NODESEL) == 0) { MAP_NodeselBegin(mv); goto out; } if ((mod & AG_KEYMOD_CTRL) == 0) { /* XXX too expensive */ for (ny = 0; ny < (int)m->maph; ny++) { for (nx = 0; nx < (int)m->mapw; nx++) { MAP_Node *node = &m->map[ny][nx]; TAILQ_FOREACH(r, &node->nrefs, nrefs) { r->flags &= ~(MAP_ITEM_SELECTED); } } } } if (mv->curtool != NULL && mv->curtool->ops == &mapRefselOps && (r = MAP_ItemLocate(m, mv->mouse.xmap, mv->mouse.ymap, mv->cam)) != NULL) { if (r->flags & MAP_ITEM_SELECTED) { r->flags &= ~(MAP_ITEM_SELECTED); } else { r->flags |= MAP_ITEM_SELECTED; mv->rsel.moving = 1; } } } if (mv->dblclicked) { AG_PostEvent(NULL, mv, "mapview-dblclick", "%i, %i, %i, %i, %i", button, x, y, mv->cxoffs, mv->cyoffs); mv->dblclicked = 0; } else { mv->dblclicked++; AG_SchedEvent(NULL, mv, agMouseDblclickDelay, "dblclick-expire", NULL); } break; case AG_MOUSE_MIDDLE: /* TODO menu */ if ((mv->flags & MAP_VIEW_EDIT) == 0 || mv->curtool == NULL) { mv->mouse.scrolling++; break; } break; case AG_MOUSE_RIGHT: mv->mouse.scrolling++; goto out; case AG_MOUSE_WHEELDOWN: if ((mv->flags & MAP_VIEW_NO_BMPSCALE) == 0) { MAP_ViewSetScale(mv, AGMZOOM(mv) - mapViewZoomInc, 1); MAP_ViewStatus(mv, _("%d%% zoom"), AGMZOOM(mv)); } break; case AG_MOUSE_WHEELUP: if ((mv->flags & MAP_VIEW_NO_BMPSCALE) == 0) { MAP_ViewSetScale(mv, AGMZOOM(mv) + mapViewZoomInc, 1); MAP_ViewStatus(mv, _("%d%% zoom"), AGMZOOM(mv)); } break; } out: AG_ObjectUnlock(m); } static void MouseButtonUp(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); MAP *m = mv->map; int button = AG_INT(1); int x = AG_INT(2); int y = AG_INT(3); MAP_Tool *tool; AG_ObjectLock(m); GetNodeCoords(mv, &x, &y); mv->flags &= ~(MAP_VIEW_SET_ATTRS); if (mv->actor != NULL && MAP_ACTOR_OPS(mv->actor)->mousebuttonup != NULL) { x = mv->cx*AGMTILESZ(mv) + mv->cxoffs; y = mv->cy*AGMTILESZ(mv) + mv->cyoffs; if (MAP_ACTOR_OPS(mv->actor)->mousebuttonup(mv->actor, x, y, button) == -1) goto out; } if ((mv->flags & MAP_VIEW_EDIT) && (mv->cx >= 0 && mv->cy >= 0)) { if (mv->mode == MAP_VIEW_EDIT_ORIGIN) { MAP_ViewSetMode(mv, MAP_VIEW_EDITION); } if (mv->curtool != NULL) { if (mv->curtool->ops->mousebuttonup != NULL && mv->curtool->ops->mousebuttonup(mv->curtool, mv->mouse.xmap, mv->mouse.ymap, button) == 1) { goto out; } } TAILQ_FOREACH(tool, &mv->tools, tools) { MAP_ToolMouseBinding *mbinding; SLIST_FOREACH(mbinding, &tool->mbindings, mbindings) { if (mbinding->button != button) { continue; } if (mbinding->edit && (OBJECT(m)->flags&AG_OBJECT_READONLY)) { continue; } tool->mv = mv; if (mbinding->func(tool, button, 0, x, y, mbinding->arg) == 1) goto out; } } if (mv->deftool != NULL && mv->deftool->ops->mousebuttonup != NULL && mv->deftool->ops->mousebuttonup(mv->deftool, mv->mouse.xmap, mv->mouse.ymap, button) == 1) { goto out; } } else { mv->mouse.scrolling = 0; if (mv->esel.set && mv->esel.moving) { MAP_NodeselEndMove(mv); } mv->rsel.moving = 0; goto out; } switch (button) { case AG_MOUSE_LEFT: if (mv->msel.set && (mv->msel.xoffs == 0 || mv->msel.yoffs == 0)) { mv->esel.set = 0; mv->msel.set = 0; } else { if (mv->msel.set) { MAP_NodeselEnd(mv); mv->msel.set = 0; } else if (mv->esel.set && mv->esel.moving) { MAP_NodeselEndMove(mv); } } mv->rsel.moving = 0; break; case AG_MOUSE_RIGHT: case AG_MOUSE_MIDDLE: mv->mouse.scrolling = 0; break; } out: AG_ObjectUnlock(m); } static void KeyUp(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); MAP *m = mv->map; int keysym = AG_INT(1); int keymod = AG_INT(2); MAP_Tool *tool; AG_ObjectLock(m); if (mv->actor != NULL && MAP_ACTOR_OPS(mv->actor)->keyup != NULL) { if (MAP_ACTOR_OPS(mv->actor)->keyup(mv->actor, keysym, keymod) == -1) goto out; } if (mv->flags & MAP_VIEW_EDIT && mv->curtool != NULL && mv->curtool->ops->keyup != NULL && mv->curtool->ops->keyup(mv->curtool, keysym, keymod) == 1) goto out; TAILQ_FOREACH(tool, &mv->tools, tools) { MAP_ToolKeyBinding *kbinding; SLIST_FOREACH(kbinding, &tool->kbindings, kbindings) { if (kbinding->key == keysym && (kbinding->mod == AG_KEYMOD_NONE || keymod & kbinding->mod)) { if (kbinding->edit && (((mv->flags & MAP_VIEW_EDIT) == 0) || ((OBJECT(mv->map)->flags & AG_OBJECT_READONLY)))) { continue; } tool->mv = mv; if (kbinding->func(tool, keysym, 0, kbinding->arg) == 1) goto out; } } } out: AG_ObjectUnlock(m); } static void KeyDown(AG_Event *_Nonnull event) { MAP_View *mv = AG_SELF(); MAP *m = mv->map; int keysym = AG_INT(1); int keymod = AG_INT(2); MAP_Tool *tool; AG_ObjectLock(m); if (mv->actor != NULL && MAP_ACTOR_OPS(mv->actor)->keydown != NULL) { if (MAP_ACTOR_OPS(mv->actor)->keydown(mv->actor, keysym, keymod) == -1) goto out; } if (mv->flags & MAP_VIEW_EDIT && mv->curtool != NULL && mv->curtool->ops->keydown != NULL && mv->curtool->ops->keydown(mv->curtool, keysym, keymod) == 1) goto out; TAILQ_FOREACH(tool, &mv->tools, tools) { MAP_ToolKeyBinding *kbinding; SLIST_FOREACH(kbinding, &tool->kbindings, kbindings) { if (kbinding->key == keysym && (kbinding->mod == AG_KEYMOD_NONE || keymod & kbinding->mod)) { if (kbinding->edit && (((mv->flags & MAP_VIEW_EDIT) == 0) || ((OBJECT(mv->map)->flags & AG_OBJECT_READONLY)))) { continue; } tool->mv = mv; if (kbinding->func(tool, keysym, 1, kbinding->arg) == 1) goto out; } } } switch (keysym) { case AG_KEY_Z: if (keymod & AG_KEYMOD_CTRL) { MAP_Undo(mv->map); } break; case AG_KEY_R: if (keymod & AG_KEYMOD_CTRL) { MAP_Redo(mv->map); } break; case AG_KEY_1: case AG_KEY_0: if ((mv->flags & MAP_VIEW_NO_BMPSCALE) == 0) { MAP_ViewSetScale(mv, 100, 1); MAP_ViewStatus(mv, _("%d%% zoom"), AGMZOOM(mv)); } break; case AG_KEY_O: CenterToOrigin(mv); break; case AG_KEY_G: if (mv->flags & MAP_VIEW_GRID) { mv->flags &= ~(MAP_VIEW_GRID); } else { mv->flags |= MAP_VIEW_GRID; } break; case AG_KEY_B: if (mv->flags & MAP_VIEW_NO_BG) { mv->flags &= ~(MAP_VIEW_NO_BG); } else { mv->flags |= MAP_VIEW_NO_BG; } break; case AG_KEY_W: if (mv->mode == MAP_VIEW_EDITION) { MAP_ViewSetMode(mv, MAP_VIEW_EDIT_ATTRS); mv->edit_attr = MAP_ITEM_BLOCK; } else { MAP_ViewSetMode(mv, MAP_VIEW_EDITION); } break; case AG_KEY_C: if (mv->mode == MAP_VIEW_EDITION) { MAP_ViewSetMode(mv, MAP_VIEW_EDIT_ATTRS); mv->edit_attr = MAP_ITEM_CLIMBABLE; } else { MAP_ViewSetMode(mv, MAP_VIEW_EDITION); } break; case AG_KEY_S: if (mv->mode == MAP_VIEW_EDITION) { MAP_ViewSetMode(mv, MAP_VIEW_EDIT_ATTRS); mv->edit_attr = MAP_ITEM_SLIPPERY; } else { MAP_ViewSetMode(mv, MAP_VIEW_EDITION); } break; case AG_KEY_J: if (mv->mode == MAP_VIEW_EDITION) { MAP_ViewSetMode(mv, MAP_VIEW_EDIT_ATTRS); mv->edit_attr = MAP_ITEM_JUMPABLE; } else { MAP_ViewSetMode(mv, MAP_VIEW_EDITION); } break; } out: AG_ObjectUnlock(m); } static void SizeRequest(void *_Nonnull obj, AG_SizeReq *_Nonnull r) { MAP_View *mv = obj; r->w = mv->prew*MAPTILESZ; r->h = mv->preh*MAPTILESZ; } static int SizeAllocate(void *_Nonnull obj, const AG_SizeAlloc *_Nonnull a) { MAP_View *mv = obj; MAP *m = mv->map; AG_SizeAlloc aBar; mv->r.w = a->w; mv->r.h = a->h; if (mv->hbar != NULL) { aBar.x = 0; aBar.y = a->h - mv->hbar->width; aBar.w = a->w; aBar.h = mv->hbar->width; AG_WidgetSizeAlloc(mv->hbar, &aBar); mv->r.h -= HEIGHT(mv->hbar); } if (mv->vbar != NULL) { aBar.x = 0; aBar.y = 0; aBar.w = mv->vbar->width; aBar.h = a->h; AG_WidgetSizeAlloc(mv->vbar, &aBar); mv->r.w -= WIDTH(mv->vbar); } AG_ObjectLock(m); MAP_ViewSetScale(mv, AGMZOOM(mv), 0); AG_ObjectUnlock(m); return (0); } static void LostFocus(AG_Event *_Nonnull event) { // MAP_View *mv = AG_SELF(); // if (mv->actor != NULL) // AG_ObjectCancelTimeouts(mv->actor, 0); } /* Set the coordinates and geometry of the selection rectangle. */ void MAP_ViewSetSelection(MAP_View *mv, int x, int y, int w, int h) { AG_ObjectLock(mv); mv->msel.set = 0; mv->esel.set = 1; mv->esel.x = x; mv->esel.y = y; mv->esel.w = w; mv->esel.h = h; AG_ObjectUnlock(mv); } /* Fetch the coordinates and geometry of the selection rectangle. */ int MAP_ViewGetSelection(MAP_View *mv, int *x, int *y, int *w, int *h) { AG_ObjectLock(mv); if (mv->esel.set) { if (x != NULL) { *x = mv->esel.x; } if (y != NULL) { *y = mv->esel.y; } if (w != NULL) { *w = mv->esel.w; } if (h != NULL) { *h = mv->esel.h; } AG_ObjectUnlock(mv); return (1); } AG_ObjectUnlock(mv); return (0); } void MAP_ViewSizeHint(MAP_View *mv, int w, int h) { AG_ObjectLock(mv); mv->prew = w; mv->preh = h; AG_ObjectUnlock(mv); } void MAP_ViewStatus(MAP_View *mv, const char *fmt, ...) { char status[AG_LABEL_MAX]; va_list ap; AG_ObjectLock(mv); if (mv->status == NULL) { AG_ObjectUnlock(mv); return; } va_start(ap, fmt); Vsnprintf(status, sizeof(status), fmt, ap); va_end(ap); AG_WidgetReplaceSurface(mv->status, mv->status->surface, AG_TextRender(status)); AG_ObjectUnlock(mv); } static void Init(void *_Nonnull obj) { MAP_View *mv = obj; WIDGET(mv)->flags |= AG_WIDGET_FOCUSABLE|AG_WIDGET_EXPAND; mv->flags = MAP_VIEW_CENTER; mv->mode = MAP_VIEW_EDITION; mv->edit_attr = 0; mv->map = NULL; mv->actor = NULL; mv->cam = 0; mv->mw = 0; /* Set on scale */ mv->mh = 0; mv->prew = 4; mv->preh = 4; mv->r.x = 0; mv->r.y = 0; mv->r.w = 0; mv->r.h = 0; mv->mouse.scrolling = 0; mv->mouse.x = 0; mv->mouse.y = 0; mv->mouse.xmap = 0; mv->mouse.ymap = 0; mv->mouse.xmap_rel = 0; mv->mouse.ymap_rel = 0; mv->dblclicked = 0; mv->hbar = NULL; mv->vbar = NULL; mv->toolbar = NULL; mv->statusbar = NULL; mv->status = NULL; mv->lib_tl = NULL; mv->objs_tl = NULL; mv->layers_tl = NULL; mv->curtool = NULL; mv->deftool = NULL; TAILQ_INIT(&mv->tools); SLIST_INIT(&mv->draw_cbs); mv->cx = -1; mv->cy = -1; mv->cxrel = 0; mv->cyrel = 0; mv->cxoffs = 0; mv->cyoffs = 0; mv->xoffs = 0; mv->yoffs = 0; mv->msel.set = 0; mv->msel.x = 0; mv->msel.y = 0; mv->msel.xoffs = 0; mv->msel.yoffs = 0; mv->esel.set = 0; mv->esel.moving = 0; mv->esel.x = 0; mv->esel.y = 0; mv->esel.w = 0; mv->esel.h = 0; mv->rsel.moving = 0; AG_ColorRGBA_8(&mv->color, 255,255,255,32); AG_SetEvent(mv, "widget-lostfocus", LostFocus, NULL); AG_SetEvent(mv, "widget-hidden", LostFocus, NULL); AG_SetEvent(mv, "key-up", KeyUp, NULL); AG_SetEvent(mv, "key-down", KeyDown, NULL); AG_SetEvent(mv, "mouse-motion", MouseMotion, NULL); AG_SetEvent(mv, "mouse-button-down", MouseButtonDown, NULL); AG_SetEvent(mv, "mouse-button-up", MouseButtonUp, NULL); AG_AddEvent(mv, "detached", OnDetach, NULL); AG_SetEvent(mv, "dblclick-expire", ExpireDblClick, NULL); AG_SetEvent(mv, "map-resized", UpdateCamera, "%p", mv); } AG_WidgetClass mapViewClass = { { "Agar(Widget):MAP(View)", sizeof(MAP_View), { 0,0 }, Init, NULL, /* reset */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate };