// 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 "Common.h" #include "RenderTest.h" #include "IRenderer.h" #include "TextHelpers.h" #include "resource.h" // Uncomment the following definition to force the DPI to a hard-coded value for testing // purposes. Otherwise the system default DPI is used. //#define FORCE_DPI 120 // Global Variables: HINSTANCE g_instance; int g_dpiX; int g_dpiY; float const g_formatWidth = 6.0f * 96; IDWriteFactory* g_dwriteFactory = NULL; ID2D1Factory* g_d2dFactory = NULL; namespace { // Global variables used only in this module. DWRITE_MEASURING_MODE g_measuringMode = DWRITE_MEASURING_MODE_NATURAL; IDWriteTextFormat* g_textFormat = NULL; std::wstring g_text; const static wchar_t g_defaultText[] = L"ClearType is a software technology developed by Microsoft that improves the " L"readability of text on existing LCDs (Liquid Crystal Displays), such as laptop " L"screens, Pocket PC screens and flat panel monitors. With ClearType font technology, " L"the words on your computer screen look almost as sharp and clear as those printed " L"on a piece of paper."; // Current monitor. HMONITOR g_monitor; // Current font. inline float PointsToDips(float points) { return points * (96.0f / 72); } wchar_t const g_defaultFamilyName[] = L"Times New Roman"; float const g_minFontSize = PointsToDips(4); float g_fontSize = PointsToDips(12); LOGFONT g_logFont; // Current angle of rotation. int g_degrees = 0; // Current renderer. enum RendererID { RendererDWrite, RendererD2D }; RendererID g_rendererID = RendererDWrite; IRenderer* g_renderer = NULL; // Current magnifier state. MagnifierInfo g_magnifier = { true, // visible MagnifierInfo::Pixel, // type 3, // scale 0 }; bool g_dragging; POINT g_dragPos; // The following variables are used for the translate animation that occurs // on the Nudge Text Left and Nudge Text Right commands. DWORD g_animationStartCount; float g_animationDuration; float g_animationStartX; float g_animationEndX; float g_animationCurrentX; } // Forward declarations of functions included in this code module: LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); void InitializeMenuItems(HMENU popupMenu); bool OnCommand(HWND hwnd, WORD commandID); void UpdateAnimation(HWND hwnd); bool IsAnimating(); HRESULT CreateRenderer(HWND hwnd); void OnPaint(HWND hwnd); void OnSize(HWND hwnd); void OnMouseDown(HWND hwnd); void OnMouseMove(HWND hwnd); void OnMouseUp(HWND hwnd); HRESULT OnChooseFont(HWND hwnd); HRESULT IncreaseFontSize(HWND hwnd); HRESULT DecreaseFontSize(HWND hwnd); HRESULT SetFontSize(HWND hwnd, float newFontSize); void SetMagnifierType(HWND hwnd, MagnifierInfo::Type newType); void SetMagnifierScale(HWND hwnd, int scale); void SetMeasuringMode(HWND hwnd, DWRITE_MEASURING_MODE newMode); void SetTransform(HWND hwnd); void SetRenderer(HWND hwnd, RendererID id); HRESULT OnCopy(HWND hwnd); HRESULT OnPaste(HWND hwnd); void SetCaption(HWND hwnd); int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR commandLine, int nCmdShow ) { // The Microsoft Security Development Lifecycle recommends that all // applications include the following call to ensure that heap corruptions // do not go unnoticed and therefore do not introduce opportunities // for security exploits. HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); HRESULT hr = S_OK; g_instance = hInstance; // Get the DPI. #ifdef FORCE_DPI g_dpiX = FORCE_DPI; g_dpiY = FORCE_DPI; #else HDC hdc = GetDC(NULL); g_dpiX = GetDeviceCaps(hdc, LOGPIXELSX); g_dpiY = GetDeviceCaps(hdc, LOGPIXELSY); ReleaseDC(NULL, hdc); #endif // We're going to make the client area 7 x 5 inches initially. int const clientWidth = 7 * g_dpiX; int const clientHeight = 5 * g_dpiY; // Initialize the magnifier size and center the focus rectangle in the client area. g_magnifier.magnifierSize.cx = g_dpiX * 3; g_magnifier.magnifierSize.cy = g_dpiY * 3 / 2; g_magnifier.focusPos.x = (clientWidth - (g_magnifier.magnifierSize.cx / g_magnifier.scale)) / 2; g_magnifier.focusPos.y = (clientHeight - (g_magnifier.magnifierSize.cy / g_magnifier.scale)) / 2; try { g_text.assign(g_defaultText); } catch (...) { hr = ExceptionToHResult(); // Do not propagate exceptions up. } // Create the factory objects. if (SUCCEEDED(hr)) { hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&g_dwriteFactory) ); } if (SUCCEEDED(hr)) { hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), NULL, (IID_PPV_ARGS(&g_d2dFactory)) ); } if (SUCCEEDED(hr)) { // Initialize the LOGFONT and use it to construct the text format object. // We use LOGFONT in this application only because we rely on the Win32 // common font dialog. memset(&g_logFont, 0, sizeof(g_logFont)); g_logFont.lfWeight = FW_NORMAL; g_logFont.lfCharSet = DEFAULT_CHARSET; g_logFont.lfOutPrecision = OUT_OUTLINE_PRECIS; memcpy(g_logFont.lfFaceName, g_defaultFamilyName, sizeof(g_defaultFamilyName)); SafeRelease(&g_textFormat); hr = CreateTextFormatFromLOGFONT(g_logFont, g_fontSize, &g_textFormat); } ATOM classAtom = 0; if (SUCCEEDED(hr)) { // Register the window class. WNDCLASSEX wcex = { sizeof(wcex), 0 }; wcex.style = CS_HREDRAW|CS_VREDRAW; wcex.lpfnWndProc = WindowProc; wcex.hInstance = hInstance; wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.lpszMenuName = MAKEINTRESOURCE(IDC_DWRITERENDERTEST); wcex.lpszClassName = L"RenderTest"; classAtom = RegisterClassEx(&wcex); if (classAtom == 0) hr = HRESULT_FROM_WIN32(GetLastError()); } HACCEL accelerators = NULL; if (SUCCEEDED(hr)) { // Load the accelerator table. accelerators = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); if (accelerators == NULL) hr = HRESULT_FROM_WIN32(GetLastError()); } HWND hwnd = NULL; if (SUCCEEDED(hr)) { // Compute a window size that will give us our desired client size. RECT windowRect = { 0, 0, clientWidth, clientHeight }; AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, TRUE); // Create the window. hwnd = CreateWindow( MAKEINTATOM(classAtom), L"", // caption (we'll set it later) WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, NULL, NULL, hInstance, NULL ); if (hwnd == NULL) hr = HRESULT_FROM_WIN32(GetLastError()); } MSG msg = {}; if (SUCCEEDED(hr)) { SetCaption(hwnd); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hwnd, accelerators, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } } delete g_renderer; g_renderer = NULL; SafeRelease(&g_textFormat); SafeRelease(&g_d2dFactory); SafeRelease(&g_dwriteFactory); return 0; } LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: if (OnCommand(hwnd, LOWORD(wParam))) break; else return DefWindowProc(hwnd, message, wParam, lParam); case WM_INITMENUPOPUP: InitializeMenuItems(reinterpret_cast(wParam)); break; case WM_PAINT: OnPaint(hwnd); break; case WM_ERASEBKGND: return true; case WM_SIZE: OnSize(hwnd); break; case WM_WINDOWPOSCHANGED: { HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); if (monitor != g_monitor) { g_monitor = monitor; if (g_renderer != NULL) g_renderer->SetMonitor(g_monitor); InvalidateRect(hwnd, NULL, TRUE); } } return DefWindowProc(hwnd, message, wParam, lParam); case WM_LBUTTONDOWN: OnMouseDown(hwnd); break; case WM_MOUSEMOVE: OnMouseMove(hwnd); break; case WM_LBUTTONUP: OnMouseUp(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } void InitializeMenuItems(HMENU popupMenu) { int count = GetMenuItemCount(popupMenu); for (int i = 0; i < count; ++i) { MENUITEMINFO info; info.cbSize = sizeof(info); info.fMask = MIIM_ID | MIIM_STATE; if (!GetMenuItemInfo(popupMenu, i, TRUE, &info)) break; bool check = false; bool disable = false; switch (info.wID) { case ID_EDIT_COPY: disable = g_text.empty(); break; case ID_EDIT_PASTE: disable = !IsClipboardFormatAvailable(CF_TEXT); break; case ID_EDIT_DECREASETEXTSIZE: disable = (g_fontSize == g_minFontSize); break; case ID_VIEW_SHOWMAGNIFIER: check = g_magnifier.visible; break; case ID_MAGNIFIERTYPE_VECTOR: check = (g_magnifier.type == MagnifierInfo::Vector); disable = (g_measuringMode != DWRITE_MEASURING_MODE_NATURAL); break; case ID_MAGNIFIERTYPE_PIXELS: check = (g_magnifier.type == MagnifierInfo::Pixel); break; case ID_MAGNIFIERTYPE_SUBPIXELS: check = (g_magnifier.type == MagnifierInfo::Subpixel); disable = (g_rendererID == RendererD2D); break; case ID_MAGNIFIERSCALE_3X: check = (g_magnifier.scale == 3); break; case ID_MAGNIFIERSCALE_6X: check = (g_magnifier.scale == 6); break; case ID_OPTIONS_NATURALMODE: check = (g_measuringMode == DWRITE_MEASURING_MODE_NATURAL); break; case ID_OPTIONS_GDICLASSICMODE: check = (g_measuringMode == DWRITE_MEASURING_MODE_GDI_CLASSIC); break; case ID_OPTIONS_GDINATURALMODE: check = (g_measuringMode == DWRITE_MEASURING_MODE_GDI_NATURAL); break; case ID_OPTIONS_USEDIRECT2D: check = (g_rendererID == RendererD2D); break; case ID_OPTIONS_USEDIRECTWRITE: check = (g_rendererID == RendererDWrite); break; } UINT newState = (check ? MFS_CHECKED : 0) | (disable ? MFS_DISABLED : 0); if (newState != info.fState) { info.fMask = MIIM_STATE; info.fState = newState; SetMenuItemInfo(popupMenu, i, TRUE, &info); } } } bool OnCommand(HWND hwnd, WORD commandID) { HRESULT hr = S_OK; switch (commandID) { case IDM_EXIT: DestroyWindow(hwnd); break; case ID_EDIT_COPY: OnCopy(hwnd); break; case ID_EDIT_PASTE: OnPaste(hwnd); break; case ID_EDIT_FONT: hr = OnChooseFont(hwnd); break; case ID_EDIT_INCREASETEXTSIZE: hr = IncreaseFontSize(hwnd); break; case ID_EDIT_DECREASETEXTSIZE: hr = DecreaseFontSize(hwnd); break; case ID_VIEW_ROTATELEFT: if ((g_degrees -= 3) < 0) g_degrees += 360; SetTransform(hwnd); InvalidateRect(hwnd, NULL, TRUE); break; case ID_VIEW_ROTATERIGHT: if ((g_degrees += 3) >= 360) g_degrees -= 360; SetTransform(hwnd); InvalidateRect(hwnd, NULL, TRUE); break; case ID_VIEW_NUDGELEFT: g_animationStartCount = GetTickCount(); g_animationDuration = 0.1f; g_animationStartX = g_animationCurrentX; g_animationEndX = g_animationCurrentX - 10.0f; InvalidateRect(hwnd, NULL, TRUE); break; case ID_VIEW_NUDGERIGHT: g_animationStartCount = GetTickCount(); g_animationDuration = 0.1f; g_animationStartX = g_animationCurrentX; g_animationEndX = g_animationCurrentX + 10.0f; InvalidateRect(hwnd, NULL, TRUE); break; case ID_VIEW_SHOWMAGNIFIER: g_magnifier.visible = !g_magnifier.visible; if (g_renderer != NULL) { g_renderer->SetMagnifier(g_magnifier); InvalidateRect(hwnd, NULL, TRUE); } break; case ID_MAGNIFIERTYPE_VECTOR: SetMagnifierType(hwnd, MagnifierInfo::Vector); break; case ID_MAGNIFIERTYPE_PIXELS: SetMagnifierType(hwnd, MagnifierInfo::Pixel); break; case ID_MAGNIFIERTYPE_SUBPIXELS: SetMagnifierType(hwnd, MagnifierInfo::Subpixel); break; case ID_MAGNIFIERSCALE_3X: SetMagnifierScale(hwnd, 3); break; case ID_MAGNIFIERSCALE_6X: SetMagnifierScale(hwnd, 6); break; case ID_OPTIONS_NATURALMODE: SetMeasuringMode(hwnd, DWRITE_MEASURING_MODE_NATURAL); break; case ID_OPTIONS_GDICLASSICMODE: SetMeasuringMode(hwnd, DWRITE_MEASURING_MODE_GDI_CLASSIC); break; case ID_OPTIONS_GDINATURALMODE: SetMeasuringMode(hwnd, DWRITE_MEASURING_MODE_GDI_NATURAL); break; case ID_OPTIONS_USEDIRECT2D: SetRenderer(hwnd, RendererD2D); break; case ID_OPTIONS_USEDIRECTWRITE: SetRenderer(hwnd, RendererDWrite); break; default: return false; } if (FAILED(hr)) PostQuitMessage(hr); return true; } HRESULT IncreaseFontSize(HWND hwnd) { float newFontSize = (g_fontSize < PointsToDips(24)) ? g_fontSize + PointsToDips(0.5f) : g_fontSize * 1.25f; return SetFontSize(hwnd, newFontSize); } HRESULT DecreaseFontSize(HWND hwnd) { float newFontSize = (g_fontSize <= PointsToDips(24)) ? g_fontSize - PointsToDips(0.5f) : g_fontSize * (1/1.25f); return SetFontSize(hwnd, std::max(newFontSize, g_minFontSize)); } HRESULT SetFontSize(HWND hwnd, float newFontSize) { IDWriteTextFormat* newTextFormat = NULL; HRESULT hr = CreateTextFormatFromLOGFONT(g_logFont, newFontSize, &newTextFormat); if (FAILED(hr)) { return (hr == DWRITE_E_NOFONT) ? S_OK : hr; } g_fontSize = newFontSize; SafeSet(&g_textFormat, newTextFormat); if (g_renderer != NULL) { g_renderer->SetFormat(g_textFormat); } InvalidateRect(hwnd, NULL, TRUE); SafeRelease(&newTextFormat); return S_OK; } HRESULT OnChooseFont(HWND hwnd) { HRESULT hr = S_OK; LOGFONT logFont = g_logFont; CHOOSEFONT font = { 0 }; font.lStructSize = sizeof(font); font.hwndOwner = hwnd; font.lpLogFont = &logFont; font.iPointSize = static_cast(g_fontSize * (720 / 96.0f)); // Don't show vertical fonts because we don't do vertical layout and don't show // bitmap fonts because DirectWrite doesn't support them. font.Flags = CF_SCREENFONTS | CF_SCALABLEONLY | CF_NOVERTFONTS | CF_INITTOLOGFONTSTRUCT; // Show the common font dialog box. if (ChooseFont(&font)) { // The lfFaceName might not be initialized if the user didn't select a face name. if (logFont.lfFaceName[0] == L'\0') memcpy(logFont.lfFaceName, g_logFont.lfFaceName, sizeof(logFont.lfFaceName)); float newFontSize = font.iPointSize * (96.0f / 720); // Map the Win32 font properties to an IDWriteTextFormat. IDWriteTextFormat* newTextFormat = NULL; HRESULT hr = CreateTextFormatFromLOGFONT(logFont, newFontSize, &newTextFormat); if (SUCCEEDED(hr)) { // Save the new font properties. g_logFont = logFont; g_fontSize = newFontSize; SafeAttach(&g_textFormat, SafeDetach(&newTextFormat)); if (g_renderer != NULL) { g_renderer->SetFormat(g_textFormat); } InvalidateRect(hwnd, NULL, TRUE); } SafeRelease(&newTextFormat); } // Potentially expected error, but not fatal, // so just do nothing. if (hr == DWRITE_E_NOFONT) hr = S_OK; return hr; } void SetMagnifierType(HWND hwnd, MagnifierInfo::Type newType) { if (newType != g_magnifier.type) { if (newType == MagnifierInfo::Vector && g_measuringMode != DWRITE_MEASURING_MODE_NATURAL) return; g_magnifier.type = newType; if (g_magnifier.visible && g_renderer != NULL) { g_renderer->SetMagnifier(g_magnifier); InvalidateRect(hwnd, NULL, TRUE); } } } void SetMagnifierScale(HWND hwnd, int scale) { if (scale != g_magnifier.scale) { g_magnifier.scale = scale; if (g_magnifier.visible && g_renderer != NULL) { g_renderer->SetMagnifier(g_magnifier); InvalidateRect(hwnd, NULL, TRUE); } } } void SetMeasuringMode(HWND hwnd, DWRITE_MEASURING_MODE newMode) { if (newMode != g_measuringMode) { if (g_renderer != NULL) { g_renderer->SetMeasuringMode(newMode); } g_measuringMode = newMode; if (newMode != DWRITE_MEASURING_MODE_NATURAL && g_magnifier.type == MagnifierInfo::Vector) { SetMagnifierType(hwnd, MagnifierInfo::Pixel); } InvalidateRect(hwnd, NULL, TRUE); SetCaption(hwnd); } } void SetRenderer(HWND hwnd, RendererID id) { if (id != g_rendererID) { delete g_renderer; g_renderer = NULL; g_rendererID = id; if (id == RendererD2D && (g_magnifier.type == MagnifierInfo::Subpixel)) { SetMagnifierType(hwnd, MagnifierInfo::Pixel); } InvalidateRect(hwnd, NULL, TRUE); SetCaption(hwnd); } } void SetTransform(HWND hwnd) { if (g_renderer != NULL) { // Compute the center of the client area in DIPs. RECT clientRect; GetClientRect(hwnd, &clientRect); float centerX = PixelsToDipsX(clientRect.right) * 0.5f;; float centerY = PixelsToDipsY(clientRect.bottom) * 0.5f; // Create a transform that rotates around the center point. DWRITE_MATRIX transform = MakeRotateTransform(float(g_degrees), centerX, centerY); // Add the current horizontal displacement. transform.dx += g_animationCurrentX; // Set the transform. g_renderer->SetTransform(transform); } } HRESULT OnCopy(HWND hwnd) { HRESULT hr = E_FAIL; if (OpenClipboard(hwnd)) { if (EmptyClipboard()) { size_t const byteCount = (g_text.size() + 1) * sizeof(wchar_t); HGLOBAL handle = GlobalAlloc(GMEM_DDESHARE, byteCount); void* data = GlobalLock(handle); if (data != NULL) { memcpy(data, g_text.c_str(), byteCount); GlobalUnlock(handle); } if (SetClipboardData(CF_UNICODETEXT, handle) == NULL) { hr = S_OK; GlobalFree(handle); } } CloseClipboard(); } return hr; } HRESULT OnPaste(HWND hwnd) { HRESULT hr = E_FAIL; bool textChanged = false; if (OpenClipboard(hwnd)) { HGLOBAL handle = GetClipboardData(CF_UNICODETEXT); if (handle != NULL) { size_t const maxLength = GlobalSize(handle) / sizeof(wchar_t); wchar_t const* begin = static_cast(GlobalLock(handle)); if (begin != NULL) { wchar_t const* end = std::find(begin, begin + maxLength, L'\0'); try { g_text.assign(begin, end); textChanged = true; hr = S_OK; } catch (...) { hr = ExceptionToHResult(); // Do not propagate exceptions up. } GlobalUnlock(handle); } } CloseClipboard(); } if (textChanged && g_renderer != NULL) { g_renderer->SetText(g_text.c_str()); InvalidateRect(hwnd, NULL, TRUE); } return hr; } bool IsAnimating() { return g_animationStartX != g_animationEndX; } void UpdateAnimation(HWND hwnd) { if (!IsAnimating()) return; // Compute the elapsed time since the start of the animation in seconds. DWORD const tickCount = GetTickCount(); float elapsed = (tickCount - g_animationStartCount) * (1.0f / 1000); if (elapsed < g_animationDuration) { // We're still animating. Compuate the current x by interpolating between // the start x and end x. float r = elapsed / g_animationDuration; g_animationCurrentX = (g_animationStartX * (1 - r)) + (g_animationEndX * r); } else { // We're done with this animation. Let both the current and start x equal the end x. g_animationCurrentX = g_animationEndX; g_animationStartX = g_animationEndX; // If we're not at zero, we'll start a new animation ending at zero. The duration of // this animation depends on the distance. g_animationEndX = 0; g_animationStartCount = tickCount; g_animationDuration = fabs(g_animationStartX) / 10; } SetTransform(hwnd); } HRESULT CreateRenderer(HWND hwnd) { RECT clientRect; GetClientRect(hwnd, &clientRect); delete g_renderer; g_renderer = NULL; if (g_rendererID == RendererD2D) { g_renderer = CreateD2DRenderer( hwnd, clientRect.right, clientRect.bottom, g_textFormat, g_text.c_str() ); } else { g_renderer = CreateDWriteRenderer( hwnd, clientRect.right, clientRect.bottom, g_textFormat, g_text.c_str() ); } if (g_renderer == NULL) return E_FAIL; g_renderer->SetMeasuringMode(g_measuringMode); if (g_monitor != NULL) g_renderer->SetMonitor(g_monitor); g_renderer->SetMagnifier(g_magnifier); SetTransform(hwnd); return S_OK; } void OnPaint(HWND hwnd) { HRESULT hr = S_OK; // Update the current state. UpdateAnimation(hwnd); if (g_renderer == NULL) { hr = CreateRenderer(hwnd); } // Paint the current frame. if (SUCCEEDED(hr)) { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); SetLayout(ps.hdc, LAYOUT_BITMAPORIENTATIONPRESERVED); hr = g_renderer->Draw(hdc); EndPaint(hwnd, &ps); } if (FAILED(hr)) { PostQuitMessage(hr); return; } // Invalidate if we're animating. if (IsAnimating()) { InvalidateRect(hwnd, NULL, TRUE); } } void OnSize(HWND hwnd) { RECT clientRect; GetClientRect(hwnd, &clientRect); // Reposition the magnifier. g_magnifier.magnifierPos.x = clientRect.right - g_magnifier.magnifierSize.cx - (g_dpiX / 8); g_magnifier.magnifierPos.y = clientRect.bottom - g_magnifier.magnifierSize.cy - (g_dpiY / 8); // Update the renderer if it exists. if (g_renderer != NULL) { g_renderer->SetWindowSize( clientRect.right, clientRect.bottom ); g_renderer->SetMagnifier(g_magnifier); SetTransform(hwnd); } } void OnMouseDown(HWND hwnd) { if (!g_magnifier.visible) return; GetCursorPos(&g_dragPos); ScreenToClient(hwnd, &g_dragPos); RECT focusRect = { g_magnifier.focusPos.x, g_magnifier.focusPos.y, g_magnifier.focusPos.x + static_cast(g_magnifier.magnifierSize.cx / g_magnifier.scale), g_magnifier.focusPos.y + static_cast(g_magnifier.magnifierSize.cy / g_magnifier.scale) }; if (!PtInRect(&focusRect, g_dragPos)) return; SetCapture(hwnd); g_dragging = true; } void OnMouseMove(HWND hwnd) { if (g_dragging) { POINT pt; GetCursorPos(&pt); ScreenToClient(hwnd, &pt); g_magnifier.focusPos.x += pt.x - g_dragPos.x; g_magnifier.focusPos.y += pt.y - g_dragPos.y; g_dragPos = pt; if (g_renderer != NULL) g_renderer->SetMagnifier(g_magnifier); InvalidateRect(hwnd, NULL, TRUE); } } void OnMouseUp(HWND hwnd) { if (g_dragging) { ReleaseCapture(); g_dragging = false; } } void SafeAppend(wchar_t* buffer, size_t bufferSize, size_t* length, wchar_t* text) { // Append the new text if it fits into the buffer, and adjust the length. // Otherwise leave the buffer alone. size_t c = wcsnlen(text, UINT32_MAX); if (*length + c < bufferSize) { memcpy(buffer + *length, text, c * sizeof(buffer[0])); *length += c; buffer[*length] = '\0'; } } void SafeAppend(wchar_t* buffer, size_t bufferSize, size_t* length, UINT stringID) { *length += LoadString( g_instance, stringID, buffer + *length, static_cast(bufferSize - *length) ); } void SetCaption(HWND hwnd) { size_t const bufferSize = 256; wchar_t caption[bufferSize]; size_t length = 0; // Load the window title. SafeAppend(caption, bufferSize, &length, IDS_APP_TITLE); // Add a hyphen separator. SafeAppend(caption, bufferSize, &length, L" - "); // Append a string representing the measuring mode. switch (g_measuringMode) { case DWRITE_MEASURING_MODE_NATURAL: SafeAppend(caption, bufferSize, &length, IDS_NATURAL_MODE); break; case DWRITE_MEASURING_MODE_GDI_CLASSIC: SafeAppend(caption, bufferSize, &length, IDS_GDI_CLASSIC_MODE); break; case DWRITE_MEASURING_MODE_GDI_NATURAL: SafeAppend(caption, bufferSize, &length, IDS_GDI_NATURAL_MODE); break; } // Add a comma separator. SafeAppend(caption, bufferSize, &length, L", "); // Append a string representing the renderer implementation. switch (g_rendererID) { case RendererD2D: SafeAppend(caption, bufferSize, &length, IDS_USING_D2D); break; case RendererDWrite: SafeAppend(caption, bufferSize, &length, IDS_USING_DWRITE); break; } SetWindowText(hwnd, caption); }