Update to Scintilla 5.5.2
[TortoiseGit.git] / ext / scintilla / win32 / PlatWin.cxx
blobb4a2d89110ac5dcb02292af41a15fc1d6e420d96
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 <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstring>
11 #include <cstdio>
12 #include <cstdarg>
13 #include <ctime>
14 #include <cmath>
15 #include <climits>
17 #include <string_view>
18 #include <vector>
19 #include <map>
20 #include <optional>
21 #include <algorithm>
22 #include <iterator>
23 #include <memory>
24 #include <mutex>
26 // Want to use std::min and std::max so don't want Windows.h version of min and max
27 #if !defined(NOMINMAX)
28 #define NOMINMAX
29 #endif
30 #undef _WIN32_WINNT
31 #define _WIN32_WINNT 0x0A00
32 #undef WINVER
33 #define WINVER 0x0A00
34 #define WIN32_LEAN_AND_MEAN 1
35 #include <windows.h>
36 #include <commctrl.h>
37 #include <richedit.h>
38 #include <windowsx.h>
39 #include <shellscalingapi.h>
41 #if !defined(DISABLE_D2D)
42 #define USE_D2D 1
43 #endif
45 #if defined(USE_D2D)
46 #include <d2d1.h>
47 #include <dwrite.h>
48 #endif
50 #include "ScintillaTypes.h"
52 #include "Debugging.h"
53 #include "Geometry.h"
54 #include "Platform.h"
55 #include "XPM.h"
56 #include "UniConversion.h"
57 #include "DBCS.h"
59 #include "WinTypes.h"
60 #include "PlatWin.h"
62 // __uuidof is a Microsoft extension but makes COM code neater, so disable warning
63 #if defined(__clang__)
64 #pragma clang diagnostic ignored "-Wlanguage-extension-token"
65 #endif
67 using namespace Scintilla;
69 namespace Scintilla::Internal {
71 UINT CodePageFromCharSet(CharacterSet characterSet, UINT documentCodePage) noexcept;
73 #if defined(USE_D2D)
74 IDWriteFactory *pIDWriteFactory = nullptr;
75 ID2D1Factory *pD2DFactory = nullptr;
76 D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE;
78 static HMODULE hDLLD2D {};
79 static HMODULE hDLLDWrite {};
81 void LoadD2DOnce() noexcept {
82 DWORD loadLibraryFlags = 0;
83 HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll");
84 if (kernel32) {
85 if (::GetProcAddress(kernel32, "SetDefaultDllDirectories")) {
86 // Availability of SetDefaultDllDirectories implies Windows 8+ or
87 // that KB2533623 has been installed so LoadLibraryEx can be called
88 // with LOAD_LIBRARY_SEARCH_SYSTEM32.
89 loadLibraryFlags = LOAD_LIBRARY_SEARCH_SYSTEM32;
93 typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
94 CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
95 typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
96 IUnknown **factory);
98 hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, loadLibraryFlags);
99 D2D1CFSig fnD2DCF = DLLFunction<D2D1CFSig>(hDLLD2D, "D2D1CreateFactory");
100 if (fnD2DCF) {
101 // A multi threaded factory in case Scintilla is used with multiple GUI threads
102 fnD2DCF(D2D1_FACTORY_TYPE_MULTI_THREADED,
103 __uuidof(ID2D1Factory),
104 nullptr,
105 reinterpret_cast<IUnknown**>(&pD2DFactory));
107 hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, loadLibraryFlags);
108 DWriteCFSig fnDWCF = DLLFunction<DWriteCFSig>(hDLLDWrite, "DWriteCreateFactory");
109 if (fnDWCF) {
110 const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec
111 { 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } };
113 const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
114 IID_IDWriteFactory2,
115 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
116 if (SUCCEEDED(hr)) {
117 // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT
118 d2dDrawTextOptions = static_cast<D2D1_DRAW_TEXT_OPTIONS>(0x00000004);
119 } else {
120 fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
121 __uuidof(IDWriteFactory),
122 reinterpret_cast<IUnknown**>(&pIDWriteFactory));
127 bool LoadD2D() noexcept {
128 static std::once_flag once;
129 try {
130 std::call_once(once, LoadD2DOnce);
131 } catch (...) {
132 // ignore
134 return pIDWriteFactory && pD2DFactory;
137 constexpr D2D_COLOR_F ColorFromColourAlpha(ColourRGBA colour) noexcept {
138 return D2D_COLOR_F{
139 colour.GetRedComponent(),
140 colour.GetGreenComponent(),
141 colour.GetBlueComponent(),
142 colour.GetAlphaComponent()
146 using BrushSolid = std::unique_ptr<ID2D1SolidColorBrush, UnknownReleaser>;
148 BrushSolid BrushSolidCreate(ID2D1RenderTarget *pTarget, COLORREF colour) noexcept {
149 ID2D1SolidColorBrush *pBrush = nullptr;
150 const D2D_COLOR_F col = ColorFromColourAlpha(ColourRGBA::FromRGB(colour));
151 const HRESULT hr = pTarget->CreateSolidColorBrush(col, &pBrush);
152 if (FAILED(hr) || !pBrush) {
153 return {};
155 return BrushSolid(pBrush);
158 using Geometry = std::unique_ptr<ID2D1PathGeometry, UnknownReleaser>;
160 Geometry GeometryCreate() noexcept {
161 ID2D1PathGeometry *geometry = nullptr;
162 const HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry);
163 if (FAILED(hr) || !geometry) {
164 return {};
166 return Geometry(geometry);
169 using GeometrySink = std::unique_ptr<ID2D1GeometrySink, UnknownReleaser>;
171 GeometrySink GeometrySinkCreate(ID2D1PathGeometry *geometry) noexcept {
172 ID2D1GeometrySink *sink = nullptr;
173 const HRESULT hr = geometry->Open(&sink);
174 if (FAILED(hr) || !sink) {
175 return {};
177 return GeometrySink(sink);
180 #endif
182 void *PointerFromWindow(HWND hWnd) noexcept {
183 return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
186 void SetWindowPointer(HWND hWnd, void *ptr) noexcept {
187 ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
190 namespace {
192 // system DPI, same for all monitor.
193 UINT uSystemDPI = USER_DEFAULT_SCREEN_DPI;
195 using GetDpiForWindowSig = UINT(WINAPI *)(HWND hwnd);
196 GetDpiForWindowSig fnGetDpiForWindow = nullptr;
198 HMODULE hDLLShcore {};
199 using GetDpiForMonitorSig = HRESULT (WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY);
200 GetDpiForMonitorSig fnGetDpiForMonitor = nullptr;
202 using GetSystemMetricsForDpiSig = int(WINAPI *)(int nIndex, UINT dpi);
203 GetSystemMetricsForDpiSig fnGetSystemMetricsForDpi = nullptr;
205 using AdjustWindowRectExForDpiSig = BOOL(WINAPI *)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
206 AdjustWindowRectExForDpiSig fnAdjustWindowRectExForDpi = nullptr;
208 using AreDpiAwarenessContextsEqualSig = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
209 AreDpiAwarenessContextsEqualSig fnAreDpiAwarenessContextsEqual = nullptr;
211 using GetWindowDpiAwarenessContextSig = DPI_AWARENESS_CONTEXT(WINAPI *)(HWND);
212 GetWindowDpiAwarenessContextSig fnGetWindowDpiAwarenessContext = nullptr;
214 using GetScaleFactorForMonitorSig = HRESULT(WINAPI *)(HMONITOR, DEVICE_SCALE_FACTOR *);
215 GetScaleFactorForMonitorSig fnGetScaleFactorForMonitor = nullptr;
217 using SetThreadDpiAwarenessContextSig = DPI_AWARENESS_CONTEXT(WINAPI *)(DPI_AWARENESS_CONTEXT);
218 SetThreadDpiAwarenessContextSig fnSetThreadDpiAwarenessContext = nullptr;
220 void LoadDpiForWindow() noexcept {
221 HMODULE user32 = ::GetModuleHandleW(L"user32.dll");
222 fnGetDpiForWindow = DLLFunction<GetDpiForWindowSig>(user32, "GetDpiForWindow");
223 fnGetSystemMetricsForDpi = DLLFunction<GetSystemMetricsForDpiSig>(user32, "GetSystemMetricsForDpi");
224 fnAdjustWindowRectExForDpi = DLLFunction<AdjustWindowRectExForDpiSig>(user32, "AdjustWindowRectExForDpi");
225 fnSetThreadDpiAwarenessContext = DLLFunction<SetThreadDpiAwarenessContextSig>(user32, "SetThreadDpiAwarenessContext");
227 using GetDpiForSystemSig = UINT(WINAPI *)(void);
228 GetDpiForSystemSig fnGetDpiForSystem = DLLFunction<GetDpiForSystemSig>(user32, "GetDpiForSystem");
229 if (fnGetDpiForSystem) {
230 uSystemDPI = fnGetDpiForSystem();
231 } else {
232 HDC hdcMeasure = ::CreateCompatibleDC({});
233 uSystemDPI = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
234 ::DeleteDC(hdcMeasure);
237 fnGetWindowDpiAwarenessContext = DLLFunction<GetWindowDpiAwarenessContextSig>(user32, "GetWindowDpiAwarenessContext");
238 fnAreDpiAwarenessContextsEqual = DLLFunction<AreDpiAwarenessContextsEqualSig>(user32, "AreDpiAwarenessContextsEqual");
240 hDLLShcore = ::LoadLibraryExW(L"shcore.dll", {}, LOAD_LIBRARY_SEARCH_SYSTEM32);
241 if (hDLLShcore) {
242 fnGetScaleFactorForMonitor = DLLFunction<GetScaleFactorForMonitorSig>(hDLLShcore, "GetScaleFactorForMonitor");
243 fnGetDpiForMonitor = DLLFunction<GetDpiForMonitorSig>(hDLLShcore, "GetDpiForMonitor");
247 HINSTANCE hinstPlatformRes {};
249 constexpr Supports SupportsGDI[] = {
250 Supports::PixelModification,
253 constexpr BYTE Win32MapFontQuality(FontQuality extraFontFlag) noexcept {
254 switch (extraFontFlag & FontQuality::QualityMask) {
256 case FontQuality::QualityNonAntialiased:
257 return NONANTIALIASED_QUALITY;
259 case FontQuality::QualityAntialiased:
260 return ANTIALIASED_QUALITY;
262 case FontQuality::QualityLcdOptimized:
263 return CLEARTYPE_QUALITY;
265 default:
266 return DEFAULT_QUALITY;
270 #if defined(USE_D2D)
271 constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(FontQuality extraFontFlag) noexcept {
272 switch (extraFontFlag & FontQuality::QualityMask) {
274 case FontQuality::QualityNonAntialiased:
275 return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
277 case FontQuality::QualityAntialiased:
278 return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
280 case FontQuality::QualityLcdOptimized:
281 return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
283 default:
284 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
287 #endif
289 // Both GDI and DirectWrite can produce a HFONT for use in list boxes
290 struct FontWin : public Font {
291 virtual HFONT HFont() const noexcept = 0;
294 void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, XYPOSITION size, FontWeight weight, bool italic, FontQuality extraFontFlag) {
295 lf = LOGFONTW();
296 // The negative is to allow for leading
297 lf.lfHeight = -(std::abs(std::lround(size)));
298 lf.lfWeight = static_cast<LONG>(weight);
299 lf.lfItalic = italic ? 1 : 0;
300 lf.lfCharSet = static_cast<BYTE>(characterSet);
301 lf.lfQuality = Win32MapFontQuality(extraFontFlag);
302 UTF16FromUTF8(faceName, lf.lfFaceName, LF_FACESIZE);
305 struct FontGDI : public FontWin {
306 HFONT hfont = {};
307 FontGDI(const FontParameters &fp) {
308 LOGFONTW lf;
309 SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
310 hfont = ::CreateFontIndirectW(&lf);
312 // Deleted so FontGDI objects can not be copied.
313 FontGDI(const FontGDI &) = delete;
314 FontGDI(FontGDI &&) = delete;
315 FontGDI &operator=(const FontGDI &) = delete;
316 FontGDI &operator=(FontGDI &&) = delete;
317 ~FontGDI() noexcept override {
318 if (hfont)
319 ::DeleteObject(hfont);
321 HFONT HFont() const noexcept override {
322 // Duplicating hfont
323 LOGFONTW lf = {};
324 if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
325 return {};
327 return ::CreateFontIndirectW(&lf);
331 #if defined(USE_D2D)
332 struct FontDirectWrite : public FontWin {
333 IDWriteTextFormat *pTextFormat = nullptr;
334 FontQuality extraFontFlag = FontQuality::QualityDefault;
335 CharacterSet characterSet = CharacterSet::Ansi;
336 FLOAT yAscent = 2.0f;
337 FLOAT yDescent = 1.0f;
338 FLOAT yInternalLeading = 0.0f;
340 FontDirectWrite(const FontParameters &fp) :
341 extraFontFlag(fp.extraFontFlag),
342 characterSet(fp.characterSet) {
343 const std::wstring wsFace = WStringFromUTF8(fp.faceName);
344 const std::wstring wsLocale = WStringFromUTF8(fp.localeName);
345 const FLOAT fHeight = static_cast<FLOAT>(fp.size);
346 const DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
347 HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr,
348 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
349 style,
350 static_cast<DWRITE_FONT_STRETCH>(fp.stretch),
351 fHeight, wsLocale.c_str(), &pTextFormat);
352 if (hr == E_INVALIDARG) {
353 // Possibly a bad locale name like "/" so try "en-us".
354 hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr,
355 static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
356 style,
357 static_cast<DWRITE_FONT_STRETCH>(fp.stretch),
358 fHeight, L"en-us", &pTextFormat);
360 if (SUCCEEDED(hr)) {
361 pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
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;
375 FLOAT emHeight;
376 hr = pTextLayout->GetFontSize(0, &emHeight);
377 if (SUCCEEDED(hr)) {
378 yInternalLeading = lineMetrics[0].height - emHeight;
381 ReleaseUnknown(pTextLayout);
382 pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline);
386 // Deleted so FontDirectWrite objects can not be copied.
387 FontDirectWrite(const FontDirectWrite &) = delete;
388 FontDirectWrite(FontDirectWrite &&) = delete;
389 FontDirectWrite &operator=(const FontDirectWrite &) = delete;
390 FontDirectWrite &operator=(FontDirectWrite &&) = delete;
391 ~FontDirectWrite() noexcept override {
392 ReleaseUnknown(pTextFormat);
394 HFONT HFont() const noexcept override {
395 LOGFONTW lf = {};
396 const HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
397 if (!SUCCEEDED(hr)) {
398 return {};
400 lf.lfWeight = pTextFormat->GetFontWeight();
401 lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
402 lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
403 return ::CreateFontIndirectW(&lf);
406 int CodePageText(int codePage) const noexcept {
407 if (!(codePage == CpUtf8) && (characterSet != CharacterSet::Ansi)) {
408 codePage = CodePageFromCharSet(characterSet, codePage);
410 return codePage;
413 static const FontDirectWrite *Cast(const Font *font_) {
414 const FontDirectWrite *pfm = dynamic_cast<const FontDirectWrite *>(font_);
415 PLATFORM_ASSERT(pfm);
416 if (!pfm) {
417 throw std::runtime_error("SurfaceD2D::SetFont: wrong Font type.");
419 return pfm;
422 #endif
426 HMONITOR MonitorFromWindowHandleScaling(HWND hWnd) noexcept {
427 constexpr DWORD monitorFlags = MONITOR_DEFAULTTONEAREST;
429 if (!fnSetThreadDpiAwarenessContext) {
430 return ::MonitorFromWindow(hWnd, monitorFlags);
433 // Temporarily switching to PerMonitorV2 to retrieve correct monitor via MonitorFromRect() in case of active GDI scaling.
434 const DPI_AWARENESS_CONTEXT oldContext = fnSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
435 PLATFORM_ASSERT(oldContext != nullptr);
437 RECT rect;
438 ::GetWindowRect(hWnd, &rect);
439 const HMONITOR monitor = ::MonitorFromRect(&rect, monitorFlags);
441 fnSetThreadDpiAwarenessContext(oldContext);
442 return monitor;
445 float GetDeviceScaleFactorWhenGdiScalingActive(HWND hWnd) noexcept {
446 if (fnAreDpiAwarenessContextsEqual) {
447 PLATFORM_ASSERT(fnGetWindowDpiAwarenessContext && fnGetScaleFactorForMonitor);
448 if (fnAreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, fnGetWindowDpiAwarenessContext(hWnd))) {
449 const HWND hRootWnd = ::GetAncestor(hWnd, GA_ROOT); // Scale factor applies to entire (root) window.
450 const HMONITOR hMonitor = MonitorFromWindowHandleScaling(hRootWnd);
451 DEVICE_SCALE_FACTOR deviceScaleFactor;
452 if (S_OK == fnGetScaleFactorForMonitor(hMonitor, &deviceScaleFactor))
453 return static_cast<int>(deviceScaleFactor) / 100.f;
456 return 1.f;
459 std::shared_ptr<Font> Font::Allocate(const FontParameters &fp) {
460 #if defined(USE_D2D)
461 if (fp.technology != Technology::Default) {
462 return std::make_shared<FontDirectWrite>(fp);
464 #endif
465 return std::make_shared<FontGDI>(fp);
468 // Buffer to hold strings and string position arrays without always allocating on heap.
469 // May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
470 // when less than safe size otherwise allocate on heap and free automatically.
471 template<typename T, int lengthStandard>
472 class VarBuffer {
473 T bufferStandard[lengthStandard];
474 public:
475 T *buffer;
476 explicit VarBuffer(size_t length) : buffer(nullptr) {
477 if (length > lengthStandard) {
478 buffer = new T[length];
479 } else {
480 buffer = bufferStandard;
483 // Deleted so VarBuffer objects can not be copied.
484 VarBuffer(const VarBuffer &) = delete;
485 VarBuffer(VarBuffer &&) = delete;
486 VarBuffer &operator=(const VarBuffer &) = delete;
487 VarBuffer &operator=(VarBuffer &&) = delete;
489 ~VarBuffer() noexcept {
490 if (buffer != bufferStandard) {
491 delete []buffer;
492 buffer = nullptr;
497 constexpr int stackBufferLength = 400;
498 class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
499 public:
500 int tlen; // Using int instead of size_t as most Win32 APIs take int.
501 TextWide(std::string_view text, int codePage) :
502 VarBuffer<wchar_t, stackBufferLength>(text.length()) {
503 if (codePage == CpUtf8) {
504 tlen = static_cast<int>(UTF16FromUTF8(text, buffer, text.length()));
505 } else {
506 // Support Asian string display in 9x English
507 tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast<int>(text.length()),
508 buffer, static_cast<int>(text.length()));
512 typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
514 UINT DpiForWindow(WindowID wid) noexcept {
515 if (fnGetDpiForWindow) {
516 return fnGetDpiForWindow(HwndFromWindowID(wid));
518 if (fnGetDpiForMonitor) {
519 HMONITOR hMonitor = ::MonitorFromWindow(HwndFromWindowID(wid), MONITOR_DEFAULTTONEAREST);
520 UINT dpiX = 0;
521 UINT dpiY = 0;
522 if (fnGetDpiForMonitor(hMonitor, 0 /*MDT_EFFECTIVE_DPI*/, &dpiX, &dpiY) == S_OK) {
523 return dpiY;
526 return uSystemDPI;
529 int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept {
530 if (fnGetSystemMetricsForDpi) {
531 return fnGetSystemMetricsForDpi(nIndex, dpi);
534 int value = ::GetSystemMetrics(nIndex);
535 value = (dpi == uSystemDPI) ? value : ::MulDiv(value, dpi, uSystemDPI);
536 return value;
539 class SurfaceGDI : public Surface {
540 SurfaceMode mode;
541 HDC hdc{};
542 bool hdcOwned=false;
543 HPEN pen{};
544 HPEN penOld{};
545 HBRUSH brush{};
546 HBRUSH brushOld{};
547 HFONT fontOld{};
548 HBITMAP bitmap{};
549 HBITMAP bitmapOld{};
551 int logPixelsY = USER_DEFAULT_SCREEN_DPI;
553 static constexpr int maxWidthMeasure = INT_MAX;
554 // There appears to be a 16 bit string length limit in GDI on NT.
555 static constexpr int maxLenText = 65535;
557 void PenColour(ColourRGBA fore, XYPOSITION widthStroke) noexcept;
559 void BrushColour(ColourRGBA back) noexcept;
560 void SetFont(const Font *font_);
561 void Clear() noexcept;
563 public:
564 SurfaceGDI() noexcept;
565 SurfaceGDI(HDC hdcCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept;
566 // Deleted so SurfaceGDI objects can not be copied.
567 SurfaceGDI(const SurfaceGDI &) = delete;
568 SurfaceGDI(SurfaceGDI &&) = delete;
569 SurfaceGDI &operator=(const SurfaceGDI &) = delete;
570 SurfaceGDI &operator=(SurfaceGDI &&) = delete;
572 ~SurfaceGDI() noexcept override;
574 void Init(WindowID wid) override;
575 void Init(SurfaceID sid, WindowID wid) override;
576 std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
578 void SetMode(SurfaceMode mode_) override;
580 void Release() noexcept override;
581 int SupportsFeature(Supports feature) noexcept override;
582 bool Initialised() override;
583 int LogPixelsY() override;
584 int PixelDivisions() override;
585 int DeviceHeightFont(int points) override;
586 void LineDraw(Point start, Point end, Stroke stroke) override;
587 void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
588 void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
589 void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
590 void RectangleFrame(PRectangle rc, Stroke stroke) override;
591 void FillRectangle(PRectangle rc, Fill fill) override;
592 void FillRectangleAligned(PRectangle rc, Fill fill) override;
593 void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
594 void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
595 void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
596 void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
597 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
598 void Ellipse(PRectangle rc, FillStroke fillStroke) override;
599 void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
600 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
602 std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
604 void DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
605 void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
606 void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
607 void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
608 void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
609 XYPOSITION WidthText(const Font *font_, std::string_view text) override;
611 void DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
612 void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
613 void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
614 void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
615 void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
616 XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
618 XYPOSITION Ascent(const Font *font_) override;
619 XYPOSITION Descent(const Font *font_) override;
620 XYPOSITION InternalLeading(const Font *font_) override;
621 XYPOSITION Height(const Font *font_) override;
622 XYPOSITION AverageCharWidth(const Font *font_) override;
624 void SetClip(PRectangle rc) override;
625 void PopClip() override;
626 void FlushCachedState() override;
627 void FlushDrawing() override;
630 SurfaceGDI::SurfaceGDI() noexcept {
633 SurfaceGDI::SurfaceGDI(HDC hdcCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept {
634 hdc = ::CreateCompatibleDC(hdcCompatible);
635 hdcOwned = true;
636 bitmap = ::CreateCompatibleBitmap(hdcCompatible, width, height);
637 bitmapOld = SelectBitmap(hdc, bitmap);
638 ::SetTextAlign(hdc, TA_BASELINE);
639 mode = mode_;
640 logPixelsY = logPixelsY_;
643 SurfaceGDI::~SurfaceGDI() noexcept {
644 Clear();
647 void SurfaceGDI::Clear() noexcept {
648 if (penOld) {
649 ::SelectObject(hdc, penOld);
650 ::DeleteObject(pen);
651 penOld = {};
653 pen = {};
654 if (brushOld) {
655 ::SelectObject(hdc, brushOld);
656 ::DeleteObject(brush);
657 brushOld = {};
659 brush = {};
660 if (fontOld) {
661 // Fonts are not deleted as they are owned by a Font object
662 ::SelectObject(hdc, fontOld);
663 fontOld = {};
665 if (bitmapOld) {
666 ::SelectObject(hdc, bitmapOld);
667 ::DeleteObject(bitmap);
668 bitmapOld = {};
670 bitmap = {};
671 if (hdcOwned) {
672 ::DeleteDC(hdc);
673 hdc = {};
674 hdcOwned = false;
678 void SurfaceGDI::Release() noexcept {
679 Clear();
682 int SurfaceGDI::SupportsFeature(Supports feature) noexcept {
683 for (const Supports f : SupportsGDI) {
684 if (f == feature)
685 return 1;
687 return 0;
690 bool SurfaceGDI::Initialised() {
691 return hdc != 0;
694 void SurfaceGDI::Init(WindowID wid) {
695 Release();
696 hdc = ::CreateCompatibleDC({});
697 hdcOwned = true;
698 ::SetTextAlign(hdc, TA_BASELINE);
699 logPixelsY = DpiForWindow(wid);
702 void SurfaceGDI::Init(SurfaceID sid, WindowID wid) {
703 Release();
704 hdc = static_cast<HDC>(sid);
705 ::SetTextAlign(hdc, TA_BASELINE);
706 // Windows on screen are scaled but printers are not.
707 const bool printing = ::GetDeviceCaps(hdc, TECHNOLOGY) != DT_RASDISPLAY;
708 logPixelsY = printing ? ::GetDeviceCaps(hdc, LOGPIXELSY) : DpiForWindow(wid);
711 std::unique_ptr<Surface> SurfaceGDI::AllocatePixMap(int width, int height) {
712 return std::make_unique<SurfaceGDI>(hdc, width, height, mode, logPixelsY);
715 void SurfaceGDI::SetMode(SurfaceMode mode_) {
716 mode = mode_;
719 void SurfaceGDI::PenColour(ColourRGBA fore, XYPOSITION widthStroke) noexcept {
720 if (pen) {
721 ::SelectObject(hdc, penOld);
722 ::DeleteObject(pen);
723 pen = {};
724 penOld = {};
726 const DWORD penWidth = std::lround(widthStroke);
727 const COLORREF penColour = fore.OpaqueRGB();
728 if (widthStroke > 1) {
729 const LOGBRUSH brushParameters{ BS_SOLID, penColour, 0 };
730 pen = ::ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER,
731 penWidth,
732 &brushParameters,
734 nullptr);
735 } else {
736 pen = ::CreatePen(PS_INSIDEFRAME, penWidth, penColour);
738 penOld = SelectPen(hdc, pen);
741 void SurfaceGDI::BrushColour(ColourRGBA back) noexcept {
742 if (brush) {
743 ::SelectObject(hdc, brushOld);
744 ::DeleteObject(brush);
745 brush = {};
746 brushOld = {};
748 brush = ::CreateSolidBrush(back.OpaqueRGB());
749 brushOld = SelectBrush(hdc, brush);
752 void SurfaceGDI::SetFont(const Font *font_) {
753 const FontGDI *pfm = dynamic_cast<const FontGDI *>(font_);
754 PLATFORM_ASSERT(pfm);
755 if (!pfm) {
756 throw std::runtime_error("SurfaceGDI::SetFont: wrong Font type.");
758 if (fontOld) {
759 SelectFont(hdc, pfm->hfont);
760 } else {
761 fontOld = SelectFont(hdc, pfm->hfont);
765 int SurfaceGDI::LogPixelsY() {
766 return logPixelsY;
769 int SurfaceGDI::PixelDivisions() {
770 // Win32 uses device pixels.
771 return 1;
774 int SurfaceGDI::DeviceHeightFont(int points) {
775 return ::MulDiv(points, LogPixelsY(), 72);
778 void SurfaceGDI::LineDraw(Point start, Point end, Stroke stroke) {
779 PenColour(stroke.colour, stroke.width);
780 ::MoveToEx(hdc, std::lround(std::floor(start.x)), std::lround(std::floor(start.y)), nullptr);
781 ::LineTo(hdc, std::lround(std::floor(end.x)), std::lround(std::floor(end.y)));
784 void SurfaceGDI::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
785 PLATFORM_ASSERT(npts > 1);
786 if (npts <= 1) {
787 return;
789 PenColour(stroke.colour, stroke.width);
790 std::vector<POINT> outline;
791 std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
792 ::Polyline(hdc, outline.data(), static_cast<int>(npts));
795 void SurfaceGDI::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
796 PenColour(fillStroke.stroke.colour.WithoutAlpha(), fillStroke.stroke.width);
797 BrushColour(fillStroke.fill.colour.WithoutAlpha());
798 std::vector<POINT> outline;
799 std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
800 ::Polygon(hdc, outline.data(), static_cast<int>(npts));
803 void SurfaceGDI::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
804 RectangleFrame(rc, fillStroke.stroke);
805 FillRectangle(rc.Inset(fillStroke.stroke.width), fillStroke.fill.colour);
808 void SurfaceGDI::RectangleFrame(PRectangle rc, Stroke stroke) {
809 BrushColour(stroke.colour);
810 const RECT rcw = RectFromPRectangle(rc);
811 ::FrameRect(hdc, &rcw, brush);
814 void SurfaceGDI::FillRectangle(PRectangle rc, Fill fill) {
815 if (fill.colour.IsOpaque()) {
816 // Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
817 // There is no need to allocate a brush either.
818 const RECT rcw = RectFromPRectangle(rc);
819 ::SetBkColor(hdc, fill.colour.OpaqueRGB());
820 ::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, nullptr);
821 } else {
822 AlphaRectangle(rc, 0, FillStroke(fill.colour));
826 void SurfaceGDI::FillRectangleAligned(PRectangle rc, Fill fill) {
827 FillRectangle(PixelAlign(rc, 1), fill);
830 void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
831 HBRUSH br;
832 if (SurfaceGDI *psgdi = dynamic_cast<SurfaceGDI *>(&surfacePattern); psgdi && psgdi->bitmap) {
833 br = ::CreatePatternBrush(psgdi->bitmap);
834 } else { // Something is wrong so display in red
835 br = ::CreateSolidBrush(RGB(0xff, 0, 0));
837 const RECT rcw = RectFromPRectangle(rc);
838 ::FillRect(hdc, &rcw, br);
839 ::DeleteObject(br);
842 void SurfaceGDI::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
843 PenColour(fillStroke.stroke.colour, fillStroke.stroke.width);
844 BrushColour(fillStroke.fill.colour);
845 const RECT rcw = RectFromPRectangle(rc);
846 ::RoundRect(hdc,
847 rcw.left + 1, rcw.top,
848 rcw.right - 1, rcw.bottom,
849 8, 8);
852 namespace {
854 constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept {
855 return (a << 24) | (r << 16) | (g << 8) | b;
858 constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept {
859 return static_cast<byte>(component * alpha / 255);
862 constexpr DWORD dwordMultiplied(ColourRGBA colour) noexcept {
863 return dwordFromBGRA(
864 AlphaScaled(colour.GetBlue(), colour.GetAlpha()),
865 AlphaScaled(colour.GetGreen(), colour.GetAlpha()),
866 AlphaScaled(colour.GetRed(), colour.GetAlpha()),
867 colour.GetAlpha());
870 class DIBSection {
871 HDC hMemDC {};
872 HBITMAP hbmMem {};
873 HBITMAP hbmOld {};
874 SIZE size {};
875 DWORD *pixels = nullptr;
876 public:
877 DIBSection(HDC hdc, SIZE size_) noexcept;
878 // Deleted so DIBSection objects can not be copied.
879 DIBSection(const DIBSection&) = delete;
880 DIBSection(DIBSection&&) = delete;
881 DIBSection &operator=(const DIBSection&) = delete;
882 DIBSection &operator=(DIBSection&&) = delete;
883 ~DIBSection() noexcept;
884 operator bool() const noexcept {
885 return hMemDC && hbmMem && pixels;
887 DWORD *Pixels() const noexcept {
888 return pixels;
890 unsigned char *Bytes() const noexcept {
891 return reinterpret_cast<unsigned char *>(pixels);
893 HDC DC() const noexcept {
894 return hMemDC;
896 void SetPixel(LONG x, LONG y, DWORD value) noexcept {
897 PLATFORM_ASSERT(x >= 0);
898 PLATFORM_ASSERT(y >= 0);
899 PLATFORM_ASSERT(x < size.cx);
900 PLATFORM_ASSERT(y < size.cy);
901 pixels[y * size.cx + x] = value;
903 void SetSymmetric(LONG x, LONG y, DWORD value) noexcept;
906 DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept {
907 hMemDC = ::CreateCompatibleDC(hdc);
908 if (!hMemDC) {
909 return;
912 size = size_;
914 // -size.y makes bitmap start from top
915 const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), size.cx, -size.cy, 1, 32, BI_RGB, 0, 0, 0, 0, 0},
916 {{0, 0, 0, 0}} };
917 void *image = nullptr;
918 hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, &image, {}, 0);
919 if (!hbmMem || !image) {
920 return;
922 pixels = static_cast<DWORD *>(image);
923 hbmOld = SelectBitmap(hMemDC, hbmMem);
926 DIBSection::~DIBSection() noexcept {
927 if (hbmOld) {
928 SelectBitmap(hMemDC, hbmOld);
929 hbmOld = {};
931 if (hbmMem) {
932 ::DeleteObject(hbmMem);
933 hbmMem = {};
935 if (hMemDC) {
936 ::DeleteDC(hMemDC);
937 hMemDC = {};
941 void DIBSection::SetSymmetric(LONG x, LONG y, DWORD value) noexcept {
942 // Plot a point symmetrically to all 4 quadrants
943 const LONG xSymmetric = size.cx - 1 - x;
944 const LONG ySymmetric = size.cy - 1 - y;
945 SetPixel(x, y, value);
946 SetPixel(xSymmetric, y, value);
947 SetPixel(x, ySymmetric, value);
948 SetPixel(xSymmetric, ySymmetric, value);
951 ColourRGBA GradientValue(const std::vector<ColourStop> &stops, XYPOSITION proportion) noexcept {
952 for (size_t stop = 0; stop < stops.size() - 1; stop++) {
953 // Loop through each pair of stops
954 const XYPOSITION positionStart = stops[stop].position;
955 const XYPOSITION positionEnd = stops[stop + 1].position;
956 if ((proportion >= positionStart) && (proportion <= positionEnd)) {
957 const XYPOSITION proportionInPair = (proportion - positionStart) /
958 (positionEnd - positionStart);
959 return stops[stop].colour.MixedWith(stops[stop + 1].colour, proportionInPair);
962 // Loop should always find a value
963 return ColourRGBA();
966 constexpr SIZE SizeOfRect(RECT rc) noexcept {
967 return { rc.right - rc.left, rc.bottom - rc.top };
970 constexpr BLENDFUNCTION mergeAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
974 void SurfaceGDI::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
975 // TODO: Implement strokeWidth
976 const RECT rcw = RectFromPRectangle(rc);
977 const SIZE size = SizeOfRect(rcw);
979 if (size.cx > 0) {
981 DIBSection section(hdc, size);
983 if (section) {
985 // Ensure not distorted too much by corners when small
986 const LONG corner = std::min(static_cast<LONG>(cornerSize), (std::min(size.cx, size.cy) / 2) - 2);
988 constexpr DWORD valEmpty = dwordFromBGRA(0,0,0,0);
989 const DWORD valFill = dwordMultiplied(fillStroke.fill.colour);
990 const DWORD valOutline = dwordMultiplied(fillStroke.stroke.colour);
992 // Draw a framed rectangle
993 for (int y=0; y<size.cy; y++) {
994 for (int x=0; x<size.cx; x++) {
995 if ((x==0) || (x==size.cx-1) || (y == 0) || (y == size.cy -1)) {
996 section.SetPixel(x, y, valOutline);
997 } else {
998 section.SetPixel(x, y, valFill);
1003 // Make the corners transparent
1004 for (LONG c=0; c<corner; c++) {
1005 for (LONG x=0; x<c+1; x++) {
1006 section.SetSymmetric(x, c - x, valEmpty);
1010 // Draw the corner frame pieces
1011 for (LONG x=1; x<corner; x++) {
1012 section.SetSymmetric(x, corner - x, valOutline);
1015 GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
1017 } else {
1018 BrushColour(fillStroke.stroke.colour);
1019 FrameRect(hdc, &rcw, brush);
1023 void SurfaceGDI::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
1025 const RECT rcw = RectFromPRectangle(rc);
1026 const SIZE size = SizeOfRect(rcw);
1028 DIBSection section(hdc, size);
1030 if (section) {
1032 if (options == GradientOptions::topToBottom) {
1033 for (LONG y = 0; y < size.cy; y++) {
1034 // Find y/height proportional colour
1035 const XYPOSITION proportion = y / (rc.Height() - 1.0f);
1036 const ColourRGBA mixed = GradientValue(stops, proportion);
1037 const DWORD valFill = dwordMultiplied(mixed);
1038 for (LONG x = 0; x < size.cx; x++) {
1039 section.SetPixel(x, y, valFill);
1042 } else {
1043 for (LONG x = 0; x < size.cx; x++) {
1044 // Find x/width proportional colour
1045 const XYPOSITION proportion = x / (rc.Width() - 1.0f);
1046 const ColourRGBA mixed = GradientValue(stops, proportion);
1047 const DWORD valFill = dwordMultiplied(mixed);
1048 for (LONG y = 0; y < size.cy; y++) {
1049 section.SetPixel(x, y, valFill);
1054 GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
1058 void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1059 if (rc.Width() > 0) {
1060 if (rc.Width() > width)
1061 rc.left += std::floor((rc.Width() - width) / 2);
1062 rc.right = rc.left + width;
1063 if (rc.Height() > height)
1064 rc.top += std::floor((rc.Height() - height) / 2);
1065 rc.bottom = rc.top + height;
1067 const SIZE size { width, height };
1068 DIBSection section(hdc, size);
1069 if (section) {
1070 RGBAImage::BGRAFromRGBA(section.Bytes(), pixelsImage, static_cast<size_t>(width) * height);
1071 GdiAlphaBlend(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
1072 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), section.DC(),
1073 0, 0, width, height, mergeAlpha);
1078 void SurfaceGDI::Ellipse(PRectangle rc, FillStroke fillStroke) {
1079 PenColour(fillStroke.stroke.colour, fillStroke.stroke.width);
1080 BrushColour(fillStroke.fill.colour);
1081 const RECT rcw = RectFromPRectangle(rc);
1082 ::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
1085 void SurfaceGDI::Stadium(PRectangle rc, FillStroke fillStroke, [[maybe_unused]] Ends ends) {
1086 // TODO: Implement properly - the rectangle is just a placeholder
1087 RectangleDraw(rc, fillStroke);
1090 void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1091 ::BitBlt(hdc,
1092 static_cast<int>(rc.left), static_cast<int>(rc.top),
1093 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()),
1094 dynamic_cast<SurfaceGDI &>(surfaceSource).hdc,
1095 static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY);
1098 std::unique_ptr<IScreenLineLayout> SurfaceGDI::Layout(const IScreenLine *) {
1099 return {};
1102 typedef VarBuffer<int, stackBufferLength> TextPositionsI;
1104 void SurfaceGDI::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
1105 SetFont(font_);
1106 const RECT rcw = RectFromPRectangle(rc);
1107 const int x = static_cast<int>(rc.left);
1108 const int yBaseInt = static_cast<int>(ybase);
1110 if (mode.codePage == CpUtf8) {
1111 const TextWide tbuf(text, mode.codePage);
1112 ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
1113 } else {
1114 ::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, text.data(), static_cast<UINT>(text.length()), nullptr);
1118 void SurfaceGDI::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1119 ColourRGBA fore, ColourRGBA back) {
1120 ::SetTextColor(hdc, fore.OpaqueRGB());
1121 ::SetBkColor(hdc, back.OpaqueRGB());
1122 DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE);
1125 void SurfaceGDI::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1126 ColourRGBA fore, ColourRGBA back) {
1127 ::SetTextColor(hdc, fore.OpaqueRGB());
1128 ::SetBkColor(hdc, back.OpaqueRGB());
1129 DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
1132 void SurfaceGDI::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1133 ColourRGBA fore) {
1134 // Avoid drawing spaces in transparent mode
1135 for (const char ch : text) {
1136 if (ch != ' ') {
1137 ::SetTextColor(hdc, fore.OpaqueRGB());
1138 ::SetBkMode(hdc, TRANSPARENT);
1139 DrawTextCommon(rc, font_, ybase, text, 0);
1140 ::SetBkMode(hdc, OPAQUE);
1141 return;
1146 void SurfaceGDI::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
1147 // Zero positions to avoid random behaviour on failure.
1148 std::fill(positions, positions + text.length(), 0.0f);
1149 SetFont(font_);
1150 SIZE sz = { 0,0 };
1151 int fit = 0;
1152 int i = 0;
1153 const int len = static_cast<int>(text.length());
1154 if (mode.codePage == CpUtf8) {
1155 const TextWide tbuf(text, mode.codePage);
1156 TextPositionsI poses(tbuf.tlen);
1157 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1158 // Failure
1159 return;
1161 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1162 for (int ui = 0; ui < fit; ui++) {
1163 const unsigned char uch = text[i];
1164 const unsigned int byteCount = UTF8BytesOfLead[uch];
1165 if (byteCount == 4) { // Non-BMP
1166 ui++;
1168 for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) {
1169 positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
1172 } else {
1173 TextPositionsI poses(len);
1174 if (!::GetTextExtentExPointA(hdc, text.data(), len, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1175 // Eeek - a NULL DC or other foolishness could cause this.
1176 return;
1178 while (i < fit) {
1179 positions[i] = static_cast<XYPOSITION>(poses.buffer[i]);
1180 i++;
1183 // If any positions not filled in then use the last position for them
1184 const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
1185 std::fill(positions + i, positions + text.length(), lastPos);
1188 XYPOSITION SurfaceGDI::WidthText(const Font *font_, std::string_view text) {
1189 SetFont(font_);
1190 SIZE sz = { 0,0 };
1191 if (!(mode.codePage == CpUtf8)) {
1192 ::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz);
1193 } else {
1194 const TextWide tbuf(text, mode.codePage);
1195 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
1197 return static_cast<XYPOSITION>(sz.cx);
1200 void SurfaceGDI::DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
1201 SetFont(font_);
1202 const RECT rcw = RectFromPRectangle(rc);
1203 const int x = static_cast<int>(rc.left);
1204 const int yBaseInt = static_cast<int>(ybase);
1206 const TextWide tbuf(text, CpUtf8);
1207 ::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
1210 void SurfaceGDI::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1211 ColourRGBA fore, ColourRGBA back) {
1212 ::SetTextColor(hdc, fore.OpaqueRGB());
1213 ::SetBkColor(hdc, back.OpaqueRGB());
1214 DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE);
1217 void SurfaceGDI::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1218 ColourRGBA fore, ColourRGBA back) {
1219 ::SetTextColor(hdc, fore.OpaqueRGB());
1220 ::SetBkColor(hdc, back.OpaqueRGB());
1221 DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
1224 void SurfaceGDI::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
1225 ColourRGBA fore) {
1226 // Avoid drawing spaces in transparent mode
1227 for (const char ch : text) {
1228 if (ch != ' ') {
1229 ::SetTextColor(hdc, fore.OpaqueRGB());
1230 ::SetBkMode(hdc, TRANSPARENT);
1231 DrawTextCommonUTF8(rc, font_, ybase, text, 0);
1232 ::SetBkMode(hdc, OPAQUE);
1233 return;
1238 void SurfaceGDI::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
1239 // Zero positions to avoid random behaviour on failure.
1240 std::fill(positions, positions + text.length(), 0.0f);
1241 SetFont(font_);
1242 SIZE sz = { 0,0 };
1243 int fit = 0;
1244 int i = 0;
1245 const int len = static_cast<int>(text.length());
1246 const TextWide tbuf(text, CpUtf8);
1247 TextPositionsI poses(tbuf.tlen);
1248 if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
1249 // Failure
1250 return;
1252 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
1253 for (int ui = 0; ui < fit; ui++) {
1254 const unsigned char uch = text[i];
1255 const unsigned int byteCount = UTF8BytesOfLead[uch];
1256 if (byteCount == 4) { // Non-BMP
1257 ui++;
1259 for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) {
1260 positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
1263 // If any positions not filled in then use the last position for them
1264 const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
1265 std::fill(positions + i, positions + text.length(), lastPos);
1268 XYPOSITION SurfaceGDI::WidthTextUTF8(const Font *font_, std::string_view text) {
1269 SetFont(font_);
1270 SIZE sz = { 0,0 };
1271 const TextWide tbuf(text, CpUtf8);
1272 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
1273 return static_cast<XYPOSITION>(sz.cx);
1276 XYPOSITION SurfaceGDI::Ascent(const Font *font_) {
1277 SetFont(font_);
1278 TEXTMETRIC tm;
1279 ::GetTextMetrics(hdc, &tm);
1280 return static_cast<XYPOSITION>(tm.tmAscent);
1283 XYPOSITION SurfaceGDI::Descent(const Font *font_) {
1284 SetFont(font_);
1285 TEXTMETRIC tm;
1286 ::GetTextMetrics(hdc, &tm);
1287 return static_cast<XYPOSITION>(tm.tmDescent);
1290 XYPOSITION SurfaceGDI::InternalLeading(const Font *font_) {
1291 SetFont(font_);
1292 TEXTMETRIC tm;
1293 ::GetTextMetrics(hdc, &tm);
1294 return static_cast<XYPOSITION>(tm.tmInternalLeading);
1297 XYPOSITION SurfaceGDI::Height(const Font *font_) {
1298 SetFont(font_);
1299 TEXTMETRIC tm;
1300 ::GetTextMetrics(hdc, &tm);
1301 return static_cast<XYPOSITION>(tm.tmHeight);
1304 XYPOSITION SurfaceGDI::AverageCharWidth(const Font *font_) {
1305 SetFont(font_);
1306 TEXTMETRIC tm;
1307 ::GetTextMetrics(hdc, &tm);
1308 return static_cast<XYPOSITION>(tm.tmAveCharWidth);
1311 void SurfaceGDI::SetClip(PRectangle rc) {
1312 ::SaveDC(hdc);
1313 ::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
1314 static_cast<int>(rc.right), static_cast<int>(rc.bottom));
1317 void SurfaceGDI::PopClip() {
1318 ::RestoreDC(hdc, -1);
1321 void SurfaceGDI::FlushCachedState() {
1322 pen = {};
1323 brush = {};
1326 void SurfaceGDI::FlushDrawing() {
1329 #if defined(USE_D2D)
1331 namespace {
1333 constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept {
1334 return {
1335 static_cast<FLOAT>(rc.left),
1336 static_cast<FLOAT>(rc.top),
1337 static_cast<FLOAT>(rc.right),
1338 static_cast<FLOAT>(rc.bottom)
1342 constexpr D2D1_POINT_2F DPointFromPoint(Point point) noexcept {
1343 return { static_cast<FLOAT>(point.x), static_cast<FLOAT>(point.y) };
1346 constexpr Supports SupportsD2D[] = {
1347 Supports::LineDrawsFinal,
1348 Supports::FractionalStrokeWidth,
1349 Supports::TranslucentStroke,
1350 Supports::PixelModification,
1351 Supports::ThreadSafeMeasureWidths,
1354 constexpr D2D1_RECT_F RectangleInset(D2D1_RECT_F rect, FLOAT inset) noexcept {
1355 return D2D1_RECT_F{
1356 rect.left + inset,
1357 rect.top + inset,
1358 rect.right - inset,
1359 rect.bottom - inset };
1364 class BlobInline;
1366 class SurfaceD2D : public Surface, public ISetRenderingParams {
1367 SurfaceMode mode;
1369 ID2D1RenderTarget *pRenderTarget = nullptr;
1370 ID2D1BitmapRenderTarget *pBitmapRenderTarget = nullptr;
1371 bool ownRenderTarget = false;
1372 int clipsActive = 0;
1374 ID2D1SolidColorBrush *pBrush = nullptr;
1376 static constexpr FontQuality invalidFontQuality = FontQuality::QualityMask;
1377 FontQuality fontQuality = invalidFontQuality;
1378 int logPixelsY = USER_DEFAULT_SCREEN_DPI;
1379 int deviceScaleFactor = 1;
1380 std::shared_ptr<RenderingParams> renderingParams;
1382 void Clear() noexcept;
1383 void SetFontQuality(FontQuality extraFontFlag);
1384 HRESULT GetBitmap(ID2D1Bitmap **ppBitmap);
1385 void SetDeviceScaleFactor(const ID2D1RenderTarget *const pRenderTarget) noexcept;
1387 public:
1388 SurfaceD2D() noexcept;
1389 SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept;
1390 // Deleted so SurfaceD2D objects can not be copied.
1391 SurfaceD2D(const SurfaceD2D &) = delete;
1392 SurfaceD2D(SurfaceD2D &&) = delete;
1393 SurfaceD2D &operator=(const SurfaceD2D &) = delete;
1394 SurfaceD2D &operator=(SurfaceD2D &&) = delete;
1395 ~SurfaceD2D() noexcept override;
1397 void SetScale(WindowID wid) noexcept;
1398 void Init(WindowID wid) override;
1399 void Init(SurfaceID sid, WindowID wid) override;
1400 std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
1402 void SetMode(SurfaceMode mode_) override;
1404 void Release() noexcept override;
1405 int SupportsFeature(Supports feature) noexcept override;
1406 bool Initialised() override;
1408 void D2DPenColourAlpha(ColourRGBA fore) noexcept;
1409 int LogPixelsY() override;
1410 int PixelDivisions() override;
1411 int DeviceHeightFont(int points) override;
1412 void LineDraw(Point start, Point end, Stroke stroke) override;
1413 static Geometry GeometricFigure(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept;
1414 void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
1415 void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
1416 void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
1417 void RectangleFrame(PRectangle rc, Stroke stroke) override;
1418 void FillRectangle(PRectangle rc, Fill fill) override;
1419 void FillRectangleAligned(PRectangle rc, Fill fill) override;
1420 void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
1421 void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
1422 void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
1423 void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
1424 void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
1425 void Ellipse(PRectangle rc, FillStroke fillStroke) override;
1426 void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
1427 void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
1429 std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
1431 void DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, int codePageOverride, UINT fuOptions);
1433 void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
1434 void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
1435 void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
1436 void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
1437 XYPOSITION WidthText(const Font *font_, std::string_view text) override;
1439 void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
1440 void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
1441 void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
1442 void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
1443 XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
1445 XYPOSITION Ascent(const Font *font_) override;
1446 XYPOSITION Descent(const Font *font_) override;
1447 XYPOSITION InternalLeading(const Font *font_) override;
1448 XYPOSITION Height(const Font *font_) override;
1449 XYPOSITION AverageCharWidth(const Font *font_) override;
1451 void SetClip(PRectangle rc) override;
1452 void PopClip() override;
1453 void FlushCachedState() override;
1454 void FlushDrawing() override;
1456 void SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) override;
1459 SurfaceD2D::SurfaceD2D() noexcept {
1462 SurfaceD2D::SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept {
1463 const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height));
1464 D2D1_PIXEL_FORMAT desiredFormat;
1465 #ifdef __MINGW32__
1466 desiredFormat.format = DXGI_FORMAT_UNKNOWN;
1467 #else
1468 desiredFormat = pRenderTargetCompatible->GetPixelFormat();
1469 #endif
1470 desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
1471 const HRESULT hr = pRenderTargetCompatible->CreateCompatibleRenderTarget(
1472 &desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pBitmapRenderTarget);
1473 if (SUCCEEDED(hr)) {
1474 pRenderTarget = pBitmapRenderTarget;
1475 SetDeviceScaleFactor(pRenderTarget);
1476 pRenderTarget->BeginDraw();
1477 ownRenderTarget = true;
1479 mode = mode_;
1480 logPixelsY = logPixelsY_;
1483 SurfaceD2D::~SurfaceD2D() noexcept {
1484 Clear();
1487 void SurfaceD2D::Clear() noexcept {
1488 ReleaseUnknown(pBrush);
1489 if (pRenderTarget) {
1490 while (clipsActive) {
1491 pRenderTarget->PopAxisAlignedClip();
1492 clipsActive--;
1494 if (ownRenderTarget) {
1495 pRenderTarget->EndDraw();
1496 ReleaseUnknown(pRenderTarget);
1497 ownRenderTarget = false;
1499 pRenderTarget = nullptr;
1501 pBitmapRenderTarget = nullptr;
1504 void SurfaceD2D::Release() noexcept {
1505 Clear();
1508 void SurfaceD2D::SetScale(WindowID wid) noexcept {
1509 fontQuality = invalidFontQuality;
1510 logPixelsY = DpiForWindow(wid);
1513 int SurfaceD2D::SupportsFeature(Supports feature) noexcept {
1514 for (const Supports f : SupportsD2D) {
1515 if (f == feature)
1516 return 1;
1518 return 0;
1521 bool SurfaceD2D::Initialised() {
1522 return pRenderTarget != nullptr;
1525 void SurfaceD2D::Init(WindowID wid) {
1526 Release();
1527 SetScale(wid);
1530 void SurfaceD2D::Init(SurfaceID sid, WindowID wid) {
1531 Release();
1532 SetScale(wid);
1533 pRenderTarget = static_cast<ID2D1RenderTarget *>(sid);
1534 SetDeviceScaleFactor(pRenderTarget);
1537 std::unique_ptr<Surface> SurfaceD2D::AllocatePixMap(int width, int height) {
1538 std::unique_ptr<SurfaceD2D> surf = std::make_unique<SurfaceD2D>(pRenderTarget, width, height, mode, logPixelsY);
1539 surf->SetRenderingParams(renderingParams);
1540 return surf;
1543 void SurfaceD2D::SetMode(SurfaceMode mode_) {
1544 mode = mode_;
1547 HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) {
1548 PLATFORM_ASSERT(pBitmapRenderTarget);
1549 return pBitmapRenderTarget->GetBitmap(ppBitmap);
1552 void SurfaceD2D::D2DPenColourAlpha(ColourRGBA fore) noexcept {
1553 if (pRenderTarget) {
1554 const D2D_COLOR_F col = ColorFromColourAlpha(fore);
1555 if (pBrush) {
1556 pBrush->SetColor(col);
1557 } else {
1558 const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
1559 if (!SUCCEEDED(hr)) {
1560 ReleaseUnknown(pBrush);
1566 void SurfaceD2D::SetFontQuality(FontQuality extraFontFlag) {
1567 if ((fontQuality != extraFontFlag) && renderingParams) {
1568 fontQuality = extraFontFlag;
1569 const D2D1_TEXT_ANTIALIAS_MODE aaMode = DWriteMapFontQuality(extraFontFlag);
1570 if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && renderingParams->customRenderingParams) {
1571 pRenderTarget->SetTextRenderingParams(renderingParams->customRenderingParams.get());
1572 } else if (renderingParams->defaultRenderingParams) {
1573 pRenderTarget->SetTextRenderingParams(renderingParams->defaultRenderingParams.get());
1575 pRenderTarget->SetTextAntialiasMode(aaMode);
1579 int SurfaceD2D::LogPixelsY() {
1580 return logPixelsY;
1583 void SurfaceD2D::SetDeviceScaleFactor(const ID2D1RenderTarget *const pD2D1RenderTarget) noexcept {
1584 FLOAT dpiX = 0.f;
1585 FLOAT dpiY = 0.f;
1586 pD2D1RenderTarget->GetDpi(&dpiX, &dpiY);
1587 deviceScaleFactor = static_cast<int>(dpiX / 96.f);
1590 int SurfaceD2D::PixelDivisions() {
1591 return deviceScaleFactor;
1594 int SurfaceD2D::DeviceHeightFont(int points) {
1595 return ::MulDiv(points, LogPixelsY(), 72);
1598 void SurfaceD2D::LineDraw(Point start, Point end, Stroke stroke) {
1599 D2DPenColourAlpha(stroke.colour);
1601 D2D1_STROKE_STYLE_PROPERTIES strokeProps {};
1602 strokeProps.startCap = D2D1_CAP_STYLE_SQUARE;
1603 strokeProps.endCap = D2D1_CAP_STYLE_SQUARE;
1604 strokeProps.dashCap = D2D1_CAP_STYLE_FLAT;
1605 strokeProps.lineJoin = D2D1_LINE_JOIN_MITER;
1606 strokeProps.miterLimit = 4.0f;
1607 strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID;
1608 strokeProps.dashOffset = 0;
1610 // get the stroke style to apply
1611 ID2D1StrokeStyle *pStrokeStyle = nullptr;
1612 const HRESULT hr = pD2DFactory->CreateStrokeStyle(
1613 strokeProps, nullptr, 0, &pStrokeStyle);
1614 if (SUCCEEDED(hr)) {
1615 pRenderTarget->DrawLine(
1616 DPointFromPoint(start),
1617 DPointFromPoint(end), pBrush, stroke.WidthF(), pStrokeStyle);
1620 ReleaseUnknown(pStrokeStyle);
1623 Geometry SurfaceD2D::GeometricFigure(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept {
1624 Geometry geometry = GeometryCreate();
1625 if (geometry) {
1626 if (const GeometrySink sink = GeometrySinkCreate(geometry.get())) {
1627 sink->BeginFigure(DPointFromPoint(pts[0]), figureBegin);
1628 for (size_t i = 1; i < npts; i++) {
1629 sink->AddLine(DPointFromPoint(pts[i]));
1631 sink->EndFigure((figureBegin == D2D1_FIGURE_BEGIN_FILLED) ?
1632 D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN);
1633 sink->Close();
1636 return geometry;
1639 void SurfaceD2D::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
1640 PLATFORM_ASSERT(pRenderTarget && (npts > 1));
1641 if (!pRenderTarget || (npts <= 1)) {
1642 return;
1645 const Geometry geometry = GeometricFigure(pts, npts, D2D1_FIGURE_BEGIN_HOLLOW);
1646 PLATFORM_ASSERT(geometry);
1647 if (!geometry) {
1648 return;
1651 D2DPenColourAlpha(stroke.colour);
1652 D2D1_STROKE_STYLE_PROPERTIES strokeProps {};
1653 strokeProps.startCap = D2D1_CAP_STYLE_ROUND;
1654 strokeProps.endCap = D2D1_CAP_STYLE_ROUND;
1655 strokeProps.dashCap = D2D1_CAP_STYLE_FLAT;
1656 strokeProps.lineJoin = D2D1_LINE_JOIN_MITER;
1657 strokeProps.miterLimit = 4.0f;
1658 strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID;
1659 strokeProps.dashOffset = 0;
1661 // get the stroke style to apply
1662 ID2D1StrokeStyle *pStrokeStyle = nullptr;
1663 const HRESULT hr = pD2DFactory->CreateStrokeStyle(
1664 strokeProps, nullptr, 0, &pStrokeStyle);
1665 if (SUCCEEDED(hr)) {
1666 pRenderTarget->DrawGeometry(geometry.get(), pBrush, stroke.WidthF(), pStrokeStyle);
1668 ReleaseUnknown(pStrokeStyle);
1671 void SurfaceD2D::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
1672 PLATFORM_ASSERT(pRenderTarget && (npts > 2));
1673 if (pRenderTarget) {
1674 const Geometry geometry = GeometricFigure(pts, npts, D2D1_FIGURE_BEGIN_FILLED);
1675 PLATFORM_ASSERT(geometry);
1676 if (geometry) {
1677 D2DPenColourAlpha(fillStroke.fill.colour);
1678 pRenderTarget->FillGeometry(geometry.get(), pBrush);
1679 D2DPenColourAlpha(fillStroke.stroke.colour);
1680 pRenderTarget->DrawGeometry(geometry.get(), pBrush, fillStroke.stroke.WidthF());
1685 void SurfaceD2D::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
1686 if (!pRenderTarget)
1687 return;
1688 const D2D1_RECT_F rect = RectangleFromPRectangle(rc);
1689 const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF());
1690 const float halfStroke = fillStroke.stroke.WidthF() / 2.0f;
1691 const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke);
1693 D2DPenColourAlpha(fillStroke.fill.colour);
1694 pRenderTarget->FillRectangle(&rectFill, pBrush);
1695 D2DPenColourAlpha(fillStroke.stroke.colour);
1696 pRenderTarget->DrawRectangle(&rectOutline, pBrush, fillStroke.stroke.WidthF());
1699 void SurfaceD2D::RectangleFrame(PRectangle rc, Stroke stroke) {
1700 if (pRenderTarget) {
1701 const XYPOSITION halfStroke = stroke.width / 2.0f;
1702 const D2D1_RECT_F rectangle1 = RectangleFromPRectangle(rc.Inset(halfStroke));
1703 D2DPenColourAlpha(stroke.colour);
1704 pRenderTarget->DrawRectangle(&rectangle1, pBrush, stroke.WidthF());
1708 void SurfaceD2D::FillRectangle(PRectangle rc, Fill fill) {
1709 if (pRenderTarget) {
1710 D2DPenColourAlpha(fill.colour);
1711 const D2D1_RECT_F rectangle = RectangleFromPRectangle(rc);
1712 pRenderTarget->FillRectangle(&rectangle, pBrush);
1716 void SurfaceD2D::FillRectangleAligned(PRectangle rc, Fill fill) {
1717 FillRectangle(PixelAlign(rc, PixelDivisions()), fill);
1720 void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
1721 SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(&surfacePattern);
1722 PLATFORM_ASSERT(psurfOther);
1723 if (!psurfOther) {
1724 throw std::runtime_error("SurfaceD2D::FillRectangle: wrong Surface type.");
1726 ID2D1Bitmap *pBitmap = nullptr;
1727 HRESULT hr = psurfOther->GetBitmap(&pBitmap);
1728 if (SUCCEEDED(hr) && pBitmap) {
1729 ID2D1BitmapBrush *pBitmapBrush = nullptr;
1730 const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
1731 D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
1732 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
1733 // Create the bitmap brush.
1734 hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
1735 ReleaseUnknown(pBitmap);
1736 if (SUCCEEDED(hr) && pBitmapBrush) {
1737 pRenderTarget->FillRectangle(
1738 RectangleFromPRectangle(rc),
1739 pBitmapBrush);
1740 ReleaseUnknown(pBitmapBrush);
1745 void SurfaceD2D::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
1746 if (pRenderTarget) {
1747 const FLOAT minDimension = static_cast<FLOAT>(std::min(rc.Width(), rc.Height())) / 2.0f;
1748 const FLOAT radius = std::min(4.0f, minDimension);
1749 if (fillStroke.fill.colour == fillStroke.stroke.colour) {
1750 const D2D1_ROUNDED_RECT roundedRectFill = {
1751 RectangleFromPRectangle(rc),
1752 radius, radius };
1753 D2DPenColourAlpha(fillStroke.fill.colour);
1754 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1755 } else {
1756 const D2D1_ROUNDED_RECT roundedRectFill = {
1757 RectangleFromPRectangle(rc.Inset(1.0)),
1758 radius-1, radius-1 };
1759 D2DPenColourAlpha(fillStroke.fill.colour);
1760 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1762 const D2D1_ROUNDED_RECT roundedRect = {
1763 RectangleFromPRectangle(rc.Inset(0.5)),
1764 radius, radius };
1765 D2DPenColourAlpha(fillStroke.stroke.colour);
1766 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.WidthF());
1771 void SurfaceD2D::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
1772 const D2D1_RECT_F rect = RectangleFromPRectangle(rc);
1773 const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF());
1774 const float halfStroke = fillStroke.stroke.WidthF() / 2.0f;
1775 const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke);
1776 if (pRenderTarget) {
1777 if (cornerSize == 0) {
1778 // When corner size is zero, draw square rectangle to prevent blurry pixels at corners
1779 D2DPenColourAlpha(fillStroke.fill.colour);
1780 pRenderTarget->FillRectangle(rectFill, pBrush);
1782 D2DPenColourAlpha(fillStroke.stroke.colour);
1783 pRenderTarget->DrawRectangle(rectOutline, pBrush, fillStroke.stroke.WidthF());
1784 } else {
1785 const float cornerSizeF = static_cast<float>(cornerSize);
1786 const D2D1_ROUNDED_RECT roundedRectFill = {
1787 rectFill, cornerSizeF - 1.0f, cornerSizeF - 1.0f };
1788 D2DPenColourAlpha(fillStroke.fill.colour);
1789 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1791 const D2D1_ROUNDED_RECT roundedRect = {
1792 rectOutline, cornerSizeF, cornerSizeF};
1793 D2DPenColourAlpha(fillStroke.stroke.colour);
1794 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.WidthF());
1799 void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
1800 if (pRenderTarget) {
1801 D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp {
1802 DPointFromPoint(Point(rc.left, rc.top)), {}
1804 switch (options) {
1805 case GradientOptions::leftToRight:
1806 lgbp.endPoint = DPointFromPoint(Point(rc.right, rc.top));
1807 break;
1808 case GradientOptions::topToBottom:
1809 default:
1810 lgbp.endPoint = DPointFromPoint(Point(rc.left, rc.bottom));
1811 break;
1814 std::vector<D2D1_GRADIENT_STOP> gradientStops;
1815 for (const ColourStop &stop : stops) {
1816 gradientStops.push_back({ static_cast<FLOAT>(stop.position), ColorFromColourAlpha(stop.colour) });
1818 ID2D1GradientStopCollection *pGradientStops = nullptr;
1819 HRESULT hr = pRenderTarget->CreateGradientStopCollection(
1820 gradientStops.data(), static_cast<UINT32>(gradientStops.size()), &pGradientStops);
1821 if (FAILED(hr) || !pGradientStops) {
1822 return;
1824 ID2D1LinearGradientBrush *pBrushLinear = nullptr;
1825 hr = pRenderTarget->CreateLinearGradientBrush(
1826 lgbp, pGradientStops, &pBrushLinear);
1827 if (SUCCEEDED(hr) && pBrushLinear) {
1828 const D2D1_RECT_F rectangle = RectangleFromPRectangle(PRectangle(
1829 std::round(rc.left), rc.top, std::round(rc.right), rc.bottom));
1830 pRenderTarget->FillRectangle(&rectangle, pBrushLinear);
1831 ReleaseUnknown(pBrushLinear);
1833 ReleaseUnknown(pGradientStops);
1837 void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
1838 if (pRenderTarget) {
1839 if (rc.Width() > width)
1840 rc.left += std::floor((rc.Width() - width) / 2);
1841 rc.right = rc.left + width;
1842 if (rc.Height() > height)
1843 rc.top += std::floor((rc.Height() - height) / 2);
1844 rc.bottom = rc.top + height;
1846 std::vector<unsigned char> image(RGBAImage::bytesPerPixel * height * width);
1847 RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast<ptrdiff_t>(height) * width);
1849 ID2D1Bitmap *bitmap = nullptr;
1850 const D2D1_SIZE_U size = D2D1::SizeU(width, height);
1851 const D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
1852 D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
1853 const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(),
1854 width * 4, &props, &bitmap);
1855 if (SUCCEEDED(hr)) {
1856 const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc);
1857 pRenderTarget->DrawBitmap(bitmap, rcDestination);
1858 ReleaseUnknown(bitmap);
1863 void SurfaceD2D::Ellipse(PRectangle rc, FillStroke fillStroke) {
1864 if (!pRenderTarget)
1865 return;
1866 const D2D1_POINT_2F centre = DPointFromPoint(rc.Centre());
1868 const FLOAT radiusFill = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width);
1869 const D2D1_ELLIPSE ellipseFill = { centre, radiusFill, radiusFill };
1871 D2DPenColourAlpha(fillStroke.fill.colour);
1872 pRenderTarget->FillEllipse(ellipseFill, pBrush);
1874 const FLOAT radiusOutline = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width / 2.0f);
1875 const D2D1_ELLIPSE ellipseOutline = { centre, radiusOutline, radiusOutline };
1877 D2DPenColourAlpha(fillStroke.stroke.colour);
1878 pRenderTarget->DrawEllipse(ellipseOutline, pBrush, fillStroke.stroke.WidthF());
1881 void SurfaceD2D::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
1882 if (!pRenderTarget)
1883 return;
1884 if (rc.Width() < rc.Height()) {
1885 // Can't draw nice ends so just draw a rectangle
1886 RectangleDraw(rc, fillStroke);
1887 return;
1889 const FLOAT radius = static_cast<FLOAT>(rc.Height() / 2.0);
1890 const FLOAT radiusFill = radius - fillStroke.stroke.WidthF();
1891 const FLOAT halfStroke = fillStroke.stroke.WidthF() / 2.0f;
1892 if (ends == Surface::Ends::semiCircles) {
1893 const D2D1_RECT_F rect = RectangleFromPRectangle(rc);
1894 const D2D1_ROUNDED_RECT roundedRectFill = { RectangleInset(rect, fillStroke.stroke.WidthF()),
1895 radiusFill, radiusFill };
1896 D2DPenColourAlpha(fillStroke.fill.colour);
1897 pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
1899 const D2D1_ROUNDED_RECT roundedRect = { RectangleInset(rect, halfStroke),
1900 radius, radius };
1901 D2DPenColourAlpha(fillStroke.stroke.colour);
1902 pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.WidthF());
1903 } else {
1904 const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf);
1905 const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0);
1906 PRectangle rcInner = rc;
1907 rcInner.left += radius;
1908 rcInner.right -= radius;
1909 const Geometry pathGeometry = GeometryCreate();
1910 if (!pathGeometry)
1911 return;
1912 if (const GeometrySink pSink = GeometrySinkCreate(pathGeometry.get())) {
1913 switch (leftSide) {
1914 case Ends::leftFlat:
1915 pSink->BeginFigure(DPointFromPoint(Point(rc.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED);
1916 pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.bottom - halfStroke)));
1917 break;
1918 case Ends::leftAngle:
1919 pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED);
1920 pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.Centre().y)));
1921 pSink->AddLine(DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke)));
1922 break;
1923 case Ends::semiCircles:
1924 default: {
1925 pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED);
1926 D2D1_ARC_SEGMENT segment{};
1927 segment.point = DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke));
1928 segment.size = D2D1::SizeF(radiusFill, radiusFill);
1929 segment.rotationAngle = 0.0f;
1930 segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
1931 segment.arcSize = D2D1_ARC_SIZE_SMALL;
1932 pSink->AddArc(segment);
1934 break;
1937 switch (rightSide) {
1938 case Ends::rightFlat:
1939 pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.bottom - halfStroke)));
1940 pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.top + halfStroke)));
1941 break;
1942 case Ends::rightAngle:
1943 pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke)));
1944 pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.Centre().y)));
1945 pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke)));
1946 break;
1947 case Ends::semiCircles:
1948 default: {
1949 pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke)));
1950 D2D1_ARC_SEGMENT segment{};
1951 segment.point = DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke));
1952 segment.size = D2D1::SizeF(radiusFill, radiusFill);
1953 segment.rotationAngle = 0.0f;
1954 segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
1955 segment.arcSize = D2D1_ARC_SIZE_SMALL;
1956 pSink->AddArc(segment);
1958 break;
1961 pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
1963 pSink->Close();
1965 D2DPenColourAlpha(fillStroke.fill.colour);
1966 pRenderTarget->FillGeometry(pathGeometry.get(), pBrush);
1967 D2DPenColourAlpha(fillStroke.stroke.colour);
1968 pRenderTarget->DrawGeometry(pathGeometry.get(), pBrush, fillStroke.stroke.WidthF());
1972 void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
1973 SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource);
1974 ID2D1Bitmap *pBitmap = nullptr;
1975 const HRESULT hr = surfOther.GetBitmap(&pBitmap);
1976 if (SUCCEEDED(hr) && pBitmap) {
1977 const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc);
1978 const D2D1_RECT_F rcSource = RectangleFromPRectangle(PRectangle(
1979 from.x, from.y, from.x + rc.Width(), from.y + rc.Height()));
1980 pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
1981 D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
1982 ReleaseUnknown(pBitmap);
1986 class BlobInline final : public IDWriteInlineObject {
1987 XYPOSITION width;
1989 // IUnknown
1990 STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
1991 STDMETHODIMP_(ULONG)AddRef() override;
1992 STDMETHODIMP_(ULONG)Release() override;
1994 // IDWriteInlineObject
1995 COM_DECLSPEC_NOTHROW STDMETHODIMP Draw(
1996 void *clientDrawingContext,
1997 IDWriteTextRenderer *renderer,
1998 FLOAT originX,
1999 FLOAT originY,
2000 BOOL isSideways,
2001 BOOL isRightToLeft,
2002 IUnknown *clientDrawingEffect
2003 ) override;
2004 COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override;
2005 COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override;
2006 COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions(
2007 DWRITE_BREAK_CONDITION *breakConditionBefore,
2008 DWRITE_BREAK_CONDITION *breakConditionAfter) override;
2009 public:
2010 BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) {
2014 /// Implement IUnknown
2015 STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) {
2016 if (!ppv)
2017 return E_POINTER;
2018 // Never called so not checked.
2019 *ppv = nullptr;
2020 if (riid == IID_IUnknown)
2021 *ppv = this;
2022 if (riid == __uuidof(IDWriteInlineObject))
2023 *ppv = this;
2024 if (!*ppv)
2025 return E_NOINTERFACE;
2026 return S_OK;
2029 STDMETHODIMP_(ULONG) BlobInline::AddRef() {
2030 // Lifetime tied to Platform methods so ignore any reference operations.
2031 return 1;
2034 STDMETHODIMP_(ULONG) BlobInline::Release() {
2035 // Lifetime tied to Platform methods so ignore any reference operations.
2036 return 1;
2039 /// Implement IDWriteInlineObject
2040 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw(
2041 void*,
2042 IDWriteTextRenderer*,
2043 FLOAT,
2044 FLOAT,
2045 BOOL,
2046 BOOL,
2047 IUnknown*) {
2048 // Since not performing drawing, not necessary to implement
2049 // Could be implemented by calling back into platform-independent code.
2050 // This would allow more of the drawing to be mediated through DirectWrite.
2051 return S_OK;
2054 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics(
2055 DWRITE_INLINE_OBJECT_METRICS *metrics
2057 if (!metrics)
2058 return E_POINTER;
2059 metrics->width = static_cast<FLOAT>(width);
2060 metrics->height = 2;
2061 metrics->baseline = 1;
2062 metrics->supportsSideways = FALSE;
2063 return S_OK;
2066 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics(
2067 DWRITE_OVERHANG_METRICS *overhangs
2069 if (!overhangs)
2070 return E_POINTER;
2071 overhangs->left = 0;
2072 overhangs->top = 0;
2073 overhangs->right = 0;
2074 overhangs->bottom = 0;
2075 return S_OK;
2078 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions(
2079 DWRITE_BREAK_CONDITION *breakConditionBefore,
2080 DWRITE_BREAK_CONDITION *breakConditionAfter
2082 if (!breakConditionBefore || !breakConditionAfter)
2083 return E_POINTER;
2084 // Since not performing 2D layout, not necessary to implement
2085 *breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL;
2086 *breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL;
2087 return S_OK;
2090 class ScreenLineLayout : public IScreenLineLayout {
2091 IDWriteTextLayout *textLayout = nullptr;
2092 std::string text;
2093 std::wstring buffer;
2094 std::vector<BlobInline> blobs;
2095 static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs);
2096 static std::wstring ReplaceRepresentation(std::string_view text);
2097 static size_t GetPositionInLayout(std::string_view text, size_t position);
2098 public:
2099 ScreenLineLayout(const IScreenLine *screenLine);
2100 // Deleted so ScreenLineLayout objects can not be copied
2101 ScreenLineLayout(const ScreenLineLayout &) = delete;
2102 ScreenLineLayout(ScreenLineLayout &&) = delete;
2103 ScreenLineLayout &operator=(const ScreenLineLayout &) = delete;
2104 ScreenLineLayout &operator=(ScreenLineLayout &&) = delete;
2105 ~ScreenLineLayout() noexcept override;
2106 size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override;
2107 XYPOSITION XFromPosition(size_t caretPosition) override;
2108 std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override;
2111 // Each char can have its own style, so we fill the textLayout with the textFormat of each char
2113 void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) {
2114 // Reserve enough entries up front so they are not moved and the pointers handed
2115 // to textLayout remain valid.
2116 const ptrdiff_t numRepresentations = screenLine->RepresentationCount();
2117 std::string_view text = screenLine->Text();
2118 const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t');
2119 blobs.reserve(numRepresentations + numTabs);
2121 UINT32 layoutPosition = 0;
2123 for (size_t bytePosition = 0; bytePosition < screenLine->Length();) {
2124 const unsigned char uch = screenLine->Text()[bytePosition];
2125 const unsigned int byteCount = UTF8BytesOfLead[uch];
2126 const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
2127 const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits };
2129 XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition);
2130 if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) {
2131 D2D1_POINT_2F realPt {};
2132 DWRITE_HIT_TEST_METRICS realCaretMetrics {};
2133 textLayout->HitTestTextPosition(
2134 layoutPosition,
2135 false, // trailing if false, else leading edge
2136 &realPt.x,
2137 &realPt.y,
2138 &realCaretMetrics
2141 const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x);
2142 representationWidth = nextTab - realPt.x;
2144 if (representationWidth > 0.0f) {
2145 blobs.push_back(BlobInline(representationWidth));
2146 textLayout->SetInlineObject(&blobs.back(), textRange);
2149 const FontDirectWrite *pfm =
2150 dynamic_cast<const FontDirectWrite *>(screenLine->FontOfPosition(bytePosition));
2151 if (!pfm) {
2152 throw std::runtime_error("FillTextLayoutFormats: wrong Font type.");
2155 const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength();
2156 std::wstring fontFamilyName(fontFamilyNameSize, 0);
2157 const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1);
2158 if (SUCCEEDED(hrFamily)) {
2159 textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange);
2162 textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange);
2163 textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange);
2164 textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange);
2166 const unsigned int localeNameSize = pfm->pTextFormat->GetLocaleNameLength();
2167 std::wstring localeName(localeNameSize, 0);
2168 const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize + 1);
2169 if (SUCCEEDED(hrLocale)) {
2170 textLayout->SetLocaleName(localeName.c_str(), textRange);
2173 textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange);
2175 IDWriteFontCollection *fontCollection = nullptr;
2176 if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) {
2177 textLayout->SetFontCollection(fontCollection, textRange);
2180 bytePosition += byteCount;
2181 layoutPosition += codeUnits;
2186 /* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */
2188 std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) {
2189 const TextWide wideText(text, CpUtf8);
2190 std::wstring ws(wideText.buffer, wideText.tlen);
2191 std::replace(ws.begin(), ws.end(), L'\t', L'X');
2192 return ws;
2195 // Finds the position in the wide character version of the text.
2197 size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) {
2198 const std::string_view textUptoPosition = text.substr(0, position);
2199 return UTF16Length(textUptoPosition);
2202 ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) {
2203 // If the text is empty, then no need to go through this function
2204 if (!screenLine || !screenLine->Length())
2205 return;
2207 text = screenLine->Text();
2209 // Get textFormat
2210 const FontDirectWrite *pfm = FontDirectWrite::Cast(screenLine->FontOfPosition(0));
2211 if (!pfm->pTextFormat) {
2212 return;
2215 // Convert the string to wstring and replace the original control characters with their representative chars.
2216 buffer = ReplaceRepresentation(screenLine->Text());
2218 // Create a text layout
2219 const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(
2220 buffer.c_str(),
2221 static_cast<UINT32>(buffer.length()),
2222 pfm->pTextFormat,
2223 static_cast<FLOAT>(screenLine->Width()),
2224 static_cast<FLOAT>(screenLine->Height()),
2225 &textLayout);
2226 if (!SUCCEEDED(hrCreate)) {
2227 return;
2230 // Fill the textLayout chars with their own formats
2231 FillTextLayoutFormats(screenLine, textLayout, blobs);
2234 ScreenLineLayout::~ScreenLineLayout() noexcept {
2235 ReleaseUnknown(textLayout);
2238 // Get the position from the provided x
2240 size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) {
2241 if (!textLayout) {
2242 return 0;
2245 // Returns the text position corresponding to the mouse x,y.
2246 // If hitting the trailing side of a cluster, return the
2247 // leading edge of the following text position.
2249 BOOL isTrailingHit = FALSE;
2250 BOOL isInside = FALSE;
2251 DWRITE_HIT_TEST_METRICS caretMetrics {};
2253 textLayout->HitTestPoint(
2254 static_cast<FLOAT>(xDistance),
2255 0.0f,
2256 &isTrailingHit,
2257 &isInside,
2258 &caretMetrics
2261 DWRITE_HIT_TEST_METRICS hitTestMetrics {};
2262 if (isTrailingHit) {
2263 FLOAT caretX = 0.0f;
2264 FLOAT caretY = 0.0f;
2266 // Uses hit-testing to align the current caret position to a whole cluster,
2267 // rather than residing in the middle of a base character + diacritic,
2268 // surrogate pair, or character + UVS.
2270 // Align the caret to the nearest whole cluster.
2271 textLayout->HitTestTextPosition(
2272 caretMetrics.textPosition,
2273 false,
2274 &caretX,
2275 &caretY,
2276 &hitTestMetrics
2280 size_t pos;
2281 if (charPosition) {
2282 pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition;
2283 } else {
2284 pos = isTrailingHit ? static_cast<size_t>(hitTestMetrics.textPosition) + hitTestMetrics.length : caretMetrics.textPosition;
2287 // Get the character position in original string
2288 return UTF8PositionFromUTF16Position(text, pos);
2291 // Finds the point of the caret position
2293 XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) {
2294 if (!textLayout) {
2295 return 0.0;
2297 // Convert byte positions to wchar_t positions
2298 const size_t position = GetPositionInLayout(text, caretPosition);
2300 // Translate text character offset to point x,y.
2301 DWRITE_HIT_TEST_METRICS caretMetrics {};
2302 D2D1_POINT_2F pt {};
2304 textLayout->HitTestTextPosition(
2305 static_cast<UINT32>(position),
2306 false, // trailing if false, else leading edge
2307 &pt.x,
2308 &pt.y,
2309 &caretMetrics
2312 return pt.x;
2315 // Find the selection range rectangles
2317 std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) {
2318 std::vector<Interval> ret;
2320 if (!textLayout || (start == end)) {
2321 return ret;
2324 // Convert byte positions to wchar_t positions
2325 const size_t startPos = GetPositionInLayout(text, start);
2326 const size_t endPos = GetPositionInLayout(text, end);
2328 // Find selection range length
2329 const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos);
2331 // Determine actual number of hit-test ranges
2332 UINT32 actualHitTestCount = 0;
2334 // First try with 2 elements and if more needed, allocate.
2335 std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(2);
2336 textLayout->HitTestTextRange(
2337 static_cast<UINT32>(startPos),
2338 static_cast<UINT32>(rangeLength),
2339 0, // x
2340 0, // y
2341 hitTestMetrics.data(),
2342 static_cast<UINT32>(hitTestMetrics.size()),
2343 &actualHitTestCount
2346 if (actualHitTestCount == 0) {
2347 return ret;
2350 if (hitTestMetrics.size() < actualHitTestCount) {
2351 // Allocate enough room to return all hit-test metrics.
2352 hitTestMetrics.resize(actualHitTestCount);
2353 textLayout->HitTestTextRange(
2354 static_cast<UINT32>(startPos),
2355 static_cast<UINT32>(rangeLength),
2356 0, // x
2357 0, // y
2358 hitTestMetrics.data(),
2359 static_cast<UINT32>(hitTestMetrics.size()),
2360 &actualHitTestCount
2364 // Get the selection ranges behind the text.
2366 for (size_t i = 0; i < actualHitTestCount; ++i) {
2367 // Store selection rectangle
2368 const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i];
2369 const Interval selectionInterval { htm.left, htm.left + htm.width };
2370 ret.push_back(selectionInterval);
2373 return ret;
2376 std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) {
2377 return std::make_unique<ScreenLineLayout>(screenLine);
2380 void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, int codePageOverride, UINT fuOptions) {
2381 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2382 if (pfm->pTextFormat && pRenderTarget && pBrush) {
2383 // Use Unicode calls
2384 const int codePageDraw = codePageOverride ? codePageOverride : pfm->CodePageText(mode.codePage);
2385 const TextWide tbuf(text, codePageDraw);
2387 SetFontQuality(pfm->extraFontFlag);
2388 if (fuOptions & ETO_CLIPPED) {
2389 const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc);
2390 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
2393 // Explicitly creating a text layout appears a little faster
2394 IDWriteTextLayout *pTextLayout = nullptr;
2395 const HRESULT hr = pIDWriteFactory->CreateTextLayout(
2396 tbuf.buffer,
2397 tbuf.tlen,
2398 pfm->pTextFormat,
2399 static_cast<FLOAT>(rc.Width()),
2400 static_cast<FLOAT>(rc.Height()),
2401 &pTextLayout);
2402 if (SUCCEEDED(hr)) {
2403 const D2D1_POINT_2F origin = DPointFromPoint(Point(rc.left, ybase - pfm->yAscent));
2404 pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, d2dDrawTextOptions);
2405 ReleaseUnknown(pTextLayout);
2408 if (fuOptions & ETO_CLIPPED) {
2409 pRenderTarget->PopAxisAlignedClip();
2414 void SurfaceD2D::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
2415 ColourRGBA fore, ColourRGBA back) {
2416 if (pRenderTarget) {
2417 FillRectangleAligned(rc, back);
2418 D2DPenColourAlpha(fore);
2419 DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE);
2423 void SurfaceD2D::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
2424 ColourRGBA fore, ColourRGBA back) {
2425 if (pRenderTarget) {
2426 FillRectangleAligned(rc, back);
2427 D2DPenColourAlpha(fore);
2428 DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE | ETO_CLIPPED);
2432 void SurfaceD2D::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
2433 ColourRGBA fore) {
2434 // Avoid drawing spaces in transparent mode
2435 for (const char ch : text) {
2436 if (ch != ' ') {
2437 if (pRenderTarget) {
2438 D2DPenColourAlpha(fore);
2439 DrawTextCommon(rc, font_, ybase, text, 0, 0);
2441 return;
2446 namespace {
2448 HRESULT MeasurePositions(TextPositions &poses, const TextWide &tbuf, IDWriteTextFormat *pTextFormat) {
2449 if (!pTextFormat) {
2450 // Unexpected failure like no access to DirectWrite so give up.
2451 return E_FAIL;
2454 // Initialize poses for safety.
2455 std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f);
2456 // Create a layout
2457 IDWriteTextLayout *pTextLayout = nullptr;
2458 const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
2459 if (!SUCCEEDED(hrCreate)) {
2460 return hrCreate;
2462 if (!pTextLayout) {
2463 return E_FAIL;
2465 VarBuffer<DWRITE_CLUSTER_METRICS, stackBufferLength> cm(tbuf.tlen);
2466 UINT32 count = 0;
2467 const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(cm.buffer, tbuf.tlen, &count);
2468 ReleaseUnknown(pTextLayout);
2469 if (!SUCCEEDED(hrGetCluster)) {
2470 return hrGetCluster;
2472 const DWRITE_CLUSTER_METRICS * const clusterMetrics = cm.buffer;
2473 // A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font
2474 XYPOSITION position = 0.0;
2475 int ti=0;
2476 for (unsigned int ci=0; ci<count; ci++) {
2477 for (unsigned int inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
2478 poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length;
2480 position += clusterMetrics[ci].width;
2482 PLATFORM_ASSERT(ti == tbuf.tlen);
2483 return S_OK;
2488 void SurfaceD2D::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
2489 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2490 const int codePageText = pfm->CodePageText(mode.codePage);
2491 const TextWide tbuf(text, codePageText);
2492 TextPositions poses(tbuf.tlen);
2493 if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat))) {
2494 return;
2496 if (codePageText == CpUtf8) {
2497 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
2498 size_t i = 0;
2499 for (int ui = 0; ui < tbuf.tlen; ui++) {
2500 const unsigned char uch = text[i];
2501 const unsigned int byteCount = UTF8BytesOfLead[uch];
2502 if (byteCount == 4) { // Non-BMP
2503 ui++;
2505 for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui<tbuf.tlen); bytePos++) {
2506 positions[i++] = poses.buffer[ui];
2509 const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0;
2510 while (i<text.length()) {
2511 positions[i++] = lastPos;
2513 } else if (!IsDBCSCodePage(codePageText)) {
2515 // One char per position
2516 PLATFORM_ASSERT(text.length() == static_cast<size_t>(tbuf.tlen));
2517 for (int kk=0; kk<tbuf.tlen; kk++) {
2518 positions[kk] = poses.buffer[kk];
2521 } else {
2523 // May be one or two bytes per position
2524 int ui = 0;
2525 for (size_t i=0; i<text.length() && ui<tbuf.tlen;) {
2526 positions[i] = poses.buffer[ui];
2527 if (DBCSIsLeadByte(codePageText, text[i])) {
2528 positions[i+1] = poses.buffer[ui];
2529 i += 2;
2530 } else {
2531 i++;
2534 ui++;
2539 XYPOSITION SurfaceD2D::WidthText(const Font *font_, std::string_view text) {
2540 FLOAT width = 1.0;
2541 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2542 if (pfm->pTextFormat) {
2543 const TextWide tbuf(text, pfm->CodePageText(mode.codePage));
2544 // Create a layout
2545 IDWriteTextLayout *pTextLayout = nullptr;
2546 const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pfm->pTextFormat, 1000.0, 1000.0, &pTextLayout);
2547 if (SUCCEEDED(hr) && pTextLayout) {
2548 DWRITE_TEXT_METRICS textMetrics;
2549 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
2550 width = textMetrics.widthIncludingTrailingWhitespace;
2551 ReleaseUnknown(pTextLayout);
2554 return width;
2557 void SurfaceD2D::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
2558 ColourRGBA fore, ColourRGBA back) {
2559 if (pRenderTarget) {
2560 FillRectangleAligned(rc, back);
2561 D2DPenColourAlpha(fore);
2562 DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE);
2566 void SurfaceD2D::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
2567 ColourRGBA fore, ColourRGBA back) {
2568 if (pRenderTarget) {
2569 FillRectangleAligned(rc, back);
2570 D2DPenColourAlpha(fore);
2571 DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE | ETO_CLIPPED);
2575 void SurfaceD2D::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
2576 ColourRGBA fore) {
2577 // Avoid drawing spaces in transparent mode
2578 for (const char ch : text) {
2579 if (ch != ' ') {
2580 if (pRenderTarget) {
2581 D2DPenColourAlpha(fore);
2582 DrawTextCommon(rc, font_, ybase, text, CpUtf8, 0);
2584 return;
2589 void SurfaceD2D::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
2590 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2591 const TextWide tbuf(text, CpUtf8);
2592 TextPositions poses(tbuf.tlen);
2593 if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat))) {
2594 return;
2596 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
2597 size_t i = 0;
2598 for (int ui = 0; ui < tbuf.tlen; ui++) {
2599 const unsigned char uch = text[i];
2600 const unsigned int byteCount = UTF8BytesOfLead[uch];
2601 if (byteCount == 4) { // Non-BMP
2602 ui++;
2603 PLATFORM_ASSERT(ui < tbuf.tlen);
2605 for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui < tbuf.tlen); bytePos++) {
2606 positions[i++] = poses.buffer[ui];
2609 const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0;
2610 while (i < text.length()) {
2611 positions[i++] = lastPos;
2615 XYPOSITION SurfaceD2D::WidthTextUTF8(const Font * font_, std::string_view text) {
2616 FLOAT width = 1.0;
2617 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2618 if (pfm->pTextFormat) {
2619 const TextWide tbuf(text, CpUtf8);
2620 // Create a layout
2621 IDWriteTextLayout *pTextLayout = nullptr;
2622 const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pfm->pTextFormat, 1000.0, 1000.0, &pTextLayout);
2623 if (SUCCEEDED(hr)) {
2624 DWRITE_TEXT_METRICS textMetrics;
2625 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
2626 width = textMetrics.widthIncludingTrailingWhitespace;
2627 ReleaseUnknown(pTextLayout);
2630 return width;
2633 XYPOSITION SurfaceD2D::Ascent(const Font *font_) {
2634 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2635 return std::ceil(pfm->yAscent);
2638 XYPOSITION SurfaceD2D::Descent(const Font *font_) {
2639 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2640 return std::ceil(pfm->yDescent);
2643 XYPOSITION SurfaceD2D::InternalLeading(const Font *font_) {
2644 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2645 return std::floor(pfm->yInternalLeading);
2648 XYPOSITION SurfaceD2D::Height(const Font *font_) {
2649 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2650 return std::ceil(pfm->yAscent) + std::ceil(pfm->yDescent);
2653 XYPOSITION SurfaceD2D::AverageCharWidth(const Font *font_) {
2654 FLOAT width = 1.0;
2655 const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
2656 if (pfm->pTextFormat) {
2657 // Create a layout
2658 IDWriteTextLayout *pTextLayout = nullptr;
2659 static constexpr WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
2660 const size_t lenAllAlpha = wcslen(wszAllAlpha);
2661 const HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(lenAllAlpha),
2662 pfm->pTextFormat, 1000.0, 1000.0, &pTextLayout);
2663 if (SUCCEEDED(hr) && pTextLayout) {
2664 DWRITE_TEXT_METRICS textMetrics;
2665 if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
2666 width = textMetrics.width / lenAllAlpha;
2667 ReleaseUnknown(pTextLayout);
2670 return width;
2673 void SurfaceD2D::SetClip(PRectangle rc) {
2674 if (pRenderTarget) {
2675 const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc);
2676 pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
2677 clipsActive++;
2681 void SurfaceD2D::PopClip() {
2682 if (pRenderTarget) {
2683 PLATFORM_ASSERT(clipsActive > 0);
2684 pRenderTarget->PopAxisAlignedClip();
2685 clipsActive--;
2689 void SurfaceD2D::FlushCachedState() {
2692 void SurfaceD2D::FlushDrawing() {
2693 if (pRenderTarget) {
2694 pRenderTarget->Flush();
2698 void SurfaceD2D::SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) {
2699 renderingParams = std::move(renderingParams_);
2702 #endif
2704 std::unique_ptr<Surface> Surface::Allocate([[maybe_unused]] Technology technology) {
2705 #if defined(USE_D2D)
2706 if (technology == Technology::Default)
2707 return std::make_unique<SurfaceGDI>();
2708 else
2709 return std::make_unique<SurfaceD2D>();
2710 #else
2711 return std::make_unique<SurfaceGDI>();
2712 #endif
2715 Window::~Window() noexcept {
2718 void Window::Destroy() noexcept {
2719 if (wid)
2720 ::DestroyWindow(HwndFromWindowID(wid));
2721 wid = nullptr;
2724 PRectangle Window::GetPosition() const {
2725 RECT rc;
2726 ::GetWindowRect(HwndFromWindowID(wid), &rc);
2727 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
2730 void Window::SetPosition(PRectangle rc) {
2731 ::SetWindowPos(HwndFromWindowID(wid),
2732 0, static_cast<int>(rc.left), static_cast<int>(rc.top),
2733 static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE);
2736 namespace {
2738 RECT RectFromMonitor(HMONITOR hMonitor) noexcept {
2739 MONITORINFO mi = {};
2740 mi.cbSize = sizeof(mi);
2741 if (GetMonitorInfo(hMonitor, &mi)) {
2742 return mi.rcWork;
2744 RECT rc = {0, 0, 0, 0};
2745 if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) {
2746 rc.left = 0;
2747 rc.top = 0;
2748 rc.right = 0;
2749 rc.bottom = 0;
2751 return rc;
2756 void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) {
2757 const DWORD style = GetWindowStyle(HwndFromWindowID(wid));
2758 if (style & WS_POPUP) {
2759 POINT ptOther = {0, 0};
2760 ::ClientToScreen(HwndFromWindow(*relativeTo), &ptOther);
2761 rc.Move(static_cast<XYPOSITION>(ptOther.x), static_cast<XYPOSITION>(ptOther.y));
2763 const RECT rcMonitor = RectFromPRectangle(rc);
2765 HMONITOR hMonitor = MonitorFromRect(&rcMonitor, MONITOR_DEFAULTTONEAREST);
2766 // If hMonitor is NULL, that's just the main screen anyways.
2767 const RECT rcWork = RectFromMonitor(hMonitor);
2769 if (rcWork.left < rcWork.right) {
2770 // Now clamp our desired rectangle to fit inside the work area
2771 // This way, the menu will fit wholly on one screen. An improvement even
2772 // if you don't have a second monitor on the left... Menu's appears half on
2773 // one screen and half on the other are just U.G.L.Y.!
2774 if (rc.right > rcWork.right)
2775 rc.Move(rcWork.right - rc.right, 0);
2776 if (rc.bottom > rcWork.bottom)
2777 rc.Move(0, rcWork.bottom - rc.bottom);
2778 if (rc.left < rcWork.left)
2779 rc.Move(rcWork.left - rc.left, 0);
2780 if (rc.top < rcWork.top)
2781 rc.Move(0, rcWork.top - rc.top);
2784 SetPosition(rc);
2787 PRectangle Window::GetClientPosition() const {
2788 RECT rc={0,0,0,0};
2789 if (wid)
2790 ::GetClientRect(HwndFromWindowID(wid), &rc);
2791 return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
2794 void Window::Show(bool show) {
2795 if (show)
2796 ::ShowWindow(HwndFromWindowID(wid), SW_SHOWNOACTIVATE);
2797 else
2798 ::ShowWindow(HwndFromWindowID(wid), SW_HIDE);
2801 void Window::InvalidateAll() {
2802 ::InvalidateRect(HwndFromWindowID(wid), nullptr, FALSE);
2805 void Window::InvalidateRectangle(PRectangle rc) {
2806 const RECT rcw = RectFromPRectangle(rc);
2807 ::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE);
2810 namespace {
2812 std::optional<DWORD> RegGetDWORD(HKEY hKey, LPCWSTR valueName) noexcept {
2813 DWORD value = 0;
2814 DWORD type = REG_NONE;
2815 DWORD size = sizeof(DWORD);
2816 const LSTATUS status = ::RegQueryValueExW(hKey, valueName, nullptr, &type, reinterpret_cast<LPBYTE>(&value), &size);
2817 if (status == ERROR_SUCCESS && type == REG_DWORD) {
2818 return value;
2820 return {};
2823 class CursorHelper {
2824 HDC hMemDC {};
2825 HBITMAP hBitmap {};
2826 HBITMAP hOldBitmap {};
2827 DWORD *pixels = nullptr;
2828 const int width;
2829 const int height;
2831 static constexpr float arrow[][2] = {
2832 { 32.0f - 12.73606f,32.0f - 19.04075f },
2833 { 32.0f - 7.80159f, 32.0f - 19.04075f },
2834 { 32.0f - 9.82813f, 32.0f - 14.91828f },
2835 { 32.0f - 6.88341f, 32.0f - 13.42515f },
2836 { 32.0f - 4.62301f, 32.0f - 18.05872f },
2837 { 32.0f - 1.26394f, 32.0f - 14.78295f },
2838 { 32.0f - 1.26394f, 32.0f - 30.57485f },
2841 public:
2842 ~CursorHelper() {
2843 if (hOldBitmap) {
2844 SelectBitmap(hMemDC, hOldBitmap);
2846 if (hBitmap) {
2847 ::DeleteObject(hBitmap);
2849 if (hMemDC) {
2850 ::DeleteDC(hMemDC);
2854 CursorHelper(int width_, int height_) noexcept : width{width_}, height{height_} {
2855 hMemDC = ::CreateCompatibleDC({});
2856 if (!hMemDC) {
2857 return;
2860 // https://learn.microsoft.com/en-us/windows/win32/menurc/using-cursors#creating-a-cursor
2861 BITMAPV5HEADER bi {};
2862 bi.bV5Size = sizeof(BITMAPV5HEADER);
2863 bi.bV5Width = width;
2864 bi.bV5Height = height;
2865 bi.bV5Planes = 1;
2866 bi.bV5BitCount = 32;
2867 bi.bV5Compression = BI_BITFIELDS;
2868 // The following mask specification specifies a supported 32 BPP alpha format for Windows XP.
2869 bi.bV5RedMask = 0x00FF0000U;
2870 bi.bV5GreenMask = 0x0000FF00U;
2871 bi.bV5BlueMask = 0x000000FFU;
2872 bi.bV5AlphaMask = 0xFF000000U;
2874 // Create the DIB section with an alpha channel.
2875 hBitmap = CreateDIBSection(hMemDC, reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS, reinterpret_cast<void **>(&pixels), nullptr, 0);
2876 if (hBitmap) {
2877 hOldBitmap = SelectBitmap(hMemDC, hBitmap);
2881 bool HasBitmap() const noexcept {
2882 return hOldBitmap != nullptr;
2885 HCURSOR Create() noexcept {
2886 HCURSOR cursor {};
2887 // Create an empty mask bitmap.
2888 HBITMAP hMonoBitmap = ::CreateBitmap(width, height, 1, 1, nullptr);
2889 if (hMonoBitmap) {
2890 // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconindirect
2891 // hBitmap should not already be selected into a device context
2892 SelectBitmap(hMemDC, hOldBitmap);
2893 hOldBitmap = {};
2894 ICONINFO info = {false, static_cast<DWORD>(width - 1), 0, hMonoBitmap, hBitmap};
2895 cursor = ::CreateIconIndirect(&info);
2896 ::DeleteObject(hMonoBitmap);
2898 return cursor;
2901 #if defined(USE_D2D)
2903 bool DrawD2D(COLORREF fillColour, COLORREF strokeColour) noexcept {
2904 if (!LoadD2D()) {
2905 return false;
2908 D2D1_RENDER_TARGET_PROPERTIES drtp {};
2909 drtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
2910 drtp.usage = D2D1_RENDER_TARGET_USAGE_NONE;
2911 drtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;
2912 drtp.dpiX = 96.f;
2913 drtp.dpiY = 96.f;
2914 drtp.pixelFormat = D2D1::PixelFormat(
2915 DXGI_FORMAT_B8G8R8A8_UNORM,
2916 D2D1_ALPHA_MODE_PREMULTIPLIED
2919 ID2D1DCRenderTarget *pTarget_ = nullptr;
2920 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&drtp, &pTarget_);
2921 if (FAILED(hr) || !pTarget_) {
2922 return false;
2924 const std::unique_ptr<ID2D1DCRenderTarget, UnknownReleaser> pTarget(pTarget_);
2926 const RECT rc = {0, 0, width, height};
2927 hr = pTarget_->BindDC(hMemDC, &rc);
2928 if (FAILED(hr)) {
2929 return false;
2932 pTarget->BeginDraw();
2934 // Draw something on the bitmap section.
2935 constexpr size_t nPoints = std::size(arrow);
2936 D2D1_POINT_2F points[nPoints]{};
2937 const FLOAT scale = width/32.0f;
2938 for (size_t i = 0; i < nPoints; i++) {
2939 points[i].x = arrow[i][0] * scale;
2940 points[i].y = arrow[i][1] * scale;
2943 const Geometry geometry = GeometryCreate();
2944 if (!geometry) {
2945 return false;
2948 const GeometrySink sink = GeometrySinkCreate(geometry.get());
2949 if (!sink) {
2950 return false;
2953 sink->BeginFigure(points[0], D2D1_FIGURE_BEGIN_FILLED);
2954 for (size_t i = 1; i < nPoints; i++) {
2955 sink->AddLine(points[i]);
2957 sink->EndFigure(D2D1_FIGURE_END_CLOSED);
2958 hr = sink->Close();
2959 if (FAILED(hr)) {
2960 return false;
2963 if (const BrushSolid pBrushFill = BrushSolidCreate(pTarget.get(), fillColour)) {
2964 pTarget->FillGeometry(geometry.get(), pBrushFill.get());
2967 if (const BrushSolid pBrushStroke = BrushSolidCreate(pTarget.get(), strokeColour)) {
2968 pTarget->DrawGeometry(geometry.get(), pBrushStroke.get(), scale);
2971 hr = pTarget->EndDraw();
2972 return SUCCEEDED(hr);
2974 #endif
2976 void Draw(COLORREF fillColour, COLORREF strokeColour) noexcept {
2977 #if defined(USE_D2D)
2978 if (DrawD2D(fillColour, strokeColour)) {
2979 return;
2981 #endif
2983 // Draw something on the DIB section.
2984 constexpr size_t nPoints = std::size(arrow);
2985 POINT points[nPoints]{};
2986 const float scale = width/32.0f;
2987 for (size_t i = 0; i < nPoints; i++) {
2988 points[i].x = std::lround(arrow[i][0] * scale);
2989 points[i].y = std::lround(arrow[i][1] * scale);
2992 const DWORD penWidth = std::lround(scale);
2993 HPEN pen;
2994 if (penWidth > 1) {
2995 const LOGBRUSH brushParameters { BS_SOLID, strokeColour, 0 };
2996 pen = ::ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER,
2997 penWidth,
2998 &brushParameters,
3000 nullptr);
3001 } else {
3002 pen = ::CreatePen(PS_INSIDEFRAME, 1, strokeColour);
3005 HPEN penOld = SelectPen(hMemDC, pen);
3006 HBRUSH brush = ::CreateSolidBrush(fillColour);
3007 HBRUSH brushOld = SelectBrush(hMemDC, brush);
3008 ::Polygon(hMemDC, points, static_cast<int>(nPoints));
3009 SelectPen(hMemDC, penOld);
3010 SelectBrush(hMemDC, brushOld);
3011 ::DeleteObject(pen);
3012 ::DeleteObject(brush);
3014 // Set the alpha values for each pixel in the cursor.
3015 for (int i = 0; i < width*height; i++) {
3016 if (*pixels != 0) {
3017 *pixels |= 0xFF000000U;
3019 pixels++;
3026 HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept {
3027 // https://learn.microsoft.com/en-us/answers/questions/815036/windows-cursor-size
3028 constexpr DWORD defaultCursorBaseSize = 32;
3029 constexpr DWORD maxCursorBaseSize = 16*(1 + 15); // 16*(1 + CursorSize)
3030 DWORD cursorBaseSize = 0;
3031 HKEY hKey {};
3032 LSTATUS status = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Control Panel\\Cursors", 0, KEY_QUERY_VALUE, &hKey);
3033 if (status == ERROR_SUCCESS) {
3034 if (std::optional<DWORD> baseSize = RegGetDWORD(hKey, L"CursorBaseSize")) {
3035 // CursorBaseSize is multiple of 16
3036 cursorBaseSize = std::min(*baseSize & ~15, maxCursorBaseSize);
3038 ::RegCloseKey(hKey);
3041 int width = 0;
3042 int height = 0;
3043 if (cursorBaseSize > defaultCursorBaseSize) {
3044 width = ::MulDiv(cursorBaseSize, dpi, USER_DEFAULT_SCREEN_DPI);
3045 height = width;
3046 } else {
3047 width = SystemMetricsForDpi(SM_CXCURSOR, dpi);
3048 height = SystemMetricsForDpi(SM_CYCURSOR, dpi);
3049 PLATFORM_ASSERT(width == height);
3052 CursorHelper cursorHelper(width, height);
3053 if (!cursorHelper.HasBitmap()) {
3054 return {};
3057 COLORREF fillColour = RGB(0xff, 0xff, 0xfe);
3058 COLORREF strokeColour = RGB(0, 0, 1);
3059 status = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Accessibility", 0, KEY_QUERY_VALUE, &hKey);
3060 if (status == ERROR_SUCCESS) {
3061 if (std::optional<DWORD> cursorType = RegGetDWORD(hKey, L"CursorType")) {
3062 switch (*cursorType) {
3063 case 1: // black
3064 case 4: // black
3065 std::swap(fillColour, strokeColour);
3066 break;
3067 case 6: // custom
3068 if (std::optional<DWORD> cursorColor = RegGetDWORD(hKey, L"CursorColor")) {
3069 fillColour = *cursorColor;
3071 break;
3072 default: // 0, 3 white, 2, 5 invert
3073 break;
3076 ::RegCloseKey(hKey);
3079 cursorHelper.Draw(fillColour, strokeColour);
3080 HCURSOR cursor = cursorHelper.Create();
3081 return cursor;
3084 void Window::SetCursor(Cursor curs) {
3085 switch (curs) {
3086 case Cursor::text:
3087 ::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
3088 break;
3089 case Cursor::up:
3090 ::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
3091 break;
3092 case Cursor::wait:
3093 ::SetCursor(::LoadCursor(NULL,IDC_WAIT));
3094 break;
3095 case Cursor::horizontal:
3096 ::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
3097 break;
3098 case Cursor::vertical:
3099 ::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
3100 break;
3101 case Cursor::hand:
3102 ::SetCursor(::LoadCursor(NULL,IDC_HAND));
3103 break;
3104 case Cursor::reverseArrow:
3105 case Cursor::arrow:
3106 case Cursor::invalid: // Should not occur, but just in case.
3107 default:
3108 ::SetCursor(::LoadCursor(NULL,IDC_ARROW));
3109 break;
3113 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
3114 coordinates */
3115 PRectangle Window::GetMonitorRect(Point pt) {
3116 const PRectangle rcPosition = GetPosition();
3117 const POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
3118 static_cast<LONG>(pt.y + rcPosition.top)};
3119 HMONITOR hMonitor = MonitorFromPoint(ptDesktop, MONITOR_DEFAULTTONEAREST);
3121 const RECT rcWork = RectFromMonitor(hMonitor);
3122 if (rcWork.left < rcWork.right) {
3123 PRectangle rcMonitor(
3124 rcWork.left - rcPosition.left,
3125 rcWork.top - rcPosition.top,
3126 rcWork.right - rcPosition.left,
3127 rcWork.bottom - rcPosition.top);
3128 return rcMonitor;
3129 } else {
3130 return PRectangle();
3134 struct ListItemData {
3135 const char *text;
3136 int pixId;
3139 class LineToItem {
3140 std::vector<char> words;
3142 std::vector<ListItemData> data;
3144 public:
3145 void Clear() noexcept {
3146 words.clear();
3147 data.clear();
3150 ListItemData Get(size_t index) const noexcept {
3151 if (index < data.size()) {
3152 return data[index];
3153 } else {
3154 ListItemData missing = {"", -1};
3155 return missing;
3158 int Count() const noexcept {
3159 return static_cast<int>(data.size());
3162 void AllocItem(const char *text, int pixId) {
3163 const ListItemData lid = { text, pixId };
3164 data.push_back(lid);
3167 char *SetWords(const char *s) {
3168 words = std::vector<char>(s, s+strlen(s)+1);
3169 return &words[0];
3173 const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
3175 ListBox::ListBox() noexcept {
3178 ListBox::~ListBox() noexcept {
3181 class ListBoxX : public ListBox {
3182 int lineHeight;
3183 HFONT fontCopy;
3184 Technology technology;
3185 RGBAImageSet images;
3186 LineToItem lti;
3187 HWND lb;
3188 bool unicodeMode;
3189 int desiredVisibleRows;
3190 unsigned int maxItemCharacters;
3191 unsigned int aveCharWidth;
3192 Window *parent;
3193 int ctrlID;
3194 UINT dpi;
3195 IListBoxDelegate *delegate;
3196 const char *widestItem;
3197 unsigned int maxCharWidth;
3198 WPARAM resizeHit;
3199 PRectangle rcPreSize;
3200 Point dragOffset;
3201 Point location; // Caret location at which the list is opened
3202 MouseWheelDelta wheelDelta;
3203 ListOptions options;
3204 DWORD frameStyle = WS_THICKFRAME;
3206 HWND GetHWND() const noexcept;
3207 void AppendListItem(const char *text, const char *numword);
3208 void AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept;
3209 int ItemHeight() const;
3210 int MinClientWidth() const noexcept;
3211 int TextOffset() const;
3212 POINT GetClientExtent() const noexcept;
3213 POINT MinTrackSize() const;
3214 POINT MaxTrackSize() const;
3215 void SetRedraw(bool on) noexcept;
3216 void OnDoubleClick();
3217 void OnSelChange();
3218 void ResizeToCursor();
3219 void StartResize(WPARAM);
3220 LRESULT NcHitTest(WPARAM, LPARAM) const;
3221 void CentreItem(int n);
3222 void Paint(HDC);
3223 static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
3225 static constexpr Point ItemInset {0, 0}; // Padding around whole item
3226 static constexpr Point TextInset {2, 0}; // Padding around text
3227 static constexpr Point ImageInset {1, 0}; // Padding around image
3229 public:
3230 ListBoxX() : lineHeight(10), fontCopy{}, technology(Technology::Default), lb{}, unicodeMode(false),
3231 desiredVisibleRows(9), maxItemCharacters(0), aveCharWidth(8),
3232 parent(nullptr), ctrlID(0), dpi(USER_DEFAULT_SCREEN_DPI),
3233 delegate(nullptr),
3234 widestItem(nullptr), maxCharWidth(1), resizeHit(0) {
3236 ListBoxX(const ListBoxX &) = delete;
3237 ListBoxX(ListBoxX &&) = delete;
3238 ListBoxX &operator=(const ListBoxX &) = delete;
3239 ListBoxX &operator=(ListBoxX &&) = delete;
3240 ~ListBoxX() noexcept override {
3241 if (fontCopy) {
3242 ::DeleteObject(fontCopy);
3243 fontCopy = 0;
3246 void SetFont(const Font *font) override;
3247 void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override;
3248 void SetAverageCharWidth(int width) override;
3249 void SetVisibleRows(int rows) override;
3250 int GetVisibleRows() const override;
3251 PRectangle GetDesiredRect() override;
3252 int CaretFromEdge() override;
3253 void Clear() noexcept override;
3254 void Append(char *s, int type = -1) override;
3255 int Length() override;
3256 void Select(int n) override;
3257 int GetSelection() override;
3258 int Find(const char *prefix) override;
3259 std::string GetValue(int n) override;
3260 void RegisterImage(int type, const char *xpm_data) override;
3261 void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
3262 void ClearRegisteredImages() override;
3263 void SetDelegate(IListBoxDelegate *lbDelegate) override;
3264 void SetList(const char *list, char separator, char typesep) override;
3265 void SetOptions(ListOptions options_) override;
3266 void Draw(DRAWITEMSTRUCT *pDrawItem);
3267 LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
3268 static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
3271 std::unique_ptr<ListBox> ListBox::Allocate() {
3272 return std::make_unique<ListBoxX>();
3275 void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) {
3276 parent = &parent_;
3277 ctrlID = ctrlID_;
3278 location = location_;
3279 lineHeight = lineHeight_;
3280 unicodeMode = unicodeMode_;
3281 technology = technology_;
3282 HWND hwndParent = HwndFromWindow(*parent);
3283 HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
3284 // Window created as popup so not clipped within parent client area
3285 wid = ::CreateWindowEx(
3286 WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
3287 WS_POPUP | frameStyle,
3288 100,100, 150,80, hwndParent,
3289 NULL,
3290 hinstanceParent,
3291 this);
3293 dpi = DpiForWindow(hwndParent);
3294 POINT locationw = POINTFromPoint(location);
3295 ::MapWindowPoints(hwndParent, NULL, &locationw, 1);
3296 location = PointFromPOINT(locationw);
3299 void ListBoxX::SetFont(const Font *font) {
3300 const FontWin *pfm = dynamic_cast<const FontWin *>(font);
3301 if (pfm) {
3302 if (fontCopy) {
3303 ::DeleteObject(fontCopy);
3304 fontCopy = 0;
3306 fontCopy = pfm->HFont();
3307 SetWindowFont(lb, fontCopy, 0);
3311 void ListBoxX::SetAverageCharWidth(int width) {
3312 aveCharWidth = width;
3315 void ListBoxX::SetVisibleRows(int rows) {
3316 desiredVisibleRows = rows;
3319 int ListBoxX::GetVisibleRows() const {
3320 return desiredVisibleRows;
3323 HWND ListBoxX::GetHWND() const noexcept {
3324 return HwndFromWindowID(GetID());
3327 PRectangle ListBoxX::GetDesiredRect() {
3328 PRectangle rcDesired = GetPosition();
3330 int rows = Length();
3331 if ((rows == 0) || (rows > desiredVisibleRows))
3332 rows = desiredVisibleRows;
3333 rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
3335 int width = MinClientWidth();
3336 HDC hdc = ::GetDC(lb);
3337 HFONT oldFont = SelectFont(hdc, fontCopy);
3338 SIZE textSize = {0, 0};
3339 int len = 0;
3340 if (widestItem) {
3341 len = static_cast<int>(strlen(widestItem));
3342 if (unicodeMode) {
3343 const TextWide tbuf(widestItem, CpUtf8);
3344 ::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
3345 } else {
3346 ::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
3349 TEXTMETRIC tm;
3350 ::GetTextMetrics(hdc, &tm);
3351 maxCharWidth = tm.tmMaxCharWidth;
3352 SelectFont(hdc, oldFont);
3353 ::ReleaseDC(lb, hdc);
3355 const int widthDesired = std::max(textSize.cx, (len + 1) * tm.tmAveCharWidth);
3356 if (width < widthDesired)
3357 width = widthDesired;
3359 rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
3360 if (Length() > rows)
3361 rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi);
3363 AdjustWindowRect(&rcDesired, dpi);
3364 return rcDesired;
3367 int ListBoxX::TextOffset() const {
3368 const int pixWidth = images.GetWidth();
3369 return static_cast<int>(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2));
3372 int ListBoxX::CaretFromEdge() {
3373 PRectangle rc;
3374 AdjustWindowRect(&rc, dpi);
3375 return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1);
3378 void ListBoxX::Clear() noexcept {
3379 ListBox_ResetContent(lb);
3380 maxItemCharacters = 0;
3381 widestItem = nullptr;
3382 lti.Clear();
3385 void ListBoxX::Append(char *, int) {
3386 // This method is no longer called in Scintilla
3387 PLATFORM_ASSERT(false);
3390 int ListBoxX::Length() {
3391 return lti.Count();
3394 void ListBoxX::Select(int n) {
3395 // We are going to scroll to centre on the new selection and then select it, so disable
3396 // redraw to avoid flicker caused by a painting new selection twice in unselected and then
3397 // selected states
3398 SetRedraw(false);
3399 CentreItem(n);
3400 ListBox_SetCurSel(lb, n);
3401 OnSelChange();
3402 SetRedraw(true);
3405 int ListBoxX::GetSelection() {
3406 return ListBox_GetCurSel(lb);
3409 // This is not actually called at present
3410 int ListBoxX::Find(const char *) {
3411 return LB_ERR;
3414 std::string ListBoxX::GetValue(int n) {
3415 const ListItemData item = lti.Get(n);
3416 return item.text;
3419 void ListBoxX::RegisterImage(int type, const char *xpm_data) {
3420 XPM xpmImage(xpm_data);
3421 images.AddImage(type, std::make_unique<RGBAImage>(xpmImage));
3424 void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
3425 images.AddImage(type, std::make_unique<RGBAImage>(width, height, 1.0f, pixelsImage));
3428 void ListBoxX::ClearRegisteredImages() {
3429 images.Clear();
3432 namespace {
3434 int ColourOfElement(std::optional<ColourRGBA> colour, int nIndex) {
3435 if (colour.has_value()) {
3436 return colour.value().OpaqueRGB();
3437 } else {
3438 return ::GetSysColor(nIndex);
3442 void FillRectColour(HDC hdc, const RECT *lprc, int colour) noexcept {
3443 const HBRUSH brush = ::CreateSolidBrush(colour);
3444 ::FillRect(hdc, lprc, brush);
3445 ::DeleteObject(brush);
3450 void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
3451 if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
3452 RECT rcBox = pDrawItem->rcItem;
3453 rcBox.left += TextOffset();
3454 if (pDrawItem->itemState & ODS_SELECTED) {
3455 RECT rcImage = pDrawItem->rcItem;
3456 rcImage.right = rcBox.left;
3457 // The image is not highlighted
3458 FillRectColour(pDrawItem->hDC, &rcImage, ColourOfElement(options.back, COLOR_WINDOW));
3459 FillRectColour(pDrawItem->hDC, &rcBox, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT));
3460 ::SetBkColor(pDrawItem->hDC, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT));
3461 ::SetTextColor(pDrawItem->hDC, ColourOfElement(options.foreSelected, COLOR_HIGHLIGHTTEXT));
3462 } else {
3463 FillRectColour(pDrawItem->hDC, &pDrawItem->rcItem, ColourOfElement(options.back, COLOR_WINDOW));
3464 ::SetBkColor(pDrawItem->hDC, ColourOfElement(options.back, COLOR_WINDOW));
3465 ::SetTextColor(pDrawItem->hDC, ColourOfElement(options.fore, COLOR_WINDOWTEXT));
3468 const ListItemData item = lti.Get(pDrawItem->itemID);
3469 const int pixId = item.pixId;
3470 const char *text = item.text;
3471 const int len = static_cast<int>(strlen(text));
3473 RECT rcText = rcBox;
3474 ::InsetRect(&rcText, static_cast<int>(TextInset.x), static_cast<int>(TextInset.y));
3476 if (unicodeMode) {
3477 const TextWide tbuf(text, CpUtf8);
3478 ::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
3479 } else {
3480 ::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
3483 // Draw the image, if any
3484 const RGBAImage *pimage = images.Get(pixId);
3485 if (pimage) {
3486 std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology));
3487 if (technology == Technology::Default) {
3488 surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
3489 const long left = pDrawItem->rcItem.left + static_cast<int>(ItemInset.x + ImageInset.x);
3490 const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
3491 left + images.GetWidth(), pDrawItem->rcItem.bottom);
3492 surfaceItem->DrawRGBAImage(rcImage,
3493 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
3494 ::SetTextAlign(pDrawItem->hDC, TA_TOP);
3495 } else {
3496 #if defined(USE_D2D)
3497 const D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
3498 D2D1_RENDER_TARGET_TYPE_DEFAULT,
3499 D2D1::PixelFormat(
3500 DXGI_FORMAT_B8G8R8A8_UNORM,
3501 D2D1_ALPHA_MODE_IGNORE),
3504 D2D1_RENDER_TARGET_USAGE_NONE,
3505 D2D1_FEATURE_LEVEL_DEFAULT
3507 ID2D1DCRenderTarget *pDCRT = nullptr;
3508 HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
3509 if (SUCCEEDED(hr) && pDCRT) {
3510 const long left = pDrawItem->rcItem.left + static_cast<long>(ItemInset.x + ImageInset.x);
3512 RECT rcItem = pDrawItem->rcItem;
3513 rcItem.left = left;
3514 rcItem.right = rcItem.left + images.GetWidth();
3516 hr = pDCRT->BindDC(pDrawItem->hDC, &rcItem);
3517 if (SUCCEEDED(hr)) {
3518 surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
3519 pDCRT->BeginDraw();
3520 const PRectangle rcImage = PRectangle::FromInts(0, 0, images.GetWidth(), rcItem.bottom - rcItem.top);
3521 surfaceItem->DrawRGBAImage(rcImage,
3522 pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
3523 pDCRT->EndDraw();
3524 ReleaseUnknown(pDCRT);
3527 #endif
3533 void ListBoxX::AppendListItem(const char *text, const char *numword) {
3534 int pixId = -1;
3535 if (numword) {
3536 pixId = 0;
3537 char ch;
3538 while ((ch = *++numword) != '\0') {
3539 pixId = 10 * pixId + (ch - '0');
3543 lti.AllocItem(text, pixId);
3544 const unsigned int len = static_cast<unsigned int>(strlen(text));
3545 if (maxItemCharacters < len) {
3546 maxItemCharacters = len;
3547 widestItem = text;
3551 void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
3552 delegate = lbDelegate;
3555 void ListBoxX::SetList(const char *list, char separator, char typesep) {
3556 // Turn off redraw while populating the list - this has a significant effect, even if
3557 // the listbox is not visible.
3558 SetRedraw(false);
3559 Clear();
3560 const size_t size = strlen(list);
3561 char *words = lti.SetWords(list);
3562 const char *startword = words;
3563 char *numword = nullptr;
3564 for (size_t i=0; i < size; i++) {
3565 if (words[i] == separator) {
3566 words[i] = '\0';
3567 if (numword)
3568 *numword = '\0';
3569 AppendListItem(startword, numword);
3570 startword = words + i + 1;
3571 numword = nullptr;
3572 } else if (words[i] == typesep) {
3573 numword = words + i;
3576 if (startword) {
3577 if (numword)
3578 *numword = '\0';
3579 AppendListItem(startword, numword);
3582 // Finally populate the listbox itself with the correct number of items
3583 const int count = lti.Count();
3584 ::SendMessage(lb, LB_INITSTORAGE, count, 0);
3585 for (intptr_t j=0; j<count; j++) {
3586 ListBox_AddItemData(lb, j+1);
3588 SetRedraw(true);
3591 void ListBoxX::SetOptions(ListOptions options_) {
3592 options = options_;
3593 frameStyle = FlagSet(options.options, AutoCompleteOption::FixedSize) ? WS_BORDER : WS_THICKFRAME;
3596 void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept {
3597 RECT rcw = RectFromPRectangle(*rc);
3598 if (fnAdjustWindowRectExForDpi) {
3599 fnAdjustWindowRectExForDpi(&rcw, frameStyle, false, WS_EX_WINDOWEDGE, dpiAdjust);
3600 } else {
3601 ::AdjustWindowRectEx(&rcw, frameStyle, false, WS_EX_WINDOWEDGE);
3603 *rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom);
3606 int ListBoxX::ItemHeight() const {
3607 int itemHeight = lineHeight + (static_cast<int>(TextInset.y) * 2);
3608 const int pixHeight = images.GetHeight() + (static_cast<int>(ImageInset.y) * 2);
3609 if (itemHeight < pixHeight) {
3610 itemHeight = pixHeight;
3612 return itemHeight;
3615 int ListBoxX::MinClientWidth() const noexcept {
3616 return 12 * (aveCharWidth+aveCharWidth/3);
3619 POINT ListBoxX::MinTrackSize() const {
3620 PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight());
3621 AdjustWindowRect(&rc, dpi);
3622 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
3623 return ret;
3626 POINT ListBoxX::MaxTrackSize() const {
3627 PRectangle rc = PRectangle::FromInts(0, 0,
3628 std::max(static_cast<unsigned int>(MinClientWidth()),
3629 maxCharWidth * maxItemCharacters + static_cast<int>(TextInset.x) * 2 +
3630 TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi)),
3631 ItemHeight() * lti.Count());
3632 AdjustWindowRect(&rc, dpi);
3633 POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
3634 return ret;
3637 void ListBoxX::SetRedraw(bool on) noexcept {
3638 ::SendMessage(lb, WM_SETREDRAW, on, 0);
3639 if (on)
3640 ::InvalidateRect(lb, nullptr, TRUE);
3643 void ListBoxX::ResizeToCursor() {
3644 PRectangle rc = GetPosition();
3645 POINT ptw;
3646 ::GetCursorPos(&ptw);
3647 const Point pt = PointFromPOINT(ptw) + dragOffset;
3649 switch (resizeHit) {
3650 case HTLEFT:
3651 rc.left = pt.x;
3652 break;
3653 case HTRIGHT:
3654 rc.right = pt.x;
3655 break;
3656 case HTTOP:
3657 rc.top = pt.y;
3658 break;
3659 case HTTOPLEFT:
3660 rc.top = pt.y;
3661 rc.left = pt.x;
3662 break;
3663 case HTTOPRIGHT:
3664 rc.top = pt.y;
3665 rc.right = pt.x;
3666 break;
3667 case HTBOTTOM:
3668 rc.bottom = pt.y;
3669 break;
3670 case HTBOTTOMLEFT:
3671 rc.bottom = pt.y;
3672 rc.left = pt.x;
3673 break;
3674 case HTBOTTOMRIGHT:
3675 rc.bottom = pt.y;
3676 rc.right = pt.x;
3677 break;
3678 default:
3679 break;
3682 const POINT ptMin = MinTrackSize();
3683 const POINT ptMax = MaxTrackSize();
3684 // We don't allow the left edge to move at present, but just in case
3685 rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x);
3686 rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y);
3687 rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x);
3688 rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y);
3690 SetPosition(rc);
3693 void ListBoxX::StartResize(WPARAM hitCode) {
3694 rcPreSize = GetPosition();
3695 POINT cursorPos;
3696 ::GetCursorPos(&cursorPos);
3698 switch (hitCode) {
3699 case HTRIGHT:
3700 case HTBOTTOM:
3701 case HTBOTTOMRIGHT:
3702 dragOffset.x = rcPreSize.right - cursorPos.x;
3703 dragOffset.y = rcPreSize.bottom - cursorPos.y;
3704 break;
3706 case HTTOPRIGHT:
3707 dragOffset.x = rcPreSize.right - cursorPos.x;
3708 dragOffset.y = rcPreSize.top - cursorPos.y;
3709 break;
3711 // Note that the current hit test code prevents the left edge cases ever firing
3712 // as we don't want the left edge to be movable
3713 case HTLEFT:
3714 case HTTOP:
3715 case HTTOPLEFT:
3716 dragOffset.x = rcPreSize.left - cursorPos.x;
3717 dragOffset.y = rcPreSize.top - cursorPos.y;
3718 break;
3719 case HTBOTTOMLEFT:
3720 dragOffset.x = rcPreSize.left - cursorPos.x;
3721 dragOffset.y = rcPreSize.bottom - cursorPos.y;
3722 break;
3724 default:
3725 return;
3728 ::SetCapture(GetHWND());
3729 resizeHit = hitCode;
3732 LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
3733 const PRectangle rc = GetPosition();
3735 LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
3736 // There is an apparent bug in the DefWindowProc hit test code whereby it will
3737 // return HTTOPXXX if the window in question is shorter than the default
3738 // window caption height + frame, even if one is hovering over the bottom edge of
3739 // the frame, so workaround that here
3740 if (hit >= HTTOP && hit <= HTTOPRIGHT) {
3741 const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi);
3742 const int yPos = GET_Y_LPARAM(lParam);
3743 if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
3744 hit += HTBOTTOM - HTTOP;
3748 // Never permit resizing that moves the left edge. Allow movement of top or bottom edge
3749 // depending on whether the list is above or below the caret
3750 switch (hit) {
3751 case HTLEFT:
3752 case HTTOPLEFT:
3753 case HTBOTTOMLEFT:
3754 hit = HTERROR;
3755 break;
3757 case HTTOP:
3758 case HTTOPRIGHT: {
3759 // Valid only if caret below list
3760 if (location.y < rc.top)
3761 hit = HTERROR;
3763 break;
3765 case HTBOTTOM:
3766 case HTBOTTOMRIGHT: {
3767 // Valid only if caret above list
3768 if (rc.bottom <= location.y)
3769 hit = HTERROR;
3771 break;
3772 default:
3773 break;
3776 return hit;
3779 void ListBoxX::OnDoubleClick() {
3780 if (delegate) {
3781 ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
3782 delegate->ListNotify(&event);
3786 void ListBoxX::OnSelChange() {
3787 if (delegate) {
3788 ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
3789 delegate->ListNotify(&event);
3793 POINT ListBoxX::GetClientExtent() const noexcept {
3794 RECT rc;
3795 ::GetWindowRect(HwndFromWindowID(wid), &rc);
3796 POINT ret { rc.right - rc.left, rc.bottom - rc.top };
3797 return ret;
3800 void ListBoxX::CentreItem(int n) {
3801 // If below mid point, scroll up to centre, but with more items below if uneven
3802 if (n >= 0) {
3803 const POINT extent = GetClientExtent();
3804 const int visible = extent.y/ItemHeight();
3805 if (visible < Length()) {
3806 const int top = ListBox_GetTopIndex(lb);
3807 const int half = (visible - 1) / 2;
3808 if (n > (top + half))
3809 ListBox_SetTopIndex(lb, n - half);
3814 // Performs a double-buffered paint operation to avoid flicker
3815 void ListBoxX::Paint(HDC hDC) {
3816 const POINT extent = GetClientExtent();
3817 HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
3818 HDC bitmapDC = ::CreateCompatibleDC(hDC);
3819 HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
3820 // The list background is mainly erased during painting, but can be a small
3821 // unpainted area when at the end of a non-integrally sized list with a
3822 // vertical scroll bar
3823 const RECT rc = { 0, 0, extent.x, extent.y };
3824 FillRectColour(bitmapDC, &rc, ColourOfElement(options.back, COLOR_WINDOWTEXT));
3825 // Paint the entire client area and vertical scrollbar
3826 ::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
3827 ::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
3828 // Select a stock brush to prevent warnings from BoundsChecker
3829 SelectBrush(bitmapDC, GetStockBrush(WHITE_BRUSH));
3830 SelectBitmap(bitmapDC, hBitmapOld);
3831 ::DeleteDC(bitmapDC);
3832 ::DeleteObject(hBitmap);
3835 LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3836 try {
3837 ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
3838 switch (iMessage) {
3839 case WM_ERASEBKGND:
3840 return TRUE;
3842 case WM_PAINT: {
3843 PAINTSTRUCT ps;
3844 HDC hDC = ::BeginPaint(hWnd, &ps);
3845 if (lbx) {
3846 lbx->Paint(hDC);
3848 ::EndPaint(hWnd, &ps);
3850 return 0;
3852 case WM_MOUSEACTIVATE:
3853 // This prevents the view activating when the scrollbar is clicked
3854 return MA_NOACTIVATE;
3856 case WM_LBUTTONDOWN: {
3857 // We must take control of selection to prevent the ListBox activating
3858 // the popup
3859 const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
3860 if (HIWORD(lResult) == 0) {
3861 ListBox_SetCurSel(hWnd, LOWORD(lResult));
3862 if (lbx) {
3863 lbx->OnSelChange();
3867 return 0;
3869 case WM_LBUTTONUP:
3870 return 0;
3872 case WM_LBUTTONDBLCLK: {
3873 if (lbx) {
3874 lbx->OnDoubleClick();
3877 return 0;
3879 case WM_MBUTTONDOWN:
3880 // disable the scroll wheel button click action
3881 return 0;
3883 default:
3884 break;
3887 WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
3888 if (prevWndProc) {
3889 return ::CallWindowProc(prevWndProc, hWnd, iMessage, wParam, lParam);
3890 } else {
3891 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3893 } catch (...) {
3895 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3898 LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
3899 switch (iMessage) {
3900 case WM_CREATE: {
3901 HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent));
3902 // Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
3903 // but has useful side effect of speeding up list population significantly
3904 lb = ::CreateWindowEx(
3905 0, TEXT("listbox"), TEXT(""),
3906 WS_CHILD | WS_VSCROLL | WS_VISIBLE |
3907 LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
3908 0, 0, 150,80, hWnd,
3909 reinterpret_cast<HMENU>(static_cast<ptrdiff_t>(ctrlID)),
3910 hinstanceParent,
3912 WNDPROC prevWndProc = SubclassWindow(lb, ControlWndProc);
3913 ::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
3915 break;
3917 case WM_SIZE:
3918 if (lb) {
3919 SetRedraw(false);
3920 ::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
3921 // Ensure the selection remains visible
3922 CentreItem(GetSelection());
3923 SetRedraw(true);
3925 break;
3927 case WM_PAINT: {
3928 PAINTSTRUCT ps;
3929 ::BeginPaint(hWnd, &ps);
3930 ::EndPaint(hWnd, &ps);
3932 break;
3934 case WM_COMMAND:
3935 // This is not actually needed now - the registered double click action is used
3936 // directly to action a choice from the list.
3937 ::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam);
3938 break;
3940 case WM_MEASUREITEM: {
3941 MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
3942 pMeasureItem->itemHeight = ItemHeight();
3944 break;
3946 case WM_DRAWITEM:
3947 Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
3948 break;
3950 case WM_DESTROY:
3951 lb = 0;
3952 SetWindowPointer(hWnd, nullptr);
3953 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3955 case WM_ERASEBKGND:
3956 // To reduce flicker we can elide background erasure since this window is
3957 // completely covered by its child.
3958 return TRUE;
3960 case WM_GETMINMAXINFO: {
3961 MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
3962 minMax->ptMaxTrackSize = MaxTrackSize();
3963 minMax->ptMinTrackSize = MinTrackSize();
3965 break;
3967 case WM_MOUSEACTIVATE:
3968 return MA_NOACTIVATE;
3970 case WM_NCHITTEST:
3971 return NcHitTest(wParam, lParam);
3973 case WM_NCLBUTTONDOWN:
3974 // We have to implement our own window resizing because the DefWindowProc
3975 // implementation insists on activating the resized window
3976 StartResize(wParam);
3977 return 0;
3979 case WM_MOUSEMOVE: {
3980 if (resizeHit == 0) {
3981 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3982 } else {
3983 ResizeToCursor();
3986 break;
3988 case WM_LBUTTONUP:
3989 case WM_CANCELMODE:
3990 if (resizeHit != 0) {
3991 resizeHit = 0;
3992 ::ReleaseCapture();
3994 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
3995 case WM_MOUSEWHEEL:
3996 if (wheelDelta.Accumulate(wParam)) {
3997 const int nRows = GetVisibleRows();
3998 int linesToScroll = std::clamp(nRows - 1, 1, 3);
3999 linesToScroll *= wheelDelta.Actions();
4000 int top = ListBox_GetTopIndex(lb) + linesToScroll;
4001 if (top < 0) {
4002 top = 0;
4004 ListBox_SetTopIndex(lb, top);
4006 break;
4008 default:
4009 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
4012 return 0;
4015 LRESULT PASCAL ListBoxX::StaticWndProc(
4016 HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
4017 if (iMessage == WM_CREATE) {
4018 CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
4019 SetWindowPointer(hWnd, pCreate->lpCreateParams);
4021 // Find C++ object associated with window.
4022 ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(hWnd));
4023 if (lbx) {
4024 return lbx->WndProc(hWnd, iMessage, wParam, lParam);
4025 } else {
4026 return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
4030 namespace {
4032 bool ListBoxX_Register() noexcept {
4033 WNDCLASSEX wndclassc {};
4034 wndclassc.cbSize = sizeof(wndclassc);
4035 // We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
4036 // truncated items in the list and the appearance/disappearance of the vertical scroll bar.
4037 // The list repaint is double-buffered to avoid the flicker this would otherwise cause.
4038 wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
4039 wndclassc.cbWndExtra = sizeof(ListBoxX *);
4040 wndclassc.hInstance = hinstPlatformRes;
4041 wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
4042 wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
4043 wndclassc.lpszClassName = ListBoxX_ClassName;
4045 return ::RegisterClassEx(&wndclassc) != 0;
4048 void ListBoxX_Unregister() noexcept {
4049 if (hinstPlatformRes) {
4050 ::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes);
4056 Menu::Menu() noexcept : mid{} {
4059 void Menu::CreatePopUp() {
4060 Destroy();
4061 mid = ::CreatePopupMenu();
4064 void Menu::Destroy() noexcept {
4065 if (mid)
4066 ::DestroyMenu(static_cast<HMENU>(mid));
4067 mid = 0;
4070 void Menu::Show(Point pt, const Window &w) {
4071 ::TrackPopupMenu(static_cast<HMENU>(mid),
4072 TPM_RIGHTBUTTON, static_cast<int>(pt.x - 4), static_cast<int>(pt.y), 0,
4073 HwndFromWindow(w), nullptr);
4074 Destroy();
4077 ColourRGBA Platform::Chrome() {
4078 return ColourRGBA::FromRGB(static_cast<int>(::GetSysColor(COLOR_3DFACE)));
4081 ColourRGBA Platform::ChromeHighlight() {
4082 return ColourRGBA::FromRGB(static_cast<int>(::GetSysColor(COLOR_3DHIGHLIGHT)));
4085 const char *Platform::DefaultFont() {
4086 return "Verdana";
4089 int Platform::DefaultFontSize() {
4090 return 8;
4093 unsigned int Platform::DoubleClickTime() {
4094 return ::GetDoubleClickTime();
4097 void Platform::DebugDisplay(const char *s) noexcept {
4098 ::OutputDebugStringA(s);
4101 //#define TRACE
4103 #ifdef TRACE
4104 void Platform::DebugPrintf(const char *format, ...) noexcept {
4105 char buffer[2000];
4106 va_list pArguments;
4107 va_start(pArguments, format);
4108 vsnprintf(buffer, std::size(buffer), format, pArguments);
4109 va_end(pArguments);
4110 Platform::DebugDisplay(buffer);
4112 #else
4113 void Platform::DebugPrintf(const char *, ...) noexcept {
4115 #endif
4117 static bool assertionPopUps = true;
4119 bool Platform::ShowAssertionPopUps(bool assertionPopUps_) noexcept {
4120 const bool ret = assertionPopUps;
4121 assertionPopUps = assertionPopUps_;
4122 return ret;
4125 void Platform::Assert(const char *c, const char *file, int line) noexcept {
4126 char buffer[2000] {};
4127 snprintf(buffer, std::size(buffer), "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n");
4128 if (assertionPopUps) {
4129 const int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
4130 MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
4131 if (idButton == IDRETRY) {
4132 ::DebugBreak();
4133 } else if (idButton == IDIGNORE) {
4134 // all OK
4135 } else {
4136 abort();
4138 } else {
4139 Platform::DebugDisplay(buffer);
4140 ::DebugBreak();
4141 abort();
4145 void Platform_Initialise(void *hInstance) noexcept {
4146 hinstPlatformRes = static_cast<HINSTANCE>(hInstance);
4147 LoadDpiForWindow();
4148 ListBoxX_Register();
4151 void Platform_Finalise(bool fromDllMain) noexcept {
4152 #if defined(USE_D2D)
4153 if (!fromDllMain) {
4154 ReleaseUnknown(pIDWriteFactory);
4155 ReleaseUnknown(pD2DFactory);
4156 if (hDLLDWrite) {
4157 FreeLibrary(hDLLDWrite);
4158 hDLLDWrite = {};
4160 if (hDLLD2D) {
4161 FreeLibrary(hDLLD2D);
4162 hDLLD2D = {};
4165 #endif
4166 if (!fromDllMain && hDLLShcore) {
4167 FreeLibrary(hDLLShcore);
4168 hDLLShcore = {};
4170 ListBoxX_Unregister();