Advertisement
alien_fx_fiend

Infinite Canvas Revamped ! For Performance Boosts: (Double Buffering+Dirty Regions+Offscreen Buffer)

Mar 19th, 2025 (edited)
146
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 25.11 KB | Source Code | 0 0
  1. ==++ Here's the full code for (file 1/2) of "Infinite-Canvas.cpp"::: ++==
  2. ```Infinite-Canvas.cpp
  3. #define NOMINMAX
  4. #define WIN32_LEAN_AND_MEAN
  5. #include <windows.h>
  6. #include <windowsx.h>
  7. #include <commctrl.h>
  8. #include <commdlg.h>
  9. #include <cmath>
  10. #include <vector>
  11. #include <mutex>
  12. #include <fstream>
  13. #include <thread>
  14. #include <algorithm>
  15. #include "resource.h"
  16.  
  17. #pragma comment(lib, "comctl32.lib")
  18.  
  19. struct DrawPoint {
  20.    int x, y;
  21.    DWORD timestamp;
  22.    DrawPoint() : x(0), y(0), timestamp(0) {}
  23.    DrawPoint(int px, int py) : x(px), y(py), timestamp(GetTickCount()) {}
  24. };
  25.  
  26. struct SerializedStroke {
  27.    std::vector<DrawPoint> points;
  28.    COLORREF color;
  29.    int brushSize;
  30.    bool isEraser;
  31. };
  32.  
  33. std::mutex strokeMutex;
  34. std::vector<SerializedStroke> strokeHistory;
  35. std::vector<DrawPoint> strokeBuffer;
  36. const double MIN_DISTANCE = 2.0;
  37.  
  38. COLORREF currentBrushColor = RGB(24, 123, 205);
  39. int brushSize = 10;
  40. bool isDrawing = false;
  41. bool isEraserMode = false;
  42. bool isPaintbrushSelected = true;
  43. bool isSpacePressed = false;
  44. POINT lastMousePos = { 0, 0 };
  45.  
  46. int scrollX = 0;
  47. int scrollY = 0;
  48. float gridZoomFactor = 1.0f;
  49. bool showGrid = true;
  50. bool useAlphaGrid = false;
  51. int gridOpacity = 255;
  52. const int GRID_SIZE = 100;
  53.  
  54. HINSTANCE hInst;
  55. HWND hWnd;
  56. HDC hOffscreenDC = NULL;
  57. HBITMAP hOffscreenBitmap = NULL;
  58. HWND hStatusBar = NULL;
  59. DWORD lastStatusUpdateTime = 0;
  60. const DWORD STATUS_UPDATE_INTERVAL = 50;
  61. HDC hStatusBufferDC = NULL;
  62. HBITMAP hStatusBufferBitmap = NULL;
  63. int lastOffscreenScrollX = 0;
  64. int lastOffscreenScrollY = 0;
  65.  
  66. const wchar_t* STATE_FILE = L"canvas_state2.bin";
  67. bool isLoading = false;
  68. bool sessionDirty = false;
  69.  
  70. // Save the current canvas settings and strokes to disk.
  71. void SaveCanvasState() {
  72.    std::ofstream file(STATE_FILE, std::ios::binary | std::ios::out);
  73.    if (!file)
  74.        return;
  75.    file.write(reinterpret_cast<const char*>(&gridZoomFactor), sizeof(float));
  76.    file.write(reinterpret_cast<const char*>(&showGrid), sizeof(bool));
  77.    file.write(reinterpret_cast<const char*>(&useAlphaGrid), sizeof(bool));
  78.    file.write(reinterpret_cast<const char*>(&gridOpacity), sizeof(int));
  79.    file.write(reinterpret_cast<const char*>(&currentBrushColor), sizeof(COLORREF));
  80.    file.write(reinterpret_cast<const char*>(&brushSize), sizeof(int));
  81.    {
  82.        std::lock_guard<std::mutex> lock(strokeMutex);
  83.        size_t strokeCount = strokeHistory.size();
  84.        file.write(reinterpret_cast<const char*>(&strokeCount), sizeof(size_t));
  85.        for (const auto& stroke : strokeHistory) {
  86.            std::vector<DrawPoint> optimizedPoints;
  87.            if (!stroke.points.empty()) {
  88.                optimizedPoints.push_back(stroke.points[0]);
  89.                for (size_t i = 1; i < stroke.points.size(); ++i) {
  90.                    const DrawPoint& prev = optimizedPoints.back();
  91.                    const DrawPoint& curr = stroke.points[i];
  92.                    double dx = curr.x - prev.x;
  93.                    double dy = curr.y - prev.y;
  94.                    double distance = sqrt(dx * dx + dy * dy);
  95.                    if (distance >= MIN_DISTANCE)
  96.                        optimizedPoints.push_back(curr);
  97.                }
  98.            }
  99.            size_t pointCount = optimizedPoints.size();
  100.            file.write(reinterpret_cast<const char*>(&pointCount), sizeof(size_t));
  101.            if (pointCount > 0)
  102.                file.write(reinterpret_cast<const char*>(optimizedPoints.data()), pointCount * sizeof(DrawPoint));
  103.            file.write(reinterpret_cast<const char*>(&stroke.color), sizeof(COLORREF));
  104.            file.write(reinterpret_cast<const char*>(&stroke.brushSize), sizeof(int));
  105.            file.write(reinterpret_cast<const char*>(&stroke.isEraser), sizeof(bool));
  106.        }
  107.    }
  108.    file.close();
  109. }
  110.  
  111. // Asynchronously load the saved canvas state from disk.
  112. void LoadCanvasStateAsync(HWND hwnd) {
  113.    isLoading = true;
  114.    std::thread([hwnd]() {
  115.        std::ifstream file(STATE_FILE, std::ios::binary | std::ios::in);
  116.        if (!file) {
  117.            isLoading = false;
  118.            return;
  119.        }
  120.        try {
  121.            file.read(reinterpret_cast<char*>(&gridZoomFactor), sizeof(float));
  122.            file.read(reinterpret_cast<char*>(&showGrid), sizeof(bool));
  123.            file.read(reinterpret_cast<char*>(&useAlphaGrid), sizeof(bool));
  124.            file.read(reinterpret_cast<char*>(&gridOpacity), sizeof(int));
  125.            file.read(reinterpret_cast<char*>(&currentBrushColor), sizeof(COLORREF));
  126.            file.read(reinterpret_cast<char*>(&brushSize), sizeof(int));
  127.            size_t strokeCount = 0;
  128.            file.read(reinterpret_cast<char*>(&strokeCount), sizeof(size_t));
  129.            std::vector<SerializedStroke> loadedStrokes;
  130.            for (size_t i = 0; i < strokeCount && file.good(); ++i) {
  131.                SerializedStroke stroke;
  132.                size_t pointCount = 0;
  133.                file.read(reinterpret_cast<char*>(&pointCount), sizeof(size_t));
  134.                if (pointCount > 0 && pointCount < 1000000) {
  135.                    for (size_t j = 0; j < pointCount; ++j) {
  136.                        DrawPoint point;
  137.                        file.read(reinterpret_cast<char*>(&point.x), sizeof(int));
  138.                        file.read(reinterpret_cast<char*>(&point.y), sizeof(int));
  139.                        file.read(reinterpret_cast<char*>(&point.timestamp), sizeof(DWORD));
  140.                        stroke.points.push_back(point);
  141.                    }
  142.                    file.read(reinterpret_cast<char*>(&stroke.color), sizeof(COLORREF));
  143.                    file.read(reinterpret_cast<char*>(&stroke.brushSize), sizeof(int));
  144.                    file.read(reinterpret_cast<char*>(&stroke.isEraser), sizeof(bool));
  145.                    loadedStrokes.push_back(stroke);
  146.                }
  147.            }
  148.            {
  149.                std::lock_guard<std::mutex> lock(strokeMutex);
  150.                strokeHistory = std::move(loadedStrokes);
  151.            }
  152.        }
  153.        catch (...) {
  154.            isLoading = false;
  155.            return;
  156.        }
  157.        file.close();
  158.        isLoading = false;
  159.        InvalidateRect(hwnd, NULL, TRUE);
  160.        }).detach();
  161. }
  162.  
  163. // Draw a smooth stroke using the given points (adjusted by the view offsets).
  164. void DrawSmoothStroke(HDC hdc, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize, int offsetX, int offsetY) {
  165.    if (points.empty())
  166.        return;
  167.    COLORREF color = isEraser ? RGB(255, 255, 255) : strokeColor;
  168.    HBRUSH brush = CreateSolidBrush(color);
  169.    HPEN pen = CreatePen(PS_SOLID, 1, color);
  170.    HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush);
  171.    HPEN oldPen = (HPEN)SelectObject(hdc, pen);
  172.    if (points.size() == 1) {
  173.        const DrawPoint& pt = points[0];
  174.        Ellipse(hdc, pt.x - offsetX - strokeSize, pt.y - offsetY - strokeSize,
  175.            pt.x - offsetX + strokeSize, pt.y - offsetY + strokeSize);
  176.    }
  177.    else {
  178.        for (size_t i = 1; i < points.size(); ++i) {
  179.            const DrawPoint& prev = points[i - 1];
  180.            const DrawPoint& curr = points[i];
  181.            double dx = curr.x - prev.x;
  182.            double dy = curr.y - prev.y;
  183.            double distance = sqrt(dx * dx + dy * dy);
  184.            if (distance > 0) {
  185.                int steps = std::max(1, (int)(distance / 2));
  186.                for (int step = 0; step <= steps; ++step) {
  187.                    double t = step / (double)steps;
  188.                    int x = (int)(prev.x + dx * t);
  189.                    int y = (int)(prev.y + dy * t);
  190.                    Ellipse(hdc, x - offsetX - strokeSize, y - offsetY - strokeSize,
  191.                        x - offsetX + strokeSize, y - offsetY + strokeSize);
  192.                }
  193.            }
  194.        }
  195.    }
  196.    SelectObject(hdc, oldBrush);
  197.    SelectObject(hdc, oldPen);
  198.    DeleteObject(brush);
  199.    DeleteObject(pen);
  200. }
  201.  
  202. // Draw grid lines relative to the current view offsets.
  203. void DrawGrid(HDC hdc, const RECT& clientRect) {
  204.    SetBkMode(hdc, TRANSPARENT);
  205.    HPEN gridPen = CreatePen(PS_SOLID, 1, RGB(255, 140, 0));
  206.    HPEN oldPen = (HPEN)SelectObject(hdc, gridPen);
  207.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  208.    int modX = scrollX % scaledGridSize;
  209.    if (modX < 0)
  210.        modX += scaledGridSize;
  211.    int startX = -modX;
  212.    for (int x = startX; x < (int)clientRect.right; x += scaledGridSize) {
  213.        MoveToEx(hdc, x, 0, NULL);
  214.        LineTo(hdc, x, clientRect.bottom);
  215.    }
  216.    int modY = scrollY % scaledGridSize;
  217.    if (modY < 0)
  218.        modY += scaledGridSize;
  219.    int startY = -modY;
  220.    for (int y = startY; y < (int)clientRect.bottom; y += scaledGridSize) {
  221.        MoveToEx(hdc, 0, y, NULL);
  222.        LineTo(hdc, clientRect.right, y);
  223.    }
  224.    SelectObject(hdc, oldPen);
  225.    DeleteObject(gridPen);
  226. }
  227.  
  228. // Helper: Redraw all strokes into the persistent offscreen buffer.
  229. void UpdateOffscreenBuffer(HWND hwnd) {
  230.    RECT rc;
  231.    GetClientRect(hwnd, &rc);
  232.    // Clear offscreen DC with a white background:
  233.    HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  234.    FillRect(hOffscreenDC, &rc, whiteBrush);
  235.    DeleteObject(whiteBrush);
  236.    // Redraw all strokes into hOffscreenDC:
  237.    {
  238.        std::lock_guard<std::mutex> lock(strokeMutex);
  239.        for (const auto& stroke : strokeHistory) {
  240.            DrawSmoothStroke(hOffscreenDC, stroke.points, stroke.isEraser, stroke.color, stroke.brushSize, scrollX, scrollY);
  241.        }
  242.    }
  243. }
  244.  
  245. void InitializeStatusBuffer(HWND hStatus) {
  246.    if (hStatusBufferDC) {
  247.        DeleteDC(hStatusBufferDC);
  248.        DeleteObject(hStatusBufferBitmap);
  249.    }
  250.    HDC hdc = GetDC(hStatus);
  251.    RECT rect;
  252.    GetClientRect(hStatus, &rect);
  253.    hStatusBufferDC = CreateCompatibleDC(hdc);
  254.    hStatusBufferBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
  255.    SelectObject(hStatusBufferDC, hStatusBufferBitmap);
  256.    ReleaseDC(hStatus, hdc);
  257. }
  258.  
  259. void UpdateStatus(HWND hwnd) {
  260.    DWORD currentTime = GetTickCount();
  261.    if (currentTime - lastStatusUpdateTime < STATUS_UPDATE_INTERVAL)
  262.        return;
  263.    lastStatusUpdateTime = currentTime;
  264.    if (!hStatusBar)
  265.        return;
  266.    if (!hStatusBufferDC) {
  267.        InitializeStatusBuffer(hStatusBar);
  268.    }
  269.    RECT statusRect;
  270.    GetClientRect(hStatusBar, &statusRect);
  271.    wchar_t status[512];
  272.    BYTE r = GetRValue(currentBrushColor);
  273.    BYTE g = GetGValue(currentBrushColor);
  274.    BYTE b = GetBValue(currentBrushColor);
  275.    // Here you can adjust what "Canvas Pos" means. For example, using scrollX and scrollY:
  276.    swprintf_s(status, 512,
  277.        L"Mode: %s | Brush: %d | Color: RGB(%d,%d,%d) | Grid: %s%s | Zoom: %.1fx | Opacity: %d%% | Canvas Pos: (%d,%d)",
  278.        isEraserMode ? L"Eraser" : L"Draw",
  279.        brushSize,
  280.        r, g, b,
  281.        showGrid ? L"On" : L"Off",
  282.        useAlphaGrid ? L"(Alpha)" : L"",
  283.        gridZoomFactor,
  284.        (gridOpacity * 100) / 255,
  285.        scrollX, scrollY    // Use scrollX and scrollY directly
  286.    );
  287.    SendMessage(hStatusBar, SB_SETTEXT, 0, (LPARAM)status);
  288. }
  289.  
  290. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  291.    switch (uMsg) {
  292.    case WM_CREATE:
  293.    {
  294.        hStatusBar = CreateWindowEx(
  295.            0,
  296.            STATUSCLASSNAME,
  297.            NULL,
  298.            WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP,
  299.            0, 0, 0, 0,
  300.            hwnd,
  301.            (HMENU)0,
  302.            hInst,
  303.            NULL
  304.        );
  305.        if (hStatusBar) {
  306.            int statwidths[] = { -1 };
  307.            SendMessage(hStatusBar, SB_SETPARTS, 1, (LPARAM)statwidths);
  308.            UpdateStatus(hwnd); // Update the status bar text on creation.
  309.        }
  310.  
  311.        // **** Insert Offscreen Buffer Initialization Here ****
  312.        HDC hdc = GetDC(hwnd);
  313.        RECT rc;
  314.        GetClientRect(hwnd, &rc);
  315.        hOffscreenDC = CreateCompatibleDC(hdc);
  316.        hOffscreenBitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
  317.        SelectObject(hOffscreenDC, hOffscreenBitmap);
  318.        HBRUSH whiteBrush = CreateSolidBrush(RGB(255, 255, 255));
  319.        FillRect(hOffscreenDC, &rc, whiteBrush);
  320.        DeleteObject(whiteBrush);
  321.        ReleaseDC(hwnd, hdc);
  322.        // **** End Offscreen Buffer Initialization ****
  323.  
  324.        LoadCanvasStateAsync(hwnd);
  325.        return 0;
  326.    }
  327.    case WM_KEYDOWN:
  328.    {
  329.        if (GetKeyState(VK_MENU) & 0x8000)
  330.            return DefWindowProc(hwnd, uMsg, wParam, lParam);
  331.        if (wParam == VK_SPACE && !isSpacePressed) {
  332.            isSpacePressed = true;
  333.            GetCursorPos(&lastMousePos);
  334.            ScreenToClient(hwnd, &lastMousePos);
  335.            SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  336.            SetCapture(hwnd);
  337.        }
  338.        else if (wParam == 0x50) {
  339.            isPaintbrushSelected = true;
  340.            isEraserMode = false;
  341.            UpdateStatus(hwnd); // <-- Added here
  342.        }
  343.        else if (wParam == 0x45) {
  344.            isPaintbrushSelected = false;
  345.            isEraserMode = true;
  346.            UpdateStatus(hwnd); // <-- Added here
  347.        }
  348.        else if (wParam == 'Q') {
  349.            CHOOSECOLOR cc = { sizeof(CHOOSECOLOR) };
  350.            static COLORREF customColors[16] = { 0 };
  351.            cc.hwndOwner = hwnd;
  352.            cc.rgbResult = currentBrushColor;
  353.            cc.lpCustColors = customColors;
  354.            cc.Flags = CC_FULLOPEN | CC_RGBINIT;
  355.            if (ChooseColor(&cc))
  356.                currentBrushColor = cc.rgbResult;
  357.            UpdateStatus(hwnd); // <-- Added here
  358.        }
  359.        else if (wParam == VK_ADD || wParam == VK_OEM_PLUS) {
  360.            brushSize = std::min(50, brushSize + 5);
  361.            UpdateStatus(hwnd); // <-- Added here
  362.        }
  363.        else if (wParam == VK_SUBTRACT || wParam == VK_OEM_MINUS) {
  364.            brushSize = std::max(5, brushSize - 5);
  365.            UpdateStatus(hwnd); // <-- Added here
  366.        }
  367.        else if (wParam == 0x43) {
  368.            std::lock_guard<std::mutex> lock(strokeMutex);
  369.            strokeHistory.clear();
  370.            sessionDirty = true;  // Mark session as changed due to canvas clear.
  371.            InvalidateRect(hwnd, NULL, TRUE);
  372.        }
  373.        else if (wParam == VK_HOME) {
  374.            scrollX = 0;
  375.            scrollY = 0;
  376.            lastOffscreenScrollX = 0;  // if you're tracking these
  377.             lastOffscreenScrollY = 0;
  378.             UpdateOffscreenBuffer(hwnd); // Rebuild the offscreen canvas with (0,0) offsets
  379.             UpdateStatus(hwnd);
  380.             InvalidateRect(hwnd, NULL, TRUE);
  381.         }
  382.         else if (wParam == 'G') {
  383.             showGrid = !showGrid;
  384.             //UpdateStatus(hwnd); // <-- Added here
  385.             InvalidateRect(hwnd, NULL, FALSE);
  386.         }
  387.         else if (wParam == 'A') {
  388.             useAlphaGrid = !useAlphaGrid;
  389.             UpdateStatus(hwnd); // <-- Added here
  390.             InvalidateRect(hwnd, NULL, FALSE);
  391.         }
  392.         else if (wParam == VK_PRIOR) {
  393.             gridZoomFactor *= 1.1f;
  394.             UpdateStatus(hwnd); // <-- Added here
  395.             InvalidateRect(hwnd, NULL, FALSE);
  396.         }
  397.         else if (wParam == VK_NEXT) {
  398.             gridZoomFactor *= 0.9f;
  399.             if (gridZoomFactor < 0.1f)
  400.                 gridZoomFactor = 0.1f;
  401.             UpdateStatus(hwnd); // <-- Added here
  402.             InvalidateRect(hwnd, NULL, FALSE);
  403.         }
  404.         else if (wParam == VK_OEM_6 && useAlphaGrid) {
  405.             gridOpacity = std::min(255, gridOpacity + 15);
  406.             UpdateStatus(hwnd); // <-- Added here
  407.             InvalidateRect(hwnd, NULL, FALSE);
  408.         }
  409.         else if (wParam == VK_OEM_4 && useAlphaGrid) {
  410.             gridOpacity = std::max(0, gridOpacity - 15);
  411.             UpdateStatus(hwnd); // <-- Added here
  412.             InvalidateRect(hwnd, NULL, FALSE);
  413.         }
  414.         else if (wParam == VK_ESCAPE) {
  415.             if (isSpacePressed) {
  416.                 isSpacePressed = false;
  417.                 ReleaseCapture();
  418.             }
  419.             if (sessionDirty) {  // Save only if there are unsaved changes.
  420.                 SaveCanvasState();
  421.                 sessionDirty = false;
  422.             }
  423.             PostQuitMessage(0);
  424.             return 0;
  425.         }
  426.         else if (wParam == VK_F1) {
  427.             MessageBox(hwnd, L"Infinite Canvas Doodle App (Infinite canvas with session serialization)\nI made an Infinite Canvas app using GDI and Memory DC, no need for bloated Godot Engine/ Frameworks or M$ Infinite Canvas Control! Eternity of effort paid off! (1383/561 lines of code) by Entisoft Software (c) Evans Thorpemorton pen=24,123,205 Teal", L"Information", MB_OK | MB_ICONINFORMATION);
  428.             return 0;
  429.         }
  430.         return 0;
  431.     }
  432.     case WM_KEYUP:
  433.     {
  434.         if (wParam == VK_SPACE) {
  435.             isSpacePressed = false;
  436.             SetCursor(LoadCursor(NULL, IDC_ARROW));
  437.             ReleaseCapture();
  438.             return 0;
  439.         }
  440.         return 0;
  441.     }
  442.     case WM_LBUTTONDOWN:
  443.     {
  444.         isDrawing = true;
  445.         int worldX = GET_X_LPARAM(lParam) + scrollX;
  446.         int worldY = GET_Y_LPARAM(lParam) + scrollY;
  447.         strokeBuffer.clear();
  448.         strokeBuffer.push_back(DrawPoint(worldX, worldY));
  449.         SetCapture(hwnd);
  450.         InvalidateRect(hwnd, NULL, FALSE);
  451.         return 0;
  452.     }
  453.     case WM_LBUTTONUP:
  454.     {
  455.         if (isDrawing) {
  456.             isDrawing = false;
  457.             SerializedStroke stroke;
  458.             stroke.points = strokeBuffer;
  459.             stroke.color = currentBrushColor;
  460.             stroke.brushSize = brushSize;
  461.             stroke.isEraser = isEraserMode;
  462.             {
  463.                 std::lock_guard<std::mutex> lock(strokeMutex);
  464.                 strokeHistory.push_back(stroke);
  465.             }
  466.             strokeBuffer.clear();
  467.             ReleaseCapture();
  468.             InvalidateRect(hwnd, NULL, FALSE);
  469.             sessionDirty = true;  // Mark that the canvas has changed.
  470.             if (sessionDirty) {   // Only save if changes were made.
  471.                 SaveCanvasState();
  472.                 sessionDirty = false; // Reset the flag after saving.
  473.             }
  474.             UpdateOffscreenBuffer(hwnd); // Update the persistent offscreen canvas.
  475.             UpdateStatus(hwnd);          // Update status bar.
  476.         }
  477.         return 0;
  478.     }
  479.     case WM_MOUSEMOVE:
  480.     {
  481.         int x = GET_X_LPARAM(lParam);
  482.         int y = GET_Y_LPARAM(lParam);
  483.         if (isSpacePressed) {
  484.             RECT clientRect;
  485.             GetClientRect(hwnd, &clientRect);
  486.             int deltaX = x - lastMousePos.x;
  487.             int deltaY = y - lastMousePos.y;
  488.             scrollX -= deltaX;
  489.             scrollY -= deltaY;
  490.             lastMousePos.x = x;
  491.             lastMousePos.y = y;
  492.             // After updating scrollX and scrollY in the panning branch:
  493.             if (scrollX != lastOffscreenScrollX || scrollY != lastOffscreenScrollY) {
  494.                 UpdateOffscreenBuffer(hwnd);
  495.                 lastOffscreenScrollX = scrollX;
  496.                 lastOffscreenScrollY = scrollY;
  497.             }
  498.             UpdateStatus(hwnd);  // <-- Added to update Canvas Pos
  499.             InvalidateRect(hwnd, NULL, FALSE);
  500.         }
  501.         else if (isDrawing && (wParam & MK_LBUTTON)) {
  502.             int worldX = x + scrollX;
  503.             int worldY = y + scrollY;
  504.             if (strokeBuffer.empty())
  505.                 strokeBuffer.push_back(DrawPoint(worldX, worldY));
  506.             else {
  507.                 const DrawPoint& lastPt = strokeBuffer.back();
  508.                 double dx = worldX - lastPt.x;
  509.                 double dy = worldY - lastPt.y;
  510.                 double distance = sqrt(dx * dx + dy * dy);
  511.                 if (distance >= MIN_DISTANCE)
  512.                     strokeBuffer.push_back(DrawPoint(worldX, worldY));
  513.             }
  514.             //InvalidateRect(hwnd, NULL, FALSE);
  515.             // Compute dirty rectangle for the new segment
  516.             worldX = x + scrollX;
  517.             worldY = y + scrollY;
  518.             if (!strokeBuffer.empty()) {
  519.                 const DrawPoint& prevPt = strokeBuffer.back();
  520.                 // Convert world coordinates to client coordinates
  521.                 int clientPrevX = prevPt.x - scrollX;
  522.                 int clientPrevY = prevPt.y - scrollY;
  523.                 int clientNewX = worldX - scrollX;
  524.                 int clientNewY = worldY - scrollY;
  525.                 RECT dirty;
  526.                 dirty.left = std::min(clientPrevX, clientNewX) - brushSize;
  527.                 dirty.top = std::min(clientPrevY, clientNewY) - brushSize;
  528.                 dirty.right = std::max(clientPrevX, clientNewX) + brushSize;
  529.                 dirty.bottom = std::max(clientPrevY, clientNewY) + brushSize;
  530.                 InvalidateRect(hwnd, &dirty, FALSE);
  531.             }
  532.             else {
  533.                 InvalidateRect(hwnd, NULL, FALSE);
  534.             }
  535.         }
  536.         return 0;
  537.     }
  538.     case WM_SIZE:
  539.     {
  540.         RECT rcClient;
  541.         GetClientRect(hwnd, &rcClient);
  542.         if (hStatusBar) {
  543.             RECT rcSB;
  544.             SendMessage(hStatusBar, SB_GETRECT, 0, (LPARAM)&rcSB);
  545.             int sbHeight = rcSB.bottom - rcSB.top;
  546.             MoveWindow(hStatusBar, 0, rcClient.bottom - sbHeight, rcClient.right, sbHeight, TRUE);
  547.         }
  548.         // Recreate offscreen buffer with new client area size:
  549.         if (hOffscreenDC) {
  550.             DeleteDC(hOffscreenDC);
  551.             hOffscreenDC = NULL;
  552.         }
  553.         if (hOffscreenBitmap) {
  554.             DeleteObject(hOffscreenBitmap);
  555.             hOffscreenBitmap = NULL;
  556.         }
  557.         HDC hdc = GetDC(hwnd);
  558.         hOffscreenDC = CreateCompatibleDC(hdc);
  559.         hOffscreenBitmap = CreateCompatibleBitmap(hdc, rcClient.right, rcClient.bottom);
  560.         SelectObject(hOffscreenDC, hOffscreenBitmap);
  561.         ReleaseDC(hwnd, hdc);
  562.  
  563.         // Redraw the offscreen buffer with all strokes:
  564.         UpdateOffscreenBuffer(hwnd);
  565.         UpdateStatus(hwnd);
  566.         // After UpdateOffscreenBuffer(hwnd);
  567.         lastOffscreenScrollX = scrollX;
  568.         lastOffscreenScrollY = scrollY;
  569.         InvalidateRect(hwnd, NULL, TRUE);
  570.         return 0;
  571.     }
  572.     case WM_ERASEBKGND:
  573.         return 1;
  574.     case WM_PAINT:
  575.     {
  576.         PAINTSTRUCT ps;
  577.         HDC hdc = BeginPaint(hwnd, &ps);
  578.         RECT clientRect;
  579.         GetClientRect(hwnd, &clientRect);
  580.         // Copy the persistent offscreen canvas to the screen:
  581.         BitBlt(hdc, 0, 0, clientRect.right, clientRect.bottom, hOffscreenDC, 0, 0, SRCCOPY);
  582.         // If a stroke is in progress, overlay it:
  583.         if (isDrawing && !strokeBuffer.empty())
  584.             DrawSmoothStroke(hdc, strokeBuffer, isEraserMode, currentBrushColor, brushSize, scrollX, scrollY);
  585.         // Draw the grid on top:
  586.         if (showGrid)
  587.             DrawGrid(hdc, clientRect);
  588.         EndPaint(hwnd, &ps);
  589.         return 0;
  590.     }
  591.     case WM_SETCURSOR:
  592.     {
  593.         if (LOWORD(lParam) == HTCLIENT) {
  594.             if (isSpacePressed) {
  595.                 SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  596.                 return TRUE;
  597.             }
  598.             else if (isPaintbrushSelected || isEraserMode) {
  599.                 SetCursor(LoadCursor(NULL, IDC_CROSS));
  600.                 return TRUE;
  601.             }
  602.         }
  603.         return DefWindowProc(hwnd, uMsg, wParam, lParam);
  604.     }
  605.     case WM_DESTROY:
  606.     {
  607.         if (sessionDirty) {  // Save only if there are unsaved changes.
  608.             SaveCanvasState();
  609.             sessionDirty = false;
  610.         }
  611.         PostQuitMessage(0);
  612.         return 0;
  613.     }
  614.     default:
  615.         return DefWindowProc(hwnd, uMsg, wParam, lParam);
  616.     }
  617.     return 0;
  618. }
  619.  
  620. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
  621.     INITCOMMONCONTROLSEX icex = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
  622.     InitCommonControlsEx(&icex);
  623.     const wchar_t CLASS_NAME[] = L"InfiniteCanvasClass";
  624.     WNDCLASS wc = {};
  625.     wc.lpfnWndProc = WindowProc;
  626.     wc.hInstance = hInstance;
  627.     wc.lpszClassName = CLASS_NAME;
  628.     wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
  629.     RegisterClass(&wc);
  630.     hInst = hInstance;
  631.     hWnd = CreateWindowEx(0, CLASS_NAME,
  632.         L"Infinite Canvas Doodle App (P=Brush, E=Eraser, C=Clear, +/�=BrushSize, Space+Drag=Scroll, Home=Center, Q=Color, G=Grid, A=Alpha, PgUp=ZoomIn, PgDn=ZoomOut, F1=About)",
  633.         WS_OVERLAPPEDWINDOW | WS_MAXIMIZE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  634.         NULL, NULL, hInstance, NULL);
  635.     if (hWnd == NULL)
  636.         return 0;
  637.     SetWindowLongPtr(hWnd, GWL_EXSTYLE, GetWindowLongPtr(hWnd, GWL_EXSTYLE) | WS_EX_COMPOSITED);
  638.     //ShowWindow(hWnd, nCmdShow);
  639.     ShowWindow(hWnd, SW_SHOWMAXIMIZED);
  640.     MSG msg = {};
  641.     while (GetMessage(&msg, NULL, 0, 0)) {
  642.         TranslateMessage(&msg);
  643.         DispatchMessage(&msg);
  644.     }
  645.     return 0;
  646. }
  647. ```
  648.  
  649. ==++ Here's the full code for (file 2/2) of "resource.h"::: ++==
  650. ```resource.h
  651. //{{NO_DEPENDENCIES}}
  652. // Microsoft Visual C++ generated include file.
  653. // Used by DoodleApp.rc
  654. //
  655. #define IDI_ICON1                       101
  656.  
  657. // Next default values for new objects
  658. //
  659. #ifdef APSTUDIO_INVOKED
  660. #ifndef APSTUDIO_READONLY_SYMBOLS
  661. #define _APS_NEXT_RESOURCE_VALUE        102
  662. #define _APS_NEXT_COMMAND_VALUE         40001
  663. #define _APS_NEXT_CONTROL_VALUE         1001
  664. #define _APS_NEXT_SYMED_VALUE           101
  665. #endif
  666. #endif
  667.  
  668. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement