// 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 "MainWindow.h" #include "ManagerEventHandler.h" #include "PriorityComparison.h" #include "UIAnimationSample.h" #include CMainWindow::CMainWindow() : m_hwnd(NULL), m_pD2DFactory(NULL), m_pRenderTarget(NULL), m_pBackgroundBrush(NULL), m_pAnimationManager(NULL), m_pAnimationTimer(NULL), m_pTransitionLibrary(NULL), m_uThumbCount(0), m_thumbs(NULL), m_pLayoutManager(NULL) { } CMainWindow::~CMainWindow() { // Layout Manager delete m_pLayoutManager; // Thumbnails delete [] m_thumbs; // Animation SafeRelease(&m_pAnimationManager); SafeRelease(&m_pAnimationTimer); SafeRelease(&m_pTransitionLibrary); // D2D SafeRelease(&m_pD2DFactory); SafeRelease(&m_pRenderTarget); SafeRelease(&m_pBackgroundBrush); } // Creates the CMainWindow window and initializes // device-independent resources HRESULT CMainWindow::Initialize( HINSTANCE hInstance ) { // Client area dimensions, in inches const FLOAT CLIENT_WIDTH = 7.0f; const FLOAT CLIENT_HEIGHT = 5.0f; HRESULT hr = CreateDeviceIndependentResources(); if (SUCCEEDED(hr)) { // Register the window class WNDCLASSEX wcex = {sizeof(wcex)}; wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = CMainWindow::WndProc; wcex.cbWndExtra = sizeof(LONG_PTR); wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.lpszClassName = L"WAMMainWindow"; RegisterClassEx(&wcex); // Because CreateWindow function takes its size in pixels, // obtain the DPI and use it to calculate the window size. // The D2D factory returns the current system DPI. This is // also the value it will use to create its own windows. FLOAT dpiX, dpiY; m_pD2DFactory->GetDesktopDpi( &dpiX, &dpiY ); // Create the CMainWindow window m_hwnd = CreateWindow( wcex.lpszClassName, L"Windows Animation - Priority Comparison Demo", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, static_cast(dpiX * CLIENT_WIDTH), static_cast(dpiY * CLIENT_HEIGHT), NULL, NULL, hInstance, this ); hr = m_hwnd ? S_OK : E_FAIL; if (SUCCEEDED(hr)) { // Initialize Animation hr = InitializeAnimation(); if (SUCCEEDED(hr)) { // Display the window ShowWindow(m_hwnd, SW_SHOWNORMAL); UpdateWindow(m_hwnd); } } } return hr; } // Invalidates the client area for redrawing HRESULT CMainWindow::Invalidate() { BOOL bResult = InvalidateRect( m_hwnd, NULL, FALSE ); HRESULT hr = bResult ? S_OK : E_FAIL; return hr; } // Creates and initializes the main animation components HRESULT CMainWindow::InitializeAnimation() { // Create Animation Manager HRESULT hr = CoCreateInstance( CLSID_UIAnimationManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pAnimationManager) ); if (SUCCEEDED(hr)) { // Create Animation Timer hr = CoCreateInstance( CLSID_UIAnimationTimer, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pAnimationTimer) ); if (SUCCEEDED(hr)) { // Create Animation Transition Library hr = CoCreateInstance( CLSID_UIAnimationTransitionLibrary, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pTransitionLibrary) ); if (SUCCEEDED(hr)) { // Create and set the ManagerEventHandler to start updating when animations are scheduled IUIAnimationManagerEventHandler *pManagerEventHandler; hr = CManagerEventHandler::CreateInstance( this, &pManagerEventHandler ); if (SUCCEEDED(hr)) { hr = m_pAnimationManager->SetManagerEventHandler( pManagerEventHandler ); pManagerEventHandler->Release(); if (SUCCEEDED(hr)) { // Create the PriorityComparisons for canceling and trimming storyboards IUIAnimationPriorityComparison *pPriorityComparisonCancel; hr = CCancelPriorityComparison::CreateInstance( &pPriorityComparisonCancel ); if (SUCCEEDED(hr)) { hr = m_pAnimationManager->SetCancelPriorityComparison( pPriorityComparisonCancel ); pPriorityComparisonCancel->Release(); if (SUCCEEDED(hr)) { IUIAnimationPriorityComparison *pPriorityComparisonTrim; hr = CTrimPriorityComparison::CreateInstance( &pPriorityComparisonTrim ); if (SUCCEEDED(hr)) { hr = m_pAnimationManager->SetTrimPriorityComparison( pPriorityComparisonTrim ); pPriorityComparisonTrim->Release(); } } } } } } } } return hr; } // Creates resources which are not bound to any device. // Their lifetime effectively extends for the duration of the app. // These resources include the Direct2D factory. HRESULT CMainWindow::CreateDeviceIndependentResources() { // Create a Direct2D factory HRESULT hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory ); return hr; } // Creates resources which are bound to a particular Direct3D device. // It's all centralized here, so the resources can be easily recreated // in the event of Direct3D device loss (e.g. display change, remoting, // removal of video card, etc). The resources will only be created // if necessary. HRESULT CMainWindow::CreateDeviceResources() { HRESULT hr = S_OK; if (m_pRenderTarget == NULL) { 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_pRenderTarget ); if (SUCCEEDED(hr)) { // Create a gradient brush for the background static const D2D1_GRADIENT_STOP stops[] = { { 0.0f, { 0.75f, 0.75f, 0.75f, 1.0f } }, { 1.0f, { 0.25f, 0.25f, 0.25f, 1.0f } }, }; ID2D1GradientStopCollection *pGradientStops; hr = m_pRenderTarget->CreateGradientStopCollection( stops, ARRAYSIZE(stops), &pGradientStops ); if (SUCCEEDED(hr)) { D2D1_SIZE_F sizeRenderTarget = m_pRenderTarget->GetSize(); hr = m_pRenderTarget->CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties( D2D1::Point2F( sizeRenderTarget.width * 0.5f, 0.0f ), D2D1::Point2F( sizeRenderTarget.width * 0.5f, sizeRenderTarget.height * 0.5f ) ), D2D1::BrushProperties( ), pGradientStops, &m_pBackgroundBrush ); if (SUCCEEDED(hr)) { // Create a brush for outlining the thumbnails hr = m_pRenderTarget->CreateSolidColorBrush( D2D1::ColorF( D2D1::ColorF::White ), &m_pOutlineBrush ); if (SUCCEEDED(hr)) { m_pOutlineBrush->SetOpacity( 0.5f ); } } } } } return hr; } // Discards device-specific resources that need to be recreated // when a Direct3D device is lost void CMainWindow::DiscardDeviceResources() { SafeRelease(&m_pRenderTarget); SafeRelease(&m_pBackgroundBrush); SafeRelease(&m_pOutlineBrush); } // Locates images in the Pictures library HRESULT CMainWindow::FindImages() { HRESULT hr = S_OK; if (m_thumbs == NULL) { // Walk the Pictures library IShellItem *pShellItemPicturesLibrary; hr = SHGetKnownFolderItem( FOLDERID_PicturesLibrary, KF_FLAG_CREATE, NULL, IID_PPV_ARGS(&pShellItemPicturesLibrary) ); if (SUCCEEDED(hr)) { INamespaceWalk *pNamespaceWalk; hr = CoCreateInstance( CLSID_NamespaceWalker, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&pNamespaceWalk) ); if (SUCCEEDED(hr)) { hr = pNamespaceWalk->Walk( pShellItemPicturesLibrary, NSWF_NONE_IMPLIES_ALL, 1, NULL ); if (SUCCEEDED(hr)) { // Retrieve the array of PIDLs gathered in the walk UINT itemCount; PIDLIST_ABSOLUTE *ppidls; hr = pNamespaceWalk->GetIDArrayResult( &itemCount, &ppidls ); if (SUCCEEDED(hr)) { // Create the uninitialized thumbnails m_thumbs = new CThumbnail[itemCount]; // Get the bitmap for each item and initialize the corresponding thumbnail object for (UINT i = 0; i < itemCount; i++) { IShellItem *pShellItem; hr = SHCreateItemFromIDList( ppidls[i], IID_PPV_ARGS(&pShellItem) ); if (SUCCEEDED(hr)) { ID2D1Bitmap *pBitmap; hr = DecodeImageFromThumbCache( pShellItem, &pBitmap ); if (SUCCEEDED(hr)) { hr = m_thumbs[m_uThumbCount].Initialize( pBitmap, m_pAnimationManager, m_pRenderTarget->GetSize().width * 0.5, m_pRenderTarget->GetSize().height * 0.5 ); if (SUCCEEDED(hr)) { m_uThumbCount++; } pBitmap->Release(); } pShellItem->Release(); } } // The calling function is responsible for freeing the PIDL array FreeIDListArray(ppidls, itemCount); if (SUCCEEDED(hr)) { // Arrange the images when they are first loaded m_pLayoutManager = new CLayoutManager(); hr = m_pLayoutManager->Initialize( m_pAnimationManager, m_pAnimationTimer, m_pTransitionLibrary, m_uThumbCount, m_thumbs ); if (SUCCEEDED(hr)) { hr = m_pLayoutManager->Resize( m_pRenderTarget->GetSize() ); } } } } pNamespaceWalk->Release(); } pShellItemPicturesLibrary->Release(); } } return hr; } // Retrieves a bitmap from the thumbnail cache and converts it to // a D2D bitmap HRESULT CMainWindow::DecodeImageFromThumbCache( IShellItem *pShellItem, ID2D1Bitmap **ppBitmap ) { const UINT THUMB_SIZE = 256; // Read the bitmap from the thumbnail cache IThumbnailCache *pThumbCache; HRESULT hr = CoCreateInstance( CLSID_LocalThumbnailCache, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pThumbCache) ); if (SUCCEEDED(hr)) { ISharedBitmap *pBitmap; hr = pThumbCache->GetThumbnail( pShellItem, THUMB_SIZE, WTS_SCALETOREQUESTEDSIZE, &pBitmap, NULL, NULL ); if (SUCCEEDED(hr)) { HBITMAP hBitmap; hr = pBitmap->GetSharedBitmap( &hBitmap ); if (SUCCEEDED(hr)) { // Create a WIC bitmap from the shared bitmap IWICImagingFactory *pFactory; hr = CoCreateInstance( CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFactory) ); if (SUCCEEDED(hr)) { IWICBitmap *pWICBitmap; hr = pFactory->CreateBitmapFromHBITMAP( hBitmap, NULL, WICBitmapIgnoreAlpha, &pWICBitmap ); if (SUCCEEDED(hr)) { // Create a D2D bitmap from the WIC bitmap hr = m_pRenderTarget->CreateBitmapFromWicBitmap( pWICBitmap, NULL, ppBitmap ); pWICBitmap->Release(); } pFactory->Release(); } } pBitmap->Release(); } pThumbCache->Release(); } return hr; } // The window message handler LRESULT CALLBACK CMainWindow::WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { const int MESSAGE_PROCESSED = 0; if (uMsg == WM_CREATE) { LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam; CMainWindow *pMainWindow = (CMainWindow *)pcs->lpCreateParams; SetWindowLongPtrW( hwnd, GWLP_USERDATA, PtrToUlong(pMainWindow) ); return MESSAGE_PROCESSED; } CMainWindow *pMainWindow = reinterpret_cast(static_cast( GetWindowLongPtrW( hwnd, GWLP_USERDATA ))); if (pMainWindow != NULL) { switch (uMsg) { case WM_SIZE: { UINT width = LOWORD(lParam); UINT height = HIWORD(lParam); pMainWindow->OnResize( width, height ); } return MESSAGE_PROCESSED; case WM_PAINT: case WM_DISPLAYCHANGE: { PAINTSTRUCT ps; BeginPaint( hwnd, &ps ); pMainWindow->OnPaint( ps.hdc, ps.rcPaint ); EndPaint( hwnd, &ps ); } return MESSAGE_PROCESSED; case WM_KEYDOWN: { UINT uVirtKey = wParam; pMainWindow->OnKeyDown( uVirtKey ); } return MESSAGE_PROCESSED; case WM_DESTROY: { pMainWindow->OnDestroy(); PostQuitMessage( 0 ); } return MESSAGE_PROCESSED; } } return DefWindowProc( hwnd, uMsg, wParam, lParam ); } // Called whenever the client area needs to be drawn HRESULT CMainWindow::OnPaint( HDC hdc, const RECT &rcPaint ) { // Update the animation manager with the current time UI_ANIMATION_SECONDS secondsNow; HRESULT hr = m_pAnimationTimer->GetTime( &secondsNow ); if (SUCCEEDED(hr)) { hr = m_pAnimationManager->Update( secondsNow ); if (SUCCEEDED(hr)) { // Read the values of the animation variables and draw the client area hr = DrawClientArea(); if (SUCCEEDED(hr)) { // Continue redrawing the client area as long as there are animations scheduled UI_ANIMATION_MANAGER_STATUS status; hr = m_pAnimationManager->GetStatus( &status ); if (SUCCEEDED(hr)) { if (status == UI_ANIMATION_MANAGER_BUSY) { InvalidateRect( m_hwnd, NULL, FALSE ); } } } } } return hr; } // Resizes the render target in response to a change in client area size HRESULT CMainWindow::OnResize( UINT width, UINT height ) { HRESULT hr = S_OK; if (m_pRenderTarget) { D2D1_SIZE_U size; size.width = width; size.height = height; hr = m_pRenderTarget->Resize( size ); if (SUCCEEDED(hr)) { D2D1_SIZE_F sizeRenderTarget = m_pRenderTarget->GetSize(); m_pBackgroundBrush->SetStartPoint( D2D1::Point2F( sizeRenderTarget.width * 0.5f, 0.0f ) ); m_pBackgroundBrush->SetEndPoint( D2D1::Point2F( sizeRenderTarget.width * 0.5f, sizeRenderTarget.height * 0.5f ) ); hr = m_pLayoutManager->Resize( m_pRenderTarget->GetSize() ); } } return hr; } // Moves the images in various ways depending on the key pressed HRESULT CMainWindow::OnKeyDown( UINT uVirtKey ) { HRESULT hr = S_OK; switch (uVirtKey) { case VK_UP: hr = m_pLayoutManager->Wave( UI_ANIMATION_SLOPE_DECREASING ); break; case VK_DOWN: hr = m_pLayoutManager->Wave( UI_ANIMATION_SLOPE_INCREASING ); break; case VK_RIGHT: hr = m_pLayoutManager->Next(); break; case VK_LEFT: hr = m_pLayoutManager->Previous(); break; case VK_SPACE: hr = m_pLayoutManager->Arrange(); break; default: break; } return hr; } // Prepares for window destruction void CMainWindow::OnDestroy() { if (m_pAnimationManager != NULL) { // Clear the manager event handler, to ensure that the one that points to this object is released (void)m_pAnimationManager->SetManagerEventHandler(NULL); } } // Draws the contents of the client area. // // Note that this function will automatically discard device-specific // resources if the Direct3D device disappears during function // invocation, and will recreate the resources the next time it's // invoked. HRESULT CMainWindow::DrawClientArea() { // Begin drawing the client area HRESULT hr = CreateDeviceResources(); if (SUCCEEDED(hr)) { hr = FindImages(); if (SUCCEEDED(hr)) { m_pRenderTarget->BeginDraw(); // Retrieve the size of the render target D2D1_SIZE_F sizeRenderTarget = m_pRenderTarget->GetSize(); // Paint the background m_pRenderTarget->FillRectangle( D2D1::RectF( 0.0f, 0.0f, sizeRenderTarget.width, sizeRenderTarget.height ), m_pBackgroundBrush ); // Paint all the thumbnails for (UINT i = 0; i < m_uThumbCount; i++) { hr = m_thumbs[i].Render( m_pRenderTarget, m_pOutlineBrush ); if (FAILED(hr)) { break; } } // Can only return one failure HRESULT HRESULT hrEndDraw = m_pRenderTarget->EndDraw(); if (SUCCEEDED(hr)) { hr = hrEndDraw; } } if (hr == D2DERR_RECREATE_TARGET) { DiscardDeviceResources(); } } return hr; }