556 lines
18 KiB
C++
556 lines
18 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
|
|
#include "SimplePathAnimationSample.h"
|
|
|
|
/******************************************************************
|
|
* *
|
|
* WinMain *
|
|
* *
|
|
* Application entrypoint *
|
|
* *
|
|
******************************************************************/
|
|
|
|
int WINAPI WinMain(
|
|
HINSTANCE /* hInstance */,
|
|
HINSTANCE /* hPrevInstance */,
|
|
LPSTR /* lpCmdLine */,
|
|
int /* nCmdShow */
|
|
)
|
|
{
|
|
// Ignoring the return value because we want to continue running even in the
|
|
// unlikely event that HeapSetInformation fails.
|
|
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
|
|
|
|
if (SUCCEEDED(CoInitialize(NULL)))
|
|
{
|
|
{
|
|
DemoApp app;
|
|
|
|
if (SUCCEEDED(app.Initialize()))
|
|
{
|
|
app.RunMessageLoop();
|
|
}
|
|
}
|
|
CoUninitialize();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::DemoApp constructor *
|
|
* *
|
|
* Initialize member data *
|
|
* *
|
|
******************************************************************/
|
|
|
|
DemoApp::DemoApp() :
|
|
m_hwnd(NULL),
|
|
m_pD2DFactory(NULL),
|
|
m_pRT(NULL),
|
|
m_pPathGeometry(NULL),
|
|
m_pObjectGeometry(NULL),
|
|
m_pRedBrush(NULL),
|
|
m_pYellowBrush(NULL),
|
|
m_Animation()
|
|
{
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::~DemoApp destructor *
|
|
* *
|
|
* Release resources. *
|
|
* *
|
|
******************************************************************/
|
|
|
|
DemoApp::~DemoApp()
|
|
{
|
|
SafeRelease(&m_pD2DFactory);
|
|
SafeRelease(&m_pRT);
|
|
SafeRelease(&m_pPathGeometry);
|
|
SafeRelease(&m_pObjectGeometry);
|
|
SafeRelease(&m_pRedBrush);
|
|
SafeRelease(&m_pYellowBrush);
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::Initialize *
|
|
* *
|
|
* Create application window and device-independent resources *
|
|
* *
|
|
******************************************************************/
|
|
|
|
HRESULT DemoApp::Initialize()
|
|
{
|
|
HRESULT hr;
|
|
|
|
//register window class
|
|
WNDCLASSEX wcex = { sizeof(WNDCLASSEX) };
|
|
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
|
wcex.lpfnWndProc = DemoApp::WndProc;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = sizeof(LONG_PTR);
|
|
wcex.hInstance = HINST_THISCOMPONENT;
|
|
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wcex.hbrBackground = NULL;
|
|
wcex.lpszMenuName = NULL;
|
|
wcex.lpszClassName = L"D2DDemoApp";
|
|
|
|
RegisterClassEx(&wcex);
|
|
|
|
hr = CreateDeviceIndependentResources();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Create the application window.
|
|
//
|
|
// Because the CreateWindow function takes its size in pixels, we
|
|
// obtain the system DPI and use it to scale the window size.
|
|
FLOAT dpiX, dpiY;
|
|
m_pD2DFactory->GetDesktopDpi(&dpiX, &dpiY);
|
|
|
|
m_hwnd = CreateWindow(
|
|
L"D2DDemoApp",
|
|
L"D2D Simple Path Animation Sample",
|
|
WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
static_cast<UINT>(ceil(512.f * dpiX / 96.f)),
|
|
static_cast<UINT>(ceil(512.f * dpiY / 96.f)),
|
|
NULL,
|
|
NULL,
|
|
HINST_THISCOMPONENT,
|
|
this
|
|
);
|
|
hr = m_hwnd ? S_OK : E_FAIL;
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
float length = 0;
|
|
|
|
hr = m_pPathGeometry->ComputeLength(
|
|
NULL, //no transform
|
|
&length
|
|
);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_Animation.SetStart(0); //start at beginning of path
|
|
m_Animation.SetEnd(length); //length at end of path
|
|
m_Animation.SetDuration(5.0f); //seconds
|
|
|
|
ZeroMemory(&m_DwmTimingInfo, sizeof(m_DwmTimingInfo));
|
|
m_DwmTimingInfo.cbSize = sizeof(m_DwmTimingInfo);
|
|
|
|
// Get the composition refresh rate. If the DWM isn't running,
|
|
// get the refresh rate from GDI -- probably going to be 60Hz
|
|
if (FAILED(DwmGetCompositionTimingInfo(NULL, &m_DwmTimingInfo)))
|
|
{
|
|
HDC hdc = GetDC(m_hwnd);
|
|
m_DwmTimingInfo.rateCompose.uiDenominator = 1;
|
|
m_DwmTimingInfo.rateCompose.uiNumerator = GetDeviceCaps(hdc, VREFRESH);
|
|
ReleaseDC(m_hwnd, hdc);
|
|
}
|
|
|
|
ShowWindow(m_hwnd, SW_SHOWNORMAL);
|
|
|
|
UpdateWindow(m_hwnd);
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::CreateDeviceIndependentResources *
|
|
* *
|
|
* This method is used to create resources which are not bound *
|
|
* to any device. Their lifetime effectively extends for the *
|
|
* duration of the app. *
|
|
* *
|
|
******************************************************************/
|
|
|
|
HRESULT DemoApp::CreateDeviceIndependentResources()
|
|
{
|
|
HRESULT hr;
|
|
ID2D1GeometrySink *pSink = NULL;
|
|
|
|
// Create a Direct2D factory.
|
|
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Create the path geometry.
|
|
hr = m_pD2DFactory->CreatePathGeometry(&m_pPathGeometry);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Write to the path geometry using the geometry sink. We are going to create a
|
|
// spiral
|
|
hr = m_pPathGeometry->Open(&pSink);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
D2D1_POINT_2F currentLocation = {0, 0};
|
|
|
|
pSink->BeginFigure(currentLocation, D2D1_FIGURE_BEGIN_FILLED);
|
|
|
|
D2D1_POINT_2F locDelta = {2, 2};
|
|
float radius = 3;
|
|
|
|
for (UINT i = 0; i < 30; ++i)
|
|
{
|
|
currentLocation.x += radius * locDelta.x;
|
|
currentLocation.y += radius * locDelta.y;
|
|
|
|
pSink->AddArc(
|
|
D2D1::ArcSegment(
|
|
currentLocation,
|
|
D2D1::SizeF(2*radius, 2*radius), // radiusx/y
|
|
0.0f, // rotation angle
|
|
D2D1_SWEEP_DIRECTION_CLOCKWISE,
|
|
D2D1_ARC_SIZE_SMALL
|
|
)
|
|
);
|
|
|
|
locDelta = D2D1::Point2F(-locDelta.y, locDelta.x);
|
|
|
|
radius += 3;
|
|
}
|
|
|
|
pSink->EndFigure(D2D1_FIGURE_END_OPEN);
|
|
|
|
hr = pSink->Close();
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Create the path geometry.
|
|
hr = m_pD2DFactory->CreatePathGeometry(&m_pObjectGeometry);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Write to the object geometry using the geometry sink.
|
|
// We are going to create a simple triangle
|
|
hr = m_pObjectGeometry->Open(&pSink);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pSink->BeginFigure(
|
|
D2D1::Point2F(0.0f, 0.0f),
|
|
D2D1_FIGURE_BEGIN_FILLED
|
|
);
|
|
|
|
const D2D1_POINT_2F ptTriangle[] = {{-10.0f, -10.0f}, {-10.0f, 10.0f}, {0.0f, 0.0f}};
|
|
pSink->AddLines(ptTriangle, 3);
|
|
|
|
pSink->EndFigure(D2D1_FIGURE_END_OPEN);
|
|
|
|
hr = pSink->Close();
|
|
}
|
|
|
|
SafeRelease(&pSink);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::CreateDeviceResources *
|
|
* *
|
|
* This method creates resources which are bound to a particular *
|
|
* D3D device. It's all centralized here, in case the resources *
|
|
* need to be recreated in case of D3D device loss (eg. display *
|
|
* change, remoting, removal of video card, etc). *
|
|
* *
|
|
******************************************************************/
|
|
|
|
HRESULT DemoApp::CreateDeviceResources()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (!m_pRT)
|
|
{
|
|
RECT rc;
|
|
GetClientRect(m_hwnd, &rc);
|
|
|
|
D2D1_SIZE_U size = D2D1::SizeU(
|
|
rc.right - rc.left,
|
|
rc.bottom - rc.top
|
|
);
|
|
|
|
// Create a Direct2D render target
|
|
hr = m_pD2DFactory->CreateHwndRenderTarget(
|
|
D2D1::RenderTargetProperties(),
|
|
D2D1::HwndRenderTargetProperties(m_hwnd, size),
|
|
&m_pRT
|
|
);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Create a red brush.
|
|
hr = m_pRT->CreateSolidColorBrush(
|
|
D2D1::ColorF(D2D1::ColorF::Red),
|
|
&m_pRedBrush
|
|
);
|
|
}
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
// Create a yellow brush.
|
|
hr = m_pRT->CreateSolidColorBrush(
|
|
D2D1::ColorF(D2D1::ColorF::Yellow),
|
|
&m_pYellowBrush
|
|
);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::DiscardDeviceResources *
|
|
* *
|
|
* Discard device-specific resources which need to be recreated *
|
|
* when a D3D device is lost *
|
|
* *
|
|
******************************************************************/
|
|
|
|
void DemoApp::DiscardDeviceResources()
|
|
{
|
|
SafeRelease(&m_pRT);
|
|
SafeRelease(&m_pRedBrush);
|
|
SafeRelease(&m_pYellowBrush);
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::RunMessageLoop *
|
|
* *
|
|
* Main window message loop *
|
|
* *
|
|
******************************************************************/
|
|
|
|
void DemoApp::RunMessageLoop()
|
|
{
|
|
MSG msg;
|
|
|
|
while (GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::OnRender *
|
|
* *
|
|
* Called whenever the application needs to display the client *
|
|
* window. This method draws a single frame of animated content *
|
|
* *
|
|
* Note that this function will not render anything if the window *
|
|
* is occluded (e.g. when the screen is locked). *
|
|
* Also, this function will automatically discard device-specific *
|
|
* resources if the D3D device disappears during function *
|
|
* invocation, and will recreate the resources the next time it's *
|
|
* invoked. *
|
|
* *
|
|
******************************************************************/
|
|
|
|
HRESULT DemoApp::OnRender()
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = CreateDeviceResources();
|
|
if (SUCCEEDED(hr) && !(m_pRT->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
|
|
{
|
|
D2D1_POINT_2F point;
|
|
D2D1_POINT_2F tangent;
|
|
D2D1_MATRIX_3X2_F triangleMatrix;
|
|
D2D1_SIZE_F rtSize = m_pRT->GetSize();
|
|
float minWidthHeightScale = min(rtSize.width, rtSize.height) / 512;
|
|
|
|
D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale(
|
|
minWidthHeightScale,
|
|
minWidthHeightScale
|
|
);
|
|
|
|
D2D1::Matrix3x2F translation = D2D1::Matrix3x2F::Translation(
|
|
rtSize.width / 2,
|
|
rtSize.height / 2
|
|
);
|
|
|
|
// Prepare to draw.
|
|
m_pRT->BeginDraw();
|
|
|
|
// Reset to identity transform
|
|
m_pRT->SetTransform(D2D1::Matrix3x2F::Identity());
|
|
|
|
//clear the render target contents
|
|
m_pRT->Clear(D2D1::ColorF(D2D1::ColorF::Black));
|
|
|
|
//center the path
|
|
m_pRT->SetTransform(scale * translation);
|
|
|
|
//draw the path in red
|
|
m_pRT->DrawGeometry(m_pPathGeometry, m_pRedBrush);
|
|
|
|
static float float_time = 0.0f;
|
|
|
|
float length = m_Animation.GetValue(float_time);
|
|
|
|
// Ask the geometry to give us the point that corresponds with the
|
|
// length at the current time.
|
|
hr = m_pPathGeometry->ComputePointAtLength(length, NULL, &point, &tangent);
|
|
|
|
Assert(SUCCEEDED(hr));
|
|
|
|
// Reorient the triangle so that it follows the
|
|
// direction of the path.
|
|
triangleMatrix = D2D1::Matrix3x2F(
|
|
tangent.x, tangent.y,
|
|
-tangent.y, tangent.x,
|
|
point.x, point.y
|
|
);
|
|
|
|
m_pRT->SetTransform(triangleMatrix * scale * translation);
|
|
|
|
// Draw the yellow triangle.
|
|
m_pRT->FillGeometry(m_pObjectGeometry, m_pYellowBrush);
|
|
|
|
// Commit the drawing operations.
|
|
hr = m_pRT->EndDraw();
|
|
|
|
if (hr == D2DERR_RECREATE_TARGET)
|
|
{
|
|
hr = S_OK;
|
|
DiscardDeviceResources();
|
|
}
|
|
|
|
// When we reach the end of the animation, loop back to the beginning.
|
|
if (float_time >= m_Animation.GetDuration())
|
|
{
|
|
float_time = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
float_time += static_cast<float>(m_DwmTimingInfo.rateCompose.uiDenominator) /
|
|
static_cast<float>(m_DwmTimingInfo.rateCompose.uiNumerator);
|
|
}
|
|
}
|
|
|
|
InvalidateRect(m_hwnd, NULL, FALSE);
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::OnResize *
|
|
* *
|
|
* If the application receives a WM_SIZE message, this method *
|
|
* resize the render target appropriately. *
|
|
* *
|
|
******************************************************************/
|
|
|
|
void DemoApp::OnResize(UINT width, UINT height)
|
|
{
|
|
if (m_pRT)
|
|
{
|
|
D2D1_SIZE_U size;
|
|
size.width = width;
|
|
size.height = height;
|
|
|
|
// Note: This method can fail, but it's okay to ignore the
|
|
// error here -- it will be repeated on the next call to
|
|
// EndDraw.
|
|
m_pRT->Resize(size);
|
|
}
|
|
}
|
|
|
|
/******************************************************************
|
|
* *
|
|
* DemoApp::WndProc *
|
|
* *
|
|
* Window message handler *
|
|
* *
|
|
******************************************************************/
|
|
|
|
LRESULT CALLBACK DemoApp::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LRESULT result = 0;
|
|
|
|
if (message == WM_CREATE)
|
|
{
|
|
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
|
|
DemoApp *pDemoApp = (DemoApp *)pcs->lpCreateParams;
|
|
|
|
::SetWindowLongPtrW(
|
|
hwnd,
|
|
GWLP_USERDATA,
|
|
reinterpret_cast<LONG_PTR>(pDemoApp)
|
|
);
|
|
|
|
result = 1;
|
|
}
|
|
else
|
|
{
|
|
DemoApp *pDemoApp = reinterpret_cast<DemoApp *>(
|
|
::GetWindowLongPtrW(
|
|
hwnd,
|
|
GWLP_USERDATA
|
|
));
|
|
|
|
bool wasHandled = false;
|
|
|
|
if (pDemoApp)
|
|
{
|
|
switch(message)
|
|
{
|
|
case WM_SIZE:
|
|
{
|
|
UINT width = LOWORD(lParam);
|
|
UINT height = HIWORD(lParam);
|
|
pDemoApp->OnResize(width, height);
|
|
}
|
|
result = 0;
|
|
wasHandled = true;
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
case WM_DISPLAYCHANGE:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
BeginPaint(hwnd, &ps);
|
|
|
|
pDemoApp->OnRender();
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
result = 0;
|
|
wasHandled = true;
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
PostQuitMessage(0);
|
|
}
|
|
result = 1;
|
|
wasHandled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!wasHandled)
|
|
{
|
|
result = DefWindowProc(hwnd, message, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|