501 lines
11 KiB
C++
501 lines
11 KiB
C++
#include <windows.h>
|
|
#include <Windowsx.h>
|
|
#include <d2d1.h>
|
|
|
|
#include <list>
|
|
#include <memory>
|
|
using namespace std;
|
|
|
|
#pragma comment(lib, "d2d1")
|
|
|
|
#include "basewin.h"
|
|
#include "resource.h"
|
|
|
|
template <class T> 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 <typename T>
|
|
static float PixelsToDipsX(T x)
|
|
{
|
|
return static_cast<float>(x) / scaleX;
|
|
}
|
|
|
|
template <typename T>
|
|
static float PixelsToDipsY(T y)
|
|
{
|
|
return static_cast<float>(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<MainWindow>
|
|
{
|
|
enum Mode
|
|
{
|
|
DrawMode,
|
|
SelectMode,
|
|
DragMode
|
|
};
|
|
|
|
HCURSOR hCursor;
|
|
|
|
ID2D1Factory *pFactory;
|
|
ID2D1HwndRenderTarget *pRenderTarget;
|
|
ID2D1SolidColorBrush *pBrush;
|
|
D2D1_POINT_2F ptMouse;
|
|
|
|
Mode mode;
|
|
size_t nextColor;
|
|
|
|
list<shared_ptr<MyEllipse>> ellipses;
|
|
list<shared_ptr<MyEllipse>>::iterator selection;
|
|
|
|
shared_ptr<MyEllipse> 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<MyEllipse>(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);
|
|
}
|