#include #include #include #include #include using namespace std; #pragma comment(lib, "d2d1") #include "basewin.h" #include "resource.h" template void SafeRelease(T **ppT) { if (*ppT) { (*ppT)->Release(); *ppT = NULL; } } class DPIScale { static float scaleX; static float scaleY; public: static void Initialize(ID2D1Factory *pFactory) { FLOAT dpiX, dpiY; pFactory->GetDesktopDpi(&dpiX, &dpiY); scaleX = dpiX/96.0f; scaleY = dpiY/96.0f; } template static float PixelsToDipsX(T x) { return static_cast(x) / scaleX; } template static float PixelsToDipsY(T y) { return static_cast(y) / scaleY; } }; float DPIScale::scaleX = 1.0f; float DPIScale::scaleY = 1.0f; struct MyEllipse { D2D1_ELLIPSE ellipse; D2D1_COLOR_F color; void Draw(ID2D1RenderTarget *pRT, ID2D1SolidColorBrush *pBrush) { pBrush->SetColor(color); pRT->FillEllipse(ellipse, pBrush); pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Black)); pRT->DrawEllipse(ellipse, pBrush, 1.0f); } BOOL HitTest(float x, float y) { const float a = ellipse.radiusX; const float b = ellipse.radiusY; const float x1 = x - ellipse.point.x; const float y1 = y - ellipse.point.y; const float d = ((x1 * x1) / (a * a)) + ((y1 * y1) / (b * b)); return d <= 1.0f; } }; D2D1::ColorF::Enum colors[] = { D2D1::ColorF::Yellow, D2D1::ColorF::Salmon, D2D1::ColorF::LimeGreen }; class MainWindow : public BaseWindow { enum Mode { DrawMode, SelectMode, DragMode }; HCURSOR hCursor; ID2D1Factory *pFactory; ID2D1HwndRenderTarget *pRenderTarget; ID2D1SolidColorBrush *pBrush; D2D1_POINT_2F ptMouse; Mode mode; size_t nextColor; list> ellipses; list>::iterator selection; shared_ptr Selection() { if (selection == ellipses.end()) { return nullptr; } else { return (*selection); } } void ClearSelection() { selection = ellipses.end(); } HRESULT InsertEllipse(float x, float y); BOOL HitTest(float x, float y); void SetMode(Mode m); void MoveSelection(float x, float y); HRESULT CreateGraphicsResources(); void DiscardGraphicsResources(); void OnPaint(); void Resize(); void OnLButtonDown(int pixelX, int pixelY, DWORD flags); void OnLButtonUp(); void OnMouseMove(int pixelX, int pixelY, DWORD flags); void OnKeyDown(UINT vkey); public: MainWindow() : pFactory(NULL), pRenderTarget(NULL), pBrush(NULL), ptMouse(D2D1::Point2F()), nextColor(0), selection(ellipses.end()) { } PCWSTR ClassName() const { return L"Circle Window Class"; } LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); }; HRESULT MainWindow::CreateGraphicsResources() { HRESULT hr = S_OK; if (pRenderTarget == NULL) { RECT rc; GetClientRect(m_hwnd, &rc); D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom); hr = pFactory->CreateHwndRenderTarget( D2D1::RenderTargetProperties(), D2D1::HwndRenderTargetProperties(m_hwnd, size), &pRenderTarget); if (SUCCEEDED(hr)) { const D2D1_COLOR_F color = D2D1::ColorF(1.0f, 1.0f, 0); hr = pRenderTarget->CreateSolidColorBrush(color, &pBrush); } } return hr; } void MainWindow::DiscardGraphicsResources() { SafeRelease(&pRenderTarget); SafeRelease(&pBrush); } void MainWindow::OnPaint() { HRESULT hr = CreateGraphicsResources(); if (SUCCEEDED(hr)) { PAINTSTRUCT ps; BeginPaint(m_hwnd, &ps); pRenderTarget->BeginDraw(); pRenderTarget->Clear( D2D1::ColorF(D2D1::ColorF::SkyBlue) ); for (auto i = ellipses.begin(); i != ellipses.end(); ++i) { (*i)->Draw(pRenderTarget, pBrush); } if (Selection()) { pBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red)); pRenderTarget->DrawEllipse(Selection()->ellipse, pBrush, 2.0f); } hr = pRenderTarget->EndDraw(); if (FAILED(hr) || hr == D2DERR_RECREATE_TARGET) { DiscardGraphicsResources(); } EndPaint(m_hwnd, &ps); } } void MainWindow::Resize() { if (pRenderTarget != NULL) { RECT rc; GetClientRect(m_hwnd, &rc); D2D1_SIZE_U size = D2D1::SizeU(rc.right, rc.bottom); pRenderTarget->Resize(size); InvalidateRect(m_hwnd, NULL, FALSE); } } void MainWindow::OnLButtonDown(int pixelX, int pixelY, DWORD flags) { const float dipX = DPIScale::PixelsToDipsX(pixelX); const float dipY = DPIScale::PixelsToDipsY(pixelY); if (mode == DrawMode) { POINT pt = { pixelX, pixelY }; if (DragDetect(m_hwnd, pt)) { SetCapture(m_hwnd); // Start a new ellipse. InsertEllipse(dipX, dipY); } } else { ClearSelection(); if (HitTest(dipX, dipY)) { SetCapture(m_hwnd); ptMouse = Selection()->ellipse.point; ptMouse.x -= dipX; ptMouse.y -= dipY; SetMode(DragMode); } } InvalidateRect(m_hwnd, NULL, FALSE); } void MainWindow::OnLButtonUp() { if ((mode == DrawMode) && Selection()) { ClearSelection(); InvalidateRect(m_hwnd, NULL, FALSE); } else if (mode == DragMode) { SetMode(SelectMode); } ReleaseCapture(); } void MainWindow::OnMouseMove(int pixelX, int pixelY, DWORD flags) { const float dipX = DPIScale::PixelsToDipsX(pixelX); const float dipY = DPIScale::PixelsToDipsY(pixelY); if ((flags & MK_LBUTTON) && Selection()) { if (mode == DrawMode) { // Resize the ellipse. const float width = (dipX - ptMouse.x) / 2; const float height = (dipY - ptMouse.y) / 2; const float x1 = ptMouse.x + width; const float y1 = ptMouse.y + height; Selection()->ellipse = D2D1::Ellipse(D2D1::Point2F(x1, y1), width, height); } else if (mode == DragMode) { // Move the ellipse. Selection()->ellipse.point.x = dipX + ptMouse.x; Selection()->ellipse.point.y = dipY + ptMouse.y; } InvalidateRect(m_hwnd, NULL, FALSE); } } void MainWindow::OnKeyDown(UINT vkey) { switch (vkey) { case VK_BACK: case VK_DELETE: if ((mode == SelectMode) && Selection()) { ellipses.erase(selection); ClearSelection(); SetMode(SelectMode); InvalidateRect(m_hwnd, NULL, FALSE); }; break; case VK_LEFT: MoveSelection(-1, 0); break; case VK_RIGHT: MoveSelection(1, 0); break; case VK_UP: MoveSelection(0, -1); break; case VK_DOWN: MoveSelection(0, 1); break; } } HRESULT MainWindow::InsertEllipse(float x, float y) { try { selection = ellipses.insert( ellipses.end(), shared_ptr(new MyEllipse())); Selection()->ellipse.point = ptMouse = D2D1::Point2F(x, y); Selection()->ellipse.radiusX = Selection()->ellipse.radiusY = 2.0f; Selection()->color = D2D1::ColorF( colors[nextColor] ); nextColor = (nextColor + 1) % ARRAYSIZE(colors); } catch (std::bad_alloc) { return E_OUTOFMEMORY; } return S_OK; } BOOL MainWindow::HitTest(float x, float y) { for (auto i = ellipses.rbegin(); i != ellipses.rend(); ++i) { if ((*i)->HitTest(x, y)) { selection = (++i).base(); return TRUE; } } return FALSE; } void MainWindow::MoveSelection(float x, float y) { if ((mode == SelectMode) && Selection()) { Selection()->ellipse.point.x += x; Selection()->ellipse.point.y += y; InvalidateRect(m_hwnd, NULL, FALSE); } } void MainWindow::SetMode(Mode m) { mode = m; LPWSTR cursor; switch (mode) { case DrawMode: cursor = IDC_CROSS; break; case SelectMode: cursor = IDC_HAND; break; case DragMode: cursor = IDC_SIZEALL; break; } hCursor = LoadCursor(NULL, cursor); SetCursor(hCursor); } int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) { MainWindow win; if (!win.Create(L"Draw Circles", WS_OVERLAPPEDWINDOW)) { return 0; } HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCEL1)); ShowWindow(win.Window(), nCmdShow); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(win.Window(), hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return 0; } LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CREATE: if (FAILED(D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, &pFactory))) { return -1; // Fail CreateWindowEx. } DPIScale::Initialize(pFactory); SetMode(DrawMode); return 0; case WM_DESTROY: DiscardGraphicsResources(); SafeRelease(&pFactory); PostQuitMessage(0); return 0; case WM_PAINT: OnPaint(); return 0; case WM_SIZE: Resize(); return 0; case WM_LBUTTONDOWN: OnLButtonDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (DWORD)wParam); return 0; case WM_LBUTTONUP: OnLButtonUp(); return 0; case WM_MOUSEMOVE: OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), (DWORD)wParam); return 0; case WM_SETCURSOR: if (LOWORD(lParam) == HTCLIENT) { SetCursor(hCursor); return TRUE; } break; case WM_KEYDOWN: OnKeyDown((UINT)wParam); return 0; case WM_COMMAND: switch (LOWORD(wParam)) { case ID_DRAW_MODE: SetMode(DrawMode); break; case ID_SELECT_MODE: SetMode(SelectMode); break; case ID_TOGGLE_MODE: if (mode == DrawMode) { SetMode(SelectMode); } else { SetMode(DrawMode); } break; } return 0; } return DefWindowProc(m_hwnd, uMsg, wParam, lParam); }