1 // Scintilla source code edit control
3 ** Implementation of platform facilities on Windows.
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.
17 #include <string_view>
26 // Want to use std::min and std::max so don't want Windows.h version of min and max
27 #if !defined(NOMINMAX)
31 #define _WIN32_WINNT 0x0A00
34 #define WIN32_LEAN_AND_MEAN 1
39 #include <shellscalingapi.h>
41 #if !defined(DISABLE_D2D)
50 #include "ScintillaTypes.h"
52 #include "Debugging.h"
56 #include "UniConversion.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"
67 using namespace Scintilla
;
69 namespace Scintilla::Internal
{
71 UINT
CodePageFromCharSet(CharacterSet characterSet
, UINT documentCodePage
) noexcept
;
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");
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
,
98 hDLLD2D
= ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, loadLibraryFlags
);
99 D2D1CFSig fnD2DCF
= DLLFunction
<D2D1CFSig
>(hDLLD2D
, "D2D1CreateFactory");
101 // A multi threaded factory in case Scintilla is used with multiple GUI threads
102 fnD2DCF(D2D1_FACTORY_TYPE_MULTI_THREADED
,
103 __uuidof(ID2D1Factory
),
105 reinterpret_cast<IUnknown
**>(&pD2DFactory
));
107 hDLLDWrite
= ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, loadLibraryFlags
);
108 DWriteCFSig fnDWCF
= DLLFunction
<DWriteCFSig
>(hDLLDWrite
, "DWriteCreateFactory");
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
,
115 reinterpret_cast<IUnknown
**>(&pIDWriteFactory
));
117 // D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT
118 d2dDrawTextOptions
= static_cast<D2D1_DRAW_TEXT_OPTIONS
>(0x00000004);
120 fnDWCF(DWRITE_FACTORY_TYPE_SHARED
,
121 __uuidof(IDWriteFactory
),
122 reinterpret_cast<IUnknown
**>(&pIDWriteFactory
));
127 bool LoadD2D() noexcept
{
128 static std::once_flag once
;
130 std::call_once(once
, LoadD2DOnce
);
134 return pIDWriteFactory
&& pD2DFactory
;
137 constexpr D2D_COLOR_F
ColorFromColourAlpha(ColourRGBA colour
) noexcept
{
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
) {
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
) {
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
) {
177 return GeometrySink(sink
);
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
));
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();
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
);
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
;
266 return DEFAULT_QUALITY
;
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
;
284 return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT
;
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
) {
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
{
307 FontGDI(const FontParameters
&fp
) {
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
{
319 ::DeleteObject(hfont
);
321 HFONT
HFont() const noexcept override
{
324 if (0 == ::GetObjectW(hfont
, sizeof(lf
), &lf
)) {
327 return ::CreateFontIndirectW(&lf
);
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
),
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
),
357 static_cast<DWRITE_FONT_STRETCH
>(fp
.stretch
),
358 fHeight
, L
"en-us", &pTextFormat
);
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
);
372 yAscent
= lineMetrics
[0].baseline
;
373 yDescent
= lineMetrics
[0].height
- lineMetrics
[0].baseline
;
376 hr
= pTextLayout
->GetFontSize(0, &emHeight
);
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
{
396 const HRESULT hr
= pTextFormat
->GetFontFamilyName(lf
.lfFaceName
, LF_FACESIZE
);
397 if (!SUCCEEDED(hr
)) {
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
);
413 static const FontDirectWrite
*Cast(const Font
*font_
) {
414 const FontDirectWrite
*pfm
= dynamic_cast<const FontDirectWrite
*>(font_
);
415 PLATFORM_ASSERT(pfm
);
417 throw std::runtime_error("SurfaceD2D::SetFont: wrong Font type.");
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);
438 ::GetWindowRect(hWnd
, &rect
);
439 const HMONITOR monitor
= ::MonitorFromRect(&rect
, monitorFlags
);
441 fnSetThreadDpiAwarenessContext(oldContext
);
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
;
459 std::shared_ptr
<Font
> Font::Allocate(const FontParameters
&fp
) {
461 if (fp
.technology
!= Technology::Default
) {
462 return std::make_shared
<FontDirectWrite
>(fp
);
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
>
473 T bufferStandard
[lengthStandard
];
476 explicit VarBuffer(size_t length
) : buffer(nullptr) {
477 if (length
> lengthStandard
) {
478 buffer
= new T
[length
];
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
) {
497 constexpr int stackBufferLength
= 400;
498 class TextWide
: public VarBuffer
<wchar_t, stackBufferLength
> {
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()));
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
);
522 if (fnGetDpiForMonitor(hMonitor
, 0 /*MDT_EFFECTIVE_DPI*/, &dpiX
, &dpiY
) == S_OK
) {
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
);
539 class SurfaceGDI
: public Surface
{
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
;
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
);
636 bitmap
= ::CreateCompatibleBitmap(hdcCompatible
, width
, height
);
637 bitmapOld
= SelectBitmap(hdc
, bitmap
);
638 ::SetTextAlign(hdc
, TA_BASELINE
);
640 logPixelsY
= logPixelsY_
;
643 SurfaceGDI::~SurfaceGDI() noexcept
{
647 void SurfaceGDI::Clear() noexcept
{
649 ::SelectObject(hdc
, penOld
);
655 ::SelectObject(hdc
, brushOld
);
656 ::DeleteObject(brush
);
661 // Fonts are not deleted as they are owned by a Font object
662 ::SelectObject(hdc
, fontOld
);
666 ::SelectObject(hdc
, bitmapOld
);
667 ::DeleteObject(bitmap
);
678 void SurfaceGDI::Release() noexcept
{
682 int SurfaceGDI::SupportsFeature(Supports feature
) noexcept
{
683 for (const Supports f
: SupportsGDI
) {
690 bool SurfaceGDI::Initialised() {
694 void SurfaceGDI::Init(WindowID wid
) {
696 hdc
= ::CreateCompatibleDC({});
698 ::SetTextAlign(hdc
, TA_BASELINE
);
699 logPixelsY
= DpiForWindow(wid
);
702 void SurfaceGDI::Init(SurfaceID sid
, WindowID wid
) {
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_
) {
719 void SurfaceGDI::PenColour(ColourRGBA fore
, XYPOSITION widthStroke
) noexcept
{
721 ::SelectObject(hdc
, 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
,
736 pen
= ::CreatePen(PS_INSIDEFRAME
, penWidth
, penColour
);
738 penOld
= SelectPen(hdc
, pen
);
741 void SurfaceGDI::BrushColour(ColourRGBA back
) noexcept
{
743 ::SelectObject(hdc
, brushOld
);
744 ::DeleteObject(brush
);
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
);
756 throw std::runtime_error("SurfaceGDI::SetFont: wrong Font type.");
759 SelectFont(hdc
, pfm
->hfont
);
761 fontOld
= SelectFont(hdc
, pfm
->hfont
);
765 int SurfaceGDI::LogPixelsY() {
769 int SurfaceGDI::PixelDivisions() {
770 // Win32 uses device pixels.
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);
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);
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
) {
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
);
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
);
847 rcw
.left
+ 1, rcw
.top
,
848 rcw
.right
- 1, rcw
.bottom
,
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()),
875 DWORD
*pixels
= nullptr;
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
{
890 unsigned char *Bytes() const noexcept
{
891 return reinterpret_cast<unsigned char *>(pixels
);
893 HDC
DC() const noexcept
{
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
);
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},
917 void *image
= nullptr;
918 hbmMem
= CreateDIBSection(hMemDC
, &bpih
, DIB_RGB_COLORS
, &image
, {}, 0);
919 if (!hbmMem
|| !image
) {
922 pixels
= static_cast<DWORD
*>(image
);
923 hbmOld
= SelectBitmap(hMemDC
, hbmMem
);
926 DIBSection::~DIBSection() noexcept
{
928 SelectBitmap(hMemDC
, hbmOld
);
932 ::DeleteObject(hbmMem
);
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
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
);
981 DIBSection
section(hdc
, size
);
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
);
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
);
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
);
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
);
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
);
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
) {
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
*) {
1102 typedef VarBuffer
<int, stackBufferLength
> TextPositionsI
;
1104 void SurfaceGDI::DrawTextCommon(PRectangle rc
, const Font
*font_
, XYPOSITION ybase
, std::string_view text
, UINT fuOptions
) {
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);
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
,
1134 // Avoid drawing spaces in transparent mode
1135 for (const char ch
: text
) {
1137 ::SetTextColor(hdc
, fore
.OpaqueRGB());
1138 ::SetBkMode(hdc
, TRANSPARENT
);
1139 DrawTextCommon(rc
, font_
, ybase
, text
, 0);
1140 ::SetBkMode(hdc
, OPAQUE
);
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
);
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
)) {
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
1168 for (unsigned int bytePos
= 0; (bytePos
< byteCount
) && (i
< len
); bytePos
++) {
1169 positions
[i
++] = static_cast<XYPOSITION
>(poses
.buffer
[ui
]);
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.
1179 positions
[i
] = static_cast<XYPOSITION
>(poses
.buffer
[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
) {
1191 if (!(mode
.codePage
== CpUtf8
)) {
1192 ::GetTextExtentPoint32A(hdc
, text
.data(), std::min(static_cast<int>(text
.length()), maxLenText
), &sz
);
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
) {
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
,
1226 // Avoid drawing spaces in transparent mode
1227 for (const char ch
: text
) {
1229 ::SetTextColor(hdc
, fore
.OpaqueRGB());
1230 ::SetBkMode(hdc
, TRANSPARENT
);
1231 DrawTextCommonUTF8(rc
, font_
, ybase
, text
, 0);
1232 ::SetBkMode(hdc
, OPAQUE
);
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
);
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
)) {
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
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
) {
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_
) {
1279 ::GetTextMetrics(hdc
, &tm
);
1280 return static_cast<XYPOSITION
>(tm
.tmAscent
);
1283 XYPOSITION
SurfaceGDI::Descent(const Font
*font_
) {
1286 ::GetTextMetrics(hdc
, &tm
);
1287 return static_cast<XYPOSITION
>(tm
.tmDescent
);
1290 XYPOSITION
SurfaceGDI::InternalLeading(const Font
*font_
) {
1293 ::GetTextMetrics(hdc
, &tm
);
1294 return static_cast<XYPOSITION
>(tm
.tmInternalLeading
);
1297 XYPOSITION
SurfaceGDI::Height(const Font
*font_
) {
1300 ::GetTextMetrics(hdc
, &tm
);
1301 return static_cast<XYPOSITION
>(tm
.tmHeight
);
1304 XYPOSITION
SurfaceGDI::AverageCharWidth(const Font
*font_
) {
1307 ::GetTextMetrics(hdc
, &tm
);
1308 return static_cast<XYPOSITION
>(tm
.tmAveCharWidth
);
1311 void SurfaceGDI::SetClip(PRectangle rc
) {
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() {
1326 void SurfaceGDI::FlushDrawing() {
1329 #if defined(USE_D2D)
1333 constexpr D2D1_RECT_F
RectangleFromPRectangle(PRectangle rc
) noexcept
{
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
{
1359 rect
.bottom
- inset
};
1366 class SurfaceD2D
: public Surface
, public ISetRenderingParams
{
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
;
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
;
1466 desiredFormat
.format
= DXGI_FORMAT_UNKNOWN
;
1468 desiredFormat
= pRenderTargetCompatible
->GetPixelFormat();
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;
1480 logPixelsY
= logPixelsY_
;
1483 SurfaceD2D::~SurfaceD2D() noexcept
{
1487 void SurfaceD2D::Clear() noexcept
{
1488 ReleaseUnknown(pBrush
);
1489 if (pRenderTarget
) {
1490 while (clipsActive
) {
1491 pRenderTarget
->PopAxisAlignedClip();
1494 if (ownRenderTarget
) {
1495 pRenderTarget
->EndDraw();
1496 ReleaseUnknown(pRenderTarget
);
1497 ownRenderTarget
= false;
1499 pRenderTarget
= nullptr;
1501 pBitmapRenderTarget
= nullptr;
1504 void SurfaceD2D::Release() noexcept
{
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
) {
1521 bool SurfaceD2D::Initialised() {
1522 return pRenderTarget
!= nullptr;
1525 void SurfaceD2D::Init(WindowID wid
) {
1530 void SurfaceD2D::Init(SurfaceID sid
, WindowID 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
);
1543 void SurfaceD2D::SetMode(SurfaceMode 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
);
1556 pBrush
->SetColor(col
);
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() {
1583 void SurfaceD2D::SetDeviceScaleFactor(const ID2D1RenderTarget
*const pD2D1RenderTarget
) noexcept
{
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();
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
);
1639 void SurfaceD2D::PolyLine(const Point
*pts
, size_t npts
, Stroke stroke
) {
1640 PLATFORM_ASSERT(pRenderTarget
&& (npts
> 1));
1641 if (!pRenderTarget
|| (npts
<= 1)) {
1645 const Geometry geometry
= GeometricFigure(pts
, npts
, D2D1_FIGURE_BEGIN_HOLLOW
);
1646 PLATFORM_ASSERT(geometry
);
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
);
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
) {
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
);
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
),
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
),
1753 D2DPenColourAlpha(fillStroke
.fill
.colour
);
1754 pRenderTarget
->FillRoundedRectangle(roundedRectFill
, pBrush
);
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)),
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());
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
)), {}
1805 case GradientOptions::leftToRight
:
1806 lgbp
.endPoint
= DPointFromPoint(Point(rc
.right
, rc
.top
));
1808 case GradientOptions::topToBottom
:
1810 lgbp
.endPoint
= DPointFromPoint(Point(rc
.left
, rc
.bottom
));
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
) {
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
) {
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
) {
1884 if (rc
.Width() < rc
.Height()) {
1885 // Can't draw nice ends so just draw a rectangle
1886 RectangleDraw(rc
, fillStroke
);
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
),
1901 D2DPenColourAlpha(fillStroke
.stroke
.colour
);
1902 pRenderTarget
->DrawRoundedRectangle(roundedRect
, pBrush
, fillStroke
.stroke
.WidthF());
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();
1912 if (const GeometrySink pSink
= GeometrySinkCreate(pathGeometry
.get())) {
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
)));
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
)));
1923 case Ends::semiCircles
:
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
);
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
)));
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
)));
1947 case Ends::semiCircles
:
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
);
1961 pSink
->EndFigure(D2D1_FIGURE_END_CLOSED
);
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
{
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
,
2002 IUnknown
*clientDrawingEffect
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
;
2010 BlobInline(XYPOSITION width_
=0.0f
) noexcept
: width(width_
) {
2014 /// Implement IUnknown
2015 STDMETHODIMP
BlobInline::QueryInterface(REFIID riid
, PVOID
*ppv
) {
2018 // Never called so not checked.
2020 if (riid
== IID_IUnknown
)
2022 if (riid
== __uuidof(IDWriteInlineObject
))
2025 return E_NOINTERFACE
;
2029 STDMETHODIMP_(ULONG
) BlobInline::AddRef() {
2030 // Lifetime tied to Platform methods so ignore any reference operations.
2034 STDMETHODIMP_(ULONG
) BlobInline::Release() {
2035 // Lifetime tied to Platform methods so ignore any reference operations.
2039 /// Implement IDWriteInlineObject
2040 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE
BlobInline::Draw(
2042 IDWriteTextRenderer
*,
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.
2054 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE
BlobInline::GetMetrics(
2055 DWRITE_INLINE_OBJECT_METRICS
*metrics
2059 metrics
->width
= static_cast<FLOAT
>(width
);
2060 metrics
->height
= 2;
2061 metrics
->baseline
= 1;
2062 metrics
->supportsSideways
= FALSE
;
2066 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE
BlobInline::GetOverhangMetrics(
2067 DWRITE_OVERHANG_METRICS
*overhangs
2071 overhangs
->left
= 0;
2073 overhangs
->right
= 0;
2074 overhangs
->bottom
= 0;
2078 COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE
BlobInline::GetBreakConditions(
2079 DWRITE_BREAK_CONDITION
*breakConditionBefore
,
2080 DWRITE_BREAK_CONDITION
*breakConditionAfter
2082 if (!breakConditionBefore
|| !breakConditionAfter
)
2084 // Since not performing 2D layout, not necessary to implement
2085 *breakConditionBefore
= DWRITE_BREAK_CONDITION_NEUTRAL
;
2086 *breakConditionAfter
= DWRITE_BREAK_CONDITION_NEUTRAL
;
2090 class ScreenLineLayout
: public IScreenLineLayout
{
2091 IDWriteTextLayout
*textLayout
= nullptr;
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
);
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(
2135 false, // trailing if false, else leading edge
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
));
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');
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())
2207 text
= screenLine
->Text();
2210 const FontDirectWrite
*pfm
= FontDirectWrite::Cast(screenLine
->FontOfPosition(0));
2211 if (!pfm
->pTextFormat
) {
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(
2221 static_cast<UINT32
>(buffer
.length()),
2223 static_cast<FLOAT
>(screenLine
->Width()),
2224 static_cast<FLOAT
>(screenLine
->Height()),
2226 if (!SUCCEEDED(hrCreate
)) {
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
) {
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
),
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
,
2282 pos
= isTrailingHit
? hitTestMetrics
.textPosition
: caretMetrics
.textPosition
;
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
) {
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
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
)) {
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
),
2341 hitTestMetrics
.data(),
2342 static_cast<UINT32
>(hitTestMetrics
.size()),
2346 if (actualHitTestCount
== 0) {
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
),
2358 hitTestMetrics
.data(),
2359 static_cast<UINT32
>(hitTestMetrics
.size()),
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
);
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(
2399 static_cast<FLOAT
>(rc
.Width()),
2400 static_cast<FLOAT
>(rc
.Height()),
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
,
2434 // Avoid drawing spaces in transparent mode
2435 for (const char ch
: text
) {
2437 if (pRenderTarget
) {
2438 D2DPenColourAlpha(fore
);
2439 DrawTextCommon(rc
, font_
, ybase
, text
, 0, 0);
2448 HRESULT
MeasurePositions(TextPositions
&poses
, const TextWide
&tbuf
, IDWriteTextFormat
*pTextFormat
) {
2450 // Unexpected failure like no access to DirectWrite so give up.
2454 // Initialize poses for safety.
2455 std::fill(poses
.buffer
, poses
.buffer
+ tbuf
.tlen
, 0.0f
);
2457 IDWriteTextLayout
*pTextLayout
= nullptr;
2458 const HRESULT hrCreate
= pIDWriteFactory
->CreateTextLayout(tbuf
.buffer
, tbuf
.tlen
, pTextFormat
, 10000.0, 1000.0, &pTextLayout
);
2459 if (!SUCCEEDED(hrCreate
)) {
2465 VarBuffer
<DWRITE_CLUSTER_METRICS
, stackBufferLength
> cm(tbuf
.tlen
);
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;
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
);
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
))) {
2496 if (codePageText
== CpUtf8
) {
2497 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
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
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
];
2523 // May be one or two bytes per position
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
];
2539 XYPOSITION
SurfaceD2D::WidthText(const Font
*font_
, std::string_view text
) {
2541 const FontDirectWrite
*pfm
= FontDirectWrite::Cast(font_
);
2542 if (pfm
->pTextFormat
) {
2543 const TextWide
tbuf(text
, pfm
->CodePageText(mode
.codePage
));
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
);
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
,
2577 // Avoid drawing spaces in transparent mode
2578 for (const char ch
: text
) {
2580 if (pRenderTarget
) {
2581 D2DPenColourAlpha(fore
);
2582 DrawTextCommon(rc
, font_
, ybase
, text
, CpUtf8
, 0);
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
))) {
2596 // Map the widths given for UTF-16 characters back onto the UTF-8 input string
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
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
) {
2617 const FontDirectWrite
*pfm
= FontDirectWrite::Cast(font_
);
2618 if (pfm
->pTextFormat
) {
2619 const TextWide
tbuf(text
, CpUtf8
);
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
);
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_
) {
2655 const FontDirectWrite
*pfm
= FontDirectWrite::Cast(font_
);
2656 if (pfm
->pTextFormat
) {
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
);
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
);
2681 void SurfaceD2D::PopClip() {
2682 if (pRenderTarget
) {
2683 PLATFORM_ASSERT(clipsActive
> 0);
2684 pRenderTarget
->PopAxisAlignedClip();
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_
);
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
>();
2709 return std::make_unique
<SurfaceD2D
>();
2711 return std::make_unique
<SurfaceGDI
>();
2715 Window::~Window() noexcept
{
2718 void Window::Destroy() noexcept
{
2720 ::DestroyWindow(HwndFromWindowID(wid
));
2724 PRectangle
Window::GetPosition() const {
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
);
2738 RECT
RectFromMonitor(HMONITOR hMonitor
) noexcept
{
2739 MONITORINFO mi
= {};
2740 mi
.cbSize
= sizeof(mi
);
2741 if (GetMonitorInfo(hMonitor
, &mi
)) {
2744 RECT rc
= {0, 0, 0, 0};
2745 if (::SystemParametersInfoA(SPI_GETWORKAREA
, 0, &rc
, 0) == 0) {
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
);
2787 PRectangle
Window::GetClientPosition() const {
2790 ::GetClientRect(HwndFromWindowID(wid
), &rc
);
2791 return PRectangle::FromInts(rc
.left
, rc
.top
, rc
.right
, rc
.bottom
);
2794 void Window::Show(bool show
) {
2796 ::ShowWindow(HwndFromWindowID(wid
), SW_SHOWNOACTIVATE
);
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
);
2812 std::optional
<DWORD
> RegGetDWORD(HKEY hKey
, LPCWSTR valueName
) noexcept
{
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
) {
2823 class CursorHelper
{
2826 HBITMAP hOldBitmap
{};
2827 DWORD
*pixels
= nullptr;
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
},
2844 SelectBitmap(hMemDC
, hOldBitmap
);
2847 ::DeleteObject(hBitmap
);
2854 CursorHelper(int width_
, int height_
) noexcept
: width
{width_
}, height
{height_
} {
2855 hMemDC
= ::CreateCompatibleDC({});
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
;
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);
2877 hOldBitmap
= SelectBitmap(hMemDC
, hBitmap
);
2881 bool HasBitmap() const noexcept
{
2882 return hOldBitmap
!= nullptr;
2885 HCURSOR
Create() noexcept
{
2887 // Create an empty mask bitmap.
2888 HBITMAP hMonoBitmap
= ::CreateBitmap(width
, height
, 1, 1, nullptr);
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
);
2894 ICONINFO info
= {false, static_cast<DWORD
>(width
- 1), 0, hMonoBitmap
, hBitmap
};
2895 cursor
= ::CreateIconIndirect(&info
);
2896 ::DeleteObject(hMonoBitmap
);
2901 #if defined(USE_D2D)
2903 bool DrawD2D(COLORREF fillColour
, COLORREF strokeColour
) noexcept
{
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
;
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_
) {
2924 const std::unique_ptr
<ID2D1DCRenderTarget
, UnknownReleaser
> pTarget(pTarget_
);
2926 const RECT rc
= {0, 0, width
, height
};
2927 hr
= pTarget_
->BindDC(hMemDC
, &rc
);
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();
2948 const GeometrySink sink
= GeometrySinkCreate(geometry
.get());
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
);
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
);
2976 void Draw(COLORREF fillColour
, COLORREF strokeColour
) noexcept
{
2977 #if defined(USE_D2D)
2978 if (DrawD2D(fillColour
, strokeColour
)) {
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
);
2995 const LOGBRUSH brushParameters
{ BS_SOLID
, strokeColour
, 0 };
2996 pen
= ::ExtCreatePen(PS_GEOMETRIC
| PS_ENDCAP_ROUND
| PS_JOIN_MITER
,
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
++) {
3017 *pixels
|= 0xFF000000U
;
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;
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
);
3043 if (cursorBaseSize
> defaultCursorBaseSize
) {
3044 width
= ::MulDiv(cursorBaseSize
, dpi
, USER_DEFAULT_SCREEN_DPI
);
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()) {
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
) {
3065 std::swap(fillColour
, strokeColour
);
3068 if (std::optional
<DWORD
> cursorColor
= RegGetDWORD(hKey
, L
"CursorColor")) {
3069 fillColour
= *cursorColor
;
3072 default: // 0, 3 white, 2, 5 invert
3076 ::RegCloseKey(hKey
);
3079 cursorHelper
.Draw(fillColour
, strokeColour
);
3080 HCURSOR cursor
= cursorHelper
.Create();
3084 void Window::SetCursor(Cursor curs
) {
3087 ::SetCursor(::LoadCursor(NULL
,IDC_IBEAM
));
3090 ::SetCursor(::LoadCursor(NULL
,IDC_UPARROW
));
3093 ::SetCursor(::LoadCursor(NULL
,IDC_WAIT
));
3095 case Cursor::horizontal
:
3096 ::SetCursor(::LoadCursor(NULL
,IDC_SIZEWE
));
3098 case Cursor::vertical
:
3099 ::SetCursor(::LoadCursor(NULL
,IDC_SIZENS
));
3102 ::SetCursor(::LoadCursor(NULL
,IDC_HAND
));
3104 case Cursor::reverseArrow
:
3106 case Cursor::invalid
: // Should not occur, but just in case.
3108 ::SetCursor(::LoadCursor(NULL
,IDC_ARROW
));
3113 /* Returns rectangle of monitor pt is on, both rect and pt are in Window's
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
);
3130 return PRectangle();
3134 struct ListItemData
{
3140 std::vector
<char> words
;
3142 std::vector
<ListItemData
> data
;
3145 void Clear() noexcept
{
3150 ListItemData
Get(size_t index
) const noexcept
{
3151 if (index
< data
.size()) {
3154 ListItemData missing
= {"", -1};
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);
3173 const TCHAR ListBoxX_ClassName
[] = TEXT("ListBoxX");
3175 ListBox::ListBox() noexcept
{
3178 ListBox::~ListBox() noexcept
{
3181 class ListBoxX
: public ListBox
{
3184 Technology technology
;
3185 RGBAImageSet images
;
3189 int desiredVisibleRows
;
3190 unsigned int maxItemCharacters
;
3191 unsigned int aveCharWidth
;
3195 IListBoxDelegate
*delegate
;
3196 const char *widestItem
;
3197 unsigned int maxCharWidth
;
3199 PRectangle rcPreSize
;
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();
3218 void ResizeToCursor();
3219 void StartResize(WPARAM
);
3220 LRESULT
NcHitTest(WPARAM
, LPARAM
) const;
3221 void CentreItem(int n
);
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
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
),
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
{
3242 ::DeleteObject(fontCopy
);
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_
) {
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
,
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
);
3303 ::DeleteObject(fontCopy
);
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};
3341 len
= static_cast<int>(strlen(widestItem
));
3343 const TextWide
tbuf(widestItem
, CpUtf8
);
3344 ::GetTextExtentPoint32W(hdc
, tbuf
.buffer
, tbuf
.tlen
, &textSize
);
3346 ::GetTextExtentPoint32A(hdc
, widestItem
, len
, &textSize
);
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
);
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() {
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;
3385 void ListBoxX::Append(char *, int) {
3386 // This method is no longer called in Scintilla
3387 PLATFORM_ASSERT(false);
3390 int ListBoxX::Length() {
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
3400 ListBox_SetCurSel(lb
, n
);
3405 int ListBoxX::GetSelection() {
3406 return ListBox_GetCurSel(lb
);
3409 // This is not actually called at present
3410 int ListBoxX::Find(const char *) {
3414 std::string
ListBoxX::GetValue(int n
) {
3415 const ListItemData item
= lti
.Get(n
);
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() {
3434 int ColourOfElement(std::optional
<ColourRGBA
> colour
, int nIndex
) {
3435 if (colour
.has_value()) {
3436 return colour
.value().OpaqueRGB();
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
));
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
));
3477 const TextWide
tbuf(text
, CpUtf8
);
3478 ::DrawTextW(pDrawItem
->hDC
, tbuf
.buffer
, tbuf
.tlen
, &rcText
, DT_NOPREFIX
|DT_END_ELLIPSIS
|DT_SINGLELINE
|DT_NOCLIP
);
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
);
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
);
3496 #if defined(USE_D2D)
3497 const D2D1_RENDER_TARGET_PROPERTIES props
= D2D1::RenderTargetProperties(
3498 D2D1_RENDER_TARGET_TYPE_DEFAULT
,
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
;
3514 rcItem
.right
= rcItem
.left
+ images
.GetWidth();
3516 hr
= pDCRT
->BindDC(pDrawItem
->hDC
, &rcItem
);
3517 if (SUCCEEDED(hr
)) {
3518 surfaceItem
->Init(pDCRT
, pDrawItem
->hwndItem
);
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());
3524 ReleaseUnknown(pDCRT
);
3533 void ListBoxX::AppendListItem(const char *text
, const char *numword
) {
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
;
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.
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
) {
3569 AppendListItem(startword
, numword
);
3570 startword
= words
+ i
+ 1;
3572 } else if (words
[i
] == typesep
) {
3573 numword
= words
+ i
;
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);
3591 void ListBoxX::SetOptions(ListOptions 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
);
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
;
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())};
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())};
3637 void ListBoxX::SetRedraw(bool on
) noexcept
{
3638 ::SendMessage(lb
, WM_SETREDRAW
, on
, 0);
3640 ::InvalidateRect(lb
, nullptr, TRUE
);
3643 void ListBoxX::ResizeToCursor() {
3644 PRectangle rc
= GetPosition();
3646 ::GetCursorPos(&ptw
);
3647 const Point pt
= PointFromPOINT(ptw
) + dragOffset
;
3649 switch (resizeHit
) {
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
);
3693 void ListBoxX::StartResize(WPARAM hitCode
) {
3694 rcPreSize
= GetPosition();
3696 ::GetCursorPos(&cursorPos
);
3702 dragOffset
.x
= rcPreSize
.right
- cursorPos
.x
;
3703 dragOffset
.y
= rcPreSize
.bottom
- cursorPos
.y
;
3707 dragOffset
.x
= rcPreSize
.right
- cursorPos
.x
;
3708 dragOffset
.y
= rcPreSize
.top
- cursorPos
.y
;
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
3716 dragOffset
.x
= rcPreSize
.left
- cursorPos
.x
;
3717 dragOffset
.y
= rcPreSize
.top
- cursorPos
.y
;
3720 dragOffset
.x
= rcPreSize
.left
- cursorPos
.x
;
3721 dragOffset
.y
= rcPreSize
.bottom
- cursorPos
.y
;
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
3759 // Valid only if caret below list
3760 if (location
.y
< rc
.top
)
3766 case HTBOTTOMRIGHT
: {
3767 // Valid only if caret above list
3768 if (rc
.bottom
<= location
.y
)
3779 void ListBoxX::OnDoubleClick() {
3781 ListBoxEvent
event(ListBoxEvent::EventType::doubleClick
);
3782 delegate
->ListNotify(&event
);
3786 void ListBoxX::OnSelChange() {
3788 ListBoxEvent
event(ListBoxEvent::EventType::selectionChange
);
3789 delegate
->ListNotify(&event
);
3793 POINT
ListBoxX::GetClientExtent() const noexcept
{
3795 ::GetWindowRect(HwndFromWindowID(wid
), &rc
);
3796 POINT ret
{ rc
.right
- rc
.left
, rc
.bottom
- rc
.top
};
3800 void ListBoxX::CentreItem(int n
) {
3801 // If below mid point, scroll up to centre, but with more items below if uneven
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
) {
3837 ListBoxX
*lbx
= static_cast<ListBoxX
*>(PointerFromWindow(::GetParent(hWnd
)));
3844 HDC hDC
= ::BeginPaint(hWnd
, &ps
);
3848 ::EndPaint(hWnd
, &ps
);
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
3859 const LRESULT lResult
= ::SendMessage(hWnd
, LB_ITEMFROMPOINT
, 0, lParam
);
3860 if (HIWORD(lResult
) == 0) {
3861 ListBox_SetCurSel(hWnd
, LOWORD(lResult
));
3872 case WM_LBUTTONDBLCLK
: {
3874 lbx
->OnDoubleClick();
3879 case WM_MBUTTONDOWN
:
3880 // disable the scroll wheel button click action
3887 WNDPROC prevWndProc
= reinterpret_cast<WNDPROC
>(GetWindowLongPtr(hWnd
, GWLP_USERDATA
));
3889 return ::CallWindowProc(prevWndProc
, hWnd
, iMessage
, wParam
, lParam
);
3891 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
3895 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
3898 LRESULT
ListBoxX::WndProc(HWND hWnd
, UINT iMessage
, WPARAM wParam
, LPARAM lParam
) {
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
,
3909 reinterpret_cast<HMENU
>(static_cast<ptrdiff_t>(ctrlID
)),
3912 WNDPROC prevWndProc
= SubclassWindow(lb
, ControlWndProc
);
3913 ::SetWindowLongPtr(lb
, GWLP_USERDATA
, reinterpret_cast<LONG_PTR
>(prevWndProc
));
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());
3929 ::BeginPaint(hWnd
, &ps
);
3930 ::EndPaint(hWnd
, &ps
);
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
);
3940 case WM_MEASUREITEM
: {
3941 MEASUREITEMSTRUCT
*pMeasureItem
= reinterpret_cast<MEASUREITEMSTRUCT
*>(lParam
);
3942 pMeasureItem
->itemHeight
= ItemHeight();
3947 Draw(reinterpret_cast<DRAWITEMSTRUCT
*>(lParam
));
3952 SetWindowPointer(hWnd
, nullptr);
3953 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
3956 // To reduce flicker we can elide background erasure since this window is
3957 // completely covered by its child.
3960 case WM_GETMINMAXINFO
: {
3961 MINMAXINFO
*minMax
= reinterpret_cast<MINMAXINFO
*>(lParam
);
3962 minMax
->ptMaxTrackSize
= MaxTrackSize();
3963 minMax
->ptMinTrackSize
= MinTrackSize();
3967 case WM_MOUSEACTIVATE
:
3968 return MA_NOACTIVATE
;
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
);
3979 case WM_MOUSEMOVE
: {
3980 if (resizeHit
== 0) {
3981 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
3990 if (resizeHit
!= 0) {
3994 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
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
;
4004 ListBox_SetTopIndex(lb
, top
);
4009 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
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
));
4024 return lbx
->WndProc(hWnd
, iMessage
, wParam
, lParam
);
4026 return ::DefWindowProc(hWnd
, iMessage
, wParam
, lParam
);
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() {
4061 mid
= ::CreatePopupMenu();
4064 void Menu::Destroy() noexcept
{
4066 ::DestroyMenu(static_cast<HMENU
>(mid
));
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);
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() {
4089 int Platform::DefaultFontSize() {
4093 unsigned int Platform::DoubleClickTime() {
4094 return ::GetDoubleClickTime();
4097 void Platform::DebugDisplay(const char *s
) noexcept
{
4098 ::OutputDebugStringA(s
);
4104 void Platform::DebugPrintf(const char *format
, ...) noexcept
{
4107 va_start(pArguments
, format
);
4108 vsnprintf(buffer
, std::size(buffer
), format
, pArguments
);
4110 Platform::DebugDisplay(buffer
);
4113 void Platform::DebugPrintf(const char *, ...) noexcept
{
4117 static bool assertionPopUps
= true;
4119 bool Platform::ShowAssertionPopUps(bool assertionPopUps_
) noexcept
{
4120 const bool ret
= assertionPopUps
;
4121 assertionPopUps
= assertionPopUps_
;
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
) {
4133 } else if (idButton
== IDIGNORE
) {
4139 Platform::DebugDisplay(buffer
);
4145 void Platform_Initialise(void *hInstance
) noexcept
{
4146 hinstPlatformRes
= static_cast<HINSTANCE
>(hInstance
);
4148 ListBoxX_Register();
4151 void Platform_Finalise(bool fromDllMain
) noexcept
{
4152 #if defined(USE_D2D)
4154 ReleaseUnknown(pIDWriteFactory
);
4155 ReleaseUnknown(pD2DFactory
);
4157 FreeLibrary(hDLLDWrite
);
4161 FreeLibrary(hDLLD2D
);
4166 if (!fromDllMain
&& hDLLShcore
) {
4167 FreeLibrary(hDLLShcore
);
4170 ListBoxX_Unregister();