// Copyright (c) Microsoft Corporation. All rights reserved #include "stdafx.h" #include "AppWindow.h" #include "objbase.h" #include #define WNDCLASSNAME (L"DirectManipulationAppWindow") #define WNDTITLE (L"Direct Manipulation Sample Application") #define WINDOW_WIDTH (1024) #define WINDOW_HEIGHT (768) #define TEXTBLOCK_WIDTH (375) #define TEXTBLOCK_HEIGHT (75) #define OUTER_VIEWPORT_TEXT_MARGIN_X (50.0f) #define OUTER_VIEWPORT_TEXT_MARGIN_Y (125.0f) #define OUTER_VIEWPORT_TEXT_WIDTH (300.0f) #define OUTER_VIEWPORT_TEXT_HEIGHT (50.0f) #define OUTER_CONTENT_WIDTH (2000) #define OUTER_CONTENT_HEIGHT (2000) #define INNER_VIEWPORT_OFFSET (200) #define BUTTON_OFFSET_X (5.0f) #define BUTTON_OFFSET_Y (200.0f) #define BUTTON_BORDER_WIDTH (2.0f) #define BUTTON_TEXT_PADDING (5.0f) #define BUTTON_WIDTH (175) #define BUTTON_HEIGHT (40) namespace DManipSample { CAppWindow::CAppWindow() { // Populate outer content rect as it has a static size. _contentOuterRect.top = 0; _contentOuterRect.left = 0; _contentOuterRect.right = OUTER_CONTENT_WIDTH; _contentOuterRect.bottom = OUTER_CONTENT_HEIGHT; } int CAppWindow::ShowAndServiceWindow() { HRESULT hr = S_OK; HINSTANCE hModule = GetModuleHandle(nullptr); // Receive mouse events as WM_POINTER* messages EnableMouseInPointer(TRUE); WNDCLASSEX wc; wc.cbSize = sizeof(wc); wc.lpszClassName = WNDCLASSNAME; wc.lpfnWndProc = CAppWindow::s_WndProc; wc.style = CS_VREDRAW | CS_HREDRAW; wc.hInstance = hModule; wc.hIcon = nullptr; wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hbrBackground = static_cast(GetStockObject(WHITE_BRUSH)); wc.lpszMenuName = nullptr; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hIconSm = nullptr; ATOM registeredClass = RegisterClassEx(&wc); if(!registeredClass) { WCHAR error[MAX_PATH] = {0}; hr = StringCchPrintf(error, ARRAYSIZE(error), L"Failed to register class. GetLastError()=%d", GetLastError()); if(SUCCEEDED(hr)) { MessageBox(nullptr, error, L"Error", MB_OK); } return 2; } _hWnd = CreateWindow(WNDCLASSNAME, WNDTITLE, WS_OVERLAPPEDWINDOW, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, nullptr, nullptr, hModule, this); if(!_hWnd) { WCHAR error[MAX_PATH] = {0}; hr = StringCchPrintf(error, ARRAYSIZE(error), L"Could not create window. GetLastError()=%d", GetLastError()); if(SUCCEEDED(hr)) { MessageBox(nullptr, error, L"Error", MB_OK); } return 1; } hr = _Initialize(); ShowWindow(_hWnd, SW_SHOW); UpdateWindow(_hWnd); RECT windowRect; GetWindowRect(_hWnd, &windowRect); if(FAILED(hr)) { WCHAR error[MAX_PATH] = {0}; hr = StringCchPrintf(error, ARRAYSIZE(error), L"Encountered error while initializing sample. HRESULT=0x%x", hr); if(SUCCEEDED(hr)) { MessageBox(nullptr, error, L"Error", MB_OK); } return -2; } if(SUCCEEDED(hr)) { MSG msg; while (GetMessage(&msg, nullptr, 0, 0) > 0) { TranslateMessage(&msg); DispatchMessage(&msg); } } return 0; } LRESULT CALLBACK CAppWindow::s_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; // If we are creating the window, set the pointer to the instance of CAppWindow associated with the window as the HWND's user data. // That way when we get messages besides WM_CREATE we can call the instance's WndProc and reference non-static member variables. if(message == WM_CREATE) { SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(static_cast(reinterpret_cast(lParam)->lpCreateParams))); } else { CAppWindow* appWindow = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); if(appWindow) { result = appWindow->_WndProc(hWnd, message, wParam, lParam); } else { result = DefWindowProc(hWnd, message, wParam, lParam); } } return result; } LRESULT CAppWindow::_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; HRESULT hr = S_OK; switch (message) { case WM_SIZE: { hr = _SizeDependentChanges(); } break; case UWM_REDRAWSTATUS: { hr = _DrawViewportStatusText(); } break; case WM_POINTERDOWN: { // Determine if the button was hit. If so, change the button state. UINT contactId = GET_POINTERID_WPARAM(wParam); BOOL buttonHit = FALSE; hr = _HitTestRect(contactId, hWnd, FALSE, BUTTON_OFFSET_X, BUTTON_OFFSET_Y, BUTTON_OFFSET_X + BUTTON_WIDTH, BUTTON_OFFSET_Y + BUTTON_HEIGHT, &buttonHit); if(SUCCEEDED(hr) && buttonHit) { hr = _ChangeButtonState(_dcompVisualButtonDown); } } break; case WM_POINTERUPDATE: { // // If the updated contact is not over the button, check to see if the contact is already being tracked by the outer viewport. If so, ignore it. // UINT contactId = GET_POINTERID_WPARAM(wParam); BOOL buttonHit = FALSE; BOOL outerViewportHasContact = _OuterViewportHasContact(contactId); if(!outerViewportHasContact) { // Check if the button was hit. hr = _HitTestRect(contactId, hWnd, FALSE, BUTTON_OFFSET_X, BUTTON_OFFSET_Y, BUTTON_OFFSET_X + BUTTON_WIDTH, BUTTON_OFFSET_Y + BUTTON_HEIGHT, &buttonHit); if(SUCCEEDED(hr) && !buttonHit) { BOOL innerViewportHit = FALSE; // Check if the inner viewport was hit. if(SUCCEEDED(hr)) { hr = _HitTestRect(contactId, hWnd, TRUE, static_cast(_viewportInnerRect.left), static_cast(_viewportInnerRect.top), static_cast(_viewportInnerRect.right), static_cast(_viewportInnerRect.bottom), &innerViewportHit); } if(SUCCEEDED(hr)) { if(innerViewportHit) { hr = _viewportInner->SetContact(contactId); } if(SUCCEEDED(hr)) { hr = _viewportOuter->SetContact(contactId); } if(SUCCEEDED(hr)) { // Add to our list of active contacts. _activeContacts.push_back(contactId); } } } } } break; case WM_POINTERUP: { _activeContacts.remove(GET_POINTERID_WPARAM(wParam)); if(_dcompVisualButtonCurrent == _dcompVisualButtonDown) { hr = _ChangeButtonState(_dcompVisualButtonUpClicked); } } break; case WM_POINTERCAPTURECHANGED: { if(_dcompVisualButtonCurrent == _dcompVisualButtonDown) { hr = _ChangeButtonState(_dcompVisualButtonUpCapturedByViewport); } } break; case WM_KEYDOWN: case WM_MOUSEWHEEL: case WM_MOUSEHWHEEL: { DWORD targetContact = (message == WM_KEYDOWN) ? DIRECTMANIPULATION_KEYBOARDFOCUS : DIRECTMANIPULATION_MOUSEFOCUS; BOOL fHandled = FALSE; MSG msg = { hWnd, message, wParam, lParam }; HRESULT setContactHr = _viewportOuter->SetContact(targetContact); if(SUCCEEDED(setContactHr)) { hr = _manager->ProcessInput(&msg, &fHandled); } else { hr = setContactHr; } // Call ReleaseContact if SetContact succeeded despite the result of ProcessInput. However, // only overwrite hr if ProcessInput succeeded. Otherwise leave the HRESULT of ProcessInput // in place. if(SUCCEEDED(setContactHr)) { HRESULT releaseContactHr = _viewportOuter->ReleaseContact(targetContact); if(SUCCEEDED(hr)) { hr = releaseContactHr; } } } break; case WM_DESTROY: PostQuitMessage(0); default: result = DefWindowProc(hWnd, message, wParam, lParam); } return result; } HRESULT CAppWindow::_HitTestRect(UINT contactId, HWND hWnd, BOOL applyTransform, float left, float top, float right, float bottom, BOOL* innerViewportHit) { HRESULT hr = S_OK; // Translate content rect based on the current output transform of the outer viewport to see if we should call SetContact on the inner (nested) viewport. POINTER_INFO info = {0}; GetPointerInfo(contactId, &info); POINTF topLeftFloat = {left, top}; POINTF bottomRightFloat = {right, bottom}; if(applyTransform) { // Determine the outer viewport's content transform (since it may have been translated or scaled) so it can be taken into account when doing the hit testing for the // inner viewport. float contentTransform[6] = {0}; hr = _contentOuter->GetContentTransform(contentTransform, ARRAYSIZE(contentTransform)); if(SUCCEEDED(hr)) { // Transform the inner viewport's top left and bottom right by the outer viewport's scale factor. topLeftFloat.x *= contentTransform[0]; topLeftFloat.y *= contentTransform[0]; bottomRightFloat.x *= contentTransform[0]; bottomRightFloat.y *= contentTransform[0]; // Transform the inner viewport's top left and bottom right by the outer viewport's translate values. If the content transform contained a rotation value, we // would need to factor the rotation in. The visuals and dmanip transforms in this sample cannot be given rotation values, simplifying the math. topLeftFloat.x += contentTransform[4]; topLeftFloat.y += contentTransform[5]; bottomRightFloat.x += contentTransform[4]; bottomRightFloat.y += contentTransform[5]; } } POINT topLeft = {static_cast(topLeftFloat.x), static_cast(topLeftFloat.y)}; POINT bottomRight = {static_cast(bottomRightFloat.x), static_cast(bottomRightFloat.y)}; ClientToScreen(hWnd, &topLeft); ClientToScreen(hWnd, &bottomRight); // Determine if the contact hit the inner viewport. RECT transformedRect = {topLeft.x, topLeft.y, bottomRight.x, bottomRight.y}; *innerViewportHit = PtInRect(&transformedRect, info.ptPixelLocation); return hr; } HRESULT CAppWindow::_InitializeDevices() { HRESULT hr = S_OK; D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1, }; hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &_d3dDevice, nullptr, nullptr); if(FAILED(hr)) { // If the device could not be created with hardware acceleration, attempt to create via WARP. hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_WARP, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &_d3dDevice, nullptr, nullptr); } if(SUCCEEDED(hr)) { hr = _d3dDevice.As(&_dxgiDevice); } if(SUCCEEDED(hr)) { hr = DCompositionCreateDevice2(_dxgiDevice.Get(), IID_PPV_ARGS(&_dcompDevice)); } if(SUCCEEDED(hr)) { hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&_d2dFactory)); } return hr; } HRESULT CAppWindow::_InitializeManagerAndViewport() { HRESULT hr = S_OK; // // Create and activate the instance of IDirectManipulationManager. This instance is used to // CoCreate other DirectManipulation objects and enable / disable DirectManipulation // per HWND. // if(SUCCEEDED(hr)) { hr = CoCreateInstance(CLSID_DirectManipulationManager, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_manager)); } ComPtr updateManager; if(SUCCEEDED(hr)) { hr = _manager->GetUpdateManager(IID_PPV_ARGS(&updateManager)); } // // Create the instance of IDirectManipulationDCompCompositor. This instance will handle transformation of // the dcomp visuals and provide the instance of IDirectManipulationFrameInfoProvider that will manage // frame timing. // if(SUCCEEDED(hr)) { hr = CoCreateInstance(CLSID_DCompManipulationCompositor, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&_compositor)); } if(SUCCEEDED(hr)) { hr = _compositor->SetUpdateManager(updateManager.Get()); } ComPtr frameInfo; if(SUCCEEDED(hr)) { hr = _compositor.As(&frameInfo); } // // Create the inner and outer viewports and set their boundaries in the associated HWND. // if(SUCCEEDED(hr)) { hr = _manager->CreateViewport(frameInfo.Get(), _hWnd, IID_PPV_ARGS(&_viewportOuter)); } if(SUCCEEDED(hr)) { hr = _manager->CreateViewport(frameInfo.Get(), _hWnd, IID_PPV_ARGS(&_viewportInner)); } // // Enable the desired configuration for each viewport. // DIRECTMANIPULATION_CONFIGURATION targetConfiguration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA | DIRECTMANIPULATION_CONFIGURATION_RAILS_X | DIRECTMANIPULATION_CONFIGURATION_RAILS_Y | DIRECTMANIPULATION_CONFIGURATION_SCALING | DIRECTMANIPULATION_CONFIGURATION_SCALING_INERTIA; if(SUCCEEDED(hr)) { hr = _viewportOuter->ActivateConfiguration(targetConfiguration); } if(SUCCEEDED(hr)) { hr = _viewportInner->ActivateConfiguration(targetConfiguration); } // // Create instances of CViewportEventHandler for each viewport and associate the instance // with the corresponding viewport. // if(SUCCEEDED(hr)) { _handlerOuter = Make(_hWnd); if(_handlerOuter == nullptr) { hr = E_OUTOFMEMORY; } } if(SUCCEEDED(hr)) { _handlerInner = Make(_hWnd); if(_handlerInner == nullptr) { hr = E_OUTOFMEMORY; } } if(SUCCEEDED(hr)) { hr = _viewportOuter->AddEventHandler(_hWnd, _handlerOuter.Get(), &_viewportOuterHandlerCookie); } if(SUCCEEDED(hr)) { hr = _viewportInner->AddEventHandler(_hWnd, _handlerInner.Get(), &_viewportInnerHandlerCookie); } // // Get the content instances for each viewport so it is available when performing hit testing and // rendering the associated viewport's DirectComposition content. // ComPtr primaryContentOuter; if(SUCCEEDED(hr)) { hr = _viewportOuter->GetPrimaryContent(IID_PPV_ARGS(&primaryContentOuter)); } ComPtr primaryContentInner; if(SUCCEEDED(hr)) { hr = _viewportInner->GetPrimaryContent(IID_PPV_ARGS(&primaryContentInner)); } if(SUCCEEDED(hr)) { hr = primaryContentOuter.As(&_contentOuter); } if(SUCCEEDED(hr)) { hr = primaryContentInner.As(&_contentInner); } // Limit how far the viewports can be zoomed (scaled). if(SUCCEEDED(hr)) { hr = primaryContentOuter->SetZoomBoundaries(1.0f, 5.0f); } if(SUCCEEDED(hr)) { hr = primaryContentInner->SetZoomBoundaries(0.5f, 5.0f); } // Activate DirectManipulation on our target window. if(SUCCEEDED(hr)) { hr = _manager->Activate(_hWnd); } return hr; } HRESULT CAppWindow::_InitializeDrawing() { HRESULT hr = S_OK; hr = _dcompDevice->CreateTargetForHwnd(_hWnd, TRUE, &_dcompTarget); if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualOuterParent); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualText); } // // The DirectComposition visuals will be set up in a hierarchy with parent-child order // _dcompVisualOuterParent->_dcompVisualOuterChild->_dcompVisualInnerParent->_dcompVisualInnerChild. // Once IDirectManipulationDCompCompositor::AddVisual is called, DirectManipulation will lie between // inner parent and child visuals and outer parent and child visuals. // if(SUCCEEDED(hr)) { hr = _dcompTarget->SetRoot(_dcompVisualOuterParent.Get()); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualOuterChild); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualInnerParent); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualInnerChild); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualButtonDown); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualButtonUpCapturedByViewport); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualButtonUpClicked); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateVisual(&_dcompVisualButtonUpDefault); } if(SUCCEEDED(hr)) { hr = _dcompVisualOuterParent->AddVisual(_dcompVisualOuterChild.Get(), FALSE, nullptr); } if(SUCCEEDED(hr)) { hr = _dcompVisualOuterChild->AddVisual(_dcompVisualInnerParent.Get(), FALSE, nullptr); } if(SUCCEEDED(hr)) { hr = _dcompVisualInnerParent->AddVisual(_dcompVisualInnerChild.Get(), FALSE, nullptr); } if(SUCCEEDED(hr)) { _dcompVisualButtonCurrent = _dcompVisualButtonUpDefault; hr = _dcompVisualOuterParent->AddVisual(_dcompVisualButtonUpDefault.Get(), FALSE, nullptr); } if(SUCCEEDED(hr)) { hr = _dcompVisualOuterParent->AddVisual(_dcompVisualText.Get(), TRUE, _dcompVisualOuterChild.Get()); } if(SUCCEEDED(hr)) { hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &_writeFactory); } if(SUCCEEDED(hr)) { hr = _writeFactory->CreateTextFormat(L"Arial", nullptr, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 12.0f, L"en-us", &_statusTextFormat); } if(SUCCEEDED(hr)) { hr = _writeFactory->CreateTextFormat(L"Arial", nullptr, DWRITE_FONT_WEIGHT_BOLD, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 25.0f, L"en-us", &_viewportTextFormat); } if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateSurface( TEXTBLOCK_WIDTH, TEXTBLOCK_HEIGHT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_ALPHA_MODE_IGNORE, &_textSurface); } if(SUCCEEDED(hr)) { hr = _dcompVisualText->SetContent(_textSurface.Get()); } if(SUCCEEDED(hr)) { hr = _dcompVisualText->SetBitmapInterpolationMode(DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); } // // Load Circles.png in preparation for painting the content of the inner viewport. // ComPtr factory; if(SUCCEEDED(hr)) { hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)); } ComPtr decoder; if(SUCCEEDED(hr)) { hr = factory->CreateDecoderFromFilename(L".\\Circles.png", nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &decoder); } ComPtr source; if(SUCCEEDED(hr)) { hr = decoder->GetFrame(0, &source); } if(SUCCEEDED(hr)) { hr = factory->CreateFormatConverter(&_bitmapConverter); } if(SUCCEEDED(hr)) { hr = _bitmapConverter->Initialize(source.Get(), GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nullptr, 0.0f, WICBitmapPaletteTypeMedianCut); } UINT imageWidth = 0; UINT imageHeight = 0; if(SUCCEEDED(hr)) { hr = _bitmapConverter->GetSize(&imageWidth, &imageHeight); } if(SUCCEEDED(hr)) { // Populate the inner viewport and client rect based on the image size. The default // size will be half the image width and height. _viewportInnerRect.top = INNER_VIEWPORT_OFFSET; _viewportInnerRect.left = INNER_VIEWPORT_OFFSET; _viewportInnerRect.right = INNER_VIEWPORT_OFFSET + (imageWidth / 2); _viewportInnerRect.bottom = INNER_VIEWPORT_OFFSET + (imageHeight / 2); _contentInnerRect.top = 0; _contentInnerRect.left = 0; _contentInnerRect.right = imageWidth; _contentInnerRect.bottom = imageHeight; } return hr; } HRESULT CAppWindow::_DrawOuterPrimaryContent() { HRESULT hr = S_OK; UINT width = _contentOuterRect.right - _contentOuterRect.left; UINT height = _contentOuterRect.bottom - _contentOuterRect.top; ComPtr outerSurface; hr = _dcompDevice->CreateSurface( width, height, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_ALPHA_MODE_IGNORE, &outerSurface); if(SUCCEEDED(hr)) { hr = _dcompVisualOuterChild->SetContent(outerSurface.Get()); } if(SUCCEEDED(hr)) { hr = _dcompVisualOuterChild->SetBitmapInterpolationMode(DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); } // Surface begin draw POINT point = {0, 0}; ComPtr dxSurface; if(SUCCEEDED(hr)) { hr = outerSurface->BeginDraw(nullptr, IID_PPV_ARGS(&dxSurface), &point); } ComPtr renderTarget; if(SUCCEEDED(hr)) { D2D1_RENDER_TARGET_PROPERTIES targetProperties = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), 96, 96); hr = _d2dFactory->CreateDxgiSurfaceRenderTarget(dxSurface.Get(), &targetProperties, &renderTarget); } // Render target begin draw if(SUCCEEDED(hr)) { renderTarget->BeginDraw(); } ComPtr purpleBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Purple), &purpleBrush); } ComPtr grayBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Gray), &grayBrush); } if(SUCCEEDED(hr)) { for(int x = 0; x < SQUARES_PER_SIDE; x++) { for(int y = 0; y < SQUARES_PER_SIDE; y++) { float fractionX = width / 8.0f; float fractionY = height / 8.0f; D2D1_RECT_F fillRect = D2D1::RectF((x * fractionX) + point.x, (y * fractionY) + point.y, ((x + 1) * fractionX) + point.x, ((y + 1) * fractionY) + point.y); ComPtr brush = nullptr; if((x + y) % 2 == 1) { brush = grayBrush; } else { brush = purpleBrush; } renderTarget->FillRectangle(fillRect, brush.Get()); } } } ComPtr whiteBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), &whiteBrush); } WCHAR panText[] = L"Pan and Zoom Me!"; D2D1_RECT_F textRect = D2D1::RectF(OUTER_VIEWPORT_TEXT_MARGIN_X, OUTER_VIEWPORT_TEXT_MARGIN_Y, OUTER_VIEWPORT_TEXT_WIDTH + OUTER_VIEWPORT_TEXT_MARGIN_X, OUTER_VIEWPORT_TEXT_HEIGHT + OUTER_VIEWPORT_TEXT_MARGIN_Y); renderTarget->DrawTextW(panText, ARRAYSIZE(panText), _viewportTextFormat.Get(), textRect, whiteBrush.Get()); // End Draw if(SUCCEEDED(hr)) { hr = renderTarget->EndDraw(); } if(SUCCEEDED(hr)) { hr = outerSurface->EndDraw(); } // Associate the DirectComposition content we just rendered with DirectManipulation. This allows DirectManipulation // to control these surfaces automatically, and off the main UI thread. if(SUCCEEDED(hr)) { hr = _compositor->AddContent(_contentOuter.Get(), _dcompDevice.Get(), _dcompVisualOuterParent.Get(), _dcompVisualOuterChild.Get()); } if(SUCCEEDED(hr)) { WCHAR buttonText[] = L"I'm a button! Press me!"; hr = _DrawButtonState(_dcompVisualButtonUpDefault, TRUE, buttonText, wcsnlen_s(buttonText, ARRAYSIZE(buttonText))); } if(SUCCEEDED(hr)) { WCHAR buttonText[] = L"I was clicked."; hr = _DrawButtonState(_dcompVisualButtonUpClicked, TRUE, buttonText, wcsnlen_s(buttonText, ARRAYSIZE(buttonText))); } if(SUCCEEDED(hr)) { WCHAR buttonText[] = L"Input was captured."; hr = _DrawButtonState(_dcompVisualButtonUpCapturedByViewport, TRUE, buttonText, wcsnlen_s(buttonText, ARRAYSIZE(buttonText))); } if(SUCCEEDED(hr)) { WCHAR buttonText[] = L"Release or drag now."; hr = _DrawButtonState(_dcompVisualButtonDown, FALSE, buttonText, wcsnlen_s(buttonText, ARRAYSIZE(buttonText))); } return hr; } HRESULT CAppWindow::_DrawInnerPrimaryContent() { HRESULT hr = S_OK; ComPtr innerSurface; hr = _dcompDevice->CreateSurface( _contentInnerRect.right - _contentInnerRect.left, _contentInnerRect.bottom - _contentInnerRect.top, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_ALPHA_MODE_IGNORE, &innerSurface); if(SUCCEEDED(hr)) { hr = _dcompVisualInnerChild->SetContent(innerSurface.Get()); } if(SUCCEEDED(hr)) { hr = _dcompVisualInnerChild->SetBitmapInterpolationMode(DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); } // Surface begin draw POINT point; ComPtr dxSurface; hr = innerSurface->BeginDraw(nullptr, IID_PPV_ARGS(&dxSurface), &point); ComPtr renderTarget; if(SUCCEEDED(hr)) { D2D1_RENDER_TARGET_PROPERTIES targetProperties = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), 96, 96); hr = _d2dFactory->CreateDxgiSurfaceRenderTarget(dxSurface.Get(), &targetProperties, &renderTarget); } // Render target begin draw if(SUCCEEDED(hr)) { renderTarget->BeginDraw(); } ComPtr bitmap; if(SUCCEEDED(hr)) { hr = renderTarget->CreateBitmapFromWicBitmap(_bitmapConverter.Get(), nullptr, &bitmap); } ComPtr bitmapBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateBitmapBrush(bitmap.Get(), &bitmapBrush); } float contentX = static_cast(_contentInnerRect.right - _contentInnerRect.left); float contentY = static_cast(_contentInnerRect.bottom - _contentInnerRect.top); D2D1_RECT_F fillRect = D2D1::RectF(0.0f + point.x, 0.0f + point.y, contentX + point.x, contentY + point.y); renderTarget->DrawBitmap(bitmap.Get(), fillRect); //End Draw if(SUCCEEDED(hr)) { hr = renderTarget->EndDraw(); } if(SUCCEEDED(hr)) { hr = innerSurface->EndDraw(); } // Associate the DirectComposition content we just rendered with DirectManipulation. This allows DirectManipulation // to control these surfaces automatically, and off the main UI thread. if(SUCCEEDED(hr)) { hr = _compositor->AddContent(_contentInner.Get(), _dcompDevice.Get(), _dcompVisualInnerParent.Get(), _dcompVisualInnerChild.Get()); } return hr; } HRESULT CAppWindow::_DrawViewportStatusText() { HRESULT hr = S_OK; WCHAR statusTextInner[MAX_PATH] = {0}; WCHAR statusTextOuter[MAX_PATH] = {0}; WCHAR statusText[MAX_PATH] = {0}; hr = _handlerOuter.Get()->GetViewportStatus(statusTextOuter, ARRAYSIZE(statusTextOuter)); if(SUCCEEDED(hr)) { hr = _handlerInner.Get()->GetViewportStatus(statusTextInner, ARRAYSIZE(statusTextInner)); } if(SUCCEEDED(hr)) { hr = StringCchPrintf(statusText, ARRAYSIZE(statusText), L"Outer viewport status: %s\r\nInner viewport status: %s", statusTextOuter, statusTextInner); } // Surface begin draw POINT point = {0, 0}; ComPtr dxSurface; if(SUCCEEDED(hr)) { hr = _textSurface->BeginDraw(nullptr, IID_PPV_ARGS(&dxSurface), &point); } ComPtr renderTarget; if(SUCCEEDED(hr)) { D2D1_RENDER_TARGET_PROPERTIES targetProperties = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), 96, 96); hr = _d2dFactory->CreateDxgiSurfaceRenderTarget(dxSurface.Get(), &targetProperties, &renderTarget); } // Render target begin draw if(SUCCEEDED(hr)) { renderTarget->BeginDraw(); } ComPtr redBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &redBrush); } ComPtr blueBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::AliceBlue), &blueBrush); } const D2D1_RECT_F fillRect = {0.0f + point.x, 0.0f + point.y, static_cast(TEXTBLOCK_WIDTH) + point.x, static_cast(TEXTBLOCK_HEIGHT) + point.y}; if(SUCCEEDED(hr)) { renderTarget->FillRectangle(fillRect, blueBrush.Get()); } if(SUCCEEDED(hr)) { renderTarget->DrawTextW(statusText, wcsnlen_s(statusText, ARRAYSIZE(statusText)), _statusTextFormat.Get(), &fillRect, redBrush.Get()); } //End Draw if(SUCCEEDED(hr)) { hr = renderTarget->EndDraw(); } if(SUCCEEDED(hr)) { hr = _textSurface->EndDraw(); } if(SUCCEEDED(hr)) { hr = _dcompDevice->Commit(); } return hr; } HRESULT CAppWindow::_DrawButtonState(ComPtr buttonVisual, BOOL isUp, WCHAR* buttonText, size_t buttonTextLength) { HRESULT hr = S_OK; ComPtr buttonSurface; hr = _dcompDevice->CreateSurface( BUTTON_WIDTH, BUTTON_HEIGHT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_ALPHA_MODE_IGNORE, &buttonSurface); if(SUCCEEDED(hr)) { hr = buttonVisual->SetOffsetX(BUTTON_OFFSET_X); } if(SUCCEEDED(hr)) { hr = buttonVisual->SetOffsetY(BUTTON_OFFSET_Y); } if(SUCCEEDED(hr)) { hr = buttonVisual->SetContent(buttonSurface.Get()); } if(SUCCEEDED(hr)) { hr = buttonVisual->SetBitmapInterpolationMode(DCOMPOSITION_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); } // Surface begin draw POINT point = {0, 0}; ComPtr dxSurface; if(SUCCEEDED(hr)) { hr = buttonSurface->BeginDraw(nullptr, IID_PPV_ARGS(&dxSurface), &point); } ComPtr renderTarget; if(SUCCEEDED(hr)) { D2D1_RENDER_TARGET_PROPERTIES targetProperties = D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), 96, 96); hr = _d2dFactory->CreateDxgiSurfaceRenderTarget(dxSurface.Get(), &targetProperties, &renderTarget); } // Render target begin draw if(SUCCEEDED(hr)) { renderTarget->BeginDraw(); } ComPtr grayBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Gray), &grayBrush); } ComPtr lightGrayBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::LightGray), &lightGrayBrush); } if(SUCCEEDED(hr)) { D2D1_RECT_F fillBorderRect = D2D1::RectF(static_cast(point.x), static_cast(point.y), static_cast(BUTTON_WIDTH + point.x), static_cast(BUTTON_HEIGHT + point.y)); D2D1_RECT_F fillInnerRect = D2D1::RectF(point.x + BUTTON_BORDER_WIDTH, point.y + BUTTON_BORDER_WIDTH, BUTTON_WIDTH + point.x - BUTTON_BORDER_WIDTH, BUTTON_HEIGHT + point.y - BUTTON_BORDER_WIDTH); if(isUp) { renderTarget->FillRectangle(fillBorderRect, grayBrush.Get()); renderTarget->FillRectangle(fillInnerRect, lightGrayBrush.Get()); } else { renderTarget->FillRectangle(fillBorderRect, lightGrayBrush.Get()); renderTarget->FillRectangle(fillInnerRect, grayBrush.Get()); } } ComPtr blackBrush; if(SUCCEEDED(hr)) { hr = renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &blackBrush); } D2D1_RECT_F textRect = D2D1::RectF(point.x + BUTTON_BORDER_WIDTH + BUTTON_TEXT_PADDING, point.y + BUTTON_BORDER_WIDTH + BUTTON_TEXT_PADDING, BUTTON_WIDTH + point.x - BUTTON_BORDER_WIDTH - BUTTON_TEXT_PADDING, + BUTTON_HEIGHT + point.y - BUTTON_BORDER_WIDTH - BUTTON_TEXT_PADDING); renderTarget->DrawTextW(buttonText, buttonTextLength, _statusTextFormat.Get(), textRect, blackBrush.Get()); // End Draw if(SUCCEEDED(hr)) { hr = renderTarget->EndDraw(); } if(SUCCEEDED(hr)) { hr = buttonSurface->EndDraw(); } return hr; } HRESULT CAppWindow::_ChangeButtonState(ComPtr buttonState) { HRESULT hr = S_OK; hr = _dcompVisualOuterParent->RemoveVisual(_dcompVisualButtonCurrent.Get()); if(SUCCEEDED(hr)) { _dcompVisualButtonCurrent = buttonState; hr = _dcompVisualOuterParent->AddVisual(buttonState.Get(), FALSE, nullptr); } if(SUCCEEDED(hr)) { hr = _dcompDevice->Commit(); } return hr; } BOOL CAppWindow::_OuterViewportHasContact(UINT contactId) { std::list::iterator contact = std::find(_activeContacts.begin(), _activeContacts.end(), contactId); if(contact != _activeContacts.end()) { return TRUE; } else { return FALSE; } } HRESULT CAppWindow::_SizeDependentChanges() { HRESULT hr = S_OK; // Set the outer viewport rect to the size of the client area. ::GetClientRect(_hWnd, &_viewportOuterRect); if(SUCCEEDED(hr)) { hr = _viewportOuter->SetViewportRect(&_viewportOuterRect); } if(SUCCEEDED(hr)) { hr = _viewportInner->SetViewportRect(&_viewportInnerRect); } // // Set the content size for each viewport's primary content object. // if(SUCCEEDED(hr)) { hr = _contentOuter->SetContentRect(&_contentOuterRect); } if(SUCCEEDED(hr)) { hr = _contentInner->SetContentRect(&_contentInnerRect); } ComPtr clipRect; if(SUCCEEDED(hr)) { hr = _dcompDevice->CreateRectangleClip(&clipRect); } if(SUCCEEDED(hr)) { clipRect->SetLeft(static_cast(_viewportOuterRect.left)); clipRect->SetTop(static_cast(_viewportOuterRect.top)); clipRect->SetRight(static_cast(_viewportOuterRect.right)); clipRect->SetBottom(static_cast(_viewportOuterRect.bottom)); hr = _dcompVisualOuterParent->SetClip(clipRect.Get()); } if(SUCCEEDED(hr)) { hr = _dcompVisualInnerParent->SetOffsetX(static_cast(_viewportInnerRect.left)); } if(SUCCEEDED(hr)) { hr = _dcompVisualInnerParent->SetOffsetY(static_cast(_viewportInnerRect.top)); } if(SUCCEEDED(hr)) { ComPtr clip; _dcompDevice->CreateRectangleClip(&clip); hr = clip->SetLeft(0.0f); if(SUCCEEDED(hr)) { hr = clip->SetTop(0.0f); } if(SUCCEEDED(hr)) { hr = clip->SetRight(static_cast(_viewportInnerRect.right - _viewportInnerRect.left)); } if(SUCCEEDED(hr)) { hr = clip->SetBottom(static_cast(_viewportInnerRect.bottom - _viewportInnerRect.top)); } hr = _dcompVisualInnerParent->SetClip(clip.Get()); } // // Render the content // if(SUCCEEDED(hr)) { hr = _DrawOuterPrimaryContent(); } if(SUCCEEDED(hr)) { hr = _DrawInnerPrimaryContent(); } if(SUCCEEDED(hr)) { hr = _dcompDevice->Commit(); } return hr; } HRESULT CAppWindow::_Initialize() { HRESULT hr = S_OK; // // Create the Direct3D, DirectComposition and Direct2D objects, then initialize our drawing code // hr = _InitializeDevices(); if(SUCCEEDED(hr)) { hr = _InitializeManagerAndViewport(); } if(SUCCEEDED(hr)) { hr = _InitializeDrawing(); } if(SUCCEEDED(hr)) { hr = _viewportOuter->SetChaining(DIRECTMANIPULATION_MOTION_ALL); } if(SUCCEEDED(hr)) { hr = _viewportInner->SetChaining(DIRECTMANIPULATION_MOTION_ALL); } if(SUCCEEDED(hr)) { hr = _viewportOuter->Enable(); } if(SUCCEEDED(hr)) { hr = _viewportInner->Enable(); } return hr; } }