1 // Scintilla source code edit control 2 /** @file PlatWin.cxx 3 ** Implementation of platform facilities on Windows. 4 **/ 5 // Copyright 1998-2003 by Neil Hodgson <[email protected]> 6 // The License.txt file describes the conditions under which this software may be distributed. 7 8 #include <cstddef> 9 #include <cstdlib> 10 #include <cstring> 11 #include <cstdio> 12 #include <cstdarg> 13 #include <ctime> 14 #include <cmath> 15 #include <climits> 16 17 #include <string_view> 18 #include <vector> 19 #include <map> 20 #include <algorithm> 21 #include <iterator> 22 #include <memory> 23 #include <mutex> 24 25 // Want to use std::min and std::max so don't want Windows.h version of min and max 26 #if !defined(NOMINMAX) 27 #define NOMINMAX 28 #endif 29 #undef _WIN32_WINNT 30 #define _WIN32_WINNT 0x0500 31 #undef WINVER 32 #define WINVER 0x0500 33 #include <windows.h> 34 #include <commctrl.h> 35 #include <richedit.h> 36 #include <windowsx.h> 37 38 #if !defined(DISABLE_D2D) 39 #define USE_D2D 1 40 #endif 41 42 #if defined(USE_D2D) 43 #include <d2d1.h> 44 #include <dwrite.h> 45 #endif 46 47 #include "Platform.h" 48 #include "XPM.h" 49 #include "UniConversion.h" 50 #include "DBCS.h" 51 #include "FontQuality.h" 52 53 #include "PlatWin.h" 54 55 #ifndef SPI_GETFONTSMOOTHINGCONTRAST 56 #define SPI_GETFONTSMOOTHINGCONTRAST 0x200C 57 #endif 58 59 #ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 60 #define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 61 #endif 62 63 // __uuidof is a Microsoft extension but makes COM code neater, so disable warning 64 #if defined(__clang__) 65 #pragma clang diagnostic ignored "-Wlanguage-extension-token" 66 #endif 67 68 namespace Scintilla { 69 70 UINT CodePageFromCharSet(DWORD characterSet, UINT documentCodePage) noexcept; 71 72 #if defined(USE_D2D) 73 IDWriteFactory *pIDWriteFactory = nullptr; 74 ID2D1Factory *pD2DFactory = nullptr; 75 IDWriteRenderingParams *defaultRenderingParams = nullptr; 76 IDWriteRenderingParams *customClearTypeRenderingParams = nullptr; 77 D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE; 78 79 static HMODULE hDLLD2D {}; 80 static HMODULE hDLLDWrite {}; 81 82 void LoadD2DOnce() noexcept { 83 DWORD loadLibraryFlags = 0; 84 HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll"); 85 if (kernel32) { 86 if (::GetProcAddress(kernel32, "SetDefaultDllDirectories")) { 87 // Availability of SetDefaultDllDirectories implies Windows 8+ or 88 // that KB2533623 has been installed so LoadLibraryEx can be called 89 // with LOAD_LIBRARY_SEARCH_SYSTEM32. 90 loadLibraryFlags = LOAD_LIBRARY_SEARCH_SYSTEM32; 91 } 92 } 93 94 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid, 95 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory); 96 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid, 97 IUnknown **factory); 98 99 hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, loadLibraryFlags); 100 D2D1CFSig fnD2DCF = DLLFunction<D2D1CFSig>(hDLLD2D, "D2D1CreateFactory"); 101 if (fnD2DCF) { 102 // A single threaded factory as Scintilla always draw on the GUI thread 103 fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED, 104 __uuidof(ID2D1Factory), 105 nullptr, 106 reinterpret_cast<IUnknown**>(&pD2DFactory)); 107 } 108 hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, loadLibraryFlags); 109 DWriteCFSig fnDWCF = DLLFunction<DWriteCFSig>(hDLLDWrite, "DWriteCreateFactory"); 110 if (fnDWCF) { 111 const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec 112 { 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } }; 113 114 const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED, 115 IID_IDWriteFactory2, 116 reinterpret_cast<IUnknown**>(&pIDWriteFactory)); 117 if (SUCCEEDED(hr)) { 118 // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT 119 d2dDrawTextOptions = static_cast<D2D1_DRAW_TEXT_OPTIONS>(0x00000004); 120 } else { 121 fnDWCF(DWRITE_FACTORY_TYPE_SHARED, 122 __uuidof(IDWriteFactory), 123 reinterpret_cast<IUnknown**>(&pIDWriteFactory)); 124 } 125 } 126 127 if (pIDWriteFactory) { 128 const HRESULT hr = pIDWriteFactory->CreateRenderingParams(&defaultRenderingParams); 129 if (SUCCEEDED(hr)) { 130 unsigned int clearTypeContrast; 131 if (::SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &clearTypeContrast, 0)) { 132 133 FLOAT gamma; 134 if (clearTypeContrast >= 1000 && clearTypeContrast <= 2200) 135 gamma = static_cast<FLOAT>(clearTypeContrast) / 1000.0f; 136 else 137 gamma = defaultRenderingParams->GetGamma(); 138 139 pIDWriteFactory->CreateCustomRenderingParams(gamma, defaultRenderingParams->GetEnhancedContrast(), defaultRenderingParams->GetClearTypeLevel(), 140 defaultRenderingParams->GetPixelGeometry(), defaultRenderingParams->GetRenderingMode(), &customClearTypeRenderingParams); 141 } 142 } 143 } 144 } 145 146 bool LoadD2D() { 147 static std::once_flag once; 148 std::call_once(once, LoadD2DOnce); 149 return pIDWriteFactory && pD2DFactory; 150 } 151 152 #endif 153 154 struct FormatAndMetrics { 155 int technology; 156 HFONT hfont; 157 #if defined(USE_D2D) 158 IDWriteTextFormat *pTextFormat; 159 #endif 160 int extraFontFlag; 161 int characterSet; 162 FLOAT yAscent; 163 FLOAT yDescent; 164 FLOAT yInternalLeading; 165 FormatAndMetrics(HFONT hfont_, int extraFontFlag_, int characterSet_) noexcept : 166 technology(SCWIN_TECH_GDI), hfont(hfont_), 167 #if defined(USE_D2D) 168 pTextFormat(nullptr), 169 #endif 170 extraFontFlag(extraFontFlag_), characterSet(characterSet_), yAscent(2), yDescent(1), yInternalLeading(0) { 171 } 172 #if defined(USE_D2D) 173 FormatAndMetrics(IDWriteTextFormat *pTextFormat_, 174 int extraFontFlag_, 175 int characterSet_, 176 FLOAT yAscent_, 177 FLOAT yDescent_, 178 FLOAT yInternalLeading_) noexcept : 179 technology(SCWIN_TECH_DIRECTWRITE), 180 hfont{}, 181 pTextFormat(pTextFormat_), 182 extraFontFlag(extraFontFlag_), 183 characterSet(characterSet_), 184 yAscent(yAscent_), 185 yDescent(yDescent_), 186 yInternalLeading(yInternalLeading_) { 187 } 188 #endif 189 FormatAndMetrics(const FormatAndMetrics &) = delete; 190 FormatAndMetrics(FormatAndMetrics &&) = delete; 191 FormatAndMetrics &operator=(const FormatAndMetrics &) = delete; 192 FormatAndMetrics &operator=(FormatAndMetrics &&) = delete; 193 194 ~FormatAndMetrics() { 195 if (hfont) 196 ::DeleteObject(hfont); 197 #if defined(USE_D2D) 198 ReleaseUnknown(pTextFormat); 199 #endif 200 extraFontFlag = 0; 201 characterSet = 0; 202 yAscent = 2; 203 yDescent = 1; 204 yInternalLeading = 0; 205 } 206 HFONT HFont() noexcept; 207 }; 208 209 HFONT FormatAndMetrics::HFont() noexcept { 210 LOGFONTW lf = {}; 211 #if defined(USE_D2D) 212 if (technology == SCWIN_TECH_GDI) { 213 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) { 214 return {}; 215 } 216 } else { 217 const HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE); 218 if (!SUCCEEDED(hr)) { 219 return {}; 220 } 221 lf.lfWeight = pTextFormat->GetFontWeight(); 222 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC; 223 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize()); 224 } 225 #else 226 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) { 227 return {}; 228 } 229 #endif 230 return ::CreateFontIndirectW(&lf); 231 } 232 233 #ifndef CLEARTYPE_QUALITY 234 #define CLEARTYPE_QUALITY 5 235 #endif 236 237 void *PointerFromWindow(HWND hWnd) noexcept { 238 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0)); 239 } 240 241 void SetWindowPointer(HWND hWnd, void *ptr) noexcept { 242 ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr)); 243 } 244 245 namespace { 246 247 // system DPI, same for all monitor. 248 UINT uSystemDPI = USER_DEFAULT_SCREEN_DPI; 249 250 using GetDpiForWindowSig = UINT(WINAPI *)(HWND hwnd); 251 GetDpiForWindowSig fnGetDpiForWindow = nullptr; 252 253 HMODULE hDLLShcore {}; 254 using GetDpiForMonitorSig = HRESULT (WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY); 255 GetDpiForMonitorSig fnGetDpiForMonitor = nullptr; 256 257 using GetSystemMetricsForDpiSig = int(WINAPI *)(int nIndex, UINT dpi); 258 GetSystemMetricsForDpiSig fnGetSystemMetricsForDpi = nullptr; 259 260 using AdjustWindowRectExForDpiSig = BOOL(WINAPI *)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); 261 AdjustWindowRectExForDpiSig fnAdjustWindowRectExForDpi = nullptr; 262 263 void LoadDpiForWindow() noexcept { 264 HMODULE user32 = ::GetModuleHandleW(L"user32.dll"); 265 fnGetDpiForWindow = DLLFunction<GetDpiForWindowSig>(user32, "GetDpiForWindow"); 266 fnGetSystemMetricsForDpi = DLLFunction<GetSystemMetricsForDpiSig>(user32, "GetSystemMetricsForDpi"); 267 fnAdjustWindowRectExForDpi = DLLFunction<AdjustWindowRectExForDpiSig>(user32, "AdjustWindowRectExForDpi"); 268 269 using GetDpiForSystemSig = UINT(WINAPI *)(void); 270 GetDpiForSystemSig fnGetDpiForSystem = DLLFunction<GetDpiForSystemSig>(user32, "GetDpiForSystem"); 271 if (fnGetDpiForSystem) { 272 uSystemDPI = fnGetDpiForSystem(); 273 } else { 274 HDC hdcMeasure = ::CreateCompatibleDC({}); 275 uSystemDPI = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY); 276 ::DeleteDC(hdcMeasure); 277 } 278 279 if (!fnGetDpiForWindow) { 280 hDLLShcore = ::LoadLibraryExW(L"shcore.dll", {}, LOAD_LIBRARY_SEARCH_SYSTEM32); 281 if (hDLLShcore) { 282 fnGetDpiForMonitor = DLLFunction<GetDpiForMonitorSig>(hDLLShcore, "GetDpiForMonitor"); 283 } 284 } 285 } 286 287 HINSTANCE hinstPlatformRes {}; 288 289 FormatAndMetrics *FamFromFontID(void *fid) noexcept { 290 return static_cast<FormatAndMetrics *>(fid); 291 } 292 293 constexpr BYTE Win32MapFontQuality(int extraFontFlag) noexcept { 294 switch (extraFontFlag & SC_EFF_QUALITY_MASK) { 295 296 case SC_EFF_QUALITY_NON_ANTIALIASED: 297 return NONANTIALIASED_QUALITY; 298 299 case SC_EFF_QUALITY_ANTIALIASED: 300 return ANTIALIASED_QUALITY; 301 302 case SC_EFF_QUALITY_LCD_OPTIMIZED: 303 return CLEARTYPE_QUALITY; 304 305 default: 306 return SC_EFF_QUALITY_DEFAULT; 307 } 308 } 309 310 #if defined(USE_D2D) 311 constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(int extraFontFlag) noexcept { 312 switch (extraFontFlag & SC_EFF_QUALITY_MASK) { 313 314 case SC_EFF_QUALITY_NON_ANTIALIASED: 315 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED; 316 317 case SC_EFF_QUALITY_ANTIALIASED: 318 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; 319 320 case SC_EFF_QUALITY_LCD_OPTIMIZED: 321 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; 322 323 default: 324 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; 325 } 326 } 327 #endif 328 329 void SetLogFont(LOGFONTW &lf, const char *faceName, int characterSet, float size, int weight, bool italic, int extraFontFlag) { 330 lf = LOGFONTW(); 331 // The negative is to allow for leading 332 lf.lfHeight = -(std::abs(std::lround(size))); 333 lf.lfWeight = weight; 334 lf.lfItalic = italic ? 1 : 0; 335 lf.lfCharSet = static_cast<BYTE>(characterSet); 336 lf.lfQuality = Win32MapFontQuality(extraFontFlag); 337 UTF16FromUTF8(faceName, lf.lfFaceName, LF_FACESIZE); 338 } 339 340 FontID CreateFontFromParameters(const FontParameters &fp) { 341 LOGFONTW lf; 342 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag); 343 FontID fid = nullptr; 344 if (fp.technology == SCWIN_TECH_GDI) { 345 HFONT hfont = ::CreateFontIndirectW(&lf); 346 fid = new FormatAndMetrics(hfont, fp.extraFontFlag, fp.characterSet); 347 } else { 348 #if defined(USE_D2D) 349 IDWriteTextFormat *pTextFormat = nullptr; 350 const std::wstring wsFace = WStringFromUTF8(fp.faceName); 351 const FLOAT fHeight = fp.size; 352 const DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; 353 HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr, 354 static_cast<DWRITE_FONT_WEIGHT>(fp.weight), 355 style, 356 DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat); 357 if (SUCCEEDED(hr)) { 358 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); 359 360 FLOAT yAscent = 1.0f; 361 FLOAT yDescent = 1.0f; 362 FLOAT yInternalLeading = 0.0f; 363 IDWriteTextLayout *pTextLayout = nullptr; 364 hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat, 365 100.0f, 100.0f, &pTextLayout); 366 if (SUCCEEDED(hr) && pTextLayout) { 367 constexpr int maxLines = 2; 368 DWRITE_LINE_METRICS lineMetrics[maxLines]{}; 369 UINT32 lineCount = 0; 370 hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount); 371 if (SUCCEEDED(hr)) { 372 yAscent = lineMetrics[0].baseline; 373 yDescent = lineMetrics[0].height - lineMetrics[0].baseline; 374 375 FLOAT emHeight; 376 hr = pTextLayout->GetFontSize(0, &emHeight); 377 if (SUCCEEDED(hr)) { 378 yInternalLeading = lineMetrics[0].height - emHeight; 379 } 380 } 381 ReleaseUnknown(pTextLayout); 382 pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline); 383 } 384 fid = new FormatAndMetrics(pTextFormat, fp.extraFontFlag, fp.characterSet, yAscent, yDescent, yInternalLeading); 385 } 386 #endif 387 } 388 return fid; 389 } 390 391 } 392 393 Font::Font() noexcept : fid{} { 394 } 395 396 Font::~Font() { 397 } 398 399 void Font::Create(const FontParameters &fp) { 400 Release(); 401 if (fp.faceName) 402 fid = CreateFontFromParameters(fp); 403 } 404 405 void Font::Release() { 406 if (fid) 407 delete FamFromFontID(fid); 408 fid = nullptr; 409 } 410 411 // Buffer to hold strings and string position arrays without always allocating on heap. 412 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer 413 // when less than safe size otherwise allocate on heap and free automatically. 414 template<typename T, int lengthStandard> 415 class VarBuffer { 416 T bufferStandard[lengthStandard]; 417 public: 418 T *buffer; 419 explicit VarBuffer(size_t length) : buffer(nullptr) { 420 if (length > lengthStandard) { 421 buffer = new T[length]; 422 } else { 423 buffer = bufferStandard; 424 } 425 } 426 // Deleted so VarBuffer objects can not be copied. 427 VarBuffer(const VarBuffer &) = delete; 428 VarBuffer(VarBuffer &&) = delete; 429 VarBuffer &operator=(const VarBuffer &) = delete; 430 VarBuffer &operator=(VarBuffer &&) = delete; 431 432 ~VarBuffer() { 433 if (buffer != bufferStandard) { 434 delete []buffer; 435 buffer = nullptr; 436 } 437 } 438 }; 439 440 constexpr int stackBufferLength = 1000; 441 class TextWide : public VarBuffer<wchar_t, stackBufferLength> { 442 public: 443 int tlen; // Using int instead of size_t as most Win32 APIs take int. 444 TextWide(std::string_view text, bool unicodeMode, int codePage=0) : 445 VarBuffer<wchar_t, stackBufferLength>(text.length()) { 446 if (unicodeMode) { 447 tlen = static_cast<int>(UTF16FromUTF8(text, buffer, text.length())); 448 } else { 449 // Support Asian string display in 9x English 450 tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast<int>(text.length()), 451 buffer, static_cast<int>(text.length())); 452 } 453 } 454 }; 455 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions; 456 457 UINT DpiForWindow(WindowID wid) noexcept { 458 if (fnGetDpiForWindow) { 459 return fnGetDpiForWindow(HwndFromWindowID(wid)); 460 } 461 if (fnGetDpiForMonitor) { 462 HMONITOR hMonitor = ::MonitorFromWindow(HwndFromWindowID(wid), MONITOR_DEFAULTTONEAREST); 463 UINT dpiX = 0; 464 UINT dpiY = 0; 465 if (fnGetDpiForMonitor(hMonitor, 0 /*MDT_EFFECTIVE_DPI*/, &dpiX, &dpiY) == S_OK) { 466 return dpiY; 467 } 468 } 469 return uSystemDPI; 470 } 471 472 int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept { 473 if (fnGetSystemMetricsForDpi) { 474 return fnGetSystemMetricsForDpi(nIndex, dpi); 475 } 476 477 int value = ::GetSystemMetrics(nIndex); 478 value = (dpi == uSystemDPI) ? value : ::MulDiv(value, dpi, uSystemDPI); 479 return value; 480 } 481 482 class SurfaceGDI : public Surface { 483 bool unicodeMode=false; 484 HDC hdc{}; 485 bool hdcOwned=false; 486 HPEN pen{}; 487 HPEN penOld{}; 488 HBRUSH brush{}; 489 HBRUSH brushOld{}; 490 HFONT fontOld{}; 491 HBITMAP bitmap{}; 492 HBITMAP bitmapOld{}; 493 494 int logPixelsY = USER_DEFAULT_SCREEN_DPI; 495 496 int maxWidthMeasure = INT_MAX; 497 // There appears to be a 16 bit string length limit in GDI on NT. 498 int maxLenText = 65535; 499 500 int codePage = 0; 501 502 void BrushColour(ColourDesired back) noexcept; 503 void SetFont(const Font &font_) noexcept; 504 void Clear() noexcept; 505 506 public: 507 SurfaceGDI() noexcept; 508 // Deleted so SurfaceGDI objects can not be copied. 509 SurfaceGDI(const SurfaceGDI &) = delete; 510 SurfaceGDI(SurfaceGDI &&) = delete; 511 SurfaceGDI &operator=(const SurfaceGDI &) = delete; 512 SurfaceGDI &operator=(SurfaceGDI &&) = delete; 513 514 ~SurfaceGDI() noexcept override; 515 516 void Init(WindowID wid) override; 517 void Init(SurfaceID sid, WindowID wid) override; 518 void InitPixMap(int width, int height, Surface *surface_, WindowID wid) override; 519 520 void Release() override; 521 bool Initialised() override; 522 void PenColour(ColourDesired fore) override; 523 int LogPixelsY() override; 524 int DeviceHeightFont(int points) override; 525 void MoveTo(int x_, int y_) override; 526 void LineTo(int x_, int y_) override; 527 void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; 528 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; 529 void FillRectangle(PRectangle rc, ColourDesired back) override; 530 void FillRectangle(PRectangle rc, Surface &surfacePattern) override; 531 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; 532 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, 533 ColourDesired outline, int alphaOutline, int flags) override; 534 void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override; 535 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; 536 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; 537 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; 538 539 std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; 540 541 void DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); 542 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; 543 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; 544 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; 545 void MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) override; 546 XYPOSITION WidthText(Font &font_, std::string_view text) override; 547 XYPOSITION Ascent(Font &font_) override; 548 XYPOSITION Descent(Font &font_) override; 549 XYPOSITION InternalLeading(Font &font_) override; 550 XYPOSITION Height(Font &font_) override; 551 XYPOSITION AverageCharWidth(Font &font_) override; 552 553 void SetClip(PRectangle rc) override; 554 void FlushCachedState() override; 555 556 void SetUnicodeMode(bool unicodeMode_) override; 557 void SetDBCSMode(int codePage_) override; 558 void SetBidiR2L(bool bidiR2L_) override; 559 }; 560 561 SurfaceGDI::SurfaceGDI() noexcept { 562 } 563 564 SurfaceGDI::~SurfaceGDI() noexcept { 565 Clear(); 566 } 567 568 void SurfaceGDI::Clear() noexcept { 569 if (penOld) { 570 ::SelectObject(hdc, penOld); 571 ::DeleteObject(pen); 572 penOld = {}; 573 } 574 pen = {}; 575 if (brushOld) { 576 ::SelectObject(hdc, brushOld); 577 ::DeleteObject(brush); 578 brushOld = {}; 579 } 580 brush = {}; 581 if (fontOld) { 582 // Fonts are not deleted as they are owned by a Font object 583 ::SelectObject(hdc, fontOld); 584 fontOld = {}; 585 } 586 if (bitmapOld) { 587 ::SelectObject(hdc, bitmapOld); 588 ::DeleteObject(bitmap); 589 bitmapOld = {}; 590 } 591 bitmap = {}; 592 if (hdcOwned) { 593 ::DeleteDC(hdc); 594 hdc = {}; 595 hdcOwned = false; 596 } 597 } 598 599 void SurfaceGDI::Release() { 600 Clear(); 601 } 602 603 bool SurfaceGDI::Initialised() { 604 return hdc != 0; 605 } 606 607 void SurfaceGDI::Init(WindowID wid) { 608 Release(); 609 hdc = ::CreateCompatibleDC({}); 610 hdcOwned = true; 611 ::SetTextAlign(hdc, TA_BASELINE); 612 logPixelsY = DpiForWindow(wid); 613 } 614 615 void SurfaceGDI::Init(SurfaceID sid, WindowID wid) { 616 Release(); 617 hdc = static_cast<HDC>(sid); 618 ::SetTextAlign(hdc, TA_BASELINE); 619 // Windows on screen are scaled but printers are not. 620 const bool printing = ::GetDeviceCaps(hdc, TECHNOLOGY) != DT_RASDISPLAY; 621 logPixelsY = printing ? ::GetDeviceCaps(hdc, LOGPIXELSY) : DpiForWindow(wid); 622 } 623 624 void SurfaceGDI::InitPixMap(int width, int height, Surface *surface_, WindowID wid) { 625 Release(); 626 SurfaceGDI *psurfOther = dynamic_cast<SurfaceGDI *>(surface_); 627 // Should only ever be called with a SurfaceGDI, not a SurfaceD2D 628 PLATFORM_ASSERT(psurfOther); 629 hdc = ::CreateCompatibleDC(psurfOther->hdc); 630 hdcOwned = true; 631 bitmap = ::CreateCompatibleBitmap(psurfOther->hdc, width, height); 632 bitmapOld = SelectBitmap(hdc, bitmap); 633 ::SetTextAlign(hdc, TA_BASELINE); 634 SetUnicodeMode(psurfOther->unicodeMode); 635 SetDBCSMode(psurfOther->codePage); 636 logPixelsY = DpiForWindow(wid); 637 } 638 639 void SurfaceGDI::PenColour(ColourDesired fore) { 640 if (pen) { 641 ::SelectObject(hdc, penOld); 642 ::DeleteObject(pen); 643 pen = {}; 644 penOld = {}; 645 } 646 pen = ::CreatePen(0,1,fore.AsInteger()); 647 penOld = SelectPen(hdc, pen); 648 } 649 650 void SurfaceGDI::BrushColour(ColourDesired back) noexcept { 651 if (brush) { 652 ::SelectObject(hdc, brushOld); 653 ::DeleteObject(brush); 654 brush = {}; 655 brushOld = {}; 656 } 657 brush = ::CreateSolidBrush(back.AsInteger()); 658 brushOld = SelectBrush(hdc, brush); 659 } 660 661 void SurfaceGDI::SetFont(const Font &font_) noexcept { 662 const FormatAndMetrics *pfm = FamFromFontID(font_.GetID()); 663 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_GDI); 664 if (fontOld) { 665 SelectFont(hdc, pfm->hfont); 666 } else { 667 fontOld = SelectFont(hdc, pfm->hfont); 668 } 669 } 670 671 int SurfaceGDI::LogPixelsY() { 672 return logPixelsY; 673 } 674 675 int SurfaceGDI::DeviceHeightFont(int points) { 676 return ::MulDiv(points, LogPixelsY(), 72); 677 } 678 679 void SurfaceGDI::MoveTo(int x_, int y_) { 680 ::MoveToEx(hdc, x_, y_, nullptr); 681 } 682 683 void SurfaceGDI::LineTo(int x_, int y_) { 684 ::LineTo(hdc, x_, y_); 685 } 686 687 void SurfaceGDI::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) { 688 PenColour(fore); 689 BrushColour(back); 690 std::vector<POINT> outline; 691 std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint); 692 ::Polygon(hdc, outline.data(), static_cast<int>(npts)); 693 } 694 695 void SurfaceGDI::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { 696 PenColour(back); 697 BrushColour(fore); 698 const RECT rcw = RectFromPRectangle(rc); 699 ::Rectangle(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); 700 } 701 702 void SurfaceGDI::FillRectangle(PRectangle rc, ColourDesired back) { 703 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs. 704 // There is no need to allocate a brush either. 705 const RECT rcw = RectFromPRectangle(rc); 706 ::SetBkColor(hdc, back.AsInteger()); 707 ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, nullptr); 708 } 709 710 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) { 711 HBRUSH br; 712 if (SurfaceGDI *psgdi = dynamic_cast<SurfaceGDI *>(&surfacePattern); psgdi && psgdi->bitmap) { 713 br = ::CreatePatternBrush(psgdi->bitmap); 714 } else { // Something is wrong so display in red 715 br = ::CreateSolidBrush(RGB(0xff, 0, 0)); 716 } 717 const RECT rcw = RectFromPRectangle(rc); 718 ::FillRect(hdc, &rcw, br); 719 ::DeleteObject(br); 720 } 721 722 void SurfaceGDI::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) { 723 PenColour(fore); 724 BrushColour(back); 725 const RECT rcw = RectFromPRectangle(rc); 726 ::RoundRect(hdc, 727 rcw.left + 1, rcw.top, 728 rcw.right - 1, rcw.bottom, 729 8, 8); 730 } 731 732 namespace { 733 734 constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept { 735 return (a << 24) | (r << 16) | (g << 8) | b; 736 } 737 738 constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept { 739 return static_cast<byte>(component * alpha / 255); 740 } 741 742 constexpr DWORD dwordMultiplied(ColourDesired colour, unsigned int alpha) noexcept { 743 return dwordFromBGRA( 744 AlphaScaled(colour.GetBlue(), alpha), 745 AlphaScaled(colour.GetGreen(), alpha), 746 AlphaScaled(colour.GetRed(), alpha), 747 static_cast<byte>(alpha)); 748 } 749 750 class DIBSection { 751 HDC hMemDC {}; 752 HBITMAP hbmMem {}; 753 HBITMAP hbmOld {}; 754 SIZE size {}; 755 DWORD *pixels = nullptr; 756 public: 757 DIBSection(HDC hdc, SIZE size_) noexcept; 758 // Deleted so DIBSection objects can not be copied. 759 DIBSection(const DIBSection&) = delete; 760 DIBSection(DIBSection&&) = delete; 761 DIBSection &operator=(const DIBSection&) = delete; 762 DIBSection &operator=(DIBSection&&) = delete; 763 ~DIBSection() noexcept; 764 operator bool() const noexcept { 765 return hMemDC && hbmMem && pixels; 766 } 767 DWORD *Pixels() const noexcept { 768 return pixels; 769 } 770 unsigned char *Bytes() const noexcept { 771 return reinterpret_cast<unsigned char *>(pixels); 772 } 773 HDC DC() const noexcept { 774 return hMemDC; 775 } 776 void SetPixel(LONG x, LONG y, DWORD value) noexcept { 777 PLATFORM_ASSERT(x >= 0); 778 PLATFORM_ASSERT(y >= 0); 779 PLATFORM_ASSERT(x < size.cx); 780 PLATFORM_ASSERT(y < size.cy); 781 pixels[y * size.cx + x] = value; 782 } 783 void SetSymmetric(LONG x, LONG y, DWORD value) noexcept; 784 }; 785 786 DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept { 787 hMemDC = ::CreateCompatibleDC(hdc); 788 if (!hMemDC) { 789 return; 790 } 791 792 size = size_; 793 794 // -size.y makes bitmap start from top 795 const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), size.cx, -size.cy, 1, 32, BI_RGB, 0, 0, 0, 0, 0}, 796 {{0, 0, 0, 0}} }; 797 void *image = nullptr; 798 hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, &image, {}, 0); 799 if (!hbmMem || !image) { 800 return; 801 } 802 pixels = static_cast<DWORD *>(image); 803 hbmOld = SelectBitmap(hMemDC, hbmMem); 804 } 805 806 DIBSection::~DIBSection() noexcept { 807 if (hbmOld) { 808 SelectBitmap(hMemDC, hbmOld); 809 hbmOld = {}; 810 } 811 if (hbmMem) { 812 ::DeleteObject(hbmMem); 813 hbmMem = {}; 814 } 815 if (hMemDC) { 816 ::DeleteDC(hMemDC); 817 hMemDC = {}; 818 } 819 } 820 821 void DIBSection::SetSymmetric(LONG x, LONG y, DWORD value) noexcept { 822 // Plot a point symmetrically to all 4 quadrants 823 const LONG xSymmetric = size.cx - 1 - x; 824 const LONG ySymmetric = size.cy - 1 - y; 825 SetPixel(x, y, value); 826 SetPixel(xSymmetric, y, value); 827 SetPixel(x, ySymmetric, value); 828 SetPixel(xSymmetric, ySymmetric, value); 829 } 830 831 constexpr unsigned int Proportional(unsigned char a, unsigned char b, float t) noexcept { 832 return static_cast<unsigned int>(a + t * (b - a)); 833 } 834 835 ColourAlpha Proportional(ColourAlpha a, ColourAlpha b, float t) noexcept { 836 return ColourAlpha( 837 Proportional(a.GetRed(), b.GetRed(), t), 838 Proportional(a.GetGreen(), b.GetGreen(), t), 839 Proportional(a.GetBlue(), b.GetBlue(), t), 840 Proportional(a.GetAlpha(), b.GetAlpha(), t)); 841 } 842 843 ColourAlpha GradientValue(const std::vector<ColourStop> &stops, float proportion) noexcept { 844 for (size_t stop = 0; stop < stops.size() - 1; stop++) { 845 // Loop through each pair of stops 846 const float positionStart = stops[stop].position; 847 const float positionEnd = stops[stop + 1].position; 848 if ((proportion >= positionStart) && (proportion <= positionEnd)) { 849 const float proportionInPair = (proportion - positionStart) / 850 (positionEnd - positionStart); 851 return Proportional(stops[stop].colour, stops[stop + 1].colour, proportionInPair); 852 } 853 } 854 // Loop should always find a value 855 return ColourAlpha(); 856 } 857 858 constexpr SIZE SizeOfRect(RECT rc) noexcept { 859 return { rc.right - rc.left, rc.bottom - rc.top }; 860 } 861 862 constexpr BLENDFUNCTION mergeAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; 863 864 } 865 866 void SurfaceGDI::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, 867 ColourDesired outline, int alphaOutline, int /* flags*/ ) { 868 const RECT rcw = RectFromPRectangle(rc); 869 const SIZE size = SizeOfRect(rcw); 870 871 if (size.cx > 0) { 872 873 DIBSection section(hdc, size); 874 875 if (section) { 876 877 // Ensure not distorted too much by corners when small 878 const LONG corner = std::min<LONG>(cornerSize, (std::min(size.cx, size.cy) / 2) - 2); 879 880 constexpr DWORD valEmpty = dwordFromBGRA(0,0,0,0); 881 const DWORD valFill = dwordMultiplied(fill, alphaFill); 882 const DWORD valOutline = dwordMultiplied(outline, alphaOutline); 883 884 // Draw a framed rectangle 885 for (int y=0; y<size.cy; y++) { 886 for (int x=0; x<size.cx; x++) { 887 if ((x==0) || (x==size.cx-1) || (y == 0) || (y == size.cy -1)) { 888 section.SetPixel(x, y, valOutline); 889 } else { 890 section.SetPixel(x, y, valFill); 891 } 892 } 893 } 894 895 // Make the corners transparent 896 for (LONG c=0; c<corner; c++) { 897 for (LONG x=0; x<c+1; x++) { 898 section.SetSymmetric(x, c - x, valEmpty); 899 } 900 } 901 902 // Draw the corner frame pieces 903 for (LONG x=1; x<corner; x++) { 904 section.SetSymmetric(x, corner - x, valOutline); 905 } 906 907 AlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha); 908 } 909 } else { 910 BrushColour(outline); 911 FrameRect(hdc, &rcw, brush); 912 } 913 } 914 915 void SurfaceGDI::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) { 916 917 const RECT rcw = RectFromPRectangle(rc); 918 const SIZE size = SizeOfRect(rcw); 919 920 DIBSection section(hdc, size); 921 922 if (section) { 923 924 if (options == GradientOptions::topToBottom) { 925 for (LONG y = 0; y < size.cy; y++) { 926 // Find y/height proportional colour 927 const float proportion = y / (rc.Height() - 1.0f); 928 const ColourAlpha mixed = GradientValue(stops, proportion); 929 const DWORD valFill = dwordMultiplied(mixed, mixed.GetAlpha()); 930 for (LONG x = 0; x < size.cx; x++) { 931 section.SetPixel(x, y, valFill); 932 } 933 } 934 } else { 935 for (LONG x = 0; x < size.cx; x++) { 936 // Find x/width proportional colour 937 const float proportion = x / (rc.Width() - 1.0f); 938 const ColourAlpha mixed = GradientValue(stops, proportion); 939 const DWORD valFill = dwordMultiplied(mixed, mixed.GetAlpha()); 940 for (LONG y = 0; y < size.cy; y++) { 941 section.SetPixel(x, y, valFill); 942 } 943 } 944 } 945 946 AlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha); 947 } 948 } 949 950 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) { 951 if (rc.Width() > 0) { 952 if (rc.Width() > width) 953 rc.left += std::floor((rc.Width() - width) / 2); 954 rc.right = rc.left + width; 955 if (rc.Height() > height) 956 rc.top += std::floor((rc.Height() - height) / 2); 957 rc.bottom = rc.top + height; 958 959 const SIZE size { width, height }; 960 DIBSection section(hdc, size); 961 if (section) { 962 RGBAImage::BGRAFromRGBA(section.Bytes(), pixelsImage, width * height); 963 AlphaBlend(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top), 964 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), section.DC(), 965 0, 0, width, height, mergeAlpha); 966 } 967 } 968 } 969 970 void SurfaceGDI::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) { 971 PenColour(fore); 972 BrushColour(back); 973 const RECT rcw = RectFromPRectangle(rc); 974 ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom); 975 } 976 977 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) { 978 ::BitBlt(hdc, 979 static_cast<int>(rc.left), static_cast<int>(rc.top), 980 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), 981 dynamic_cast<SurfaceGDI &>(surfaceSource).hdc, 982 static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY); 983 } 984 985 std::unique_ptr<IScreenLineLayout> SurfaceGDI::Layout(const IScreenLine *) { 986 return {}; 987 } 988 989 typedef VarBuffer<int, stackBufferLength> TextPositionsI; 990 991 void SurfaceGDI::DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { 992 SetFont(font_); 993 const RECT rcw = RectFromPRectangle(rc); 994 const int x = static_cast<int>(rc.left); 995 const int yBaseInt = static_cast<int>(ybase); 996 997 if (unicodeMode) { 998 const TextWide tbuf(text, unicodeMode, codePage); 999 ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr); 1000 } else { 1001 ::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, text.data(), static_cast<UINT>(text.length()), nullptr); 1002 } 1003 } 1004 1005 void SurfaceGDI::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, 1006 ColourDesired fore, ColourDesired back) { 1007 ::SetTextColor(hdc, fore.AsInteger()); 1008 ::SetBkColor(hdc, back.AsInteger()); 1009 DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE); 1010 } 1011 1012 void SurfaceGDI::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, 1013 ColourDesired fore, ColourDesired back) { 1014 ::SetTextColor(hdc, fore.AsInteger()); 1015 ::SetBkColor(hdc, back.AsInteger()); 1016 DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED); 1017 } 1018 1019 void SurfaceGDI::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, 1020 ColourDesired fore) { 1021 // Avoid drawing spaces in transparent mode 1022 for (const char ch : text) { 1023 if (ch != ' ') { 1024 ::SetTextColor(hdc, fore.AsInteger()); 1025 ::SetBkMode(hdc, TRANSPARENT); 1026 DrawTextCommon(rc, font_, ybase, text, 0); 1027 ::SetBkMode(hdc, OPAQUE); 1028 return; 1029 } 1030 } 1031 } 1032 1033 XYPOSITION SurfaceGDI::WidthText(Font &font_, std::string_view text) { 1034 SetFont(font_); 1035 SIZE sz={0,0}; 1036 if (!unicodeMode) { 1037 ::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz); 1038 } else { 1039 const TextWide tbuf(text, unicodeMode, codePage); 1040 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz); 1041 } 1042 return static_cast<XYPOSITION>(sz.cx); 1043 } 1044 1045 void SurfaceGDI::MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) { 1046 // Zero positions to avoid random behaviour on failure. 1047 std::fill(positions, positions + text.length(), 0.0f); 1048 SetFont(font_); 1049 SIZE sz={0,0}; 1050 int fit = 0; 1051 int i = 0; 1052 const int len = static_cast<int>(text.length()); 1053 if (unicodeMode) { 1054 const TextWide tbuf(text, unicodeMode, codePage); 1055 TextPositionsI poses(tbuf.tlen); 1056 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) { 1057 // Failure 1058 return; 1059 } 1060 // Map the widths given for UTF-16 characters back onto the UTF-8 input string 1061 for (int ui = 0; ui < fit; ui++) { 1062 const unsigned char uch = text[i]; 1063 const unsigned int byteCount = UTF8BytesOfLead[uch]; 1064 if (byteCount == 4) { // Non-BMP 1065 ui++; 1066 } 1067 for (unsigned int bytePos=0; (bytePos<byteCount) && (i<len); bytePos++) { 1068 positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]); 1069 } 1070 } 1071 } else { 1072 TextPositionsI poses(len); 1073 if (!::GetTextExtentExPointA(hdc, text.data(), len, maxWidthMeasure, &fit, poses.buffer, &sz)) { 1074 // Eeek - a NULL DC or other foolishness could cause this. 1075 return; 1076 } 1077 while (i<fit) { 1078 positions[i] = static_cast<XYPOSITION>(poses.buffer[i]); 1079 i++; 1080 } 1081 } 1082 // If any positions not filled in then use the last position for them 1083 const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f; 1084 std::fill(positions+i, positions + text.length(), lastPos); 1085 } 1086 1087 XYPOSITION SurfaceGDI::Ascent(Font &font_) { 1088 SetFont(font_); 1089 TEXTMETRIC tm; 1090 ::GetTextMetrics(hdc, &tm); 1091 return static_cast<XYPOSITION>(tm.tmAscent); 1092 } 1093 1094 XYPOSITION SurfaceGDI::Descent(Font &font_) { 1095 SetFont(font_); 1096 TEXTMETRIC tm; 1097 ::GetTextMetrics(hdc, &tm); 1098 return static_cast<XYPOSITION>(tm.tmDescent); 1099 } 1100 1101 XYPOSITION SurfaceGDI::InternalLeading(Font &font_) { 1102 SetFont(font_); 1103 TEXTMETRIC tm; 1104 ::GetTextMetrics(hdc, &tm); 1105 return static_cast<XYPOSITION>(tm.tmInternalLeading); 1106 } 1107 1108 XYPOSITION SurfaceGDI::Height(Font &font_) { 1109 SetFont(font_); 1110 TEXTMETRIC tm; 1111 ::GetTextMetrics(hdc, &tm); 1112 return static_cast<XYPOSITION>(tm.tmHeight); 1113 } 1114 1115 XYPOSITION SurfaceGDI::AverageCharWidth(Font &font_) { 1116 SetFont(font_); 1117 TEXTMETRIC tm; 1118 ::GetTextMetrics(hdc, &tm); 1119 return static_cast<XYPOSITION>(tm.tmAveCharWidth); 1120 } 1121 1122 void SurfaceGDI::SetClip(PRectangle rc) { 1123 ::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top), 1124 static_cast<int>(rc.right), static_cast<int>(rc.bottom)); 1125 } 1126 1127 void SurfaceGDI::FlushCachedState() { 1128 pen = {}; 1129 brush = {}; 1130 } 1131 1132 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) { 1133 unicodeMode=unicodeMode_; 1134 } 1135 1136 void SurfaceGDI::SetDBCSMode(int codePage_) { 1137 // No action on window as automatically handled by system. 1138 codePage = codePage_; 1139 } 1140 1141 void SurfaceGDI::SetBidiR2L(bool) { 1142 } 1143 1144 #if defined(USE_D2D) 1145 1146 namespace { 1147 1148 constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept { 1149 return { rc.left, rc.top, rc.right, rc.bottom }; 1150 } 1151 1152 } 1153 1154 class BlobInline; 1155 1156 class SurfaceD2D : public Surface { 1157 bool unicodeMode; 1158 int x, y; 1159 1160 int codePage; 1161 int codePageText; 1162 1163 ID2D1RenderTarget *pRenderTarget; 1164 ID2D1BitmapRenderTarget *pBitmapRenderTarget; 1165 bool ownRenderTarget; 1166 int clipsActive; 1167 1168 IDWriteTextFormat *pTextFormat; 1169 FLOAT yAscent; 1170 FLOAT yDescent; 1171 FLOAT yInternalLeading; 1172 1173 ID2D1SolidColorBrush *pBrush; 1174 1175 int logPixelsY; 1176 1177 void Clear() noexcept; 1178 void SetFont(const Font &font_) noexcept; 1179 HRESULT GetBitmap(ID2D1Bitmap **ppBitmap); 1180 1181 public: 1182 SurfaceD2D() noexcept; 1183 // Deleted so SurfaceD2D objects can not be copied. 1184 SurfaceD2D(const SurfaceD2D &) = delete; 1185 SurfaceD2D(SurfaceD2D &&) = delete; 1186 SurfaceD2D &operator=(const SurfaceD2D &) = delete; 1187 SurfaceD2D &operator=(SurfaceD2D &&) = delete; 1188 ~SurfaceD2D() override; 1189 1190 void SetScale(WindowID wid) noexcept; 1191 void Init(WindowID wid) override; 1192 void Init(SurfaceID sid, WindowID wid) override; 1193 void InitPixMap(int width, int height, Surface *surface_, WindowID wid) override; 1194 1195 void Release() override; 1196 bool Initialised() override; 1197 1198 HRESULT FlushDrawing(); 1199 1200 void PenColour(ColourDesired fore) override; 1201 void D2DPenColour(ColourDesired fore, int alpha=255); 1202 int LogPixelsY() override; 1203 int DeviceHeightFont(int points) override; 1204 void MoveTo(int x_, int y_) override; 1205 void LineTo(int x_, int y_) override; 1206 void Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) override; 1207 void RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) override; 1208 void FillRectangle(PRectangle rc, ColourDesired back) override; 1209 void FillRectangle(PRectangle rc, Surface &surfacePattern) override; 1210 void RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) override; 1211 void AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, 1212 ColourDesired outline, int alphaOutline, int flags) override; 1213 void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override; 1214 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override; 1215 void Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) override; 1216 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override; 1217 1218 std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override; 1219 1220 void DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions); 1221 void DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; 1222 void DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore, ColourDesired back) override; 1223 void DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, ColourDesired fore) override; 1224 void MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) override; 1225 XYPOSITION WidthText(Font &font_, std::string_view text) override; 1226 XYPOSITION Ascent(Font &font_) override; 1227 XYPOSITION Descent(Font &font_) override; 1228 XYPOSITION InternalLeading(Font &font_) override; 1229 XYPOSITION Height(Font &font_) override; 1230 XYPOSITION AverageCharWidth(Font &font_) override; 1231 1232 void SetClip(PRectangle rc) override; 1233 void FlushCachedState() override; 1234 1235 void SetUnicodeMode(bool unicodeMode_) override; 1236 void SetDBCSMode(int codePage_) override; 1237 void SetBidiR2L(bool bidiR2L_) override; 1238 }; 1239 1240 SurfaceD2D::SurfaceD2D() noexcept : 1241 unicodeMode(false), 1242 x(0), y(0) { 1243 1244 codePage = 0; 1245 codePageText = 0; 1246 1247 pRenderTarget = nullptr; 1248 pBitmapRenderTarget = nullptr; 1249 ownRenderTarget = false; 1250 clipsActive = 0; 1251 1252 // From selected font 1253 pTextFormat = nullptr; 1254 yAscent = 2; 1255 yDescent = 1; 1256 yInternalLeading = 0; 1257 1258 pBrush = nullptr; 1259 1260 logPixelsY = USER_DEFAULT_SCREEN_DPI; 1261 } 1262 1263 SurfaceD2D::~SurfaceD2D() { 1264 Clear(); 1265 } 1266 1267 void SurfaceD2D::Clear() noexcept { 1268 ReleaseUnknown(pBrush); 1269 if (pRenderTarget) { 1270 while (clipsActive) { 1271 pRenderTarget->PopAxisAlignedClip(); 1272 clipsActive--; 1273 } 1274 if (ownRenderTarget) { 1275 pRenderTarget->EndDraw(); 1276 ReleaseUnknown(pRenderTarget); 1277 ownRenderTarget = false; 1278 } 1279 pRenderTarget = nullptr; 1280 } 1281 pBitmapRenderTarget = nullptr; 1282 } 1283 1284 void SurfaceD2D::Release() { 1285 Clear(); 1286 } 1287 1288 void SurfaceD2D::SetScale(WindowID wid) noexcept { 1289 logPixelsY = DpiForWindow(wid); 1290 } 1291 1292 bool SurfaceD2D::Initialised() { 1293 return pRenderTarget != nullptr; 1294 } 1295 1296 HRESULT SurfaceD2D::FlushDrawing() { 1297 return pRenderTarget->Flush(); 1298 } 1299 1300 void SurfaceD2D::Init(WindowID wid) { 1301 Release(); 1302 SetScale(wid); 1303 } 1304 1305 void SurfaceD2D::Init(SurfaceID sid, WindowID wid) { 1306 Release(); 1307 SetScale(wid); 1308 pRenderTarget = static_cast<ID2D1RenderTarget *>(sid); 1309 } 1310 1311 void SurfaceD2D::InitPixMap(int width, int height, Surface *surface_, WindowID wid) { 1312 Release(); 1313 SetScale(wid); 1314 SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(surface_); 1315 // Should only ever be called with a SurfaceD2D, not a SurfaceGDI 1316 PLATFORM_ASSERT(psurfOther); 1317 const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height)); 1318 D2D1_PIXEL_FORMAT desiredFormat; 1319 #ifdef __MINGW32__ 1320 desiredFormat.format = DXGI_FORMAT_UNKNOWN; 1321 #else 1322 desiredFormat = psurfOther->pRenderTarget->GetPixelFormat(); 1323 #endif 1324 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; 1325 const HRESULT hr = psurfOther->pRenderTarget->CreateCompatibleRenderTarget( 1326 &desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pBitmapRenderTarget); 1327 if (SUCCEEDED(hr)) { 1328 pRenderTarget = pBitmapRenderTarget; 1329 pRenderTarget->BeginDraw(); 1330 ownRenderTarget = true; 1331 } 1332 SetUnicodeMode(psurfOther->unicodeMode); 1333 SetDBCSMode(psurfOther->codePage); 1334 } 1335 1336 HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) { 1337 PLATFORM_ASSERT(pBitmapRenderTarget); 1338 return pBitmapRenderTarget->GetBitmap(ppBitmap); 1339 } 1340 1341 void SurfaceD2D::PenColour(ColourDesired fore) { 1342 D2DPenColour(fore); 1343 } 1344 1345 void SurfaceD2D::D2DPenColour(ColourDesired fore, int alpha) { 1346 if (pRenderTarget) { 1347 D2D_COLOR_F col; 1348 col.r = fore.GetRedComponent(); 1349 col.g = fore.GetGreenComponent(); 1350 col.b = fore.GetBlueComponent(); 1351 col.a = alpha / 255.0f; 1352 if (pBrush) { 1353 pBrush->SetColor(col); 1354 } else { 1355 const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush); 1356 if (!SUCCEEDED(hr)) { 1357 ReleaseUnknown(pBrush); 1358 } 1359 } 1360 } 1361 } 1362 1363 void SurfaceD2D::SetFont(const Font &font_) noexcept { 1364 const FormatAndMetrics *pfm = FamFromFontID(font_.GetID()); 1365 PLATFORM_ASSERT(pfm->technology == SCWIN_TECH_DIRECTWRITE); 1366 pTextFormat = pfm->pTextFormat; 1367 yAscent = pfm->yAscent; 1368 yDescent = pfm->yDescent; 1369 yInternalLeading = pfm->yInternalLeading; 1370 codePageText = codePage; 1371 if (!unicodeMode && pfm->characterSet) { 1372 codePageText = Scintilla::CodePageFromCharSet(pfm->characterSet, codePage); 1373 } 1374 if (pRenderTarget) { 1375 D2D1_TEXT_ANTIALIAS_MODE aaMode; 1376 aaMode = DWriteMapFontQuality(pfm->extraFontFlag); 1377 1378 if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && customClearTypeRenderingParams) 1379 pRenderTarget->SetTextRenderingParams(customClearTypeRenderingParams); 1380 else if (defaultRenderingParams) 1381 pRenderTarget->SetTextRenderingParams(defaultRenderingParams); 1382 1383 pRenderTarget->SetTextAntialiasMode(aaMode); 1384 } 1385 } 1386 1387 int SurfaceD2D::LogPixelsY() { 1388 return logPixelsY; 1389 } 1390 1391 int SurfaceD2D::DeviceHeightFont(int points) { 1392 return ::MulDiv(points, LogPixelsY(), 72); 1393 } 1394 1395 void SurfaceD2D::MoveTo(int x_, int y_) { 1396 x = x_; 1397 y = y_; 1398 } 1399 1400 static constexpr int Delta(int difference) noexcept { 1401 if (difference < 0) 1402 return -1; 1403 else if (difference > 0) 1404 return 1; 1405 else 1406 return 0; 1407 } 1408 1409 void SurfaceD2D::LineTo(int x_, int y_) { 1410 if (pRenderTarget) { 1411 const int xDiff = x_ - x; 1412 const int xDelta = Delta(xDiff); 1413 const int yDiff = y_ - y; 1414 const int yDelta = Delta(yDiff); 1415 if ((xDiff == 0) || (yDiff == 0)) { 1416 // Horizontal or vertical lines can be more precisely drawn as a filled rectangle 1417 const int xEnd = x_ - xDelta; 1418 const int left = std::min(x, xEnd); 1419 const int width = std::abs(x - xEnd) + 1; 1420 const int yEnd = y_ - yDelta; 1421 const int top = std::min(y, yEnd); 1422 const int height = std::abs(y - yEnd) + 1; 1423 const D2D1_RECT_F rectangle1 = D2D1::RectF(static_cast<float>(left), static_cast<float>(top), 1424 static_cast<float>(left+width), static_cast<float>(top+height)); 1425 pRenderTarget->FillRectangle(&rectangle1, pBrush); 1426 } else if ((std::abs(xDiff) == std::abs(yDiff))) { 1427 // 45 degree slope 1428 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f), 1429 D2D1::Point2F(x_ + 0.5f - xDelta, y_ + 0.5f - yDelta), pBrush); 1430 } else { 1431 // Line has a different slope so difficult to avoid last pixel 1432 pRenderTarget->DrawLine(D2D1::Point2F(x + 0.5f, y + 0.5f), 1433 D2D1::Point2F(x_ + 0.5f, y_ + 0.5f), pBrush); 1434 } 1435 x = x_; 1436 y = y_; 1437 } 1438 } 1439 1440 void SurfaceD2D::Polygon(Point *pts, size_t npts, ColourDesired fore, ColourDesired back) { 1441 PLATFORM_ASSERT(pRenderTarget && (npts > 2)); 1442 if (pRenderTarget) { 1443 ID2D1PathGeometry *geometry = nullptr; 1444 HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry); 1445 PLATFORM_ASSERT(geometry); 1446 if (SUCCEEDED(hr) && geometry) { 1447 ID2D1GeometrySink *sink = nullptr; 1448 hr = geometry->Open(&sink); 1449 PLATFORM_ASSERT(sink); 1450 if (SUCCEEDED(hr) && sink) { 1451 sink->BeginFigure(D2D1::Point2F(pts[0].x + 0.5f, pts[0].y + 0.5f), D2D1_FIGURE_BEGIN_FILLED); 1452 for (size_t i=1; i<npts; i++) { 1453 sink->AddLine(D2D1::Point2F(pts[i].x + 0.5f, pts[i].y + 0.5f)); 1454 } 1455 sink->EndFigure(D2D1_FIGURE_END_CLOSED); 1456 sink->Close(); 1457 ReleaseUnknown(sink); 1458 1459 D2DPenColour(back); 1460 pRenderTarget->FillGeometry(geometry,pBrush); 1461 D2DPenColour(fore); 1462 pRenderTarget->DrawGeometry(geometry,pBrush); 1463 } 1464 ReleaseUnknown(geometry); 1465 } 1466 } 1467 } 1468 1469 void SurfaceD2D::RectangleDraw(PRectangle rc, ColourDesired fore, ColourDesired back) { 1470 if (pRenderTarget) { 1471 const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left) + 0.5f, rc.top+0.5f, std::round(rc.right) - 0.5f, rc.bottom-0.5f); 1472 D2DPenColour(back); 1473 pRenderTarget->FillRectangle(&rectangle1, pBrush); 1474 D2DPenColour(fore); 1475 pRenderTarget->DrawRectangle(&rectangle1, pBrush); 1476 } 1477 } 1478 1479 void SurfaceD2D::FillRectangle(PRectangle rc, ColourDesired back) { 1480 if (pRenderTarget) { 1481 D2DPenColour(back); 1482 const D2D1_RECT_F rectangle1 = D2D1::RectF(std::round(rc.left), rc.top, std::round(rc.right), rc.bottom); 1483 pRenderTarget->FillRectangle(&rectangle1, pBrush); 1484 } 1485 } 1486 1487 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) { 1488 SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(&surfacePattern); 1489 PLATFORM_ASSERT(psurfOther); 1490 psurfOther->FlushDrawing(); 1491 ID2D1Bitmap *pBitmap = nullptr; 1492 HRESULT hr = psurfOther->GetBitmap(&pBitmap); 1493 if (SUCCEEDED(hr) && pBitmap) { 1494 ID2D1BitmapBrush *pBitmapBrush = nullptr; 1495 const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties = 1496 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP, 1497 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR); 1498 // Create the bitmap brush. 1499 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush); 1500 ReleaseUnknown(pBitmap); 1501 if (SUCCEEDED(hr) && pBitmapBrush) { 1502 pRenderTarget->FillRectangle( 1503 D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), 1504 pBitmapBrush); 1505 ReleaseUnknown(pBitmapBrush); 1506 } 1507 } 1508 } 1509 1510 void SurfaceD2D::RoundedRectangle(PRectangle rc, ColourDesired fore, ColourDesired back) { 1511 if (pRenderTarget) { 1512 D2D1_ROUNDED_RECT roundedRectFill = { 1513 D2D1::RectF(rc.left+1.0f, rc.top+1.0f, rc.right-1.0f, rc.bottom-1.0f), 1514 4, 4}; 1515 D2DPenColour(back); 1516 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush); 1517 1518 D2D1_ROUNDED_RECT roundedRect = { 1519 D2D1::RectF(rc.left + 0.5f, rc.top+0.5f, rc.right - 0.5f, rc.bottom-0.5f), 1520 4, 4}; 1521 D2DPenColour(fore); 1522 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush); 1523 } 1524 } 1525 1526 void SurfaceD2D::AlphaRectangle(PRectangle rc, int cornerSize, ColourDesired fill, int alphaFill, 1527 ColourDesired outline, int alphaOutline, int /* flags*/ ) { 1528 if (pRenderTarget) { 1529 if (cornerSize == 0) { 1530 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners 1531 const D2D1_RECT_F rectFill = D2D1::RectF(std::round(rc.left) + 1.0f, rc.top + 1.0f, std::round(rc.right) - 1.0f, rc.bottom - 1.0f); 1532 D2DPenColour(fill, alphaFill); 1533 pRenderTarget->FillRectangle(rectFill, pBrush); 1534 1535 const D2D1_RECT_F rectOutline = D2D1::RectF(std::round(rc.left) + 0.5f, rc.top + 0.5f, std::round(rc.right) - 0.5f, rc.bottom - 0.5f); 1536 D2DPenColour(outline, alphaOutline); 1537 pRenderTarget->DrawRectangle(rectOutline, pBrush); 1538 } else { 1539 const float cornerSizeF = static_cast<float>(cornerSize); 1540 D2D1_ROUNDED_RECT roundedRectFill = { 1541 D2D1::RectF(std::round(rc.left) + 1.0f, rc.top + 1.0f, std::round(rc.right) - 1.0f, rc.bottom - 1.0f), 1542 cornerSizeF - 1.0f, cornerSizeF - 1.0f }; 1543 D2DPenColour(fill, alphaFill); 1544 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush); 1545 1546 D2D1_ROUNDED_RECT roundedRect = { 1547 D2D1::RectF(std::round(rc.left) + 0.5f, rc.top + 0.5f, std::round(rc.right) - 0.5f, rc.bottom - 0.5f), 1548 cornerSizeF, cornerSizeF}; 1549 D2DPenColour(outline, alphaOutline); 1550 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush); 1551 } 1552 } 1553 } 1554 1555 namespace { 1556 1557 D2D_COLOR_F ColorFromColourAlpha(ColourAlpha colour) noexcept { 1558 D2D_COLOR_F col; 1559 col.r = colour.GetRedComponent(); 1560 col.g = colour.GetGreenComponent(); 1561 col.b = colour.GetBlueComponent(); 1562 col.a = colour.GetAlphaComponent(); 1563 return col; 1564 } 1565 1566 } 1567 1568 void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) { 1569 if (pRenderTarget) { 1570 D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp; 1571 lgbp.startPoint = D2D1::Point2F(rc.left, rc.top); 1572 switch (options) { 1573 case GradientOptions::leftToRight: 1574 lgbp.endPoint = D2D1::Point2F(rc.right, rc.top); 1575 break; 1576 case GradientOptions::topToBottom: 1577 default: 1578 lgbp.endPoint = D2D1::Point2F(rc.left, rc.bottom); 1579 break; 1580 } 1581 1582 std::vector<D2D1_GRADIENT_STOP> gradientStops; 1583 for (const ColourStop &stop : stops) { 1584 gradientStops.push_back({ stop.position, ColorFromColourAlpha(stop.colour) }); 1585 } 1586 ID2D1GradientStopCollection *pGradientStops = nullptr; 1587 HRESULT hr = pRenderTarget->CreateGradientStopCollection( 1588 gradientStops.data(), static_cast<UINT32>(gradientStops.size()), &pGradientStops); 1589 if (FAILED(hr) || !pGradientStops) { 1590 return; 1591 } 1592 ID2D1LinearGradientBrush *pBrushLinear = nullptr; 1593 hr = pRenderTarget->CreateLinearGradientBrush( 1594 lgbp, pGradientStops, &pBrushLinear); 1595 if (SUCCEEDED(hr) && pBrushLinear) { 1596 const D2D1_RECT_F rectangle = D2D1::RectF(std::round(rc.left), rc.top, std::round(rc.right), rc.bottom); 1597 pRenderTarget->FillRectangle(&rectangle, pBrushLinear); 1598 ReleaseUnknown(pBrushLinear); 1599 } 1600 ReleaseUnknown(pGradientStops); 1601 } 1602 } 1603 1604 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) { 1605 if (pRenderTarget) { 1606 if (rc.Width() > width) 1607 rc.left += std::floor((rc.Width() - width) / 2); 1608 rc.right = rc.left + width; 1609 if (rc.Height() > height) 1610 rc.top += std::floor((rc.Height() - height) / 2); 1611 rc.bottom = rc.top + height; 1612 1613 std::vector<unsigned char> image(RGBAImage::bytesPerPixel * height * width); 1614 RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast<ptrdiff_t>(height) * width); 1615 1616 ID2D1Bitmap *bitmap = nullptr; 1617 const D2D1_SIZE_U size = D2D1::SizeU(width, height); 1618 D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM, 1619 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0}; 1620 const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(), 1621 width * 4, &props, &bitmap); 1622 if (SUCCEEDED(hr)) { 1623 const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); 1624 pRenderTarget->DrawBitmap(bitmap, rcDestination); 1625 ReleaseUnknown(bitmap); 1626 } 1627 } 1628 } 1629 1630 void SurfaceD2D::Ellipse(PRectangle rc, ColourDesired fore, ColourDesired back) { 1631 if (pRenderTarget) { 1632 const FLOAT radius = rc.Width() / 2.0f; 1633 D2D1_ELLIPSE ellipse = { 1634 D2D1::Point2F((rc.left + rc.right) / 2.0f, (rc.top + rc.bottom) / 2.0f), 1635 radius,radius}; 1636 1637 PenColour(back); 1638 pRenderTarget->FillEllipse(ellipse, pBrush); 1639 PenColour(fore); 1640 pRenderTarget->DrawEllipse(ellipse, pBrush); 1641 } 1642 } 1643 1644 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) { 1645 SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource); 1646 surfOther.FlushDrawing(); 1647 ID2D1Bitmap *pBitmap = nullptr; 1648 HRESULT hr = surfOther.GetBitmap(&pBitmap); 1649 if (SUCCEEDED(hr) && pBitmap) { 1650 const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc); 1651 D2D1_RECT_F rcSource = {from.x, from.y, from.x + rc.Width(), from.y + rc.Height()}; 1652 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f, 1653 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource); 1654 hr = pRenderTarget->Flush(); 1655 if (FAILED(hr)) { 1656 Platform::DebugPrintf("Failed Flush 0x%lx\n", hr); 1657 } 1658 ReleaseUnknown(pBitmap); 1659 } 1660 } 1661 1662 class BlobInline : public IDWriteInlineObject { 1663 XYPOSITION width; 1664 1665 // IUnknown 1666 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override; 1667 STDMETHODIMP_(ULONG)AddRef() override; 1668 STDMETHODIMP_(ULONG)Release() override; 1669 1670 // IDWriteInlineObject 1671 COM_DECLSPEC_NOTHROW STDMETHODIMP Draw( 1672 void *clientDrawingContext, 1673 IDWriteTextRenderer *renderer, 1674 FLOAT originX, 1675 FLOAT originY, 1676 BOOL isSideways, 1677 BOOL isRightToLeft, 1678 IUnknown *clientDrawingEffect 1679 ) override; 1680 COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override; 1681 COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override; 1682 COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions( 1683 DWRITE_BREAK_CONDITION *breakConditionBefore, 1684 DWRITE_BREAK_CONDITION *breakConditionAfter) override; 1685 public: 1686 BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) { 1687 } 1688 // Defaulted so BlobInline objects can be copied. 1689 BlobInline(const BlobInline &) = default; 1690 BlobInline(BlobInline &&) = default; 1691 BlobInline &operator=(const BlobInline &) = default; 1692 BlobInline &operator=(BlobInline &&) = default; 1693 virtual ~BlobInline() { 1694 } 1695 }; 1696 1697 /// Implement IUnknown 1698 STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) { 1699 if (!ppv) 1700 return E_POINTER; 1701 // Never called so not checked. 1702 *ppv = nullptr; 1703 if (riid == IID_IUnknown) 1704 *ppv = static_cast<IUnknown *>(this); 1705 if (riid == __uuidof(IDWriteInlineObject)) 1706 *ppv = static_cast<IDWriteInlineObject *>(this); 1707 if (!*ppv) 1708 return E_NOINTERFACE; 1709 return S_OK; 1710 } 1711 1712 STDMETHODIMP_(ULONG) BlobInline::AddRef() { 1713 // Lifetime tied to Platform methods so ignore any reference operations. 1714 return 1; 1715 } 1716 1717 STDMETHODIMP_(ULONG) BlobInline::Release() { 1718 // Lifetime tied to Platform methods so ignore any reference operations. 1719 return 1; 1720 } 1721 1722 /// Implement IDWriteInlineObject 1723 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw( 1724 void*, 1725 IDWriteTextRenderer*, 1726 FLOAT, 1727 FLOAT, 1728 BOOL, 1729 BOOL, 1730 IUnknown*) { 1731 // Since not performing drawing, not necessary to implement 1732 // Could be implemented by calling back into platform-independent code. 1733 // This would allow more of the drawing to be mediated through DirectWrite. 1734 return S_OK; 1735 } 1736 1737 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics( 1738 DWRITE_INLINE_OBJECT_METRICS *metrics 1739 ) { 1740 if (!metrics) 1741 return E_POINTER; 1742 metrics->width = width; 1743 metrics->height = 2; 1744 metrics->baseline = 1; 1745 metrics->supportsSideways = FALSE; 1746 return S_OK; 1747 } 1748 1749 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics( 1750 DWRITE_OVERHANG_METRICS *overhangs 1751 ) { 1752 if (!overhangs) 1753 return E_POINTER; 1754 overhangs->left = 0; 1755 overhangs->top = 0; 1756 overhangs->right = 0; 1757 overhangs->bottom = 0; 1758 return S_OK; 1759 } 1760 1761 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions( 1762 DWRITE_BREAK_CONDITION *breakConditionBefore, 1763 DWRITE_BREAK_CONDITION *breakConditionAfter 1764 ) { 1765 if (!breakConditionBefore || !breakConditionAfter) 1766 return E_POINTER; 1767 // Since not performing 2D layout, not necessary to implement 1768 *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL; 1769 *breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL; 1770 return S_OK; 1771 } 1772 1773 class ScreenLineLayout : public IScreenLineLayout { 1774 IDWriteTextLayout *textLayout = nullptr; 1775 std::string text; 1776 std::wstring buffer; 1777 std::vector<BlobInline> blobs; 1778 static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs); 1779 static std::wstring ReplaceRepresentation(std::string_view text); 1780 static size_t GetPositionInLayout(std::string_view text, size_t position); 1781 public: 1782 ScreenLineLayout(const IScreenLine *screenLine); 1783 // Deleted so ScreenLineLayout objects can not be copied 1784 ScreenLineLayout(const ScreenLineLayout &) = delete; 1785 ScreenLineLayout(ScreenLineLayout &&) = delete; 1786 ScreenLineLayout &operator=(const ScreenLineLayout &) = delete; 1787 ScreenLineLayout &operator=(ScreenLineLayout &&) = delete; 1788 ~ScreenLineLayout() override; 1789 size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override; 1790 XYPOSITION XFromPosition(size_t caretPosition) override; 1791 std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override; 1792 }; 1793 1794 // Each char can have its own style, so we fill the textLayout with the textFormat of each char 1795 1796 void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) { 1797 // Reserve enough entries up front so they are not moved and the pointers handed 1798 // to textLayout remain valid. 1799 const ptrdiff_t numRepresentations = screenLine->RepresentationCount(); 1800 std::string_view text = screenLine->Text(); 1801 const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t'); 1802 blobs.reserve(numRepresentations + numTabs); 1803 1804 UINT32 layoutPosition = 0; 1805 1806 for (size_t bytePosition = 0; bytePosition < screenLine->Length();) { 1807 const unsigned char uch = screenLine->Text()[bytePosition]; 1808 const unsigned int byteCount = UTF8BytesOfLead[uch]; 1809 const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount); 1810 const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits }; 1811 1812 XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition); 1813 if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) { 1814 Point realPt; 1815 DWRITE_HIT_TEST_METRICS realCaretMetrics {}; 1816 textLayout->HitTestTextPosition( 1817 layoutPosition, 1818 false, // trailing if false, else leading edge 1819 &realPt.x, 1820 &realPt.y, 1821 &realCaretMetrics 1822 ); 1823 1824 const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x); 1825 representationWidth = nextTab - realPt.x; 1826 } 1827 if (representationWidth > 0.0f) { 1828 blobs.push_back(BlobInline(representationWidth)); 1829 textLayout->SetInlineObject(&blobs.back(), textRange); 1830 }; 1831 1832 FormatAndMetrics *pfm = 1833 static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(bytePosition)->GetID()); 1834 1835 const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength(); 1836 std::wstring fontFamilyName(fontFamilyNameSize, 0); 1837 const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1); 1838 if (SUCCEEDED(hrFamily)) { 1839 textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange); 1840 } 1841 1842 textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange); 1843 textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange); 1844 textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange); 1845 1846 const unsigned int localeNameSize = pfm->pTextFormat->GetLocaleNameLength(); 1847 std::wstring localeName(localeNameSize, 0); 1848 const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize + 1); 1849 if (SUCCEEDED(hrLocale)) { 1850 textLayout->SetLocaleName(localeName.c_str(), textRange); 1851 } 1852 1853 textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange); 1854 1855 IDWriteFontCollection *fontCollection = nullptr; 1856 if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) { 1857 textLayout->SetFontCollection(fontCollection, textRange); 1858 } 1859 1860 bytePosition += byteCount; 1861 layoutPosition += codeUnits; 1862 } 1863 1864 } 1865 1866 /* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */ 1867 1868 std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) { 1869 const TextWide wideText(text, true); 1870 std::wstring ws(wideText.buffer, wideText.tlen); 1871 std::replace(ws.begin(), ws.end(), L'\t', L'X'); 1872 return ws; 1873 } 1874 1875 // Finds the position in the wide character version of the text. 1876 1877 size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) { 1878 const std::string_view textUptoPosition = text.substr(0, position); 1879 return UTF16Length(textUptoPosition); 1880 } 1881 1882 ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) { 1883 // If the text is empty, then no need to go through this function 1884 if (!screenLine || !screenLine->Length()) 1885 return; 1886 1887 text = screenLine->Text(); 1888 1889 // Get textFormat 1890 FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(screenLine->FontOfPosition(0)->GetID()); 1891 1892 if (!pIDWriteFactory || !pfm->pTextFormat) { 1893 return; 1894 } 1895 1896 // Convert the string to wstring and replace the original control characters with their representative chars. 1897 buffer = ReplaceRepresentation(screenLine->Text()); 1898 1899 // Create a text layout 1900 const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(buffer.c_str(), static_cast<UINT32>(buffer.length()), 1901 pfm->pTextFormat, screenLine->Width(), screenLine->Height(), &textLayout); 1902 if (!SUCCEEDED(hrCreate)) { 1903 return; 1904 } 1905 1906 // Fill the textLayout chars with their own formats 1907 FillTextLayoutFormats(screenLine, textLayout, blobs); 1908 } 1909 1910 ScreenLineLayout::~ScreenLineLayout() { 1911 ReleaseUnknown(textLayout); 1912 } 1913 1914 // Get the position from the provided x 1915 1916 size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) { 1917 if (!textLayout) { 1918 return 0; 1919 } 1920 1921 // Returns the text position corresponding to the mouse x,y. 1922 // If hitting the trailing side of a cluster, return the 1923 // leading edge of the following text position. 1924 1925 BOOL isTrailingHit = FALSE; 1926 BOOL isInside = FALSE; 1927 DWRITE_HIT_TEST_METRICS caretMetrics {}; 1928 1929 textLayout->HitTestPoint( 1930 xDistance, 1931 0.0f, 1932 &isTrailingHit, 1933 &isInside, 1934 &caretMetrics 1935 ); 1936 1937 DWRITE_HIT_TEST_METRICS hitTestMetrics {}; 1938 if (isTrailingHit) { 1939 FLOAT caretX = 0.0f; 1940 FLOAT caretY = 0.0f; 1941 1942 // Uses hit-testing to align the current caret position to a whole cluster, 1943 // rather than residing in the middle of a base character + diacritic, 1944 // surrogate pair, or character + UVS. 1945 1946 // Align the caret to the nearest whole cluster. 1947 textLayout->HitTestTextPosition( 1948 caretMetrics.textPosition, 1949 false, 1950 &caretX, 1951 &caretY, 1952 &hitTestMetrics 1953 ); 1954 } 1955 1956 size_t pos; 1957 if (charPosition) { 1958 pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition; 1959 } else { 1960 pos = isTrailingHit ? hitTestMetrics.textPosition + hitTestMetrics.length : caretMetrics.textPosition; 1961 } 1962 1963 // Get the character position in original string 1964 return UTF8PositionFromUTF16Position(text, pos); 1965 } 1966 1967 // Finds the point of the caret position 1968 1969 XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) { 1970 if (!textLayout) { 1971 return 0.0; 1972 } 1973 // Convert byte positions to wchar_t positions 1974 const size_t position = GetPositionInLayout(text, caretPosition); 1975 1976 // Translate text character offset to point x,y. 1977 DWRITE_HIT_TEST_METRICS caretMetrics {}; 1978 Point pt; 1979 1980 textLayout->HitTestTextPosition( 1981 static_cast<UINT32>(position), 1982 false, // trailing if false, else leading edge 1983 &pt.x, 1984 &pt.y, 1985 &caretMetrics 1986 ); 1987 1988 return pt.x; 1989 } 1990 1991 // Find the selection range rectangles 1992 1993 std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) { 1994 std::vector<Interval> ret; 1995 1996 if (!textLayout || (start == end)) { 1997 return ret; 1998 } 1999 2000 // Convert byte positions to wchar_t positions 2001 const size_t startPos = GetPositionInLayout(text, start); 2002 const size_t endPos = GetPositionInLayout(text, end); 2003 2004 // Find selection range length 2005 const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos); 2006 2007 // Determine actual number of hit-test ranges 2008 UINT32 actualHitTestCount = 0; 2009 2010 // First try with 2 elements and if more needed, allocate. 2011 std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(2); 2012 textLayout->HitTestTextRange( 2013 static_cast<UINT32>(startPos), 2014 static_cast<UINT32>(rangeLength), 2015 0, // x 2016 0, // y 2017 hitTestMetrics.data(), 2018 static_cast<UINT32>(hitTestMetrics.size()), 2019 &actualHitTestCount 2020 ); 2021 2022 if (actualHitTestCount == 0) { 2023 return ret; 2024 } 2025 2026 if (hitTestMetrics.size() < actualHitTestCount) { 2027 // Allocate enough room to return all hit-test metrics. 2028 hitTestMetrics.resize(actualHitTestCount); 2029 textLayout->HitTestTextRange( 2030 static_cast<UINT32>(startPos), 2031 static_cast<UINT32>(rangeLength), 2032 0, // x 2033 0, // y 2034 hitTestMetrics.data(), 2035 static_cast<UINT32>(hitTestMetrics.size()), 2036 &actualHitTestCount 2037 ); 2038 } 2039 2040 // Get the selection ranges behind the text. 2041 2042 for (size_t i = 0; i < actualHitTestCount; ++i) { 2043 // Store selection rectangle 2044 const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i]; 2045 Interval selectionInterval; 2046 2047 selectionInterval.left = htm.left; 2048 selectionInterval.right = htm.left + htm.width; 2049 2050 ret.push_back(selectionInterval); 2051 } 2052 2053 return ret; 2054 } 2055 2056 std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) { 2057 return std::make_unique<ScreenLineLayout>(screenLine); 2058 } 2059 2060 void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font &font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) { 2061 SetFont(font_); 2062 2063 // Use Unicode calls 2064 const TextWide tbuf(text, unicodeMode, codePageText); 2065 if (pRenderTarget && pTextFormat && pBrush) { 2066 if (fuOptions & ETO_CLIPPED) { 2067 const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); 2068 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); 2069 } 2070 2071 // Explicitly creating a text layout appears a little faster 2072 IDWriteTextLayout *pTextLayout = nullptr; 2073 const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 2074 rc.Width(), rc.Height(), &pTextLayout); 2075 if (SUCCEEDED(hr)) { 2076 D2D1_POINT_2F origin = {rc.left, ybase-yAscent}; 2077 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, d2dDrawTextOptions); 2078 ReleaseUnknown(pTextLayout); 2079 } 2080 2081 if (fuOptions & ETO_CLIPPED) { 2082 pRenderTarget->PopAxisAlignedClip(); 2083 } 2084 } 2085 } 2086 2087 void SurfaceD2D::DrawTextNoClip(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, 2088 ColourDesired fore, ColourDesired back) { 2089 if (pRenderTarget) { 2090 FillRectangle(rc, back); 2091 D2DPenColour(fore); 2092 DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE); 2093 } 2094 } 2095 2096 void SurfaceD2D::DrawTextClipped(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, 2097 ColourDesired fore, ColourDesired back) { 2098 if (pRenderTarget) { 2099 FillRectangle(rc, back); 2100 D2DPenColour(fore); 2101 DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED); 2102 } 2103 } 2104 2105 void SurfaceD2D::DrawTextTransparent(PRectangle rc, Font &font_, XYPOSITION ybase, std::string_view text, 2106 ColourDesired fore) { 2107 // Avoid drawing spaces in transparent mode 2108 for (const char ch : text) { 2109 if (ch != ' ') { 2110 if (pRenderTarget) { 2111 D2DPenColour(fore); 2112 DrawTextCommon(rc, font_, ybase, text, 0); 2113 } 2114 return; 2115 } 2116 } 2117 } 2118 2119 XYPOSITION SurfaceD2D::WidthText(Font &font_, std::string_view text) { 2120 FLOAT width = 1.0; 2121 SetFont(font_); 2122 const TextWide tbuf(text, unicodeMode, codePageText); 2123 if (pIDWriteFactory && pTextFormat) { 2124 // Create a layout 2125 IDWriteTextLayout *pTextLayout = nullptr; 2126 const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 1000.0, 1000.0, &pTextLayout); 2127 if (SUCCEEDED(hr) && pTextLayout) { 2128 DWRITE_TEXT_METRICS textMetrics; 2129 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) 2130 width = textMetrics.widthIncludingTrailingWhitespace; 2131 ReleaseUnknown(pTextLayout); 2132 } 2133 } 2134 return width; 2135 } 2136 2137 void SurfaceD2D::MeasureWidths(Font &font_, std::string_view text, XYPOSITION *positions) { 2138 SetFont(font_); 2139 if (!pIDWriteFactory || !pTextFormat) { 2140 // SetFont failed or no access to DirectWrite so give up. 2141 return; 2142 } 2143 const TextWide tbuf(text, unicodeMode, codePageText); 2144 TextPositions poses(tbuf.tlen); 2145 // Initialize poses for safety. 2146 std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f); 2147 // Create a layout 2148 IDWriteTextLayout *pTextLayout = nullptr; 2149 const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout); 2150 if (!SUCCEEDED(hrCreate) || !pTextLayout) { 2151 return; 2152 } 2153 constexpr int clusters = stackBufferLength; 2154 DWRITE_CLUSTER_METRICS clusterMetrics[clusters]; 2155 UINT32 count = 0; 2156 const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(clusterMetrics, clusters, &count); 2157 ReleaseUnknown(pTextLayout); 2158 if (!SUCCEEDED(hrGetCluster)) { 2159 return; 2160 } 2161 // A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font 2162 FLOAT position = 0.0f; 2163 int ti=0; 2164 for (unsigned int ci=0; ci<count; ci++) { 2165 for (unsigned int inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) { 2166 poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length; 2167 } 2168 position += clusterMetrics[ci].width; 2169 } 2170 PLATFORM_ASSERT(ti == tbuf.tlen); 2171 if (unicodeMode) { 2172 // Map the widths given for UTF-16 characters back onto the UTF-8 input string 2173 int ui=0; 2174 size_t i=0; 2175 while (ui<tbuf.tlen) { 2176 const unsigned char uch = text[i]; 2177 const unsigned int byteCount = UTF8BytesOfLead[uch]; 2178 if (byteCount == 4) { // Non-BMP 2179 ui++; 2180 } 2181 for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui<tbuf.tlen); bytePos++) { 2182 positions[i++] = poses.buffer[ui]; 2183 } 2184 ui++; 2185 } 2186 XYPOSITION lastPos = 0.0f; 2187 if (i > 0) 2188 lastPos = positions[i-1]; 2189 while (i<text.length()) { 2190 positions[i++] = lastPos; 2191 } 2192 } else if (!IsDBCSCodePage(codePageText)) { 2193 2194 // One char per position 2195 PLATFORM_ASSERT(text.length() == static_cast<size_t>(tbuf.tlen)); 2196 for (int kk=0; kk<tbuf.tlen; kk++) { 2197 positions[kk] = poses.buffer[kk]; 2198 } 2199 2200 } else { 2201 2202 // May be one or two bytes per position 2203 int ui = 0; 2204 for (size_t i=0; i<text.length() && ui<tbuf.tlen;) { 2205 positions[i] = poses.buffer[ui]; 2206 if (DBCSIsLeadByte(codePageText, text[i])) { 2207 positions[i+1] = poses.buffer[ui]; 2208 i += 2; 2209 } else { 2210 i++; 2211 } 2212 2213 ui++; 2214 } 2215 } 2216 } 2217 2218 XYPOSITION SurfaceD2D::Ascent(Font &font_) { 2219 SetFont(font_); 2220 return std::ceil(yAscent); 2221 } 2222 2223 XYPOSITION SurfaceD2D::Descent(Font &font_) { 2224 SetFont(font_); 2225 return std::ceil(yDescent); 2226 } 2227 2228 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) { 2229 SetFont(font_); 2230 return std::floor(yInternalLeading); 2231 } 2232 2233 XYPOSITION SurfaceD2D::Height(Font &font_) { 2234 return Ascent(font_) + Descent(font_); 2235 } 2236 2237 XYPOSITION SurfaceD2D::AverageCharWidth(Font &font_) { 2238 FLOAT width = 1.0; 2239 SetFont(font_); 2240 if (pIDWriteFactory && pTextFormat) { 2241 // Create a layout 2242 IDWriteTextLayout *pTextLayout = nullptr; 2243 const WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 2244 const size_t lenAllAlpha = wcslen(wszAllAlpha); 2245 const HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(lenAllAlpha), 2246 pTextFormat, 1000.0, 1000.0, &pTextLayout); 2247 if (SUCCEEDED(hr) && pTextLayout) { 2248 DWRITE_TEXT_METRICS textMetrics; 2249 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics))) 2250 width = textMetrics.width / lenAllAlpha; 2251 ReleaseUnknown(pTextLayout); 2252 } 2253 } 2254 return width; 2255 } 2256 2257 void SurfaceD2D::SetClip(PRectangle rc) { 2258 if (pRenderTarget) { 2259 const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc); 2260 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED); 2261 clipsActive++; 2262 } 2263 } 2264 2265 void SurfaceD2D::FlushCachedState() { 2266 } 2267 2268 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) { 2269 unicodeMode=unicodeMode_; 2270 } 2271 2272 void SurfaceD2D::SetDBCSMode(int codePage_) { 2273 // No action on window as automatically handled by system. 2274 codePage = codePage_; 2275 } 2276 2277 void SurfaceD2D::SetBidiR2L(bool) { 2278 } 2279 2280 #endif 2281 2282 Surface *Surface::Allocate(int technology) { 2283 #if defined(USE_D2D) 2284 if (technology == SCWIN_TECH_GDI) 2285 return new SurfaceGDI; 2286 else 2287 return new SurfaceD2D; 2288 #else 2289 return new SurfaceGDI; 2290 #endif 2291 } 2292 2293 Window::~Window() { 2294 } 2295 2296 void Window::Destroy() { 2297 if (wid) 2298 ::DestroyWindow(HwndFromWindowID(wid)); 2299 wid = nullptr; 2300 } 2301 2302 PRectangle Window::GetPosition() const { 2303 RECT rc; 2304 ::GetWindowRect(HwndFromWindowID(wid), &rc); 2305 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom); 2306 } 2307 2308 void Window::SetPosition(PRectangle rc) { 2309 ::SetWindowPos(HwndFromWindowID(wid), 2310 0, static_cast<int>(rc.left), static_cast<int>(rc.top), 2311 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE); 2312 } 2313 2314 namespace { 2315 2316 static RECT RectFromMonitor(HMONITOR hMonitor) noexcept { 2317 MONITORINFO mi = {}; 2318 mi.cbSize = sizeof(mi); 2319 if (GetMonitorInfo(hMonitor, &mi)) { 2320 return mi.rcWork; 2321 } 2322 RECT rc = {0, 0, 0, 0}; 2323 if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) { 2324 rc.left = 0; 2325 rc.top = 0; 2326 rc.right = 0; 2327 rc.bottom = 0; 2328 } 2329 return rc; 2330 } 2331 2332 } 2333 2334 void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) { 2335 const DWORD style = GetWindowStyle(HwndFromWindowID(wid)); 2336 if (style & WS_POPUP) { 2337 POINT ptOther = {0, 0}; 2338 ::ClientToScreen(HwndFromWindow(*relativeTo), &ptOther); 2339 rc.Move(static_cast<XYPOSITION>(ptOther.x), static_cast<XYPOSITION>(ptOther.y)); 2340 2341 const RECT rcMonitor = RectFromPRectangle(rc); 2342 2343 HMONITOR hMonitor = MonitorFromRect(&rcMonitor, MONITOR_DEFAULTTONEAREST); 2344 // If hMonitor is NULL, that's just the main screen anyways. 2345 const RECT rcWork = RectFromMonitor(hMonitor); 2346 2347 if (rcWork.left < rcWork.right) { 2348 // Now clamp our desired rectangle to fit inside the work area 2349 // This way, the menu will fit wholly on one screen. An improvement even 2350 // if you don't have a second monitor on the left... Menu's appears half on 2351 // one screen and half on the other are just U.G.L.Y.! 2352 if (rc.right > rcWork.right) 2353 rc.Move(rcWork.right - rc.right, 0); 2354 if (rc.bottom > rcWork.bottom) 2355 rc.Move(0, rcWork.bottom - rc.bottom); 2356 if (rc.left < rcWork.left) 2357 rc.Move(rcWork.left - rc.left, 0); 2358 if (rc.top < rcWork.top) 2359 rc.Move(0, rcWork.top - rc.top); 2360 } 2361 } 2362 SetPosition(rc); 2363 } 2364 2365 PRectangle Window::GetClientPosition() const { 2366 RECT rc={0,0,0,0}; 2367 if (wid) 2368 ::GetClientRect(HwndFromWindowID(wid), &rc); 2369 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom); 2370 } 2371 2372 void Window::Show(bool show) { 2373 if (show) 2374 ::ShowWindow(HwndFromWindowID(wid), SW_SHOWNOACTIVATE); 2375 else 2376 ::ShowWindow(HwndFromWindowID(wid), SW_HIDE); 2377 } 2378 2379 void Window::InvalidateAll() { 2380 ::InvalidateRect(HwndFromWindowID(wid), nullptr, FALSE); 2381 } 2382 2383 void Window::InvalidateRectangle(PRectangle rc) { 2384 const RECT rcw = RectFromPRectangle(rc); 2385 ::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE); 2386 } 2387 2388 void Window::SetFont(Font &font) { 2389 SetWindowFont(HwndFromWindowID(wid), font.GetID(), 0); 2390 } 2391 2392 namespace { 2393 2394 void FlipBitmap(HBITMAP bitmap, int width, int height) noexcept { 2395 HDC hdc = ::CreateCompatibleDC({}); 2396 if (hdc) { 2397 HBITMAP prevBmp = SelectBitmap(hdc, bitmap); 2398 ::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY); 2399 SelectBitmap(hdc, prevBmp); 2400 ::DeleteDC(hdc); 2401 } 2402 } 2403 2404 } 2405 2406 HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept { 2407 HCURSOR reverseArrowCursor {}; 2408 2409 bool created = false; 2410 HCURSOR cursor = ::LoadCursor({}, IDC_ARROW); 2411 2412 if (dpi != uSystemDPI) { 2413 const int width = SystemMetricsForDpi(SM_CXCURSOR, dpi); 2414 const int height = SystemMetricsForDpi(SM_CYCURSOR, dpi); 2415 HCURSOR copy = static_cast<HCURSOR>(::CopyImage(cursor, IMAGE_CURSOR, width, height, LR_COPYFROMRESOURCE | LR_COPYRETURNORG)); 2416 if (copy) { 2417 created = copy != cursor; 2418 cursor = copy; 2419 } 2420 } 2421 2422 ICONINFO info; 2423 if (::GetIconInfo(cursor, &info)) { 2424 BITMAP bmp; 2425 if (::GetObject(info.hbmMask, sizeof(bmp), &bmp)) { 2426 FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight); 2427 if (info.hbmColor) 2428 FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight); 2429 info.xHotspot = bmp.bmWidth - 1 - info.xHotspot; 2430 2431 reverseArrowCursor = ::CreateIconIndirect(&info); 2432 } 2433 2434 ::DeleteObject(info.hbmMask); 2435 if (info.hbmColor) 2436 ::DeleteObject(info.hbmColor); 2437 } 2438 2439 if (created) { 2440 ::DestroyCursor(cursor); 2441 } 2442 return reverseArrowCursor; 2443 } 2444 2445 void Window::SetCursor(Cursor curs) { 2446 switch (curs) { 2447 case cursorText: 2448 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM)); 2449 break; 2450 case cursorUp: 2451 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW)); 2452 break; 2453 case cursorWait: 2454 ::SetCursor(::LoadCursor(NULL,IDC_WAIT)); 2455 break; 2456 case cursorHoriz: 2457 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE)); 2458 break; 2459 case cursorVert: 2460 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS)); 2461 break; 2462 case cursorHand: 2463 ::SetCursor(::LoadCursor(NULL,IDC_HAND)); 2464 break; 2465 case cursorReverseArrow: 2466 case cursorArrow: 2467 case cursorInvalid: // Should not occur, but just in case. 2468 ::SetCursor(::LoadCursor(NULL,IDC_ARROW)); 2469 break; 2470 } 2471 } 2472 2473 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's 2474 coordinates */ 2475 PRectangle Window::GetMonitorRect(Point pt) { 2476 const PRectangle rcPosition = GetPosition(); 2477 POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left), 2478 static_cast<LONG>(pt.y + rcPosition.top)}; 2479 HMONITOR hMonitor = MonitorFromPoint(ptDesktop, MONITOR_DEFAULTTONEAREST); 2480 2481 const RECT rcWork = RectFromMonitor(hMonitor); 2482 if (rcWork.left < rcWork.right) { 2483 PRectangle rcMonitor( 2484 rcWork.left - rcPosition.left, 2485 rcWork.top - rcPosition.top, 2486 rcWork.right - rcPosition.left, 2487 rcWork.bottom - rcPosition.top); 2488 return rcMonitor; 2489 } else { 2490 return PRectangle(); 2491 } 2492 } 2493 2494 struct ListItemData { 2495 const char *text; 2496 int pixId; 2497 }; 2498 2499 class LineToItem { 2500 std::vector<char> words; 2501 2502 std::vector<ListItemData> data; 2503 2504 public: 2505 void Clear() noexcept { 2506 words.clear(); 2507 data.clear(); 2508 } 2509 2510 ListItemData Get(size_t index) const noexcept { 2511 if (index < data.size()) { 2512 return data[index]; 2513 } else { 2514 ListItemData missing = {"", -1}; 2515 return missing; 2516 } 2517 } 2518 int Count() const noexcept { 2519 return static_cast<int>(data.size()); 2520 } 2521 2522 void AllocItem(const char *text, int pixId) { 2523 ListItemData lid = { text, pixId }; 2524 data.push_back(lid); 2525 } 2526 2527 char *SetWords(const char *s) { 2528 words = std::vector<char>(s, s+strlen(s)+1); 2529 return &words[0]; 2530 } 2531 }; 2532 2533 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX"); 2534 2535 ListBox::ListBox() noexcept { 2536 } 2537 2538 ListBox::~ListBox() { 2539 } 2540 2541 class ListBoxX : public ListBox { 2542 int lineHeight; 2543 FontID fontCopy; 2544 int technology; 2545 RGBAImageSet images; 2546 LineToItem lti; 2547 HWND lb; 2548 bool unicodeMode; 2549 int desiredVisibleRows; 2550 unsigned int maxItemCharacters; 2551 unsigned int aveCharWidth; 2552 Window *parent; 2553 int ctrlID; 2554 UINT dpi; 2555 IListBoxDelegate *delegate; 2556 const char *widestItem; 2557 unsigned int maxCharWidth; 2558 WPARAM resizeHit; 2559 PRectangle rcPreSize; 2560 Point dragOffset; 2561 Point location; // Caret location at which the list is opened 2562 int wheelDelta; // mouse wheel residue 2563 2564 HWND GetHWND() const noexcept; 2565 void AppendListItem(const char *text, const char *numword); 2566 static void AdjustWindowRect(PRectangle *rc, UINT dpi) noexcept; 2567 int ItemHeight() const; 2568 int MinClientWidth() const noexcept; 2569 int TextOffset() const; 2570 POINT GetClientExtent() const noexcept; 2571 POINT MinTrackSize() const; 2572 POINT MaxTrackSize() const; 2573 void SetRedraw(bool on) noexcept; 2574 void OnDoubleClick(); 2575 void OnSelChange(); 2576 void ResizeToCursor(); 2577 void StartResize(WPARAM); 2578 LRESULT NcHitTest(WPARAM, LPARAM) const; 2579 void CentreItem(int n); 2580 void Paint(HDC) noexcept; 2581 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); 2582 2583 static constexpr Point ItemInset {0, 0}; // Padding around whole item 2584 static constexpr Point TextInset {2, 0}; // Padding around text 2585 static constexpr Point ImageInset {1, 0}; // Padding around image 2586 2587 public: 2588 ListBoxX() : lineHeight(10), fontCopy{}, technology(0), lb{}, unicodeMode(false), 2589 desiredVisibleRows(9), maxItemCharacters(0), aveCharWidth(8), 2590 parent(nullptr), ctrlID(0), dpi(USER_DEFAULT_SCREEN_DPI), 2591 delegate(nullptr), 2592 widestItem(nullptr), maxCharWidth(1), resizeHit(0), wheelDelta(0) { 2593 } 2594 ~ListBoxX() override { 2595 if (fontCopy) { 2596 ::DeleteObject(fontCopy); 2597 fontCopy = 0; 2598 } 2599 } 2600 void SetFont(Font &font) override; 2601 void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) override; 2602 void SetAverageCharWidth(int width) override; 2603 void SetVisibleRows(int rows) override; 2604 int GetVisibleRows() const override; 2605 PRectangle GetDesiredRect() override; 2606 int CaretFromEdge() override; 2607 void Clear() override; 2608 void Append(char *s, int type = -1) override; 2609 int Length() override; 2610 void Select(int n) override; 2611 int GetSelection() override; 2612 int Find(const char *prefix) override; 2613 void GetValue(int n, char *value, int len) override; 2614 void RegisterImage(int type, const char *xpm_data) override; 2615 void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override; 2616 void ClearRegisteredImages() override; 2617 void SetDelegate(IListBoxDelegate *lbDelegate) override; 2618 void SetList(const char *list, char separator, char typesep) override; 2619 void Draw(DRAWITEMSTRUCT *pDrawItem); 2620 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); 2621 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam); 2622 }; 2623 2624 ListBox *ListBox::Allocate() { 2625 ListBoxX *lb = new ListBoxX(); 2626 return lb; 2627 } 2628 2629 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, int technology_) { 2630 parent = &parent_; 2631 ctrlID = ctrlID_; 2632 location = location_; 2633 lineHeight = lineHeight_; 2634 unicodeMode = unicodeMode_; 2635 technology = technology_; 2636 HWND hwndParent = HwndFromWindow(*parent); 2637 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent); 2638 // Window created as popup so not clipped within parent client area 2639 wid = ::CreateWindowEx( 2640 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""), 2641 WS_POPUP | WS_THICKFRAME, 2642 100,100, 150,80, hwndParent, 2643 NULL, 2644 hinstanceParent, 2645 this); 2646 2647 dpi = DpiForWindow(hwndParent); 2648 POINT locationw = POINTFromPoint(location); 2649 ::MapWindowPoints(hwndParent, NULL, &locationw, 1); 2650 location = PointFromPOINT(locationw); 2651 } 2652 2653 void ListBoxX::SetFont(Font &font) { 2654 if (font.GetID()) { 2655 if (fontCopy) { 2656 ::DeleteObject(fontCopy); 2657 fontCopy = 0; 2658 } 2659 FormatAndMetrics *pfm = static_cast<FormatAndMetrics *>(font.GetID()); 2660 fontCopy = pfm->HFont(); 2661 SetWindowFont(lb, fontCopy, 0); 2662 } 2663 } 2664 2665 void ListBoxX::SetAverageCharWidth(int width) { 2666 aveCharWidth = width; 2667 } 2668 2669 void ListBoxX::SetVisibleRows(int rows) { 2670 desiredVisibleRows = rows; 2671 } 2672 2673 int ListBoxX::GetVisibleRows() const { 2674 return desiredVisibleRows; 2675 } 2676 2677 HWND ListBoxX::GetHWND() const noexcept { 2678 return HwndFromWindowID(GetID()); 2679 } 2680 2681 PRectangle ListBoxX::GetDesiredRect() { 2682 PRectangle rcDesired = GetPosition(); 2683 2684 int rows = Length(); 2685 if ((rows == 0) || (rows > desiredVisibleRows)) 2686 rows = desiredVisibleRows; 2687 rcDesired.bottom = rcDesired.top + ItemHeight() * rows; 2688 2689 int width = MinClientWidth(); 2690 HDC hdc = ::GetDC(lb); 2691 HFONT oldFont = SelectFont(hdc, fontCopy); 2692 SIZE textSize = {0, 0}; 2693 int len = 0; 2694 if (widestItem) { 2695 len = static_cast<int>(strlen(widestItem)); 2696 if (unicodeMode) { 2697 const TextWide tbuf(widestItem, unicodeMode); 2698 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize); 2699 } else { 2700 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize); 2701 } 2702 } 2703 TEXTMETRIC tm; 2704 ::GetTextMetrics(hdc, &tm); 2705 maxCharWidth = tm.tmMaxCharWidth; 2706 SelectFont(hdc, oldFont); 2707 ::ReleaseDC(lb, hdc); 2708 2709 const int widthDesired = std::max(textSize.cx, (len + 1) * tm.tmAveCharWidth); 2710 if (width < widthDesired) 2711 width = widthDesired; 2712 2713 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2); 2714 if (Length() > rows) 2715 rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi); 2716 2717 AdjustWindowRect(&rcDesired, dpi); 2718 return rcDesired; 2719 } 2720 2721 int ListBoxX::TextOffset() const { 2722 const int pixWidth = images.GetWidth(); 2723 return static_cast<int>(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2)); 2724 } 2725 2726 int ListBoxX::CaretFromEdge() { 2727 PRectangle rc; 2728 AdjustWindowRect(&rc, dpi); 2729 return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1); 2730 } 2731 2732 void ListBoxX::Clear() { 2733 ListBox_ResetContent(lb); 2734 maxItemCharacters = 0; 2735 widestItem = nullptr; 2736 lti.Clear(); 2737 } 2738 2739 void ListBoxX::Append(char *, int) { 2740 // This method is no longer called in Scintilla 2741 PLATFORM_ASSERT(false); 2742 } 2743 2744 int ListBoxX::Length() { 2745 return lti.Count(); 2746 } 2747 2748 void ListBoxX::Select(int n) { 2749 // We are going to scroll to centre on the new selection and then select it, so disable 2750 // redraw to avoid flicker caused by a painting new selection twice in unselected and then 2751 // selected states 2752 SetRedraw(false); 2753 CentreItem(n); 2754 ListBox_SetCurSel(lb, n); 2755 OnSelChange(); 2756 SetRedraw(true); 2757 } 2758 2759 int ListBoxX::GetSelection() { 2760 return ListBox_GetCurSel(lb); 2761 } 2762 2763 // This is not actually called at present 2764 int ListBoxX::Find(const char *) { 2765 return LB_ERR; 2766 } 2767 2768 void ListBoxX::GetValue(int n, char *value, int len) { 2769 const ListItemData item = lti.Get(n); 2770 strncpy(value, item.text, len); 2771 value[len-1] = '\0'; 2772 } 2773 2774 void ListBoxX::RegisterImage(int type, const char *xpm_data) { 2775 XPM xpmImage(xpm_data); 2776 images.Add(type, new RGBAImage(xpmImage)); 2777 } 2778 2779 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) { 2780 images.Add(type, new RGBAImage(width, height, 1.0, pixelsImage)); 2781 } 2782 2783 void ListBoxX::ClearRegisteredImages() { 2784 images.Clear(); 2785 } 2786 2787 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) { 2788 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) { 2789 RECT rcBox = pDrawItem->rcItem; 2790 rcBox.left += TextOffset(); 2791 if (pDrawItem->itemState & ODS_SELECTED) { 2792 RECT rcImage = pDrawItem->rcItem; 2793 rcImage.right = rcBox.left; 2794 // The image is not highlighted 2795 ::FillRect(pDrawItem->hDC, &rcImage, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1)); 2796 ::FillRect(pDrawItem->hDC, &rcBox, reinterpret_cast<HBRUSH>(COLOR_HIGHLIGHT+1)); 2797 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHT)); 2798 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_HIGHLIGHTTEXT)); 2799 } else { 2800 ::FillRect(pDrawItem->hDC, &pDrawItem->rcItem, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1)); 2801 ::SetBkColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOW)); 2802 ::SetTextColor(pDrawItem->hDC, ::GetSysColor(COLOR_WINDOWTEXT)); 2803 } 2804 2805 const ListItemData item = lti.Get(pDrawItem->itemID); 2806 const int pixId = item.pixId; 2807 const char *text = item.text; 2808 const int len = static_cast<int>(strlen(text)); 2809 2810 RECT rcText = rcBox; 2811 ::InsetRect(&rcText, static_cast<int>(TextInset.x), static_cast<int>(TextInset.y)); 2812 2813 if (unicodeMode) { 2814 const TextWide tbuf(text, unicodeMode); 2815 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP); 2816 } else { 2817 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP); 2818 } 2819 2820 // Draw the image, if any 2821 const RGBAImage *pimage = images.Get(pixId); 2822 if (pimage) { 2823 std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology)); 2824 if (technology == SCWIN_TECH_GDI) { 2825 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem); 2826 const long left = pDrawItem->rcItem.left + static_cast<int>(ItemInset.x + ImageInset.x); 2827 const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top, 2828 left + images.GetWidth(), pDrawItem->rcItem.bottom); 2829 surfaceItem->DrawRGBAImage(rcImage, 2830 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); 2831 ::SetTextAlign(pDrawItem->hDC, TA_TOP); 2832 } else { 2833 #if defined(USE_D2D) 2834 const D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties( 2835 D2D1_RENDER_TARGET_TYPE_DEFAULT, 2836 D2D1::PixelFormat( 2837 DXGI_FORMAT_B8G8R8A8_UNORM, 2838 D2D1_ALPHA_MODE_IGNORE), 2839 0, 2840 0, 2841 D2D1_RENDER_TARGET_USAGE_NONE, 2842 D2D1_FEATURE_LEVEL_DEFAULT 2843 ); 2844 ID2D1DCRenderTarget *pDCRT = nullptr; 2845 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT); 2846 if (SUCCEEDED(hr) && pDCRT) { 2847 RECT rcWindow; 2848 GetClientRect(pDrawItem->hwndItem, &rcWindow); 2849 hr = pDCRT->BindDC(pDrawItem->hDC, &rcWindow); 2850 if (SUCCEEDED(hr)) { 2851 surfaceItem->Init(pDCRT, pDrawItem->hwndItem); 2852 pDCRT->BeginDraw(); 2853 const long left = pDrawItem->rcItem.left + static_cast<long>(ItemInset.x + ImageInset.x); 2854 const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top, 2855 left + images.GetWidth(), pDrawItem->rcItem.bottom); 2856 surfaceItem->DrawRGBAImage(rcImage, 2857 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels()); 2858 pDCRT->EndDraw(); 2859 ReleaseUnknown(pDCRT); 2860 } 2861 } 2862 #endif 2863 } 2864 } 2865 } 2866 } 2867 2868 void ListBoxX::AppendListItem(const char *text, const char *numword) { 2869 int pixId = -1; 2870 if (numword) { 2871 pixId = 0; 2872 char ch; 2873 while ((ch = *++numword) != '\0') { 2874 pixId = 10 * pixId + (ch - '0'); 2875 } 2876 } 2877 2878 lti.AllocItem(text, pixId); 2879 const unsigned int len = static_cast<unsigned int>(strlen(text)); 2880 if (maxItemCharacters < len) { 2881 maxItemCharacters = len; 2882 widestItem = text; 2883 } 2884 } 2885 2886 void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) { 2887 delegate = lbDelegate; 2888 } 2889 2890 void ListBoxX::SetList(const char *list, char separator, char typesep) { 2891 // Turn off redraw while populating the list - this has a significant effect, even if 2892 // the listbox is not visible. 2893 SetRedraw(false); 2894 Clear(); 2895 const size_t size = strlen(list); 2896 char *words = lti.SetWords(list); 2897 char *startword = words; 2898 char *numword = nullptr; 2899 for (size_t i=0; i < size; i++) { 2900 if (words[i] == separator) { 2901 words[i] = '\0'; 2902 if (numword) 2903 *numword = '\0'; 2904 AppendListItem(startword, numword); 2905 startword = words + i + 1; 2906 numword = nullptr; 2907 } else if (words[i] == typesep) { 2908 numword = words + i; 2909 } 2910 } 2911 if (startword) { 2912 if (numword) 2913 *numword = '\0'; 2914 AppendListItem(startword, numword); 2915 } 2916 2917 // Finally populate the listbox itself with the correct number of items 2918 const int count = lti.Count(); 2919 ::SendMessage(lb, LB_INITSTORAGE, count, 0); 2920 for (intptr_t j=0; j<count; j++) { 2921 ListBox_AddItemData(lb, j+1); 2922 } 2923 SetRedraw(true); 2924 } 2925 2926 void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpi) noexcept { 2927 RECT rcw = RectFromPRectangle(*rc); 2928 if (fnAdjustWindowRectExForDpi) { 2929 fnAdjustWindowRectExForDpi(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE, dpi); 2930 } else { 2931 ::AdjustWindowRectEx(&rcw, WS_THICKFRAME, false, WS_EX_WINDOWEDGE); 2932 } 2933 *rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom); 2934 } 2935 2936 int ListBoxX::ItemHeight() const { 2937 int itemHeight = lineHeight + (static_cast<int>(TextInset.y) * 2); 2938 const int pixHeight = images.GetHeight() + (static_cast<int>(ImageInset.y) * 2); 2939 if (itemHeight < pixHeight) { 2940 itemHeight = pixHeight; 2941 } 2942 return itemHeight; 2943 } 2944 2945 int ListBoxX::MinClientWidth() const noexcept { 2946 return 12 * (aveCharWidth+aveCharWidth/3); 2947 } 2948 2949 POINT ListBoxX::MinTrackSize() const { 2950 PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight()); 2951 AdjustWindowRect(&rc, dpi); 2952 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())}; 2953 return ret; 2954 } 2955 2956 POINT ListBoxX::MaxTrackSize() const { 2957 PRectangle rc = PRectangle::FromInts(0, 0, 2958 std::max(static_cast<unsigned int>(MinClientWidth()), 2959 maxCharWidth * maxItemCharacters + static_cast<int>(TextInset.x) * 2 + 2960 TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi)), 2961 ItemHeight() * lti.Count()); 2962 AdjustWindowRect(&rc, dpi); 2963 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())}; 2964 return ret; 2965 } 2966 2967 void ListBoxX::SetRedraw(bool on) noexcept { 2968 ::SendMessage(lb, WM_SETREDRAW, on, 0); 2969 if (on) 2970 ::InvalidateRect(lb, nullptr, TRUE); 2971 } 2972 2973 void ListBoxX::ResizeToCursor() { 2974 PRectangle rc = GetPosition(); 2975 POINT ptw; 2976 ::GetCursorPos(&ptw); 2977 const Point pt = PointFromPOINT(ptw) + dragOffset; 2978 2979 switch (resizeHit) { 2980 case HTLEFT: 2981 rc.left = pt.x; 2982 break; 2983 case HTRIGHT: 2984 rc.right = pt.x; 2985 break; 2986 case HTTOP: 2987 rc.top = pt.y; 2988 break; 2989 case HTTOPLEFT: 2990 rc.top = pt.y; 2991 rc.left = pt.x; 2992 break; 2993 case HTTOPRIGHT: 2994 rc.top = pt.y; 2995 rc.right = pt.x; 2996 break; 2997 case HTBOTTOM: 2998 rc.bottom = pt.y; 2999 break; 3000 case HTBOTTOMLEFT: 3001 rc.bottom = pt.y; 3002 rc.left = pt.x; 3003 break; 3004 case HTBOTTOMRIGHT: 3005 rc.bottom = pt.y; 3006 rc.right = pt.x; 3007 break; 3008 } 3009 3010 const POINT ptMin = MinTrackSize(); 3011 const POINT ptMax = MaxTrackSize(); 3012 // We don't allow the left edge to move at present, but just in case 3013 rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x); 3014 rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y); 3015 rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x); 3016 rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y); 3017 3018 SetPosition(rc); 3019 } 3020 3021 void ListBoxX::StartResize(WPARAM hitCode) { 3022 rcPreSize = GetPosition(); 3023 POINT cursorPos; 3024 ::GetCursorPos(&cursorPos); 3025 3026 switch (hitCode) { 3027 case HTRIGHT: 3028 case HTBOTTOM: 3029 case HTBOTTOMRIGHT: 3030 dragOffset.x = rcPreSize.right - cursorPos.x; 3031 dragOffset.y = rcPreSize.bottom - cursorPos.y; 3032 break; 3033 3034 case HTTOPRIGHT: 3035 dragOffset.x = rcPreSize.right - cursorPos.x; 3036 dragOffset.y = rcPreSize.top - cursorPos.y; 3037 break; 3038 3039 // Note that the current hit test code prevents the left edge cases ever firing 3040 // as we don't want the left edge to be movable 3041 case HTLEFT: 3042 case HTTOP: 3043 case HTTOPLEFT: 3044 dragOffset.x = rcPreSize.left - cursorPos.x; 3045 dragOffset.y = rcPreSize.top - cursorPos.y; 3046 break; 3047 case HTBOTTOMLEFT: 3048 dragOffset.x = rcPreSize.left - cursorPos.x; 3049 dragOffset.y = rcPreSize.bottom - cursorPos.y; 3050 break; 3051 3052 default: 3053 return; 3054 } 3055 3056 ::SetCapture(GetHWND()); 3057 resizeHit = hitCode; 3058 } 3059 3060 LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const { 3061 const PRectangle rc = GetPosition(); 3062 3063 LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam); 3064 // There is an apparent bug in the DefWindowProc hit test code whereby it will 3065 // return HTTOPXXX if the window in question is shorter than the default 3066 // window caption height + frame, even if one is hovering over the bottom edge of 3067 // the frame, so workaround that here 3068 if (hit >= HTTOP && hit <= HTTOPRIGHT) { 3069 const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi); 3070 const int yPos = GET_Y_LPARAM(lParam); 3071 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) { 3072 hit += HTBOTTOM - HTTOP; 3073 } 3074 } 3075 3076 // Never permit resizing that moves the left edge. Allow movement of top or bottom edge 3077 // depending on whether the list is above or below the caret 3078 switch (hit) { 3079 case HTLEFT: 3080 case HTTOPLEFT: 3081 case HTBOTTOMLEFT: 3082 hit = HTERROR; 3083 break; 3084 3085 case HTTOP: 3086 case HTTOPRIGHT: { 3087 // Valid only if caret below list 3088 if (location.y < rc.top) 3089 hit = HTERROR; 3090 } 3091 break; 3092 3093 case HTBOTTOM: 3094 case HTBOTTOMRIGHT: { 3095 // Valid only if caret above list 3096 if (rc.bottom <= location.y) 3097 hit = HTERROR; 3098 } 3099 break; 3100 } 3101 3102 return hit; 3103 } 3104 3105 void ListBoxX::OnDoubleClick() { 3106 if (delegate) { 3107 ListBoxEvent event(ListBoxEvent::EventType::doubleClick); 3108 delegate->ListNotify(&event); 3109 } 3110 } 3111 3112 void ListBoxX::OnSelChange() { 3113 if (delegate) { 3114 ListBoxEvent event(ListBoxEvent::EventType::selectionChange); 3115 delegate->ListNotify(&event); 3116 } 3117 } 3118 3119 POINT ListBoxX::GetClientExtent() const noexcept { 3120 RECT rc; 3121 ::GetWindowRect(HwndFromWindowID(wid), &rc); 3122 POINT ret { rc.right - rc.left, rc.bottom - rc.top }; 3123 return ret; 3124 } 3125 3126 void ListBoxX::CentreItem(int n) { 3127 // If below mid point, scroll up to centre, but with more items below if uneven 3128 if (n >= 0) { 3129 const POINT extent = GetClientExtent(); 3130 const int visible = extent.y/ItemHeight(); 3131 if (visible < Length()) { 3132 const int top = ListBox_GetTopIndex(lb); 3133 const int half = (visible - 1) / 2; 3134 if (n > (top + half)) 3135 ListBox_SetTopIndex(lb, n - half); 3136 } 3137 } 3138 } 3139 3140 // Performs a double-buffered paint operation to avoid flicker 3141 void ListBoxX::Paint(HDC hDC) noexcept { 3142 const POINT extent = GetClientExtent(); 3143 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y); 3144 HDC bitmapDC = ::CreateCompatibleDC(hDC); 3145 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap); 3146 // The list background is mainly erased during painting, but can be a small 3147 // unpainted area when at the end of a non-integrally sized list with a 3148 // vertical scroll bar 3149 const RECT rc = { 0, 0, extent.x, extent.y }; 3150 ::FillRect(bitmapDC, &rc, reinterpret_cast<HBRUSH>(COLOR_WINDOW+1)); 3151 // Paint the entire client area and vertical scrollbar 3152 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT); 3153 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY); 3154 // Select a stock brush to prevent warnings from BoundsChecker 3155 SelectBrush(bitmapDC, GetStockBrush(WHITE_BRUSH)); 3156 SelectBitmap(bitmapDC, hBitmapOld); 3157 ::DeleteDC(bitmapDC); 3158 ::DeleteObject(hBitmap); 3159 } 3160 3161 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { 3162 try { 3163 ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd))); 3164 switch (iMessage) { 3165 case WM_ERASEBKGND: 3166 return TRUE; 3167 3168 case WM_PAINT: { 3169 PAINTSTRUCT ps; 3170 HDC hDC = ::BeginPaint(hWnd, &ps); 3171 if (lbx) { 3172 lbx->Paint(hDC); 3173 } 3174 ::EndPaint(hWnd, &ps); 3175 } 3176 return 0; 3177 3178 case WM_MOUSEACTIVATE: 3179 // This prevents the view activating when the scrollbar is clicked 3180 return MA_NOACTIVATE; 3181 3182 case WM_LBUTTONDOWN: { 3183 // We must take control of selection to prevent the ListBox activating 3184 // the popup 3185 const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam); 3186 const int item = LOWORD(lResult); 3187 if (HIWORD(lResult) == 0 && item >= 0) { 3188 ListBox_SetCurSel(hWnd, item); 3189 if (lbx) { 3190 lbx->OnSelChange(); 3191 } 3192 } 3193 } 3194 return 0; 3195 3196 case WM_LBUTTONUP: 3197 return 0; 3198 3199 case WM_LBUTTONDBLCLK: { 3200 if (lbx) { 3201 lbx->OnDoubleClick(); 3202 } 3203 } 3204 return 0; 3205 3206 case WM_MBUTTONDOWN: 3207 // disable the scroll wheel button click action 3208 return 0; 3209 } 3210 3211 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA)); 3212 if (prevWndProc) { 3213 return ::CallWindowProc(prevWndProc, hWnd, iMessage, wParam, lParam); 3214 } else { 3215 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3216 } 3217 } catch (...) { 3218 } 3219 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3220 } 3221 3222 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { 3223 switch (iMessage) { 3224 case WM_CREATE: { 3225 HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent)); 3226 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list 3227 // but has useful side effect of speeding up list population significantly 3228 lb = ::CreateWindowEx( 3229 0, TEXT("listbox"), TEXT(""), 3230 WS_CHILD | WS_VSCROLL | WS_VISIBLE | 3231 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT, 3232 0, 0, 150,80, hWnd, 3233 reinterpret_cast<HMENU>(static_cast<ptrdiff_t>(ctrlID)), 3234 hinstanceParent, 3235 0); 3236 WNDPROC prevWndProc = SubclassWindow(lb, ControlWndProc); 3237 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc)); 3238 } 3239 break; 3240 3241 case WM_SIZE: 3242 if (lb) { 3243 SetRedraw(false); 3244 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE); 3245 // Ensure the selection remains visible 3246 CentreItem(GetSelection()); 3247 SetRedraw(true); 3248 } 3249 break; 3250 3251 case WM_PAINT: { 3252 PAINTSTRUCT ps; 3253 ::BeginPaint(hWnd, &ps); 3254 ::EndPaint(hWnd, &ps); 3255 } 3256 break; 3257 3258 case WM_COMMAND: 3259 // This is not actually needed now - the registered double click action is used 3260 // directly to action a choice from the list. 3261 ::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam); 3262 break; 3263 3264 case WM_MEASUREITEM: { 3265 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam); 3266 pMeasureItem->itemHeight = ItemHeight(); 3267 } 3268 break; 3269 3270 case WM_DRAWITEM: 3271 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam)); 3272 break; 3273 3274 case WM_DESTROY: 3275 lb = 0; 3276 SetWindowPointer(hWnd, nullptr); 3277 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3278 3279 case WM_ERASEBKGND: 3280 // To reduce flicker we can elide background erasure since this window is 3281 // completely covered by its child. 3282 return TRUE; 3283 3284 case WM_GETMINMAXINFO: { 3285 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam); 3286 minMax->ptMaxTrackSize = MaxTrackSize(); 3287 minMax->ptMinTrackSize = MinTrackSize(); 3288 } 3289 break; 3290 3291 case WM_MOUSEACTIVATE: 3292 return MA_NOACTIVATE; 3293 3294 case WM_NCHITTEST: 3295 return NcHitTest(wParam, lParam); 3296 3297 case WM_NCLBUTTONDOWN: 3298 // We have to implement our own window resizing because the DefWindowProc 3299 // implementation insists on activating the resized window 3300 StartResize(wParam); 3301 return 0; 3302 3303 case WM_MOUSEMOVE: { 3304 if (resizeHit == 0) { 3305 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3306 } else { 3307 ResizeToCursor(); 3308 } 3309 } 3310 break; 3311 3312 case WM_LBUTTONUP: 3313 case WM_CANCELMODE: 3314 if (resizeHit != 0) { 3315 resizeHit = 0; 3316 ::ReleaseCapture(); 3317 } 3318 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3319 case WM_MOUSEWHEEL: 3320 wheelDelta -= GET_WHEEL_DELTA_WPARAM(wParam); 3321 if (std::abs(wheelDelta) >= WHEEL_DELTA) { 3322 const int nRows = GetVisibleRows(); 3323 int linesToScroll = 1; 3324 if (nRows > 1) { 3325 linesToScroll = nRows - 1; 3326 } 3327 if (linesToScroll > 3) { 3328 linesToScroll = 3; 3329 } 3330 linesToScroll *= (wheelDelta / WHEEL_DELTA); 3331 int top = ListBox_GetTopIndex(lb) + linesToScroll; 3332 if (top < 0) { 3333 top = 0; 3334 } 3335 ListBox_SetTopIndex(lb, top); 3336 // update wheel delta residue 3337 if (wheelDelta >= 0) 3338 wheelDelta = wheelDelta % WHEEL_DELTA; 3339 else 3340 wheelDelta = - (-wheelDelta % WHEEL_DELTA); 3341 } 3342 break; 3343 3344 default: 3345 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3346 } 3347 3348 return 0; 3349 } 3350 3351 LRESULT PASCAL ListBoxX::StaticWndProc( 3352 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) { 3353 if (iMessage == WM_CREATE) { 3354 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam); 3355 SetWindowPointer(hWnd, pCreate->lpCreateParams); 3356 } 3357 // Find C++ object associated with window. 3358 ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(hWnd)); 3359 if (lbx) { 3360 return lbx->WndProc(hWnd, iMessage, wParam, lParam); 3361 } else { 3362 return ::DefWindowProc(hWnd, iMessage, wParam, lParam); 3363 } 3364 } 3365 3366 namespace { 3367 3368 bool ListBoxX_Register() noexcept { 3369 WNDCLASSEX wndclassc {}; 3370 wndclassc.cbSize = sizeof(wndclassc); 3371 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for 3372 // truncated items in the list and the appearance/disappearance of the vertical scroll bar. 3373 // The list repaint is double-buffered to avoid the flicker this would otherwise cause. 3374 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW; 3375 wndclassc.cbWndExtra = sizeof(ListBoxX *); 3376 wndclassc.hInstance = hinstPlatformRes; 3377 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc; 3378 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW); 3379 wndclassc.lpszClassName = ListBoxX_ClassName; 3380 3381 return ::RegisterClassEx(&wndclassc) != 0; 3382 } 3383 3384 void ListBoxX_Unregister() noexcept { 3385 if (hinstPlatformRes) { 3386 ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes); 3387 } 3388 } 3389 3390 } 3391 3392 Menu::Menu() noexcept : mid{} { 3393 } 3394 3395 void Menu::CreatePopUp() { 3396 Destroy(); 3397 mid = ::CreatePopupMenu(); 3398 } 3399 3400 void Menu::Destroy() { 3401 if (mid) 3402 ::DestroyMenu(static_cast<HMENU>(mid)); 3403 mid = 0; 3404 } 3405 3406 void Menu::Show(Point pt, Window &w) { 3407 ::TrackPopupMenu(static_cast<HMENU>(mid), 3408 TPM_RIGHTBUTTON, static_cast<int>(pt.x - 4), static_cast<int>(pt.y), 0, 3409 HwndFromWindow(w), nullptr); 3410 Destroy(); 3411 } 3412 3413 class DynamicLibraryImpl : public DynamicLibrary { 3414 protected: 3415 HMODULE h; 3416 public: 3417 explicit DynamicLibraryImpl(const char *modulePath) noexcept { 3418 h = ::LoadLibraryA(modulePath); 3419 } 3420 3421 ~DynamicLibraryImpl() override { 3422 if (h) 3423 ::FreeLibrary(h); 3424 } 3425 3426 // Use GetProcAddress to get a pointer to the relevant function. 3427 Function FindFunction(const char *name) noexcept override { 3428 if (h) { 3429 // Use memcpy as it doesn't invoke undefined or conditionally defined behaviour. 3430 FARPROC fp = ::GetProcAddress(h, name); 3431 Function f = nullptr; 3432 static_assert(sizeof(f) == sizeof(fp)); 3433 memcpy(&f, &fp, sizeof(f)); 3434 return f; 3435 } else { 3436 return nullptr; 3437 } 3438 } 3439 3440 bool IsValid() noexcept override { 3441 return h != NULL; 3442 } 3443 }; 3444 3445 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) { 3446 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath)); 3447 } 3448 3449 ColourDesired Platform::Chrome() { 3450 return ColourDesired(::GetSysColor(COLOR_3DFACE)); 3451 } 3452 3453 ColourDesired Platform::ChromeHighlight() { 3454 return ColourDesired(::GetSysColor(COLOR_3DHIGHLIGHT)); 3455 } 3456 3457 const char *Platform::DefaultFont() { 3458 return "Verdana"; 3459 } 3460 3461 int Platform::DefaultFontSize() { 3462 return 8; 3463 } 3464 3465 unsigned int Platform::DoubleClickTime() { 3466 return ::GetDoubleClickTime(); 3467 } 3468 3469 void Platform::DebugDisplay(const char *s) { 3470 ::OutputDebugStringA(s); 3471 } 3472 3473 //#define TRACE 3474 3475 #ifdef TRACE 3476 void Platform::DebugPrintf(const char *format, ...) { 3477 char buffer[2000]; 3478 va_list pArguments; 3479 va_start(pArguments, format); 3480 vsprintf(buffer,format,pArguments); 3481 va_end(pArguments); 3482 Platform::DebugDisplay(buffer); 3483 } 3484 #else 3485 void Platform::DebugPrintf(const char *, ...) { 3486 } 3487 #endif 3488 3489 static bool assertionPopUps = true; 3490 3491 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) { 3492 const bool ret = assertionPopUps; 3493 assertionPopUps = assertionPopUps_; 3494 return ret; 3495 } 3496 3497 void Platform::Assert(const char *c, const char *file, int line) { 3498 char buffer[2000] {}; 3499 sprintf(buffer, "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n"); 3500 if (assertionPopUps) { 3501 const int idButton = ::MessageBoxA(0, buffer, "Assertion failure", 3502 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); 3503 if (idButton == IDRETRY) { 3504 ::DebugBreak(); 3505 } else if (idButton == IDIGNORE) { 3506 // all OK 3507 } else { 3508 abort(); 3509 } 3510 } else { 3511 Platform::DebugDisplay(buffer); 3512 ::DebugBreak(); 3513 abort(); 3514 } 3515 } 3516 3517 void Platform_Initialise(void *hInstance) noexcept { 3518 hinstPlatformRes = static_cast<HINSTANCE>(hInstance); 3519 LoadDpiForWindow(); 3520 ListBoxX_Register(); 3521 } 3522 3523 void Platform_Finalise(bool fromDllMain) noexcept { 3524 #if defined(USE_D2D) 3525 if (!fromDllMain) { 3526 ReleaseUnknown(defaultRenderingParams); 3527 ReleaseUnknown(customClearTypeRenderingParams); 3528 ReleaseUnknown(pIDWriteFactory); 3529 ReleaseUnknown(pD2DFactory); 3530 if (hDLLDWrite) { 3531 FreeLibrary(hDLLDWrite); 3532 hDLLDWrite = {}; 3533 } 3534 if (hDLLD2D) { 3535 FreeLibrary(hDLLD2D); 3536 hDLLD2D = {}; 3537 } 3538 } 3539 #endif 3540 if (!fromDllMain && hDLLShcore) { 3541 FreeLibrary(hDLLShcore); 3542 hDLLShcore = {}; 3543 } 3544 ListBoxX_Unregister(); 3545 } 3546 3547 } 3548