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
LoadD2DOnce()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
LoadD2D()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;
FormatAndMetricsScintilla::FormatAndMetrics165 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)
FormatAndMetricsScintilla::FormatAndMetrics173 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
~FormatAndMetricsScintilla::FormatAndMetrics194 ~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
HFont()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
PointerFromWindow(HWND hWnd)237 void *PointerFromWindow(HWND hWnd) noexcept {
238 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
239 }
240
SetWindowPointer(HWND hWnd,void * ptr)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
LoadDpiForWindow()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
FamFromFontID(void * fid)289 FormatAndMetrics *FamFromFontID(void *fid) noexcept {
290 return static_cast<FormatAndMetrics *>(fid);
291 }
292
Win32MapFontQuality(int extraFontFlag)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)
DWriteMapFontQuality(int extraFontFlag)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
SetLogFont(LOGFONTW & lf,const char * faceName,int characterSet,float size,int weight,bool italic,int extraFontFlag)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
CreateFontFromParameters(const FontParameters & fp)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
Font()393 Font::Font() noexcept : fid{} {
394 }
395
~Font()396 Font::~Font() {
397 }
398
Create(const FontParameters & fp)399 void Font::Create(const FontParameters &fp) {
400 Release();
401 if (fp.faceName)
402 fid = CreateFontFromParameters(fp);
403 }
404
Release()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;
VarBuffer(size_t length)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
~VarBuffer()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.
TextWide(std::string_view text,bool unicodeMode,int codePage=0)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
DpiForWindow(WindowID wid)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
SystemMetricsForDpi(int nIndex,UINT dpi)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
SurfaceGDI()561 SurfaceGDI::SurfaceGDI() noexcept {
562 }
563
~SurfaceGDI()564 SurfaceGDI::~SurfaceGDI() noexcept {
565 Clear();
566 }
567
Clear()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
Release()599 void SurfaceGDI::Release() {
600 Clear();
601 }
602
Initialised()603 bool SurfaceGDI::Initialised() {
604 return hdc != 0;
605 }
606
Init(WindowID wid)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
Init(SurfaceID sid,WindowID wid)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
InitPixMap(int width,int height,Surface * surface_,WindowID wid)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
PenColour(ColourDesired fore)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
BrushColour(ColourDesired back)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
SetFont(const Font & font_)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
LogPixelsY()671 int SurfaceGDI::LogPixelsY() {
672 return logPixelsY;
673 }
674
DeviceHeightFont(int points)675 int SurfaceGDI::DeviceHeightFont(int points) {
676 return ::MulDiv(points, LogPixelsY(), 72);
677 }
678
MoveTo(int x_,int y_)679 void SurfaceGDI::MoveTo(int x_, int y_) {
680 ::MoveToEx(hdc, x_, y_, nullptr);
681 }
682
LineTo(int x_,int y_)683 void SurfaceGDI::LineTo(int x_, int y_) {
684 ::LineTo(hdc, x_, y_);
685 }
686
Polygon(Point * pts,size_t npts,ColourDesired fore,ColourDesired back)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
RectangleDraw(PRectangle rc,ColourDesired fore,ColourDesired back)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
FillRectangle(PRectangle rc,ColourDesired back)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
FillRectangle(PRectangle rc,Surface & surfacePattern)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
RoundedRectangle(PRectangle rc,ColourDesired fore,ColourDesired back)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
dwordFromBGRA(byte b,byte g,byte r,byte a)734 constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept {
735 return (a << 24) | (r << 16) | (g << 8) | b;
736 }
737
AlphaScaled(unsigned char component,unsigned int alpha)738 constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept {
739 return static_cast<byte>(component * alpha / 255);
740 }
741
dwordMultiplied(ColourDesired colour,unsigned int alpha)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;
operator bool() const764 operator bool() const noexcept {
765 return hMemDC && hbmMem && pixels;
766 }
Pixels() const767 DWORD *Pixels() const noexcept {
768 return pixels;
769 }
Bytes() const770 unsigned char *Bytes() const noexcept {
771 return reinterpret_cast<unsigned char *>(pixels);
772 }
DC() const773 HDC DC() const noexcept {
774 return hMemDC;
775 }
SetPixel(LONG x,LONG y,DWORD value)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
DIBSection(HDC hdc,SIZE size_)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
~DIBSection()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
SetSymmetric(LONG x,LONG y,DWORD value)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
Proportional(unsigned char a,unsigned char b,float t)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
Proportional(ColourAlpha a,ColourAlpha b,float t)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
GradientValue(const std::vector<ColourStop> & stops,float proportion)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
SizeOfRect(RECT rc)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
AlphaRectangle(PRectangle rc,int cornerSize,ColourDesired fill,int alphaFill,ColourDesired outline,int alphaOutline,int)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
GradientRectangle(PRectangle rc,const std::vector<ColourStop> & stops,GradientOptions options)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
DrawRGBAImage(PRectangle rc,int width,int height,const unsigned char * pixelsImage)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
Ellipse(PRectangle rc,ColourDesired fore,ColourDesired back)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
Copy(PRectangle rc,Point from,Surface & surfaceSource)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
Layout(const IScreenLine *)985 std::unique_ptr<IScreenLineLayout> SurfaceGDI::Layout(const IScreenLine *) {
986 return {};
987 }
988
989 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
990
DrawTextCommon(PRectangle rc,const Font & font_,XYPOSITION ybase,std::string_view text,UINT fuOptions)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
DrawTextNoClip(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)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
DrawTextClipped(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)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
DrawTextTransparent(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore)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
WidthText(Font & font_,std::string_view text)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
MeasureWidths(Font & font_,std::string_view text,XYPOSITION * positions)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
Ascent(Font & font_)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
Descent(Font & font_)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
InternalLeading(Font & font_)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
Height(Font & font_)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
AverageCharWidth(Font & font_)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
SetClip(PRectangle rc)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
FlushCachedState()1127 void SurfaceGDI::FlushCachedState() {
1128 pen = {};
1129 brush = {};
1130 }
1131
SetUnicodeMode(bool unicodeMode_)1132 void SurfaceGDI::SetUnicodeMode(bool unicodeMode_) {
1133 unicodeMode=unicodeMode_;
1134 }
1135
SetDBCSMode(int codePage_)1136 void SurfaceGDI::SetDBCSMode(int codePage_) {
1137 // No action on window as automatically handled by system.
1138 codePage = codePage_;
1139 }
1140
SetBidiR2L(bool)1141 void SurfaceGDI::SetBidiR2L(bool) {
1142 }
1143
1144 #if defined(USE_D2D)
1145
1146 namespace {
1147
RectangleFromPRectangle(PRectangle rc)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
SurfaceD2D()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
~SurfaceD2D()1263 SurfaceD2D::~SurfaceD2D() {
1264 Clear();
1265 }
1266
Clear()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
Release()1284 void SurfaceD2D::Release() {
1285 Clear();
1286 }
1287
SetScale(WindowID wid)1288 void SurfaceD2D::SetScale(WindowID wid) noexcept {
1289 logPixelsY = DpiForWindow(wid);
1290 }
1291
Initialised()1292 bool SurfaceD2D::Initialised() {
1293 return pRenderTarget != nullptr;
1294 }
1295
FlushDrawing()1296 HRESULT SurfaceD2D::FlushDrawing() {
1297 return pRenderTarget->Flush();
1298 }
1299
Init(WindowID wid)1300 void SurfaceD2D::Init(WindowID wid) {
1301 Release();
1302 SetScale(wid);
1303 }
1304
Init(SurfaceID sid,WindowID wid)1305 void SurfaceD2D::Init(SurfaceID sid, WindowID wid) {
1306 Release();
1307 SetScale(wid);
1308 pRenderTarget = static_cast<ID2D1RenderTarget *>(sid);
1309 }
1310
InitPixMap(int width,int height,Surface * surface_,WindowID wid)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
GetBitmap(ID2D1Bitmap ** ppBitmap)1336 HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) {
1337 PLATFORM_ASSERT(pBitmapRenderTarget);
1338 return pBitmapRenderTarget->GetBitmap(ppBitmap);
1339 }
1340
PenColour(ColourDesired fore)1341 void SurfaceD2D::PenColour(ColourDesired fore) {
1342 D2DPenColour(fore);
1343 }
1344
D2DPenColour(ColourDesired fore,int alpha)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
SetFont(const Font & font_)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
LogPixelsY()1387 int SurfaceD2D::LogPixelsY() {
1388 return logPixelsY;
1389 }
1390
DeviceHeightFont(int points)1391 int SurfaceD2D::DeviceHeightFont(int points) {
1392 return ::MulDiv(points, LogPixelsY(), 72);
1393 }
1394
MoveTo(int x_,int y_)1395 void SurfaceD2D::MoveTo(int x_, int y_) {
1396 x = x_;
1397 y = y_;
1398 }
1399
Delta(int difference)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
LineTo(int x_,int y_)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
Polygon(Point * pts,size_t npts,ColourDesired fore,ColourDesired back)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
RectangleDraw(PRectangle rc,ColourDesired fore,ColourDesired back)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
FillRectangle(PRectangle rc,ColourDesired back)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
FillRectangle(PRectangle rc,Surface & surfacePattern)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
RoundedRectangle(PRectangle rc,ColourDesired fore,ColourDesired back)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
AlphaRectangle(PRectangle rc,int cornerSize,ColourDesired fill,int alphaFill,ColourDesired outline,int alphaOutline,int)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
ColorFromColourAlpha(ColourAlpha colour)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
GradientRectangle(PRectangle rc,const std::vector<ColourStop> & stops,GradientOptions options)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
DrawRGBAImage(PRectangle rc,int width,int height,const unsigned char * pixelsImage)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
Ellipse(PRectangle rc,ColourDesired fore,ColourDesired back)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
Copy(PRectangle rc,Point from,Surface & surfaceSource)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:
BlobInline(XYPOSITION width_=0.0f)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;
~BlobInline()1693 virtual ~BlobInline() {
1694 }
1695 };
1696
1697 /// Implement IUnknown
QueryInterface(REFIID riid,PVOID * ppv)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
STDMETHODIMP_(ULONG)1712 STDMETHODIMP_(ULONG) BlobInline::AddRef() {
1713 // Lifetime tied to Platform methods so ignore any reference operations.
1714 return 1;
1715 }
1716
STDMETHODIMP_(ULONG)1717 STDMETHODIMP_(ULONG) BlobInline::Release() {
1718 // Lifetime tied to Platform methods so ignore any reference operations.
1719 return 1;
1720 }
1721
1722 /// Implement IDWriteInlineObject
Draw(void *,IDWriteTextRenderer *,FLOAT,FLOAT,BOOL,BOOL,IUnknown *)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
GetMetrics(DWRITE_INLINE_OBJECT_METRICS * metrics)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
GetOverhangMetrics(DWRITE_OVERHANG_METRICS * overhangs)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
GetBreakConditions(DWRITE_BREAK_CONDITION * breakConditionBefore,DWRITE_BREAK_CONDITION * breakConditionAfter)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
FillTextLayoutFormats(const IScreenLine * screenLine,IDWriteTextLayout * textLayout,std::vector<BlobInline> & blobs)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
ReplaceRepresentation(std::string_view text)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
GetPositionInLayout(std::string_view text,size_t position)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
ScreenLineLayout(const IScreenLine * screenLine)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
~ScreenLineLayout()1910 ScreenLineLayout::~ScreenLineLayout() {
1911 ReleaseUnknown(textLayout);
1912 }
1913
1914 // Get the position from the provided x
1915
PositionFromX(XYPOSITION xDistance,bool charPosition)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
XFromPosition(size_t caretPosition)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
FindRangeIntervals(size_t start,size_t end)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
Layout(const IScreenLine * screenLine)2056 std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) {
2057 return std::make_unique<ScreenLineLayout>(screenLine);
2058 }
2059
DrawTextCommon(PRectangle rc,const Font & font_,XYPOSITION ybase,std::string_view text,UINT fuOptions)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
DrawTextNoClip(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)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
DrawTextClipped(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore,ColourDesired back)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
DrawTextTransparent(PRectangle rc,Font & font_,XYPOSITION ybase,std::string_view text,ColourDesired fore)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
WidthText(Font & font_,std::string_view text)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
MeasureWidths(Font & font_,std::string_view text,XYPOSITION * positions)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
Ascent(Font & font_)2218 XYPOSITION SurfaceD2D::Ascent(Font &font_) {
2219 SetFont(font_);
2220 return std::ceil(yAscent);
2221 }
2222
Descent(Font & font_)2223 XYPOSITION SurfaceD2D::Descent(Font &font_) {
2224 SetFont(font_);
2225 return std::ceil(yDescent);
2226 }
2227
InternalLeading(Font & font_)2228 XYPOSITION SurfaceD2D::InternalLeading(Font &font_) {
2229 SetFont(font_);
2230 return std::floor(yInternalLeading);
2231 }
2232
Height(Font & font_)2233 XYPOSITION SurfaceD2D::Height(Font &font_) {
2234 return Ascent(font_) + Descent(font_);
2235 }
2236
AverageCharWidth(Font & font_)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
SetClip(PRectangle rc)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
FlushCachedState()2265 void SurfaceD2D::FlushCachedState() {
2266 }
2267
SetUnicodeMode(bool unicodeMode_)2268 void SurfaceD2D::SetUnicodeMode(bool unicodeMode_) {
2269 unicodeMode=unicodeMode_;
2270 }
2271
SetDBCSMode(int codePage_)2272 void SurfaceD2D::SetDBCSMode(int codePage_) {
2273 // No action on window as automatically handled by system.
2274 codePage = codePage_;
2275 }
2276
SetBidiR2L(bool)2277 void SurfaceD2D::SetBidiR2L(bool) {
2278 }
2279
2280 #endif
2281
Allocate(int technology)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
~Window()2293 Window::~Window() {
2294 }
2295
Destroy()2296 void Window::Destroy() {
2297 if (wid)
2298 ::DestroyWindow(HwndFromWindowID(wid));
2299 wid = nullptr;
2300 }
2301
GetPosition() const2302 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
SetPosition(PRectangle rc)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
RectFromMonitor(HMONITOR hMonitor)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
SetPositionRelative(PRectangle rc,const Window * relativeTo)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
GetClientPosition() const2365 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
Show(bool show)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
InvalidateAll()2379 void Window::InvalidateAll() {
2380 ::InvalidateRect(HwndFromWindowID(wid), nullptr, FALSE);
2381 }
2382
InvalidateRectangle(PRectangle rc)2383 void Window::InvalidateRectangle(PRectangle rc) {
2384 const RECT rcw = RectFromPRectangle(rc);
2385 ::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE);
2386 }
2387
SetFont(Font & font)2388 void Window::SetFont(Font &font) {
2389 SetWindowFont(HwndFromWindowID(wid), font.GetID(), 0);
2390 }
2391
2392 namespace {
2393
FlipBitmap(HBITMAP bitmap,int width,int height)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
LoadReverseArrowCursor(UINT dpi)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
SetCursor(Cursor curs)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 */
GetMonitorRect(Point pt)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:
Clear()2505 void Clear() noexcept {
2506 words.clear();
2507 data.clear();
2508 }
2509
Get(size_t index) const2510 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 }
Count() const2518 int Count() const noexcept {
2519 return static_cast<int>(data.size());
2520 }
2521
AllocItem(const char * text,int pixId)2522 void AllocItem(const char *text, int pixId) {
2523 ListItemData lid = { text, pixId };
2524 data.push_back(lid);
2525 }
2526
SetWords(const char * s)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
ListBox()2535 ListBox::ListBox() noexcept {
2536 }
2537
~ListBox()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:
ListBoxX()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 }
~ListBoxX()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
Allocate()2624 ListBox *ListBox::Allocate() {
2625 ListBoxX *lb = new ListBoxX();
2626 return lb;
2627 }
2628
Create(Window & parent_,int ctrlID_,Point location_,int lineHeight_,bool unicodeMode_,int technology_)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
SetFont(Font & font)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
SetAverageCharWidth(int width)2665 void ListBoxX::SetAverageCharWidth(int width) {
2666 aveCharWidth = width;
2667 }
2668
SetVisibleRows(int rows)2669 void ListBoxX::SetVisibleRows(int rows) {
2670 desiredVisibleRows = rows;
2671 }
2672
GetVisibleRows() const2673 int ListBoxX::GetVisibleRows() const {
2674 return desiredVisibleRows;
2675 }
2676
GetHWND() const2677 HWND ListBoxX::GetHWND() const noexcept {
2678 return HwndFromWindowID(GetID());
2679 }
2680
GetDesiredRect()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
TextOffset() const2721 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
CaretFromEdge()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
Clear()2732 void ListBoxX::Clear() {
2733 ListBox_ResetContent(lb);
2734 maxItemCharacters = 0;
2735 widestItem = nullptr;
2736 lti.Clear();
2737 }
2738
Append(char *,int)2739 void ListBoxX::Append(char *, int) {
2740 // This method is no longer called in Scintilla
2741 PLATFORM_ASSERT(false);
2742 }
2743
Length()2744 int ListBoxX::Length() {
2745 return lti.Count();
2746 }
2747
Select(int n)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
GetSelection()2759 int ListBoxX::GetSelection() {
2760 return ListBox_GetCurSel(lb);
2761 }
2762
2763 // This is not actually called at present
Find(const char *)2764 int ListBoxX::Find(const char *) {
2765 return LB_ERR;
2766 }
2767
GetValue(int n,char * value,int len)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
RegisterImage(int type,const char * xpm_data)2774 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
2775 XPM xpmImage(xpm_data);
2776 images.Add(type, new RGBAImage(xpmImage));
2777 }
2778
RegisterRGBAImage(int type,int width,int height,const unsigned char * pixelsImage)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
ClearRegisteredImages()2783 void ListBoxX::ClearRegisteredImages() {
2784 images.Clear();
2785 }
2786
Draw(DRAWITEMSTRUCT * pDrawItem)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
AppendListItem(const char * text,const char * numword)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
SetDelegate(IListBoxDelegate * lbDelegate)2886 void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
2887 delegate = lbDelegate;
2888 }
2889
SetList(const char * list,char separator,char typesep)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
AdjustWindowRect(PRectangle * rc,UINT dpi)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
ItemHeight() const2936 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
MinClientWidth() const2945 int ListBoxX::MinClientWidth() const noexcept {
2946 return 12 * (aveCharWidth+aveCharWidth/3);
2947 }
2948
MinTrackSize() const2949 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
MaxTrackSize() const2956 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
SetRedraw(bool on)2967 void ListBoxX::SetRedraw(bool on) noexcept {
2968 ::SendMessage(lb, WM_SETREDRAW, on, 0);
2969 if (on)
2970 ::InvalidateRect(lb, nullptr, TRUE);
2971 }
2972
ResizeToCursor()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
StartResize(WPARAM hitCode)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
NcHitTest(WPARAM wParam,LPARAM lParam) const3060 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
OnDoubleClick()3105 void ListBoxX::OnDoubleClick() {
3106 if (delegate) {
3107 ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
3108 delegate->ListNotify(&event);
3109 }
3110 }
3111
OnSelChange()3112 void ListBoxX::OnSelChange() {
3113 if (delegate) {
3114 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
3115 delegate->ListNotify(&event);
3116 }
3117 }
3118
GetClientExtent() const3119 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
CentreItem(int n)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
Paint(HDC hDC)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
ControlWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)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
WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)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
StaticWndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)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
ListBoxX_Register()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
ListBoxX_Unregister()3384 void ListBoxX_Unregister() noexcept {
3385 if (hinstPlatformRes) {
3386 ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes);
3387 }
3388 }
3389
3390 }
3391
Menu()3392 Menu::Menu() noexcept : mid{} {
3393 }
3394
CreatePopUp()3395 void Menu::CreatePopUp() {
3396 Destroy();
3397 mid = ::CreatePopupMenu();
3398 }
3399
Destroy()3400 void Menu::Destroy() {
3401 if (mid)
3402 ::DestroyMenu(static_cast<HMENU>(mid));
3403 mid = 0;
3404 }
3405
Show(Point pt,Window & w)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:
DynamicLibraryImpl(const char * modulePath)3417 explicit DynamicLibraryImpl(const char *modulePath) noexcept {
3418 h = ::LoadLibraryA(modulePath);
3419 }
3420
~DynamicLibraryImpl()3421 ~DynamicLibraryImpl() override {
3422 if (h)
3423 ::FreeLibrary(h);
3424 }
3425
3426 // Use GetProcAddress to get a pointer to the relevant function.
FindFunction(const char * name)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
IsValid()3440 bool IsValid() noexcept override {
3441 return h != NULL;
3442 }
3443 };
3444
Load(const char * modulePath)3445 DynamicLibrary *DynamicLibrary::Load(const char *modulePath) {
3446 return static_cast<DynamicLibrary *>(new DynamicLibraryImpl(modulePath));
3447 }
3448
Chrome()3449 ColourDesired Platform::Chrome() {
3450 return ColourDesired(::GetSysColor(COLOR_3DFACE));
3451 }
3452
ChromeHighlight()3453 ColourDesired Platform::ChromeHighlight() {
3454 return ColourDesired(::GetSysColor(COLOR_3DHIGHLIGHT));
3455 }
3456
DefaultFont()3457 const char *Platform::DefaultFont() {
3458 return "Verdana";
3459 }
3460
DefaultFontSize()3461 int Platform::DefaultFontSize() {
3462 return 8;
3463 }
3464
DoubleClickTime()3465 unsigned int Platform::DoubleClickTime() {
3466 return ::GetDoubleClickTime();
3467 }
3468
DebugDisplay(const char * s)3469 void Platform::DebugDisplay(const char *s) {
3470 ::OutputDebugStringA(s);
3471 }
3472
3473 //#define TRACE
3474
3475 #ifdef TRACE
DebugPrintf(const char * format,...)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
DebugPrintf(const char *,...)3485 void Platform::DebugPrintf(const char *, ...) {
3486 }
3487 #endif
3488
3489 static bool assertionPopUps = true;
3490
ShowAssertionPopUps(bool assertionPopUps_)3491 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) {
3492 const bool ret = assertionPopUps;
3493 assertionPopUps = assertionPopUps_;
3494 return ret;
3495 }
3496
Assert(const char * c,const char * file,int line)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
Platform_Initialise(void * hInstance)3517 void Platform_Initialise(void *hInstance) noexcept {
3518 hinstPlatformRes = static_cast<HINSTANCE>(hInstance);
3519 LoadDpiForWindow();
3520 ListBoxX_Register();
3521 }
3522
Platform_Finalise(bool fromDllMain)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