// SVG Scene/Canvas library for cairo & Pango // Copyright 2005 by Liam Breck // Licensed under GNU LGPL http://gnu.org/copyleft/lesser.html // // Home page: http://networkimprov.net/airwrx/awscene.html // // Simple interactive example application that creates two scenes, // each of which may be viewed in any number of windows. // Demonstrated SVG elements: text, rect, image, circle, ellipse, line, path, transform #include #include #include #include #include "svgscene.h" static HINSTANCE sInst; static ShMem* sImgSh; static AwImageSet* sIset; // an image repository for use with the scenes struct ExSet { ShMem* sh; AwScene* scene; // a master scene AwSi top, lab, bm, xform; int img; // special scene element ids }; static ExSet s1, s2; // app provides two scenes static void ExInit(HDC iDc, HFONT iFont) { // init the library AwRenderUtil::Init(iDc, iFont); AwSvgTerm::Init(); // create image repository and load an image sImgSh = AwImageSet::New(16*1024); sIset = (AwImageSet*) sImgSh->mem(); s1.img = s2.img = sIset->alloc(2*1024); FILE* aFile = fopen("airwrx.gif", "rb"); fread(sIset->get(s1.img), 2*1024, 1, aFile); fclose(aFile); sIset->setType(s1.img, AwImageSet::eGif); // create the master scene objects s1.sh = AwScene::New(1024, 256, sImgSh); s1.scene = (AwScene*) s1.sh->mem(); s2.sh = AwScene::New(1024, 256, sImgSh); s2.scene = (AwScene*) s2.sh->mem(); // print the demo instructions in a multiline text element AwSceneEl aEl; // a standalone scene element aEl.init(AwSceneEl::eTflow); aEl.text.x = aEl.text.y = 10; aEl.text.w = 400; // height will be calculated char aMsg[] = "Spacebar creates a view. Right-click switches scene.\n" "Drag to create a shape. Click shape to change color.\n" "Arrow keys shift scene.\n" "Scene 1"; AwSi aT = s1.scene->ins(0, AwSceneEl::eTflow, 0); // insert new element in scene s1.scene->set(aT, &aEl, aMsg, strlen(aMsg)); // set the new element from standalone element aMsg[strlen(aMsg)-1] = '2'; aT = s2.scene->ins(0, AwSceneEl::eTflow, 0); s2.scene->set(aT, &aEl, aMsg, strlen(aMsg)); // add view-specific element set for labels s1.lab = s1.scene->ins(0, AwSceneEl::eClient, 0); s2.lab = s2.scene->ins(0, AwSceneEl::eClient, 0); // add bitmap widget aEl.init(AwSceneEl::eText); aEl.text.x = 400; aEl.text.y = 300; aEl.text.wMax = 100; aT = s1.scene->ins(0, AwSceneEl::eText, 22); // note widget id 22 s1.scene->set(aT, &aEl, "click me", 8); s1.bm = s1.scene->ins(aT, AwSceneEl::eImage, 22); s1.scene->show(s1.bm, AwSceneEl::eHide); // generate an image for the bitmap widget int aTest = sIset->alloc(10*1024); unsigned int* aBuf = (unsigned int*) sIset->get(aTest); unsigned int aCol[] = { 0xff, 0xff00, 0xff0000, 0xffff, 0xff00ff, 0xffff00, 0x808080, 0 }; for (int aR = 0; aR < 32; ++aR) for (int aC = 0; aC < 8; ++aC) for (int aP = 0; aP < 4; ++aP) *(aBuf+aR*32+aC*4+aP) = aCol[aC]; aEl.init(AwSceneEl::eImage); aEl.image.x = 410; aEl.image.y = 290; aEl.image.w = aEl.image.dataW = aEl.image.h = aEl.image.dataH = 32; aEl.image.ibuf = aTest; aEl.image.type = AwSceneEl::eImgRgb24; s1.scene->set(s1.bm, &aEl); // add the transform that the arrow keys affect aEl.init(AwSceneEl::eTransform); s1.xform = s1.scene->ins(0, AwSceneEl::eTransform, 0); s1.scene->set(s1.xform, &aEl); s2.xform = s2.scene->ins(0, AwSceneEl::eTransform, 0); s2.scene->set(s2.xform, &aEl); s1.top = s1.xform; s2.top = s2.xform; } class ExView : public AwConn { // the view object has a window, scene copy, and scene update queue // in a multi-client app, the svgterm queue would live on the host where the master scene lives public: static ExView* sListHead; static int sN; static int sShapeHit; int mId; // id of the view AwScid mSid; // id of the scene showing in the view HWND mWnd; // window for the view AwSceneR mScene; // scene copy and renderer AwSvgTerm mTerm; // update queue for the scene ExView* mNext; ExView() : mScene(1024, 256) { mNext = sListHead; sListHead = this; mId = sN++; mSid = 1; mTerm.reset(this); // the view is an AwConn for the svgterm // label this view in the view-specific set already created AwSceneEl aTx; aTx.init(AwSceneEl::eText); aTx.text.x = 10; aTx.text.y = 310; aTx.text.wMax = 400; char aMsg[64]; int aLen = sprintf(aMsg, "View no. %d", mId); AwSi aT = s1.scene->insClient(s1.lab, mId, AwSceneEl::eText, 0); s1.scene->set(aT, &aTx, aMsg, aLen); aT = s2.scene->insClient(s2.lab, mId, AwSceneEl::eText, 0); s2.scene->set(aT, &aTx, aMsg, aLen); this->setScene(); // setup the win32 window mWnd = CreateWindow("svgsceneExample", "SvgScene Example", WS_OVERLAPPEDWINDOW, sN*20, sN*20, 480, 360, NULL, NULL, sInst, NULL); SetWindowLongPtr(mWnd, GWLP_USERDATA, (LONG_PTR)this); ShowWindow(mWnd, SW_SHOW); UpdateWindow(mWnd); } void setScene() { // collect and submit a complete copy of the scene for this svgterm AwScene* aS = mSid==1 ? s1.scene : s2.scene; for (char aType = 0; aType < AwSvgUpdt::eEnd; ++aType) { int aUps = aS->renderGroup(0, aType, &mTerm, mId, AwScene::kAll); mTerm.submit(aUps, mSid, aS); } } // implement AwConn interface for our svgterm int send(AwSvgUpdt* iUpdt, size_t iLen, int iPacketN) { // typically you'd send a TCP message here, // and do the rest of this routine on the other side mScene.update(iUpdt); if (iUpdt->endset) { RECT aR; bool aPartial = mScene.getRedraw(&aR, iUpdt->sceneId); InvalidateRect(mWnd, aPartial ? &aR : 0, true); } return eOk; } ~ExView() { // linked list housekeeping if (sListHead == this) sListHead = mNext; else for (ExView *aPrev, *a = sListHead; a; aPrev = a, a = a->mNext) if (a == this) aPrev->mNext = mNext; } static void Update(AwScid iSid, AwSi iEl) { // send changed and new elements to all svgterms AwScene* aS = iSid==1 ? s1.scene : s2.scene; for (char aType = 0; aType < AwSvgUpdt::eEnd; ++aType) { // make a pass for each update type: scene, text, image int aUps = -1, aCopyPos = -1; ExView* aCopyFrom; int aTot = 0; for (ExView* aV = sListHead; aV; aV = aV->mNext) { // prepare updates for terms of this scene if (aV->mSid != iSid) continue; if (aUps == -1) { // collect non-view-specific elements once aCopyPos = aV->mTerm.qpos(); aUps = aS->renderGroup(iEl, aType, &aV->mTerm); aCopyFrom = aV; } else // duplicate non-view-specific elements for remaining terms aV->mTerm.copy(&aCopyFrom->mTerm, aCopyPos, aUps); // collect view-specific elements int aUpsCl = aS->renderGroup(iEl, aType, &aV->mTerm, aV->mId); // queue the collected updates aV->mTerm.submit(aUps+aUpsCl, 0, aS, AwSvgTerm::kDefer); aTot += aUps+aUpsCl; } if (aTot) AwSvgTerm::Process(); // start the queue processor } } }; ExView* ExView::sListHead; int ExView::sN, ExView::sShapeHit; static HDC sMemDc; // offscreen bitmap for flicker-free rendering #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // retrieve the ExView ptr stored in the HWND ExView* aV = (ExView*)GetWindowLongPtr(hWnd, GWLP_USERDATA); static bool sDrag; static Pt sDown; switch (message) { case WM_PAINT: { PAINTSTRUCT ps; HDC aDc = BeginPaint(hWnd, &ps); // clear the area to be redrawn FillRect(sMemDc, &ps.rcPaint, (HBRUSH)GetStockObject(WHITE_BRUSH)); // render the area aV->mScene.RenderSvg(sMemDc, &ps.rcPaint); // update the actual window from the offscreen bitmap BitBlt(aDc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, sMemDc, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY); EndPaint(hWnd, &ps); } break; case WM_LBUTTONDOWN: { ExSet* aS = aV->mSid==1 ? &s1 : &s2; AwSceneEl aEl; int aX = GET_X_LPARAM(lParam), aY = GET_Y_LPARAM(lParam); // hit-test the mouse click AwScene::Find aF; Pt aPt = { aX,aY }; AwSi aHit = aS->scene->findTarget(&aPt, aV->mId, &aF); if (aHit == 0) { // now dragging on bg sDrag = true; sDown.x = aX; sDown.y = aY; break; } else if (aS->scene->slot[aHit].widgetT == 22) { // user hit the bitmap widget aS->scene->show(s1.bm, AwSceneEl::eFlip); } else if (aS->scene->slot[aHit].svgT != AwSceneEl::eImage) { // user clicked in a shape; change color aS->scene->getPaint(aHit, &aEl); switch (aS->scene->slot[aHit].svgT) { case AwSceneEl::eLine: case AwSceneEl::eText: case AwSceneEl::eTflow: aEl.paint.stroke.r -= 31; aEl.paint.stroke.g -= 41; aEl.paint.stroke.b -= 53; break; default: aEl.paint.fill.r -= 31; aEl.paint.fill.g -= 41; aEl.paint.fill.b -= 53; } aS->scene->setPaint(aHit, &aEl); } // bring shape forward a notch if (aS->scene->slot[aHit].widgetT != 22) aS->scene->zForward(aHit); ExView::Update(aV->mSid, 0); // update all views } break; case WM_LBUTTONUP: { if (!sDrag) break; sDrag = false; // user dragged on bg; draw a shape ExSet* aS = aV->mSid==1 ? &s1 : &s2; AwSceneEl aEl; int aX = GET_X_LPARAM(lParam), aY = GET_Y_LPARAM(lParam); Pt aOriga={sDown.x,sDown.y}, aOrigb={aX,aY}; if (aX < sDown.x) sDown.x = aX, aX = aOriga.x; if (aY < sDown.y) sDown.y = aY, aY = aOriga.y; int aW = aX - sDown.x; int aH = aY - sDown.y; if (aW <= 10 && aH <= 10) break; // apply transform to mouse points aS->scene->get(aS->xform, &aEl); aX -= aEl.transform.translate.x; aY -= aEl.transform.translate.y; sDown.x -= aEl.transform.translate.x; sDown.y -= aEl.transform.translate.y; switch (++ExView::sShapeHit) { case 1: aS->top = aS->scene->ins(aS->top, AwSceneEl::eCircle, 0); aEl.init(AwSceneEl::eCircle); aEl.circle.x = sDown.x+aW/2; aEl.circle.y = sDown.y+aH/2; aEl.circle.r = (aW > aH ? aW : aH) / 2; aS->scene->set(aS->top, &aEl); break; case 2: aS->top = aS->scene->ins(aS->top, AwSceneEl::eRect, 0); aEl.init(AwSceneEl::eRect); aEl.rect.x = sDown.x; aEl.rect.y = sDown.y; aEl.rect.w = aW; aEl.rect.h = aH; aS->scene->set(aS->top, &aEl); break; case 3: aS->top = aS->scene->ins(aS->top, AwSceneEl::eEllipse, 0); aEl.init(AwSceneEl::eEllipse); aEl.ellipse.x = sDown.x+aW/2; aEl.ellipse.y = sDown.y+aH/2; aEl.ellipse.rx = aW/2; aEl.ellipse.ry = aH/2; aS->scene->set(aS->top, &aEl); break; case 4: { aS->top = aS->scene->ins(aS->top, AwSceneEl::ePath, 0); aEl.init(AwSceneEl::ePath); aEl.path.x = sDown.x; aEl.path.y = sDown.y; aEl.path.w = aW; aEl.path.h = aH; aS->scene->set(aS->top, &aEl); aEl.init(AwSceneEl::ePmoveto); aEl.moveto.x = sDown.x; aEl.moveto.y = sDown.y; aS->scene->insPath(aS->top, AwSceneEl::ePmoveto, &aEl); aEl.init(AwSceneEl::ePlineto); aEl.lineto.x = aX; aEl.lineto.y = sDown.y; aS->scene->insPath(aS->top, AwSceneEl::ePlineto, &aEl); aEl.init(AwSceneEl::ePcurveto); aEl.curveto.a.x = aX; aEl.curveto.a.y = aY; aEl.curveto.via.x = sDown.x+aW/2; aEl.curveto.via.y = sDown.y+aH/2; aEl.curveto.b.x = sDown.x; aEl.curveto.b.y = aY; aS->scene->insPath(aS->top, AwSceneEl::ePcurveto, &aEl); aS->scene->insPath(aS->top, AwSceneEl::ePclosePath); } break; case 5: { aS->top = aS->scene->ins(aS->top, AwSceneEl::eLine, 0); aEl.init(AwSceneEl::eLine); aEl.line.a.x = aOriga.x; aEl.line.a.y = aOriga.y; aEl.line.b.x = aOrigb.x; aEl.line.b.y = aOrigb.y; aS->scene->set(aS->top, &aEl); aS->scene->getPaint(aS->top, &aEl); aEl.paint.wStroke = 10; aS->scene->setPaint(aS->top, &aEl); } break; case 6: aS->top = aS->scene->ins(aS->top, AwSceneEl::eImage, 0); aEl.init(AwSceneEl::eImage); aEl.image.x = sDown.x; aEl.image.y = sDown.y; sIset->size(aS->img, &aEl.image.w, &aEl.image.h); aEl.image.ibuf = aS->img; aEl.image.type = AwSceneEl::eImgComprss; aS->scene->set(aS->top, &aEl); ExView::sShapeHit = 0; break; } ExView::Update(aV->mSid, 0); // update all views } break; case WM_RBUTTONDOWN: aV->mSid = aV->mSid==1 ? 2 : 1; aV->setScene(); break; case WM_KEYDOWN: { if (wParam == VK_SPACE) { new ExView; break; } ExSet* aS = aV->mSid==1 ? &s1 : &s2; AwSceneEl aEl; aS->scene->get(aS->xform, &aEl); switch (wParam) { case VK_UP: aEl.transform.translate.y -= 10; break; case VK_DOWN: aEl.transform.translate.y += 10; break; case VK_LEFT: aEl.transform.translate.x -= 10; break; case VK_RIGHT: aEl.transform.translate.x += 10; break; } aS->scene->set(aS->xform, &aEl); ExView::Update(aV->mSid, 0); // update all views } break; case WM_DESTROY: delete aV; if (ExView::sListHead == 0) PostQuitMessage(0); // quit app when last window closed return DefWindowProc(hWnd, message, wParam, lParam); default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // win32 overhead follows; no svgscene content beyond this point using namespace Gdiplus; int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { sInst = hInstance; WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = (WNDPROC)WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = 0; wcex.hCursor = LoadCursor(0, IDC_ARROW); wcex.hbrBackground = 0;//(HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = NULL; wcex.lpszClassName = "svgsceneExample"; wcex.hIconSm = 0; RegisterClassEx(&wcex); HFONT aFont = CreateFont( -12, 0, 0, 0, 400, 0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, DEFAULT_PITCH|FF_DONTCARE, "verdana"); HWND hWnd = CreateWindow("svgsceneExample", "SvgScene Example", WS_OVERLAPPEDWINDOW, 50, 50, 800, 600, NULL, NULL, hInstance, NULL); HDC aDc = GetDC(hWnd); sMemDc = CreateCompatibleDC(aDc); HBITMAP aBm = CreateCompatibleBitmap(aDc, 1024, 768); SelectObject(sMemDc, aBm); SelectObject(sMemDc, aFont); SetGraphicsMode(sMemDc, GM_ADVANCED); ExInit(aDc, aFont); ReleaseDC(hWnd, aDc); ULONG_PTR aGdiplusTok, aHookTok; GdiplusStartupInput aInp(NULL, TRUE); GdiplusStartupOutput aOutp; GdiplusStartup(&aGdiplusTok, &aInp, &aOutp); (*aOutp.NotificationHook)(&aHookTok); new ExView; MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } (*aOutp.NotificationUnhook)(aHookTok); GdiplusShutdown(aGdiplusTok); return (int) msg.wParam; }