FrameDrawer.cpp
1 #include "pch.h" 2 #include "FrameDrawer.h" 3 4 #include <dwmapi.h> 5 6 #include <ScalingUtils.h> 7 8 namespace 9 { 10 size_t D2DRectUHash(D2D1_SIZE_U rect) 11 { 12 using pod_repr_t = uint64_t; 13 static_assert(sizeof(D2D1_SIZE_U) == sizeof(pod_repr_t)); 14 std::hash<pod_repr_t> hasher{}; 15 return hasher(*reinterpret_cast<const pod_repr_t*>(&rect)); 16 } 17 } 18 19 std::unique_ptr<FrameDrawer> FrameDrawer::Create(HWND window) 20 { 21 auto self = std::make_unique<FrameDrawer>(window); 22 if (self->Init()) 23 { 24 return self; 25 } 26 27 return nullptr; 28 } 29 30 FrameDrawer::FrameDrawer(HWND window) : 31 m_window(window) 32 { 33 } 34 35 bool FrameDrawer::CreateRenderTargets(const RECT& clientRect) 36 { 37 HRESULT hr; 38 39 constexpr float DPI = 96.f; // Always using the default in DPI-aware mode 40 const auto renderTargetProperties = D2D1::RenderTargetProperties( 41 D2D1_RENDER_TARGET_TYPE_DEFAULT, 42 D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), 43 DPI, 44 DPI); 45 46 const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); 47 const auto rectHash = D2DRectUHash(renderTargetSize); 48 if (m_renderTarget && rectHash == m_renderTargetSizeHash) 49 { 50 // Already at the desired size -> do nothing 51 return true; 52 } 53 54 m_renderTarget = nullptr; 55 56 const auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(m_window, renderTargetSize, D2D1_PRESENT_OPTIONS_NONE); 57 58 hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, m_renderTarget.put()); 59 60 if (!SUCCEEDED(hr) || !m_renderTarget) 61 { 62 return false; 63 } 64 65 m_renderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); 66 m_renderTargetSizeHash = rectHash; 67 68 return true; 69 } 70 71 bool FrameDrawer::Init() 72 { 73 RECT clientRect; 74 if (!SUCCEEDED(DwmGetWindowAttribute(m_window, DWMWA_EXTENDED_FRAME_BOUNDS, &clientRect, sizeof(clientRect)))) 75 { 76 return false; 77 } 78 79 return CreateRenderTargets(clientRect); 80 } 81 82 void FrameDrawer::Hide() 83 { 84 ShowWindow(m_window, SW_HIDE); 85 } 86 87 void FrameDrawer::Show() 88 { 89 ShowWindow(m_window, SW_SHOWNA); 90 Render(); 91 } 92 93 void FrameDrawer::SetBorderRect(RECT windowRect, COLORREF rgb, float alpha, int thickness, float radius) 94 { 95 auto newSceneRect = DrawableRect{ 96 .borderColor = ConvertColor(rgb, alpha), 97 .thickness = thickness, 98 }; 99 100 if (radius != 0) 101 { 102 newSceneRect.roundedRect = ConvertRect(windowRect, thickness, radius); 103 } 104 else 105 { 106 newSceneRect.rect = ConvertRect(windowRect, thickness); 107 } 108 109 const bool colorUpdated = std::memcmp(&m_sceneRect.borderColor, &newSceneRect.borderColor, sizeof(newSceneRect.borderColor)); 110 const bool thicknessUpdated = m_sceneRect.thickness != newSceneRect.thickness; 111 const bool cornersUpdated = m_sceneRect.rect.has_value() != newSceneRect.rect.has_value() || m_sceneRect.roundedRect.has_value() != newSceneRect.roundedRect.has_value(); 112 const bool needsRedraw = colorUpdated || thicknessUpdated || cornersUpdated; 113 114 RECT clientRect; 115 if (!SUCCEEDED(DwmGetWindowAttribute(m_window, DWMWA_EXTENDED_FRAME_BOUNDS, &clientRect, sizeof(clientRect)))) 116 { 117 return; 118 } 119 120 m_sceneRect = std::move(newSceneRect); 121 122 const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); 123 124 const auto rectHash = D2DRectUHash(renderTargetSize); 125 126 const bool atTheDesiredSize = (rectHash == m_renderTargetSizeHash) && m_renderTarget; 127 if (!atTheDesiredSize) 128 { 129 const bool resizeOk = m_renderTarget && SUCCEEDED(m_renderTarget->Resize(renderTargetSize)); 130 if (!resizeOk) 131 { 132 if (!CreateRenderTargets(clientRect)) 133 { 134 Logger::error(L"Failed to create render targets"); 135 } 136 } 137 else 138 { 139 m_renderTargetSizeHash = rectHash; 140 } 141 } 142 143 if (colorUpdated) 144 { 145 m_borderBrush = nullptr; 146 if (m_renderTarget) 147 { 148 m_renderTarget->CreateSolidColorBrush(m_sceneRect.borderColor, m_borderBrush.put()); 149 } 150 } 151 152 if (!atTheDesiredSize || needsRedraw) 153 { 154 Render(); 155 } 156 } 157 158 ID2D1Factory* FrameDrawer::GetD2DFactory() 159 { 160 static auto pD2DFactory = [] { 161 ID2D1Factory* res = nullptr; 162 D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &res); 163 return res; 164 }(); 165 return pD2DFactory; 166 } 167 168 IDWriteFactory* FrameDrawer::GetWriteFactory() 169 { 170 static auto pDWriteFactory = [] { 171 IUnknown* res = nullptr; 172 DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &res); 173 return reinterpret_cast<IDWriteFactory*>(res); 174 }(); 175 return pDWriteFactory; 176 } 177 178 D2D1_COLOR_F FrameDrawer::ConvertColor(COLORREF color, float alpha) 179 { 180 return D2D1::ColorF(GetRValue(color) / 255.f, 181 GetGValue(color) / 255.f, 182 GetBValue(color) / 255.f, 183 alpha); 184 } 185 186 D2D1_ROUNDED_RECT FrameDrawer::ConvertRect(RECT rect, int thickness, float radius) 187 { 188 float halfThickness = thickness / 2.0f; 189 190 // 1 is needed to eliminate the gap between border and window 191 auto d2d1Rect = D2D1::RectF(static_cast<float>(rect.left) + halfThickness + 1, 192 static_cast<float>(rect.top) + halfThickness + 1, 193 static_cast<float>(rect.right) - halfThickness - 1, 194 static_cast<float>(rect.bottom) - halfThickness - 1); 195 return D2D1::RoundedRect(d2d1Rect, radius, radius); 196 } 197 198 D2D1_RECT_F FrameDrawer::ConvertRect(RECT rect, int thickness) 199 { 200 float halfThickness = thickness / 2.0f; 201 202 // 1 is needed to eliminate the gap between border and window 203 return D2D1::RectF(static_cast<float>(rect.left) + halfThickness + 1, 204 static_cast<float>(rect.top) + halfThickness + 1, 205 static_cast<float>(rect.right) - halfThickness - 1, 206 static_cast<float>(rect.bottom) - halfThickness - 1); 207 } 208 209 void FrameDrawer::Render() 210 { 211 if (!m_renderTarget || !m_borderBrush) 212 { 213 return; 214 } 215 216 m_renderTarget->BeginDraw(); 217 218 m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f)); 219 220 // The border stroke is centered on the line. 221 222 if (m_sceneRect.roundedRect) 223 { 224 m_renderTarget->DrawRoundedRectangle(m_sceneRect.roundedRect.value(), m_borderBrush.get(), static_cast<float>(m_sceneRect.thickness)); 225 } 226 else if (m_sceneRect.rect) 227 { 228 m_renderTarget->DrawRectangle(m_sceneRect.rect.value(), m_borderBrush.get(), static_cast<float>(m_sceneRect.thickness)); 229 } 230 231 m_renderTarget->EndDraw(); 232 }