/ src / modules / MeasureTool / MeasureToolCore / D2DState.cpp
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  }