Advertisement
alien_fx_fiend

Infinite Doodle Canvas Direct2D Fix For ArrowKeys/Home Pan Update CanvasPosition! MultiThreaded!

Mar 27th, 2025 (edited)
248
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 32.60 KB | Source Code | 0 0
  1. ==++ Here's the full source code for (file 1/1) "Doodle-D2D-HAcc.cpp"::: ++==
  2.  
  3. ```Doodle-D2D-HAcc.cpp
  4. #define NOMINMAX
  5. #define WIN32_LEAN_AND_MEAN
  6.  
  7. #include <windows.h>
  8. #include <windowsx.h>
  9. #include <commctrl.h>
  10. #include <commdlg.h>
  11. #include <d2d1.h>
  12. #include <dwrite.h>
  13. #include <string>  // add if not already included
  14. #include <cmath>
  15. #include <vector>
  16. #include <mutex>
  17. #include <fstream>
  18. #include <thread>
  19. #include <algorithm>
  20. #include "resource.h"
  21.  
  22. #pragma comment(lib, "d2d1.lib")
  23. #pragma comment(lib, "comctl32.lib")
  24.  
  25. //----------------------------------------------------------------
  26. // Data Structures and Globals
  27. //----------------------------------------------------------------
  28.  
  29. struct DrawPoint {
  30.    int x, y;
  31.    DWORD timestamp;
  32.    DrawPoint() : x(0), y(0), timestamp(0) {}
  33.    DrawPoint(int px, int py) : x(px), y(py), timestamp(GetTickCount()) {}
  34. };
  35.  
  36. struct SerializedStroke {
  37.    std::vector<DrawPoint> points;
  38.    COLORREF color;
  39.    int brushSize;
  40.    bool isEraser;
  41. };
  42.  
  43. std::mutex strokeMutex;
  44. std::vector<SerializedStroke> strokeHistory;
  45. std::vector<DrawPoint> strokeBuffer;
  46. const double MIN_DISTANCE = 2.0;
  47.  
  48. COLORREF currentBrushColor = RGB(24, 123, 205);
  49. int brushSize = 10;
  50. int currentStrokeBrushSize = 10;
  51. bool isDrawing = false;
  52. bool isEraserMode = false;
  53. bool isPaintbrushSelected = true;
  54. bool isSpacePressed = false;
  55. POINT lastMousePos = { 0, 0 };
  56.  
  57. int scrollX = 0;
  58. int scrollY = 0;
  59. float gridZoomFactor = 1.0f;
  60. bool showGrid = true;
  61. bool useAlphaGrid = false;
  62. int gridOpacity = 255;
  63. // Global view transform for panning.
  64. D2D1_MATRIX_3X2_F g_viewTransform = D2D1::Matrix3x2F::Identity();
  65. const int GRID_SIZE = 100;
  66.  
  67. HINSTANCE hInst;
  68. HWND hWnd;
  69. //HWND hStatusBar = NULL;
  70. // Global DirectWrite objects:
  71. IDWriteFactory* pDWriteFactory = nullptr;
  72. IDWriteTextFormat* pTextFormat = nullptr;
  73. std::wstring g_statusText = L"";
  74. // Add DirectWrite globals and a global status string:
  75. //IDWriteFactory* pDWriteFactory = nullptr;
  76. //IDWriteTextFormat* pTextFormat = nullptr;
  77. DWORD lastStatusUpdateTime = 0;
  78. const DWORD STATUS_UPDATE_INTERVAL = 50;
  79. HDC hStatusBufferDC = NULL;
  80. HBITMAP hStatusBufferBitmap = NULL;
  81.  
  82. // Serialization globals
  83. const wchar_t* STATE_FILE = L"canvas_state2.bin";
  84. bool isLoading = false;
  85. bool sessionDirty = false;
  86.  
  87. // For Direct2D
  88. ID2D1Factory* pFactory = nullptr;
  89. ID2D1HwndRenderTarget* pRenderTarget = nullptr;
  90. ID2D1BitmapRenderTarget* pOffscreenRT = nullptr;
  91. bool offscreenDirty = true;
  92. int lastOffscreenScrollX = 0;
  93. int lastOffscreenScrollY = 0;
  94.  
  95. //----------------------------------------------------------------
  96. // Function Declarations
  97. //----------------------------------------------------------------
  98.  
  99. void SaveCanvasState();
  100. void LoadCanvasStateAsync(HWND hwnd);
  101. void UpdateStatus(HWND hwnd);
  102. void InitializeStatusBuffer(HWND hStatus);
  103. void UpdateOffscreenBuffer(HWND hwnd);
  104. HRESULT CreateDeviceResources(HWND hwnd);
  105. void DiscardDeviceResources();
  106. void DrawSmoothStroke(ID2D1RenderTarget* pRT, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize, int offsetX, int offsetY);
  107. void DrawGrid(ID2D1RenderTarget* pRT, const D2D1_RECT_F& rect);
  108.  
  109. //----------------------------------------------------------------
  110. // Serialization Functions
  111. //----------------------------------------------------------------
  112.  
  113. void SaveCanvasState() {
  114.    std::ofstream file(STATE_FILE, std::ios::binary | std::ios::out);
  115.    if (!file)
  116.        return;
  117.    file.write(reinterpret_cast<const char*>(&gridZoomFactor), sizeof(float));
  118.    file.write(reinterpret_cast<const char*>(&showGrid), sizeof(bool));
  119.    file.write(reinterpret_cast<const char*>(&useAlphaGrid), sizeof(bool));
  120.    file.write(reinterpret_cast<const char*>(&gridOpacity), sizeof(int));
  121.    file.write(reinterpret_cast<const char*>(&currentBrushColor), sizeof(COLORREF));
  122.    file.write(reinterpret_cast<const char*>(&brushSize), sizeof(int));
  123.    {
  124.        std::lock_guard<std::mutex> lock(strokeMutex);
  125.        size_t strokeCount = strokeHistory.size();
  126.        file.write(reinterpret_cast<const char*>(&strokeCount), sizeof(size_t));
  127.        for (const auto& stroke : strokeHistory) {
  128.            std::vector<DrawPoint> optimizedPoints;
  129.            if (!stroke.points.empty()) {
  130.                optimizedPoints.push_back(stroke.points[0]);
  131.                for (size_t i = 1; i < stroke.points.size(); ++i) {
  132.                    const DrawPoint& prev = optimizedPoints.back();
  133.                    const DrawPoint& curr = stroke.points[i];
  134.                    double dx = curr.x - prev.x;
  135.                    double dy = curr.y - prev.y;
  136.                    double distance = sqrt(dx * dx + dy * dy);
  137.                    if (distance >= MIN_DISTANCE)
  138.                        optimizedPoints.push_back(curr);
  139.                }
  140.            }
  141.            size_t pointCount = optimizedPoints.size();
  142.            file.write(reinterpret_cast<const char*>(&pointCount), sizeof(size_t));
  143.            if (pointCount > 0)
  144.                file.write(reinterpret_cast<const char*>(optimizedPoints.data()), pointCount * sizeof(DrawPoint));
  145.            file.write(reinterpret_cast<const char*>(&stroke.color), sizeof(COLORREF));
  146.            file.write(reinterpret_cast<const char*>(&stroke.brushSize), sizeof(int));
  147.            file.write(reinterpret_cast<const char*>(&stroke.isEraser), sizeof(bool));
  148.        }
  149.    }
  150.    file.close();
  151. }
  152.  
  153. void LoadCanvasStateAsync(HWND hwnd) {
  154.    isLoading = true;
  155.    std::thread([hwnd]() {
  156.        std::ifstream file(STATE_FILE, std::ios::binary | std::ios::in);
  157.        if (!file) {
  158.            isLoading = false;
  159.            return;
  160.        }
  161.        try {
  162.            file.read(reinterpret_cast<char*>(&gridZoomFactor), sizeof(float));
  163.            file.read(reinterpret_cast<char*>(&showGrid), sizeof(bool));
  164.            file.read(reinterpret_cast<char*>(&useAlphaGrid), sizeof(bool));
  165.            file.read(reinterpret_cast<char*>(&gridOpacity), sizeof(int));
  166.            file.read(reinterpret_cast<char*>(&currentBrushColor), sizeof(COLORREF));
  167.            file.read(reinterpret_cast<char*>(&brushSize), sizeof(int));
  168.            size_t strokeCount = 0;
  169.            file.read(reinterpret_cast<char*>(&strokeCount), sizeof(size_t));
  170.            std::vector<SerializedStroke> loadedStrokes;
  171.            for (size_t i = 0; i < strokeCount && file.good(); ++i) {
  172.                SerializedStroke stroke;
  173.                size_t pointCount = 0;
  174.                file.read(reinterpret_cast<char*>(&pointCount), sizeof(size_t));
  175.                if (pointCount > 0 && pointCount < 1000000) {
  176.                    for (size_t j = 0; j < pointCount; ++j) {
  177.                        DrawPoint point;
  178.                        file.read(reinterpret_cast<char*>(&point.x), sizeof(int));
  179.                        file.read(reinterpret_cast<char*>(&point.y), sizeof(int));
  180.                        file.read(reinterpret_cast<char*>(&point.timestamp), sizeof(DWORD));
  181.                        stroke.points.push_back(point);
  182.                    }
  183.                    file.read(reinterpret_cast<char*>(&stroke.color), sizeof(COLORREF));
  184.                    file.read(reinterpret_cast<char*>(&stroke.brushSize), sizeof(int));
  185.                    file.read(reinterpret_cast<char*>(&stroke.isEraser), sizeof(bool));
  186.                    loadedStrokes.push_back(stroke);
  187.                }
  188.            }
  189.            {
  190.                std::lock_guard<std::mutex> lock(strokeMutex);
  191.                strokeHistory = std::move(loadedStrokes);
  192.            }
  193.        }
  194.        catch (...) {
  195.            isLoading = false;
  196.            return;
  197.        }
  198.        file.close();
  199.        isLoading = false;
  200.        // Post a message to update offscreen buffer after loading
  201.        PostMessage(hwnd, WM_USER + 1, 0, 0);
  202.        }).detach();
  203. }
  204.  
  205. //----------------------------------------------------------------
  206. // Direct2D Initialization and Resource Management
  207. //----------------------------------------------------------------
  208.  
  209. HRESULT CreateDeviceResources(HWND hwnd) {
  210.    if (pRenderTarget)
  211.        return S_OK;
  212.    RECT rc;
  213.    GetClientRect(hwnd, &rc);
  214.    D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom);
  215.  
  216.    HRESULT hr = pFactory->CreateHwndRenderTarget(
  217.        D2D1::RenderTargetProperties(),
  218.        D2D1::HwndRenderTargetProperties(hwnd, size),
  219.        &pRenderTarget
  220.    );
  221.    if (SUCCEEDED(hr)) {
  222.        // Create an offscreen compatible render target for persistent drawing.
  223.        hr = pRenderTarget->CreateCompatibleRenderTarget(
  224.            D2D1::SizeF((FLOAT)rc.right, (FLOAT)rc.bottom),
  225.            &pOffscreenRT
  226.        );
  227.        if (SUCCEEDED(hr)) {
  228.            // Mark offscreen as dirty so it is initially updated.
  229.            offscreenDirty = true;
  230.            lastOffscreenScrollX = scrollX;
  231.            lastOffscreenScrollY = scrollY;
  232.        }
  233.    }
  234.    return hr;
  235. }
  236.  
  237. void DiscardDeviceResources() {
  238.    if (pOffscreenRT) {
  239.        pOffscreenRT->Release();
  240.        pOffscreenRT = nullptr;
  241.    }
  242.    if (pRenderTarget) {
  243.        pRenderTarget->Release();
  244.        pRenderTarget = nullptr;
  245.    }
  246. }
  247.  
  248. //----------------------------------------------------------------
  249. // Drawing Functions (Direct2D versions)
  250. //----------------------------------------------------------------
  251.  
  252. void DrawSmoothStroke(ID2D1RenderTarget* pRT, const std::vector<DrawPoint>& points, bool isEraser, COLORREF strokeColor, int strokeSize, int offsetX, int offsetY) {
  253.    if (points.empty())
  254.        return;
  255.  
  256.    // Determine color; for eraser use white.
  257.    D2D1_COLOR_F color = isEraser ? D2D1::ColorF(D2D1::ColorF::White) :
  258.        D2D1::ColorF(
  259.            GetRValue(strokeColor) / 255.0f,
  260.            GetGValue(strokeColor) / 255.0f,
  261.            GetBValue(strokeColor) / 255.0f
  262.        );
  263.  
  264.    ID2D1SolidColorBrush* pBrush = nullptr;
  265.    if (FAILED(pRT->CreateSolidColorBrush(color, &pBrush)))
  266.        return;
  267.  
  268.    if (points.size() == 1) {
  269.        const DrawPoint& pt = points[0];
  270.        D2D1_ELLIPSE ellipse = D2D1::Ellipse(
  271.            D2D1::Point2F((FLOAT)(pt.x - offsetX), (FLOAT)(pt.y - offsetY)),
  272.            (FLOAT)strokeSize, (FLOAT)strokeSize);
  273.        pRT->FillEllipse(ellipse, pBrush);
  274.    }
  275.    else {
  276.        for (size_t i = 1; i < points.size(); ++i) {
  277.            const DrawPoint& prev = points[i - 1];
  278.            const DrawPoint& curr = points[i];
  279.            double dx = curr.x - prev.x;
  280.            double dy = curr.y - prev.y;
  281.            double distance = sqrt(dx * dx + dy * dy);
  282.            if (distance > 0) {
  283.                int steps = std::max(1, (int)(distance / 2));
  284.                for (int step = 0; step <= steps; ++step) {
  285.                    double t = step / (double)steps;
  286.                    int x = (int)(prev.x + dx * t);
  287.                    int y = (int)(prev.y + dy * t);
  288.                    D2D1_ELLIPSE ellipse = D2D1::Ellipse(
  289.                        D2D1::Point2F((FLOAT)(x - offsetX), (FLOAT)(y - offsetY)),
  290.                        (FLOAT)strokeSize, (FLOAT)strokeSize);
  291.                    pRT->FillEllipse(ellipse, pBrush);
  292.                }
  293.            }
  294.        }
  295.    }
  296.    pBrush->Release();
  297. }
  298.  
  299. void DrawGrid(ID2D1RenderTarget* pRT, const D2D1_RECT_F& rect) {
  300.    ID2D1SolidColorBrush* pGridBrush = nullptr;
  301.    HRESULT hr = pRT->CreateSolidColorBrush(D2D1::ColorF(1.0f, 0.55f, 0.0f), &pGridBrush);
  302.    if (FAILED(hr)) return;
  303.    int scaledGridSize = (int)(GRID_SIZE * gridZoomFactor);
  304.  
  305.    // Compute proper mod values so grid lines appear even for negative scrolls.
  306.    int modX = scrollX % scaledGridSize;
  307.    if (modX < 0)
  308.        modX += scaledGridSize;
  309.    // Determine starting X in world coordinates.
  310.    float startX = (float)(scrollX - modX);
  311.    int modY = scrollY % scaledGridSize;
  312.    if (modY < 0)
  313.        modY += scaledGridSize;
  314.    float startY = (float)(scrollY - modY);
  315.  
  316.    // Draw vertical gridlines covering the rect.
  317.    for (float x = startX; x < rect.right; x += scaledGridSize) {
  318.        pRT->DrawLine(D2D1::Point2F(x, rect.top), D2D1::Point2F(x, rect.bottom), pGridBrush, 1.0f);
  319.    }
  320.    // Draw horizontal gridlines covering the rect.
  321.    for (float y = startY; y < rect.bottom; y += scaledGridSize) {
  322.        pRT->DrawLine(D2D1::Point2F(rect.left, y), D2D1::Point2F(rect.right, y), pGridBrush, 1.0f);
  323.    }
  324.    pGridBrush->Release();
  325. }
  326.  
  327. //----------------------------------------------------------------
  328. // Offscreen Buffer Update (using pOffscreenRT)
  329. //----------------------------------------------------------------
  330.  
  331. void UpdateOffscreenBuffer(HWND hwnd) {
  332.    if (!pOffscreenRT)
  333.        return;
  334.    pOffscreenRT->BeginDraw();
  335.    // Clear offscreen render target to white.
  336.    pOffscreenRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
  337.    // Redraw all strokes.
  338.    {
  339.        std::lock_guard<std::mutex> lock(strokeMutex);
  340.        for (const auto& stroke : strokeHistory) {
  341.            DrawSmoothStroke(pOffscreenRT, stroke.points, stroke.isEraser, stroke.color, stroke.brushSize, scrollX, scrollY);
  342.        }
  343.    }
  344.    HRESULT hr = pOffscreenRT->EndDraw();
  345.    // Mark offscreen as clean.
  346.    offscreenDirty = false;
  347.    lastOffscreenScrollX = scrollX;
  348.    lastOffscreenScrollY = scrollY;
  349. }
  350.  
  351. //----------------------------------------------------------------
  352. // Status Bar Functions (GDI remains unchanged)
  353. //----------------------------------------------------------------
  354.  
  355. void InitializeStatusBuffer(HWND hStatus) {
  356.    if (hStatusBufferDC) {
  357.        DeleteDC(hStatusBufferDC);
  358.        DeleteObject(hStatusBufferBitmap);
  359.    }
  360.    HDC hdc = GetDC(hStatus);
  361.    RECT rect;
  362.    GetClientRect(hStatus, &rect);
  363.    hStatusBufferDC = CreateCompatibleDC(hdc);
  364.    hStatusBufferBitmap = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
  365.    SelectObject(hStatusBufferDC, hStatusBufferBitmap);
  366.    ReleaseDC(hStatus, hdc);
  367. }
  368.  
  369. void UpdateStatus(HWND hwnd) {
  370.    wchar_t status[512];
  371.    BYTE r = GetRValue(currentBrushColor);
  372.    BYTE g = GetGValue(currentBrushColor);
  373.    BYTE b = GetBValue(currentBrushColor);
  374.    swprintf_s(status, 512,
  375.        L"Mode: %s | Brush: %d | Color: RGB(%d,%d,%d) | Grid: %s | Zoom: %.1fx | Canvas Pos: (%d,%d)",
  376.        isEraserMode ? L"Eraser" : L"Draw",
  377.        brushSize,
  378.        r, g, b,
  379.        showGrid ? L"On" : L"Off",
  380.        gridZoomFactor,
  381.        scrollX, scrollY
  382.    );
  383.    g_statusText = status;
  384. }
  385.  
  386. /* void UpdateStatus(HWND hwnd) {
  387.    DWORD currentTime = GetTickCount();
  388.    if (currentTime - lastStatusUpdateTime < STATUS_UPDATE_INTERVAL)
  389.        return;
  390.    lastStatusUpdateTime = currentTime;
  391.    if (!hStatusBar)
  392.        return;
  393.    if (!hStatusBufferDC) {
  394.        InitializeStatusBuffer(hStatusBar);
  395.    }
  396.    RECT statusRect;
  397.    GetClientRect(hStatusBar, &statusRect);
  398.    wchar_t status[512];
  399.    BYTE r = GetRValue(currentBrushColor);
  400.    BYTE g = GetGValue(currentBrushColor);
  401.    BYTE b = GetBValue(currentBrushColor);
  402.    swprintf_s(status, 512,
  403.        L"Mode: %s | Brush: %d | Color: RGB(%d,%d,%d) | Grid: %s%s | Zoom: %.1fx | Opacity: %d%% | Canvas Pos: (%d,%d)",
  404.        isEraserMode ? L"Eraser" : L"Draw",
  405.        brushSize,
  406.        r, g, b,
  407.        showGrid ? L"On" : L"Off",
  408.        useAlphaGrid ? L"(Alpha)" : L"",
  409.        gridZoomFactor,
  410.        (gridOpacity * 100) / 255,
  411.        scrollX, scrollY
  412.    );
  413.    SendMessage(hStatusBar, SB_SETTEXT, 0, (LPARAM)status);
  414. } */
  415.  
  416. //----------------------------------------------------------------
  417. // Window Procedure
  418. //----------------------------------------------------------------
  419.  
  420. LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  421.    HRESULT hr;
  422.    switch (uMsg) {
  423.    case WM_CREATE:
  424.    {
  425.        // Initialize Direct2D Factory
  426.        HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &pFactory);
  427.        if (FAILED(hr))
  428.            return -1;
  429.  
  430.        // Initialize DirectWrite Factory and Text Format for the status text.
  431.        HRESULT hrDWrite = DWriteCreateFactory(
  432.            DWRITE_FACTORY_TYPE_SHARED,
  433.            __uuidof(IDWriteFactory),
  434.            reinterpret_cast<IUnknown**>(&pDWriteFactory)
  435.        );
  436.        if (SUCCEEDED(hrDWrite))
  437.        {
  438.            hrDWrite = pDWriteFactory->CreateTextFormat(
  439.                L"Segoe UI",                // Font family name.
  440.                NULL,                       // Use system font collection.
  441.                DWRITE_FONT_WEIGHT_NORMAL,
  442.                DWRITE_FONT_STYLE_NORMAL,
  443.                DWRITE_FONT_STRETCH_NORMAL,
  444.                14.0f,                      // Font size.
  445.                L"",                        // Locale.
  446.                &pTextFormat
  447.            );
  448.            if (SUCCEEDED(hrDWrite))
  449.            {
  450.                pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
  451.                pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
  452.            }
  453.        }
  454.  
  455.        // (Remove GDI status bar creation; status will be rendered via Direct2D.)
  456.  
  457.        // Device resources (pRenderTarget and pOffscreenRT) will be created in WM_SIZE.
  458.        LoadCanvasStateAsync(hwnd);
  459.        return 0;
  460.    }
  461.    case WM_SIZE:
  462.    {
  463.        RECT rcClient;
  464.        GetClientRect(hwnd, &rcClient);
  465.  
  466.        // Resize (or create) the main render target.
  467.        if (pRenderTarget)
  468.        {
  469.            pRenderTarget->Resize(D2D1::SizeU(rcClient.right, rcClient.bottom));
  470.        }
  471.        else
  472.        {
  473.            HRESULT hr = CreateDeviceResources(hwnd);
  474.            if (FAILED(hr))
  475.                return -1;
  476.        }
  477.  
  478.        // Recreate the offscreen render target.
  479.        if (pOffscreenRT)
  480.        {
  481.            pOffscreenRT->Release();
  482.            pOffscreenRT = nullptr;
  483.        }
  484.        HRESULT hr = pRenderTarget->CreateCompatibleRenderTarget(
  485.            D2D1::SizeF((FLOAT)rcClient.right, (FLOAT)rcClient.bottom),
  486.            &pOffscreenRT
  487.        );
  488.        if (SUCCEEDED(hr))
  489.        {
  490.            offscreenDirty = true;               // Force update of the offscreen buffer.
  491.            lastOffscreenScrollX = scrollX;
  492.            lastOffscreenScrollY = scrollY;
  493.            UpdateOffscreenBuffer(hwnd);         // Rebuild the offscreen content.
  494.        }
  495.  
  496.        // Update status (which now contains the grid state) and force a full redraw.
  497.        UpdateStatus(hwnd);
  498.        InvalidateRect(hwnd, NULL, TRUE);
  499.        return 0;
  500.    }
  501.        case WM_KEYDOWN:
  502.    {
  503.            if (GetKeyState(VK_MENU) & 0x8000)
  504.                return DefWindowProc(hwnd, uMsg, wParam, lParam);
  505.            if (wParam == VK_LEFT) {
  506.                scrollX -= 200; //default scroll for all arrowkeys = 20
  507.                g_viewTransform = D2D1::Matrix3x2F::Translation((FLOAT)-scrollX, (FLOAT)-scrollY);
  508.                UpdateStatus(hwnd);           // <-- Added this line
  509.                InvalidateRect(hwnd, NULL, FALSE);
  510.            }
  511.            else if (wParam == VK_RIGHT) {
  512.                scrollX += 200;
  513.                g_viewTransform = D2D1::Matrix3x2F::Translation((FLOAT)-scrollX, (FLOAT)-scrollY);
  514.                UpdateStatus(hwnd);           // <-- Added this line
  515.                InvalidateRect(hwnd, NULL, FALSE);
  516.            }
  517.            else if (wParam == VK_UP) {
  518.                scrollY -= 200;
  519.                g_viewTransform = D2D1::Matrix3x2F::Translation((FLOAT)-scrollX, (FLOAT)-scrollY);
  520.                UpdateStatus(hwnd);           // <-- Added this line
  521.                InvalidateRect(hwnd, NULL, FALSE);
  522.            }
  523.            else if (wParam == VK_DOWN) {
  524.                scrollY += 200;
  525.                g_viewTransform = D2D1::Matrix3x2F::Translation((FLOAT)-scrollX, (FLOAT)-scrollY);
  526.                UpdateStatus(hwnd);           // <-- Added this line
  527.                InvalidateRect(hwnd, NULL, FALSE);
  528.            }
  529.        else if (wParam == VK_SPACE && !isSpacePressed) {
  530.            isSpacePressed = true;
  531.            GetCursorPos(&lastMousePos);
  532.            ScreenToClient(hwnd, &lastMousePos);
  533.            SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  534.            SetCapture(hwnd);
  535.        }
  536.        else if (wParam == 0x50) {
  537.            isPaintbrushSelected = true;
  538.            isEraserMode = false;
  539.            UpdateStatus(hwnd);
  540.            InvalidateRect(hwnd, NULL, TRUE);
  541.        }
  542.        else if (wParam == 0x45) {
  543.            isPaintbrushSelected = false;
  544.            isEraserMode = true;
  545.            UpdateStatus(hwnd);
  546.            InvalidateRect(hwnd, NULL, TRUE);
  547.        }
  548.        else if (wParam == 'Q') {
  549.            CHOOSECOLOR cc = { sizeof(CHOOSECOLOR) };
  550.            static COLORREF customColors[16] = { 0 };
  551.            cc.hwndOwner = hwnd;
  552.            cc.rgbResult = currentBrushColor;
  553.            cc.lpCustColors = customColors;
  554.            cc.Flags = CC_FULLOPEN | CC_RGBINIT;
  555.            if (ChooseColor(&cc))
  556.                currentBrushColor = cc.rgbResult;
  557.            UpdateStatus(hwnd);
  558.            offscreenDirty = true;
  559.            InvalidateRect(hwnd, NULL, TRUE);
  560.        }
  561.        else if (wParam == VK_ADD || wParam == VK_OEM_PLUS) {
  562.            brushSize = std::min(50, brushSize + 5);
  563.            offscreenDirty = true;  // Ensure new brush size is applied in drawing.
  564.            UpdateStatus(hwnd);
  565.            InvalidateRect(hwnd, NULL, TRUE);
  566.        }
  567.        else if (wParam == VK_SUBTRACT || wParam == VK_OEM_MINUS) {
  568.            brushSize = std::max(5, brushSize - 5);
  569.            offscreenDirty = true;
  570.            UpdateStatus(hwnd);
  571.            InvalidateRect(hwnd, NULL, TRUE);
  572.        }
  573.        else if (wParam == 0x43) {
  574.            std::lock_guard<std::mutex> lock(strokeMutex);
  575.            strokeHistory.clear();
  576.            sessionDirty = true;  // Mark session as changed.
  577.            offscreenDirty = true;
  578.            InvalidateRect(hwnd, NULL, TRUE);
  579.        }
  580.        else if (wParam == VK_HOME) {
  581.                scrollX = 0;
  582.                scrollY = 0;
  583.                g_viewTransform = D2D1::Matrix3x2F::Translation(0.0f, 0.0f);
  584.                UpdateStatus(hwnd);  // <-- Add this line to update the status bar.
  585.                InvalidateRect(hwnd, NULL, TRUE);
  586.            }
  587.        else if (wParam == 'G') {
  588.            showGrid = !showGrid;
  589.            offscreenDirty = true;  // Mark offscreen dirty so grid redraws.
  590.            UpdateStatus(hwnd);
  591.            InvalidateRect(hwnd, NULL, TRUE);
  592.        }
  593.        else if (wParam == 'A') {
  594.            useAlphaGrid = !useAlphaGrid;
  595.            UpdateStatus(hwnd);
  596.            InvalidateRect(hwnd, NULL, FALSE);
  597.        }
  598.        else if (wParam == VK_PRIOR) {
  599.            gridZoomFactor *= 1.1f;
  600.            offscreenDirty = true;
  601.            UpdateStatus(hwnd);
  602.            InvalidateRect(hwnd, NULL, FALSE);
  603.        }
  604.        else if (wParam == VK_NEXT) {
  605.            gridZoomFactor *= 0.9f;
  606.            if (gridZoomFactor < 0.1f)
  607.                gridZoomFactor = 0.1f;
  608.            offscreenDirty = true;
  609.            UpdateStatus(hwnd);
  610.            InvalidateRect(hwnd, NULL, FALSE);
  611.        }
  612.        else if (wParam == VK_OEM_6 && useAlphaGrid) {
  613.            gridOpacity = std::min(255, gridOpacity + 15);
  614.            offscreenDirty = true;
  615.            UpdateStatus(hwnd);
  616.            InvalidateRect(hwnd, NULL, FALSE);
  617.        }
  618.        else if (wParam == VK_OEM_4 && useAlphaGrid) {
  619.            gridOpacity = std::max(0, gridOpacity - 15);
  620.            offscreenDirty = true;
  621.            UpdateStatus(hwnd);
  622.            InvalidateRect(hwnd, NULL, FALSE);
  623.        }
  624.        else if (wParam == VK_ESCAPE) {
  625.            if (isSpacePressed) {
  626.                isSpacePressed = false;
  627.                ReleaseCapture();
  628.            }
  629.            if (sessionDirty) {
  630.                SaveCanvasState();
  631.                sessionDirty = false;
  632.            }
  633.            PostQuitMessage(0);
  634.            return 0;
  635.        }
  636.        else if (wParam == VK_F1) {
  637.            MessageBox(hwnd,
  638.                L"Infinite Canvas Doodle App (Direct2D Accelerated)\n"
  639.                L"P=Brush, E=Eraser, C=Clear, +/-=BrushSize, Space+Drag/Arrow Keys=Panning, Home=Reset, Q=Color, G=Grid, A=Alpha, PgUp=ZoomIn, PgDn=ZoomOut, F1=About || Dream Come True! (877 lines of code) by Entisoft Software (c) Evans Thorpemorton Pen=24,123,205",
  640.                L"Information", MB_OK | MB_ICONINFORMATION);
  641.            return 0;
  642.        }
  643.        return 0;
  644.    }
  645.    case WM_KEYUP:
  646.    {
  647.        if (wParam == VK_SPACE) {
  648.            isSpacePressed = false;
  649.            SetCursor(LoadCursor(NULL, IDC_ARROW));
  650.            ReleaseCapture();
  651.            return 0;
  652.        }
  653.        return 0;
  654.    }
  655.    case WM_LBUTTONDOWN:
  656.    {
  657.        isDrawing = true;
  658.        currentStrokeBrushSize = brushSize;
  659.        int worldX = GET_X_LPARAM(lParam) + scrollX;
  660.        int worldY = GET_Y_LPARAM(lParam) + scrollY;
  661.        strokeBuffer.clear();
  662.        strokeBuffer.push_back(DrawPoint(worldX, worldY));
  663.        SetCapture(hwnd);
  664.        InvalidateRect(hwnd, NULL, FALSE);
  665.        return 0;
  666.    }
  667.    case WM_LBUTTONUP:
  668.    {
  669.        if (isDrawing) {
  670.            isDrawing = false;
  671.            SerializedStroke stroke;
  672.            stroke.points = strokeBuffer;
  673.            stroke.color = currentBrushColor;
  674.            stroke.brushSize = brushSize;
  675.            stroke.isEraser = isEraserMode;
  676.            {
  677.                std::lock_guard<std::mutex> lock(strokeMutex);
  678.                strokeHistory.push_back(stroke);
  679.            }
  680.            strokeBuffer.clear();
  681.            ReleaseCapture();
  682.            InvalidateRect(hwnd, NULL, FALSE);
  683.            sessionDirty = true;
  684.            if (sessionDirty) {
  685.                SaveCanvasState();
  686.                sessionDirty = false;
  687.            }
  688.            offscreenDirty = true;
  689.            UpdateOffscreenBuffer(hwnd);
  690.            UpdateStatus(hwnd);
  691.        }
  692.        return 0;
  693.    }
  694.    case WM_MOUSEMOVE:
  695.    {
  696.        int x = GET_X_LPARAM(lParam);
  697.        int y = GET_Y_LPARAM(lParam);
  698.        if (isSpacePressed) {
  699.            int deltaX = x - lastMousePos.x;
  700.            int deltaY = y - lastMousePos.y;
  701.            scrollX -= deltaX;
  702.            scrollY -= deltaY;
  703.            lastMousePos.x = x;
  704.            lastMousePos.y = y;
  705.            // Update the global view transform.
  706.            g_viewTransform = D2D1::Matrix3x2F::Translation((FLOAT)-scrollX, (FLOAT)-scrollY);
  707.            UpdateStatus(hwnd);  // <-- Added: update status after panning.
  708.            InvalidateRect(hwnd, NULL, FALSE);
  709.        }
  710.        else if (isDrawing && (wParam & MK_LBUTTON)) {
  711.            int worldX = x + scrollX;
  712.            int worldY = y + scrollY;
  713.            if (strokeBuffer.empty())
  714.                strokeBuffer.push_back(DrawPoint(worldX, worldY));
  715.            else {
  716.                const DrawPoint& lastPt = strokeBuffer.back();
  717.                double dx = worldX - lastPt.x;
  718.                double dy = worldY - lastPt.y;
  719.                double distance = sqrt(dx * dx + dy * dy);
  720.                if (distance >= MIN_DISTANCE)
  721.                    strokeBuffer.push_back(DrawPoint(worldX, worldY));
  722.            }
  723.            RECT dirty;
  724.            int clientPrevX = strokeBuffer.back().x - scrollX;
  725.            int clientPrevY = strokeBuffer.back().y - scrollY;
  726.            int clientNewX = x;
  727.            int clientNewY = y;
  728.            dirty.left = std::min(clientPrevX, clientNewX) - brushSize;
  729.            dirty.top = std::min(clientPrevY, clientNewY) - brushSize;
  730.            dirty.right = std::max(clientPrevX, clientNewX) + brushSize;
  731.            dirty.bottom = std::max(clientPrevY, clientNewY) + brushSize;
  732.            InvalidateRect(hwnd, &dirty, FALSE);
  733.        }
  734.        return 0;
  735.    }
  736.    case WM_USER + 1:
  737.    {
  738.        // Custom message after state loading.
  739.        offscreenDirty = true;
  740.        UpdateOffscreenBuffer(hwnd);
  741.        InvalidateRect(hwnd, NULL, TRUE);
  742.        break;
  743.    }
  744.    case WM_ERASEBKGND:
  745.        return 1;
  746.    case WM_PAINT:
  747.    {
  748.        PAINTSTRUCT ps;
  749.        BeginPaint(hwnd, &ps);
  750.  
  751.        pRenderTarget->BeginDraw();
  752.  
  753.        // Clear to white.
  754.        pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
  755.  
  756.        // Set transform for panning (world coordinates).
  757.        pRenderTarget->SetTransform(g_viewTransform);
  758.  
  759.        // Draw all strokes in world coordinates.
  760.        {
  761.            std::lock_guard<std::mutex> lock(strokeMutex);
  762.            for (const auto& stroke : strokeHistory) {
  763.                DrawSmoothStroke(pRenderTarget, stroke.points, stroke.isEraser, stroke.color, stroke.brushSize, 0, 0);
  764.            }
  765.        }
  766.        if (isDrawing && !strokeBuffer.empty()) {
  767.            DrawSmoothStroke(pRenderTarget, strokeBuffer, isEraserMode, currentBrushColor, currentStrokeBrushSize, 0, 0);
  768.        }
  769.  
  770.        // Draw grid in world coordinates.
  771.        RECT rcClient;
  772.        GetClientRect(hwnd, &rcClient);
  773.        D2D1_RECT_F worldRect = D2D1::RectF((FLOAT)scrollX, (FLOAT)scrollY,
  774.            (FLOAT)(scrollX + rcClient.right), (FLOAT)(scrollY + rcClient.bottom));
  775.        if (showGrid) {
  776.            DrawGrid(pRenderTarget, worldRect);
  777.        }
  778.  
  779.        // Reset transform to identity for UI.
  780.        pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
  781.  
  782.        // Draw status bar in screen coordinates.
  783.        D2D1_RECT_F statusRect = D2D1::RectF(0, (FLOAT)rcClient.bottom - 30.0f, (FLOAT)rcClient.right, (FLOAT)rcClient.bottom);
  784.        ID2D1SolidColorBrush* pStatusBgBrush = nullptr;
  785.        pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(0.2f, 0.2f, 0.2f), &pStatusBgBrush);
  786.        pRenderTarget->FillRectangle(statusRect, pStatusBgBrush);
  787.        pStatusBgBrush->Release();
  788.        ID2D1SolidColorBrush* pTextBrush = nullptr;
  789.        pRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &pTextBrush);
  790.        pRenderTarget->DrawTextW(
  791.            g_statusText.c_str(),
  792.            static_cast<UINT32>(g_statusText.length()),
  793.            pTextFormat,
  794.            &statusRect,
  795.            pTextBrush,
  796.            D2D1_DRAW_TEXT_OPTIONS_NONE,
  797.            DWRITE_MEASURING_MODE_NATURAL
  798.        );
  799.        pTextBrush->Release();
  800.  
  801.        HRESULT hr = pRenderTarget->EndDraw();
  802.        EndPaint(hwnd, &ps);
  803.        return 0;
  804.    }
  805.    case WM_SETCURSOR:
  806.    {
  807.        if (LOWORD(lParam) == HTCLIENT) {
  808.            if (isSpacePressed) {
  809.                SetCursor(LoadCursor(NULL, IDC_SIZEALL));
  810.                return TRUE;
  811.            }
  812.            else if (isPaintbrushSelected || isEraserMode) {
  813.                SetCursor(LoadCursor(NULL, IDC_CROSS));
  814.                return TRUE;
  815.            }
  816.        }
  817.        return DefWindowProc(hwnd, uMsg, wParam, lParam);
  818.    }
  819.    case WM_DESTROY:
  820.    {
  821.        if (sessionDirty)
  822.        {
  823.            SaveCanvasState();
  824.            sessionDirty = false;
  825.        }
  826.        DiscardDeviceResources();
  827.        if (pFactory)
  828.        {
  829.            pFactory->Release();
  830.            pFactory = nullptr;
  831.        }
  832.        if (pTextFormat)
  833.        {
  834.            pTextFormat->Release();
  835.            pTextFormat = nullptr;
  836.        }
  837.        if (pDWriteFactory)
  838.        {
  839.            pDWriteFactory->Release();
  840.            pDWriteFactory = nullptr;
  841.        }
  842.        PostQuitMessage(0);
  843.        return 0;
  844.    }
  845.    default:
  846.        return DefWindowProc(hwnd, uMsg, wParam, lParam);
  847.    }
  848.    return 0;
  849. }
  850.  
  851. //----------------------------------------------------------------
  852. // WinMain
  853. //----------------------------------------------------------------
  854.  
  855. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) {
  856.    INITCOMMONCONTROLSEX icex = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES };
  857.    InitCommonControlsEx(&icex);
  858.    const wchar_t CLASS_NAME[] = L"InfiniteCanvasClass";
  859.    WNDCLASS wc = {};
  860.    wc.lpfnWndProc = WindowProc;
  861.    wc.hInstance = hInstance;
  862.    wc.lpszClassName = CLASS_NAME;
  863.    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
  864.    RegisterClass(&wc);
  865.    hInst = hInstance;
  866.    hWnd = CreateWindowEx(0, CLASS_NAME,
  867.        L"Infinite Canvas Doodle App (Direct2D Accelerated, P=Brush, E=Eraser, C=Clear, +/-=BrushSize, Space+Drag/Arrow=Panning, Home=Reset, Q=Color, G=Grid, A=Alpha, PgUp=ZoomIn, PgDn=ZoomOut, F1=About)",
  868.        WS_OVERLAPPEDWINDOW | WS_MAXIMIZE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  869.        NULL, NULL, hInstance, NULL);
  870.    if (hWnd == NULL)
  871.        return 0;
  872.    // Enable double buffering via WS_EX_COMPOSITED.
  873.    SetWindowLongPtr(hWnd, GWL_EXSTYLE, GetWindowLongPtr(hWnd, GWL_EXSTYLE) | WS_EX_COMPOSITED);
  874.    ShowWindow(hWnd, SW_SHOWMAXIMIZED);
  875.    MSG msg = {};
  876.    while (GetMessage(&msg, NULL, 0, 0)) {
  877.        TranslateMessage(&msg);
  878.        DispatchMessage(&msg);
  879.    }
  880.    return 0;
  881. }
  882. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement