D2DState.cpp
1 #include "pch.h" 2 3 #include "constants.h" 4 #include "D2DState.h" 5 #include "DxgiAPI.h" 6 7 #include <common/Display/dpi_aware.h> 8 #include <ToolState.h> 9 10 namespace 11 { 12 void DetermineScreenQuadrant(const HWND window, long x, long y, bool& inLeftHalf, bool& inTopHalf) 13 { 14 RECT windowRect{}; 15 GetWindowRect(window, &windowRect); 16 const long w = windowRect.right - windowRect.left; 17 const long h = windowRect.bottom - windowRect.top; 18 inLeftHalf = x < w / 2; 19 inTopHalf = y < h / 2; 20 } 21 } 22 23 D2DState::D2DState(const DxgiAPI* dxgi, 24 HWND window, 25 std::vector<D2D1::ColorF> solidBrushesColors) 26 { 27 dxgiAPI = dxgi; 28 29 unsigned dpi = DPIAware::DEFAULT_DPI; 30 DPIAware::GetScreenDPIForWindow(window, dpi); 31 dpiScale = dpi / static_cast<float>(DPIAware::DEFAULT_DPI); 32 33 dxgiWindowState = dxgiAPI->CreateD2D1RenderTarget(window); 34 35 winrt::check_hresult(dxgiWindowState.rt->CreateCompatibleRenderTarget(bitmapRt.put())); 36 37 winrt::check_hresult(dxgiAPI->writeFactory->CreateTextFormat(L"Segoe UI Variable Text", 38 nullptr, 39 DWRITE_FONT_WEIGHT_NORMAL, 40 DWRITE_FONT_STYLE_NORMAL, 41 DWRITE_FONT_STRETCH_NORMAL, 42 consts::FONT_SIZE * dpiScale, 43 L"en-US", 44 textFormat.put())); 45 winrt::check_hresult(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); 46 winrt::check_hresult(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); 47 winrt::check_hresult(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP)); 48 49 solidBrushes.resize(solidBrushesColors.size()); 50 for (size_t i = 0; i < solidBrushes.size(); ++i) 51 { 52 winrt::check_hresult(dxgiWindowState.rt->CreateSolidColorBrush(solidBrushesColors[i], solidBrushes[i].put())); 53 } 54 55 const auto deviceContext = dxgiWindowState.rt.as<ID2D1DeviceContext>(); 56 winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, shadowEffect.put())); 57 winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, consts::SHADOW_RADIUS)); 58 winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, D2D1::ColorF(0.f, 0.f, 0.f, consts::SHADOW_OPACITY))); 59 60 winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, affineTransformEffect.put())); 61 affineTransformEffect->SetInputEffect(0, shadowEffect.get()); 62 63 textRenderer = winrt::make_self<PerGlyphOpacityTextRender>(dxgi->d2dFactory2, dxgiWindowState.rt, solidBrushes[Brush::foreground]); 64 } 65 66 void D2DState::DrawTextBox(const wchar_t* text, 67 const size_t textLen, 68 const size_t halfOpaqueSymbolPos[2], 69 const D2D_POINT_2F center, 70 const bool screenQuadrantAware, 71 const HWND window) const 72 { 73 wil::com_ptr<IDWriteTextLayout> textLayout; 74 winrt::check_hresult( 75 dxgiAPI->writeFactory->CreateTextLayout(text, 76 static_cast<uint32_t>(textLen), 77 textFormat.get(), 78 std::numeric_limits<float>::max(), 79 std::numeric_limits<float>::max(), 80 &textLayout)); 81 DWRITE_TEXT_METRICS textMetrics = {}; 82 winrt::check_hresult(textLayout->GetMetrics(&textMetrics)); 83 const float lineHeight = textMetrics.height / (textMetrics.lineCount ? textMetrics.lineCount : 1); 84 textMetrics.width += lineHeight; 85 textMetrics.height += lineHeight * .5f; 86 winrt::check_hresult(textLayout->SetMaxWidth(textMetrics.width)); 87 winrt::check_hresult(textLayout->SetMaxHeight(textMetrics.height)); 88 89 D2D1_RECT_F textRect{ .left = center.x - textMetrics.width / 2.f, 90 .top = center.y - textMetrics.height / 2.f, 91 .right = center.x + textMetrics.width / 2.f, 92 .bottom = center.y + textMetrics.height / 2.f }; 93 94 const float SHADOW_OFFSET = consts::SHADOW_OFFSET * dpiScale; 95 if (screenQuadrantAware) 96 { 97 bool cursorInLeftScreenHalf = false; 98 bool cursorInTopScreenHalf = false; 99 DetermineScreenQuadrant(window, 100 static_cast<long>(center.x), 101 static_cast<long>(center.y), 102 cursorInLeftScreenHalf, 103 cursorInTopScreenHalf); 104 float textQuadrantOffsetX = textMetrics.width / 2.f + SHADOW_OFFSET; 105 float textQuadrantOffsetY = textMetrics.height / 2.f + SHADOW_OFFSET; 106 if (!cursorInLeftScreenHalf) 107 textQuadrantOffsetX *= -1.f; 108 if (!cursorInTopScreenHalf) 109 textQuadrantOffsetY *= -1.f; 110 textRect.left += textQuadrantOffsetX; 111 textRect.right += textQuadrantOffsetX; 112 textRect.top += textQuadrantOffsetY; 113 textRect.bottom += textQuadrantOffsetY; 114 } 115 116 // Draw shadow 117 bitmapRt->BeginDraw(); 118 bitmapRt->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f)); 119 D2D1_ROUNDED_RECT textBoxRect; 120 textBoxRect.radiusX = textBoxRect.radiusY = consts::TEXT_BOX_CORNER_RADIUS * dpiScale; 121 textBoxRect.rect.bottom = textRect.bottom; 122 textBoxRect.rect.top = textRect.top; 123 textBoxRect.rect.left = textRect.left; 124 textBoxRect.rect.right = textRect.right; 125 bitmapRt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get()); 126 bitmapRt->EndDraw(); 127 128 wil::com_ptr<ID2D1Bitmap> rtBitmap; 129 bitmapRt->GetBitmap(&rtBitmap); 130 131 shadowEffect->SetInput(0, rtBitmap.get()); 132 const auto shadowMatrix = D2D1::Matrix3x2F::Translation(SHADOW_OFFSET, SHADOW_OFFSET); 133 winrt::check_hresult(affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX, 134 shadowMatrix)); 135 auto deviceContext = dxgiWindowState.rt.as<ID2D1DeviceContext>(); 136 deviceContext->DrawImage(affineTransformEffect.get(), D2D1_INTERPOLATION_MODE_LINEAR); 137 138 // Draw text box border rectangle 139 dxgiWindowState.rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get()); 140 const float TEXT_BOX_PADDING = 1.f * dpiScale; 141 textBoxRect.rect.bottom -= TEXT_BOX_PADDING; 142 textBoxRect.rect.top += TEXT_BOX_PADDING; 143 textBoxRect.rect.left += TEXT_BOX_PADDING; 144 textBoxRect.rect.right -= TEXT_BOX_PADDING; 145 146 // Draw text & its box 147 dxgiWindowState.rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get()); 148 149 if (halfOpaqueSymbolPos[0] > 0) 150 { 151 DWRITE_TEXT_RANGE textRange = { static_cast<uint32_t>(*halfOpaqueSymbolPos), 2 }; 152 auto opacityEffect = winrt::make_self<OpacityEffect>(); 153 opacityEffect->alpha = consts::CROSS_OPACITY; 154 winrt::check_hresult(textLayout->SetDrawingEffect(opacityEffect.get(), textRange)); 155 if (halfOpaqueSymbolPos[1] > halfOpaqueSymbolPos[0]) 156 { 157 textRange = { static_cast<uint32_t>(halfOpaqueSymbolPos[1]), 2 }; 158 winrt::check_hresult(textLayout->SetDrawingEffect(opacityEffect.get(), textRange)); 159 } 160 } 161 winrt::check_hresult(textLayout->Draw(nullptr, textRenderer.get(), textRect.left, textRect.top)); 162 } 163 164 void D2DState::ToggleAliasedLinesMode(const bool enabled) const 165 { 166 if (enabled) 167 { 168 // Draw lines in the middle of a pixel to avoid bleeding, since [0,0] pixel is 169 // a rectangle filled from (0,0) to (1,1) and the lines use thickness = 1. 170 dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Translation(.5f, .5f)); 171 dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); 172 } 173 else 174 { 175 dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Identity()); 176 dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); 177 } 178 }