2009-04-24 23:35:41 +00:00
|
|
|
// Scintilla source code edit control
|
|
|
|
/** @file PlatWin.cxx
|
|
|
|
** Implementation of platform facilities on Windows.
|
|
|
|
**/
|
|
|
|
// Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
|
|
|
|
// The License.txt file describes the conditions under which this software may be distributed.
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#include <cstddef>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
|
|
|
#include <cstdio>
|
|
|
|
#include <cstdarg>
|
|
|
|
#include <ctime>
|
|
|
|
#include <cmath>
|
|
|
|
#include <climits>
|
|
|
|
|
|
|
|
#include <string_view>
|
2013-08-28 00:44:27 +00:00
|
|
|
#include <vector>
|
|
|
|
#include <map>
|
2022-01-04 23:07:50 +00:00
|
|
|
#include <optional>
|
2019-05-04 18:14:48 +00:00
|
|
|
#include <algorithm>
|
2021-02-21 04:53:09 +00:00
|
|
|
#include <iterator>
|
2019-05-04 18:14:48 +00:00
|
|
|
#include <memory>
|
2021-02-21 04:53:09 +00:00
|
|
|
#include <mutex>
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
// Want to use std::min and std::max so don't want Windows.h version of min and max
|
|
|
|
#if !defined(NOMINMAX)
|
|
|
|
#define NOMINMAX
|
|
|
|
#endif
|
2010-08-21 23:59:56 +00:00
|
|
|
#undef _WIN32_WINNT
|
2022-04-13 11:10:12 +00:00
|
|
|
#define _WIN32_WINNT 0x0A00
|
2013-08-28 00:44:27 +00:00
|
|
|
#undef WINVER
|
2022-04-13 11:10:12 +00:00
|
|
|
#define WINVER 0x0A00
|
2022-01-04 23:07:50 +00:00
|
|
|
#define WIN32_LEAN_AND_MEAN 1
|
2009-04-24 23:35:41 +00:00
|
|
|
#include <windows.h>
|
|
|
|
#include <commctrl.h>
|
|
|
|
#include <richedit.h>
|
|
|
|
#include <windowsx.h>
|
2022-12-10 12:35:16 +00:00
|
|
|
#include <shellscalingapi.h>
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
#if !defined(DISABLE_D2D)
|
2013-08-28 00:44:27 +00:00
|
|
|
#define USE_D2D 1
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(USE_D2D)
|
|
|
|
#include <d2d1.h>
|
|
|
|
#include <dwrite.h>
|
|
|
|
#endif
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
#include "ScintillaTypes.h"
|
|
|
|
|
|
|
|
#include "Debugging.h"
|
|
|
|
#include "Geometry.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
#include "Platform.h"
|
|
|
|
#include "XPM.h"
|
2015-06-07 21:19:26 +00:00
|
|
|
#include "UniConversion.h"
|
2019-05-04 18:14:48 +00:00
|
|
|
#include "DBCS.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
#include "WinTypes.h"
|
2019-05-04 18:14:48 +00:00
|
|
|
#include "PlatWin.h"
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
// __uuidof is a Microsoft extension but makes COM code neater, so disable warning
|
|
|
|
#if defined(__clang__)
|
|
|
|
#pragma clang diagnostic ignored "-Wlanguage-extension-token"
|
|
|
|
#endif
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
using namespace Scintilla;
|
|
|
|
|
|
|
|
namespace Scintilla::Internal {
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
UINT CodePageFromCharSet(CharacterSet characterSet, UINT documentCodePage) noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
#if defined(USE_D2D)
|
2019-05-04 18:14:48 +00:00
|
|
|
IDWriteFactory *pIDWriteFactory = nullptr;
|
|
|
|
ID2D1Factory *pD2DFactory = nullptr;
|
2019-07-21 13:26:02 +00:00
|
|
|
D2D1_DRAW_TEXT_OPTIONS d2dDrawTextOptions = D2D1_DRAW_TEXT_OPTIONS_NONE;
|
2015-06-07 21:19:26 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
static HMODULE hDLLD2D {};
|
|
|
|
static HMODULE hDLLDWrite {};
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void LoadD2DOnce() noexcept {
|
|
|
|
DWORD loadLibraryFlags = 0;
|
|
|
|
HMODULE kernel32 = ::GetModuleHandleW(L"kernel32.dll");
|
|
|
|
if (kernel32) {
|
|
|
|
if (::GetProcAddress(kernel32, "SetDefaultDllDirectories")) {
|
|
|
|
// Availability of SetDefaultDllDirectories implies Windows 8+ or
|
|
|
|
// that KB2533623 has been installed so LoadLibraryEx can be called
|
|
|
|
// with LOAD_LIBRARY_SEARCH_SYSTEM32.
|
|
|
|
loadLibraryFlags = LOAD_LIBRARY_SEARCH_SYSTEM32;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef HRESULT (WINAPI *D2D1CFSig)(D2D1_FACTORY_TYPE factoryType, REFIID riid,
|
|
|
|
CONST D2D1_FACTORY_OPTIONS *pFactoryOptions, IUnknown **factory);
|
|
|
|
typedef HRESULT (WINAPI *DWriteCFSig)(DWRITE_FACTORY_TYPE factoryType, REFIID iid,
|
|
|
|
IUnknown **factory);
|
|
|
|
|
|
|
|
hDLLD2D = ::LoadLibraryEx(TEXT("D2D1.DLL"), 0, loadLibraryFlags);
|
|
|
|
D2D1CFSig fnD2DCF = DLLFunction<D2D1CFSig>(hDLLD2D, "D2D1CreateFactory");
|
|
|
|
if (fnD2DCF) {
|
|
|
|
// A single threaded factory as Scintilla always draw on the GUI thread
|
|
|
|
fnD2DCF(D2D1_FACTORY_TYPE_SINGLE_THREADED,
|
|
|
|
__uuidof(ID2D1Factory),
|
|
|
|
nullptr,
|
|
|
|
reinterpret_cast<IUnknown**>(&pD2DFactory));
|
|
|
|
}
|
|
|
|
hDLLDWrite = ::LoadLibraryEx(TEXT("DWRITE.DLL"), 0, loadLibraryFlags);
|
|
|
|
DWriteCFSig fnDWCF = DLLFunction<DWriteCFSig>(hDLLDWrite, "DWriteCreateFactory");
|
|
|
|
if (fnDWCF) {
|
|
|
|
const GUID IID_IDWriteFactory2 = // 0439fc60-ca44-4994-8dee-3a9af7b732ec
|
|
|
|
{ 0x0439fc60, 0xca44, 0x4994, { 0x8d, 0xee, 0x3a, 0x9a, 0xf7, 0xb7, 0x32, 0xec } };
|
|
|
|
|
|
|
|
const HRESULT hr = fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
|
|
|
|
IID_IDWriteFactory2,
|
|
|
|
reinterpret_cast<IUnknown**>(&pIDWriteFactory));
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
// D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT
|
|
|
|
d2dDrawTextOptions = static_cast<D2D1_DRAW_TEXT_OPTIONS>(0x00000004);
|
|
|
|
} else {
|
|
|
|
fnDWCF(DWRITE_FACTORY_TYPE_SHARED,
|
|
|
|
__uuidof(IDWriteFactory),
|
|
|
|
reinterpret_cast<IUnknown**>(&pIDWriteFactory));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LoadD2D() {
|
|
|
|
static std::once_flag once;
|
|
|
|
std::call_once(once, LoadD2DOnce);
|
2013-08-28 00:44:27 +00:00
|
|
|
return pIDWriteFactory && pD2DFactory;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void *PointerFromWindow(HWND hWnd) noexcept {
|
|
|
|
return reinterpret_cast<void *>(::GetWindowLongPtr(hWnd, 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetWindowPointer(HWND hWnd, void *ptr) noexcept {
|
|
|
|
::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(ptr));
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// system DPI, same for all monitor.
|
|
|
|
UINT uSystemDPI = USER_DEFAULT_SCREEN_DPI;
|
|
|
|
|
|
|
|
using GetDpiForWindowSig = UINT(WINAPI *)(HWND hwnd);
|
|
|
|
GetDpiForWindowSig fnGetDpiForWindow = nullptr;
|
|
|
|
|
|
|
|
HMODULE hDLLShcore {};
|
|
|
|
using GetDpiForMonitorSig = HRESULT (WINAPI *)(HMONITOR hmonitor, /*MONITOR_DPI_TYPE*/int dpiType, UINT *dpiX, UINT *dpiY);
|
|
|
|
GetDpiForMonitorSig fnGetDpiForMonitor = nullptr;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
using GetSystemMetricsForDpiSig = int(WINAPI *)(int nIndex, UINT dpi);
|
|
|
|
GetSystemMetricsForDpiSig fnGetSystemMetricsForDpi = nullptr;
|
|
|
|
|
|
|
|
using AdjustWindowRectExForDpiSig = BOOL(WINAPI *)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
|
|
|
|
AdjustWindowRectExForDpiSig fnAdjustWindowRectExForDpi = nullptr;
|
|
|
|
|
2022-12-10 12:35:16 +00:00
|
|
|
using AreDpiAwarenessContextsEqualSig = BOOL(WINAPI *)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
|
|
|
|
AreDpiAwarenessContextsEqualSig fnAreDpiAwarenessContextsEqual = nullptr;
|
|
|
|
|
|
|
|
using GetWindowDpiAwarenessContextSig = DPI_AWARENESS_CONTEXT(WINAPI *)(HWND);
|
|
|
|
GetWindowDpiAwarenessContextSig fnGetWindowDpiAwarenessContext = nullptr;
|
|
|
|
|
|
|
|
using GetScaleFactorForMonitorSig = HRESULT(WINAPI *)(HMONITOR, DEVICE_SCALE_FACTOR *);
|
|
|
|
GetScaleFactorForMonitorSig fnGetScaleFactorForMonitor = nullptr;
|
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
using GetThreadDpiAwarenessContextSig = DPI_AWARENESS_CONTEXT(WINAPI *)();
|
|
|
|
GetThreadDpiAwarenessContextSig fnGetThreadDpiAwarenessContext = nullptr;
|
|
|
|
|
2022-12-10 12:35:16 +00:00
|
|
|
using SetThreadDpiAwarenessContextSig = DPI_AWARENESS_CONTEXT(WINAPI *)(DPI_AWARENESS_CONTEXT);
|
|
|
|
SetThreadDpiAwarenessContextSig fnSetThreadDpiAwarenessContext = nullptr;
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void LoadDpiForWindow() noexcept {
|
|
|
|
HMODULE user32 = ::GetModuleHandleW(L"user32.dll");
|
|
|
|
fnGetDpiForWindow = DLLFunction<GetDpiForWindowSig>(user32, "GetDpiForWindow");
|
|
|
|
fnGetSystemMetricsForDpi = DLLFunction<GetSystemMetricsForDpiSig>(user32, "GetSystemMetricsForDpi");
|
|
|
|
fnAdjustWindowRectExForDpi = DLLFunction<AdjustWindowRectExForDpiSig>(user32, "AdjustWindowRectExForDpi");
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
fnGetThreadDpiAwarenessContext = DLLFunction<GetThreadDpiAwarenessContextSig>(user32, "GetThreadDpiAwarenessContext");
|
2022-12-10 12:35:16 +00:00
|
|
|
fnSetThreadDpiAwarenessContext = DLLFunction<SetThreadDpiAwarenessContextSig>(user32, "SetThreadDpiAwarenessContext");
|
2021-02-21 04:53:09 +00:00
|
|
|
|
|
|
|
using GetDpiForSystemSig = UINT(WINAPI *)(void);
|
|
|
|
GetDpiForSystemSig fnGetDpiForSystem = DLLFunction<GetDpiForSystemSig>(user32, "GetDpiForSystem");
|
|
|
|
if (fnGetDpiForSystem) {
|
|
|
|
uSystemDPI = fnGetDpiForSystem();
|
|
|
|
} else {
|
|
|
|
HDC hdcMeasure = ::CreateCompatibleDC({});
|
|
|
|
uSystemDPI = ::GetDeviceCaps(hdcMeasure, LOGPIXELSY);
|
|
|
|
::DeleteDC(hdcMeasure);
|
|
|
|
}
|
|
|
|
|
2022-12-10 12:35:16 +00:00
|
|
|
fnGetWindowDpiAwarenessContext = DLLFunction<GetWindowDpiAwarenessContextSig>(user32, "GetWindowDpiAwarenessContext");
|
|
|
|
fnAreDpiAwarenessContextsEqual = DLLFunction<AreDpiAwarenessContextsEqualSig>(user32, "AreDpiAwarenessContextsEqual");
|
|
|
|
|
|
|
|
hDLLShcore = ::LoadLibraryExW(L"shcore.dll", {}, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
|
|
|
if (hDLLShcore) {
|
|
|
|
fnGetScaleFactorForMonitor = DLLFunction<GetScaleFactorForMonitorSig>(hDLLShcore, "GetScaleFactorForMonitor");
|
|
|
|
fnGetDpiForMonitor = DLLFunction<GetDpiForMonitorSig>(hDLLShcore, "GetDpiForMonitor");
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HINSTANCE hinstPlatformRes {};
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr Supports SupportsGDI[] = {
|
|
|
|
Supports::PixelModification,
|
|
|
|
};
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr BYTE Win32MapFontQuality(FontQuality extraFontFlag) noexcept {
|
|
|
|
switch (extraFontFlag & FontQuality::QualityMask) {
|
2010-07-12 22:19:51 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
case FontQuality::QualityNonAntialiased:
|
2010-07-12 22:19:51 +00:00
|
|
|
return NONANTIALIASED_QUALITY;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
case FontQuality::QualityAntialiased:
|
2010-07-12 22:19:51 +00:00
|
|
|
return ANTIALIASED_QUALITY;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
case FontQuality::QualityLcdOptimized:
|
2010-07-12 22:19:51 +00:00
|
|
|
return CLEARTYPE_QUALITY;
|
|
|
|
|
|
|
|
default:
|
2022-01-04 23:07:50 +00:00
|
|
|
return DEFAULT_QUALITY;
|
2010-07-12 22:19:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
#if defined(USE_D2D)
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr D2D1_TEXT_ANTIALIAS_MODE DWriteMapFontQuality(FontQuality extraFontFlag) noexcept {
|
|
|
|
switch (extraFontFlag & FontQuality::QualityMask) {
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
case FontQuality::QualityNonAntialiased:
|
2013-08-28 00:44:27 +00:00
|
|
|
return D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
case FontQuality::QualityAntialiased:
|
2013-08-28 00:44:27 +00:00
|
|
|
return D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
case FontQuality::QualityLcdOptimized:
|
2013-08-28 00:44:27 +00:00
|
|
|
return D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
// Both GDI and DirectWrite can produce a HFONT for use in list boxes
|
|
|
|
struct FontWin : public Font {
|
|
|
|
virtual HFONT HFont() const noexcept = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
void SetLogFont(LOGFONTW &lf, const char *faceName, CharacterSet characterSet, XYPOSITION size, FontWeight weight, bool italic, FontQuality extraFontFlag) {
|
2015-06-07 21:19:26 +00:00
|
|
|
lf = LOGFONTW();
|
2009-04-24 23:35:41 +00:00
|
|
|
// The negative is to allow for leading
|
2019-07-21 13:26:02 +00:00
|
|
|
lf.lfHeight = -(std::abs(std::lround(size)));
|
2022-01-04 23:07:50 +00:00
|
|
|
lf.lfWeight = static_cast<LONG>(weight);
|
2019-05-04 18:14:48 +00:00
|
|
|
lf.lfItalic = italic ? 1 : 0;
|
2009-04-24 23:35:41 +00:00
|
|
|
lf.lfCharSet = static_cast<BYTE>(characterSet);
|
2010-07-12 22:19:51 +00:00
|
|
|
lf.lfQuality = Win32MapFontQuality(extraFontFlag);
|
2019-05-04 18:14:48 +00:00
|
|
|
UTF16FromUTF8(faceName, lf.lfFaceName, LF_FACESIZE);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
struct FontGDI : public FontWin {
|
|
|
|
HFONT hfont = {};
|
|
|
|
FontGDI(const FontParameters &fp) {
|
|
|
|
LOGFONTW lf;
|
|
|
|
SetLogFont(lf, fp.faceName, fp.characterSet, fp.size, fp.weight, fp.italic, fp.extraFontFlag);
|
|
|
|
hfont = ::CreateFontIndirectW(&lf);
|
|
|
|
}
|
|
|
|
// Deleted so FontGDI objects can not be copied.
|
|
|
|
FontGDI(const FontGDI &) = delete;
|
|
|
|
FontGDI(FontGDI &&) = delete;
|
|
|
|
FontGDI &operator=(const FontGDI &) = delete;
|
|
|
|
FontGDI &operator=(FontGDI &&) = delete;
|
|
|
|
~FontGDI() noexcept override {
|
|
|
|
if (hfont)
|
|
|
|
::DeleteObject(hfont);
|
|
|
|
}
|
|
|
|
HFONT HFont() const noexcept override {
|
|
|
|
// Duplicating hfont
|
|
|
|
LOGFONTW lf = {};
|
|
|
|
if (0 == ::GetObjectW(hfont, sizeof(lf), &lf)) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return ::CreateFontIndirectW(&lf);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
#if defined(USE_D2D)
|
2022-01-04 23:07:50 +00:00
|
|
|
struct FontDirectWrite : public FontWin {
|
|
|
|
IDWriteTextFormat *pTextFormat = nullptr;
|
|
|
|
FontQuality extraFontFlag = FontQuality::QualityDefault;
|
|
|
|
CharacterSet characterSet = CharacterSet::Ansi;
|
|
|
|
FLOAT yAscent = 2.0f;
|
|
|
|
FLOAT yDescent = 1.0f;
|
|
|
|
FLOAT yInternalLeading = 0.0f;
|
|
|
|
|
|
|
|
FontDirectWrite(const FontParameters &fp) :
|
|
|
|
extraFontFlag(fp.extraFontFlag),
|
|
|
|
characterSet(fp.characterSet) {
|
2019-07-21 13:26:02 +00:00
|
|
|
const std::wstring wsFace = WStringFromUTF8(fp.faceName);
|
2022-01-04 23:07:50 +00:00
|
|
|
const std::wstring wsLocale = WStringFromUTF8(fp.localeName);
|
|
|
|
const FLOAT fHeight = static_cast<FLOAT>(fp.size);
|
2019-05-04 18:14:48 +00:00
|
|
|
const DWRITE_FONT_STYLE style = fp.italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
|
2019-07-21 13:26:02 +00:00
|
|
|
HRESULT hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr,
|
2013-08-28 00:44:27 +00:00
|
|
|
static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
|
|
|
|
style,
|
2022-01-04 23:07:50 +00:00
|
|
|
DWRITE_FONT_STRETCH_NORMAL, fHeight, wsLocale.c_str(), &pTextFormat);
|
|
|
|
if (hr == E_INVALIDARG) {
|
|
|
|
// Possibly a bad locale name like "/" so try "en-us".
|
|
|
|
hr = pIDWriteFactory->CreateTextFormat(wsFace.c_str(), nullptr,
|
|
|
|
static_cast<DWRITE_FONT_WEIGHT>(fp.weight),
|
|
|
|
style,
|
|
|
|
DWRITE_FONT_STRETCH_NORMAL, fHeight, L"en-us", &pTextFormat);
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
pTextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
IDWriteTextLayout *pTextLayout = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
hr = pIDWriteFactory->CreateTextLayout(L"X", 1, pTextFormat,
|
|
|
|
100.0f, 100.0f, &pTextLayout);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (SUCCEEDED(hr) && pTextLayout) {
|
|
|
|
constexpr int maxLines = 2;
|
2019-05-04 18:14:48 +00:00
|
|
|
DWRITE_LINE_METRICS lineMetrics[maxLines]{};
|
|
|
|
UINT32 lineCount = 0;
|
2013-08-28 00:44:27 +00:00
|
|
|
hr = pTextLayout->GetLineMetrics(lineMetrics, maxLines, &lineCount);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
yAscent = lineMetrics[0].baseline;
|
|
|
|
yDescent = lineMetrics[0].height - lineMetrics[0].baseline;
|
|
|
|
|
|
|
|
FLOAT emHeight;
|
|
|
|
hr = pTextLayout->GetFontSize(0, &emHeight);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
yInternalLeading = lineMetrics[0].height - emHeight;
|
|
|
|
}
|
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pTextLayout);
|
2013-08-28 00:44:27 +00:00
|
|
|
pTextFormat->SetLineSpacing(DWRITE_LINE_SPACING_METHOD_UNIFORM, lineMetrics[0].height, lineMetrics[0].baseline);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
// Deleted so FontDirectWrite objects can not be copied.
|
|
|
|
FontDirectWrite(const FontDirectWrite &) = delete;
|
|
|
|
FontDirectWrite(FontDirectWrite &&) = delete;
|
|
|
|
FontDirectWrite &operator=(const FontDirectWrite &) = delete;
|
|
|
|
FontDirectWrite &operator=(FontDirectWrite &&) = delete;
|
|
|
|
~FontDirectWrite() noexcept override {
|
|
|
|
ReleaseUnknown(pTextFormat);
|
|
|
|
}
|
|
|
|
HFONT HFont() const noexcept override {
|
|
|
|
LOGFONTW lf = {};
|
|
|
|
const HRESULT hr = pTextFormat->GetFontFamilyName(lf.lfFaceName, LF_FACESIZE);
|
|
|
|
if (!SUCCEEDED(hr)) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
lf.lfWeight = pTextFormat->GetFontWeight();
|
|
|
|
lf.lfItalic = pTextFormat->GetFontStyle() == DWRITE_FONT_STYLE_ITALIC;
|
|
|
|
lf.lfHeight = -static_cast<int>(pTextFormat->GetFontSize());
|
|
|
|
return ::CreateFontIndirectW(&lf);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int CodePageText(int codePage) const noexcept {
|
|
|
|
if (!(codePage == CpUtf8) && (characterSet != CharacterSet::Ansi)) {
|
|
|
|
codePage = CodePageFromCharSet(characterSet, codePage);
|
|
|
|
}
|
|
|
|
return codePage;
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
static const FontDirectWrite *Cast(const Font *font_) {
|
|
|
|
const FontDirectWrite *pfm = dynamic_cast<const FontDirectWrite *>(font_);
|
|
|
|
PLATFORM_ASSERT(pfm);
|
|
|
|
if (!pfm) {
|
|
|
|
throw std::runtime_error("SurfaceD2D::SetFont: wrong Font type.");
|
|
|
|
}
|
|
|
|
return pfm;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-12-10 12:35:16 +00:00
|
|
|
HMONITOR MonitorFromWindowHandleScaling(HWND hWnd) noexcept {
|
|
|
|
constexpr DWORD monitorFlags = MONITOR_DEFAULTTONEAREST;
|
|
|
|
|
|
|
|
if (!fnSetThreadDpiAwarenessContext) {
|
|
|
|
return ::MonitorFromWindow(hWnd, monitorFlags);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Temporarily switching to PerMonitorV2 to retrieve correct monitor via MonitorFromRect() in case of active GDI scaling.
|
|
|
|
const DPI_AWARENESS_CONTEXT oldContext = fnSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
|
|
PLATFORM_ASSERT(oldContext != nullptr);
|
|
|
|
|
|
|
|
RECT rect;
|
|
|
|
::GetWindowRect(hWnd, &rect);
|
|
|
|
const HMONITOR monitor = ::MonitorFromRect(&rect, monitorFlags);
|
|
|
|
|
|
|
|
fnSetThreadDpiAwarenessContext(oldContext);
|
|
|
|
return monitor;
|
|
|
|
}
|
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
float GetDeviceScaleFactorWhenGdiScalingActive(HWND hWnd) noexcept {
|
2022-12-10 12:35:16 +00:00
|
|
|
if (fnAreDpiAwarenessContextsEqual) {
|
|
|
|
PLATFORM_ASSERT(fnGetWindowDpiAwarenessContext && fnGetScaleFactorForMonitor);
|
|
|
|
if (fnAreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, fnGetWindowDpiAwarenessContext(hWnd))) {
|
|
|
|
const HWND hRootWnd = ::GetAncestor(hWnd, GA_ROOT); // Scale factor applies to entire (root) window.
|
|
|
|
const HMONITOR hMonitor = MonitorFromWindowHandleScaling(hRootWnd);
|
|
|
|
DEVICE_SCALE_FACTOR deviceScaleFactor;
|
|
|
|
if (S_OK == fnGetScaleFactorForMonitor(hMonitor, &deviceScaleFactor))
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
return deviceScaleFactor / 100.f;
|
2022-12-10 12:35:16 +00:00
|
|
|
}
|
|
|
|
}
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
return 1.f;
|
2022-12-10 12:35:16 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::shared_ptr<Font> Font::Allocate(const FontParameters &fp) {
|
|
|
|
#if defined(USE_D2D)
|
|
|
|
if (fp.technology != Technology::Default) {
|
|
|
|
return std::make_shared<FontDirectWrite>(fp);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return std::make_shared<FontGDI>(fp);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
// Buffer to hold strings and string position arrays without always allocating on heap.
|
|
|
|
// May sometimes have string too long to allocate on stack. So use a fixed stack-allocated buffer
|
|
|
|
// when less than safe size otherwise allocate on heap and free automatically.
|
|
|
|
template<typename T, int lengthStandard>
|
|
|
|
class VarBuffer {
|
|
|
|
T bufferStandard[lengthStandard];
|
|
|
|
public:
|
|
|
|
T *buffer;
|
2019-05-04 18:14:48 +00:00
|
|
|
explicit VarBuffer(size_t length) : buffer(nullptr) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (length > lengthStandard) {
|
|
|
|
buffer = new T[length];
|
|
|
|
} else {
|
|
|
|
buffer = bufferStandard;
|
|
|
|
}
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
// Deleted so VarBuffer objects can not be copied.
|
|
|
|
VarBuffer(const VarBuffer &) = delete;
|
|
|
|
VarBuffer(VarBuffer &&) = delete;
|
|
|
|
VarBuffer &operator=(const VarBuffer &) = delete;
|
|
|
|
VarBuffer &operator=(VarBuffer &&) = delete;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
~VarBuffer() noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (buffer != bufferStandard) {
|
|
|
|
delete []buffer;
|
2019-05-04 18:14:48 +00:00
|
|
|
buffer = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr int stackBufferLength = 400;
|
2013-08-28 00:44:27 +00:00
|
|
|
class TextWide : public VarBuffer<wchar_t, stackBufferLength> {
|
|
|
|
public:
|
2019-05-04 18:14:48 +00:00
|
|
|
int tlen; // Using int instead of size_t as most Win32 APIs take int.
|
2022-01-04 23:07:50 +00:00
|
|
|
TextWide(std::string_view text, int codePage) :
|
2019-05-04 18:14:48 +00:00
|
|
|
VarBuffer<wchar_t, stackBufferLength>(text.length()) {
|
2022-01-04 23:07:50 +00:00
|
|
|
if (codePage == CpUtf8) {
|
2019-05-04 18:14:48 +00:00
|
|
|
tlen = static_cast<int>(UTF16FromUTF8(text, buffer, text.length()));
|
2013-08-28 00:44:27 +00:00
|
|
|
} else {
|
|
|
|
// Support Asian string display in 9x English
|
2019-05-04 18:14:48 +00:00
|
|
|
tlen = ::MultiByteToWideChar(codePage, 0, text.data(), static_cast<int>(text.length()),
|
|
|
|
buffer, static_cast<int>(text.length()));
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
typedef VarBuffer<XYPOSITION, stackBufferLength> TextPositions;
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
UINT DpiForWindow(WindowID wid) noexcept {
|
|
|
|
if (fnGetDpiForWindow) {
|
|
|
|
return fnGetDpiForWindow(HwndFromWindowID(wid));
|
|
|
|
}
|
|
|
|
if (fnGetDpiForMonitor) {
|
|
|
|
HMONITOR hMonitor = ::MonitorFromWindow(HwndFromWindowID(wid), MONITOR_DEFAULTTONEAREST);
|
|
|
|
UINT dpiX = 0;
|
|
|
|
UINT dpiY = 0;
|
|
|
|
if (fnGetDpiForMonitor(hMonitor, 0 /*MDT_EFFECTIVE_DPI*/, &dpiX, &dpiY) == S_OK) {
|
|
|
|
return dpiY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return uSystemDPI;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SystemMetricsForDpi(int nIndex, UINT dpi) noexcept {
|
|
|
|
if (fnGetSystemMetricsForDpi) {
|
|
|
|
return fnGetSystemMetricsForDpi(nIndex, dpi);
|
|
|
|
}
|
|
|
|
|
|
|
|
int value = ::GetSystemMetrics(nIndex);
|
|
|
|
value = (dpi == uSystemDPI) ? value : ::MulDiv(value, dpi, uSystemDPI);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
class SurfaceGDI : public Surface {
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceMode mode;
|
2019-05-04 18:14:48 +00:00
|
|
|
HDC hdc{};
|
|
|
|
bool hdcOwned=false;
|
|
|
|
HPEN pen{};
|
|
|
|
HPEN penOld{};
|
|
|
|
HBRUSH brush{};
|
|
|
|
HBRUSH brushOld{};
|
|
|
|
HFONT fontOld{};
|
|
|
|
HBITMAP bitmap{};
|
|
|
|
HBITMAP bitmapOld{};
|
2021-02-21 04:53:09 +00:00
|
|
|
|
|
|
|
int logPixelsY = USER_DEFAULT_SCREEN_DPI;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
static constexpr int maxWidthMeasure = INT_MAX;
|
2019-05-04 18:14:48 +00:00
|
|
|
// There appears to be a 16 bit string length limit in GDI on NT.
|
2022-01-04 23:07:50 +00:00
|
|
|
static constexpr int maxLenText = 65535;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void PenColour(ColourRGBA fore, XYPOSITION widthStroke) noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void BrushColour(ColourRGBA back) noexcept;
|
|
|
|
void SetFont(const Font *font_);
|
2019-05-04 18:14:48 +00:00
|
|
|
void Clear() noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
public:
|
2019-05-04 18:14:48 +00:00
|
|
|
SurfaceGDI() noexcept;
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceGDI(HDC hdcCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept;
|
2019-05-04 18:14:48 +00:00
|
|
|
// Deleted so SurfaceGDI objects can not be copied.
|
|
|
|
SurfaceGDI(const SurfaceGDI &) = delete;
|
|
|
|
SurfaceGDI(SurfaceGDI &&) = delete;
|
|
|
|
SurfaceGDI &operator=(const SurfaceGDI &) = delete;
|
|
|
|
SurfaceGDI &operator=(SurfaceGDI &&) = delete;
|
|
|
|
|
|
|
|
~SurfaceGDI() noexcept override;
|
|
|
|
|
|
|
|
void Init(WindowID wid) override;
|
|
|
|
void Init(SurfaceID sid, WindowID wid) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetMode(SurfaceMode mode_) override;
|
|
|
|
|
|
|
|
void Release() noexcept override;
|
|
|
|
int SupportsFeature(Supports feature) noexcept override;
|
2019-05-04 18:14:48 +00:00
|
|
|
bool Initialised() override;
|
|
|
|
int LogPixelsY() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
int PixelDivisions() override;
|
2019-05-04 18:14:48 +00:00
|
|
|
int DeviceHeightFont(int points) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void LineDraw(Point start, Point end, Stroke stroke) override;
|
|
|
|
void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
|
|
|
|
void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
|
|
|
|
void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void RectangleFrame(PRectangle rc, Stroke stroke) override;
|
|
|
|
void FillRectangle(PRectangle rc, Fill fill) override;
|
|
|
|
void FillRectangleAligned(PRectangle rc, Fill fill) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
|
|
|
|
void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void Ellipse(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
|
|
|
|
|
|
|
|
std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
|
|
|
|
void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
|
|
|
|
void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
|
|
|
|
XYPOSITION WidthText(const Font *font_, std::string_view text) override;
|
|
|
|
|
|
|
|
void DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions);
|
|
|
|
void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
|
|
|
|
void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
|
|
|
|
XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
|
|
|
|
|
|
|
|
XYPOSITION Ascent(const Font *font_) override;
|
|
|
|
XYPOSITION Descent(const Font *font_) override;
|
|
|
|
XYPOSITION InternalLeading(const Font *font_) override;
|
|
|
|
XYPOSITION Height(const Font *font_) override;
|
|
|
|
XYPOSITION AverageCharWidth(const Font *font_) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
void SetClip(PRectangle rc) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void PopClip() override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void FlushCachedState() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void FlushDrawing() override;
|
2009-04-24 23:35:41 +00:00
|
|
|
};
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
SurfaceGDI::SurfaceGDI() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceGDI::SurfaceGDI(HDC hdcCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept {
|
2022-04-10 15:01:44 +00:00
|
|
|
hdc = ::CreateCompatibleDC(hdcCompatible);
|
2022-01-04 23:07:50 +00:00
|
|
|
hdcOwned = true;
|
|
|
|
bitmap = ::CreateCompatibleBitmap(hdcCompatible, width, height);
|
|
|
|
bitmapOld = SelectBitmap(hdc, bitmap);
|
|
|
|
::SetTextAlign(hdc, TA_BASELINE);
|
|
|
|
mode = mode_;
|
|
|
|
logPixelsY = logPixelsY_;
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
SurfaceGDI::~SurfaceGDI() noexcept {
|
|
|
|
Clear();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void SurfaceGDI::Clear() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (penOld) {
|
2019-05-04 18:14:48 +00:00
|
|
|
::SelectObject(hdc, penOld);
|
2009-04-24 23:35:41 +00:00
|
|
|
::DeleteObject(pen);
|
2021-02-21 04:53:09 +00:00
|
|
|
penOld = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
pen = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
if (brushOld) {
|
2019-05-04 18:14:48 +00:00
|
|
|
::SelectObject(hdc, brushOld);
|
2009-04-24 23:35:41 +00:00
|
|
|
::DeleteObject(brush);
|
2021-02-21 04:53:09 +00:00
|
|
|
brushOld = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
brush = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
if (fontOld) {
|
|
|
|
// Fonts are not deleted as they are owned by a Font object
|
2019-05-04 18:14:48 +00:00
|
|
|
::SelectObject(hdc, fontOld);
|
2021-02-21 04:53:09 +00:00
|
|
|
fontOld = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
if (bitmapOld) {
|
2019-05-04 18:14:48 +00:00
|
|
|
::SelectObject(hdc, bitmapOld);
|
2009-04-24 23:35:41 +00:00
|
|
|
::DeleteObject(bitmap);
|
2021-02-21 04:53:09 +00:00
|
|
|
bitmapOld = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
bitmap = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
if (hdcOwned) {
|
2019-05-04 18:14:48 +00:00
|
|
|
::DeleteDC(hdc);
|
2021-02-21 04:53:09 +00:00
|
|
|
hdc = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
hdcOwned = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::Release() noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
Clear();
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int SurfaceGDI::SupportsFeature(Supports feature) noexcept {
|
|
|
|
for (const Supports f : SupportsGDI) {
|
|
|
|
if (f == feature)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
bool SurfaceGDI::Initialised() {
|
2009-04-24 23:35:41 +00:00
|
|
|
return hdc != 0;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SurfaceGDI::Init(WindowID wid) {
|
2009-04-24 23:35:41 +00:00
|
|
|
Release();
|
2021-02-21 04:53:09 +00:00
|
|
|
hdc = ::CreateCompatibleDC({});
|
2009-04-24 23:35:41 +00:00
|
|
|
hdcOwned = true;
|
2019-05-04 18:14:48 +00:00
|
|
|
::SetTextAlign(hdc, TA_BASELINE);
|
2021-02-21 04:53:09 +00:00
|
|
|
logPixelsY = DpiForWindow(wid);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SurfaceGDI::Init(SurfaceID sid, WindowID wid) {
|
2009-04-24 23:35:41 +00:00
|
|
|
Release();
|
2019-05-04 18:14:48 +00:00
|
|
|
hdc = static_cast<HDC>(sid);
|
|
|
|
::SetTextAlign(hdc, TA_BASELINE);
|
2021-02-21 04:53:09 +00:00
|
|
|
// Windows on screen are scaled but printers are not.
|
|
|
|
const bool printing = ::GetDeviceCaps(hdc, TECHNOLOGY) != DT_RASDISPLAY;
|
|
|
|
logPixelsY = printing ? ::GetDeviceCaps(hdc, LOGPIXELSY) : DpiForWindow(wid);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> SurfaceGDI::AllocatePixMap(int width, int height) {
|
|
|
|
return std::make_unique<SurfaceGDI>(hdc, width, height, mode, logPixelsY);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::SetMode(SurfaceMode mode_) {
|
|
|
|
mode = mode_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::PenColour(ColourRGBA fore, XYPOSITION widthStroke) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (pen) {
|
|
|
|
::SelectObject(hdc, penOld);
|
|
|
|
::DeleteObject(pen);
|
2021-02-21 04:53:09 +00:00
|
|
|
pen = {};
|
|
|
|
penOld = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
const DWORD penWidth = std::lround(widthStroke);
|
|
|
|
const COLORREF penColour = fore.OpaqueRGB();
|
|
|
|
if (widthStroke > 1) {
|
|
|
|
const LOGBRUSH brushParameters{ BS_SOLID, penColour, 0 };
|
|
|
|
pen = ::ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_ROUND | PS_JOIN_MITER,
|
|
|
|
penWidth,
|
|
|
|
&brushParameters,
|
|
|
|
0,
|
|
|
|
nullptr);
|
|
|
|
} else {
|
|
|
|
pen = ::CreatePen(PS_INSIDEFRAME, penWidth, penColour);
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
penOld = SelectPen(hdc, pen);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::BrushColour(ColourRGBA back) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (brush) {
|
|
|
|
::SelectObject(hdc, brushOld);
|
|
|
|
::DeleteObject(brush);
|
2021-02-21 04:53:09 +00:00
|
|
|
brush = {};
|
|
|
|
brushOld = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
brush = ::CreateSolidBrush(back.OpaqueRGB());
|
2019-05-04 18:14:48 +00:00
|
|
|
brushOld = SelectBrush(hdc, brush);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2019-07-21 13:26:02 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::SetFont(const Font *font_) {
|
|
|
|
const FontGDI *pfm = dynamic_cast<const FontGDI *>(font_);
|
|
|
|
PLATFORM_ASSERT(pfm);
|
|
|
|
if (!pfm) {
|
|
|
|
throw std::runtime_error("SurfaceGDI::SetFont: wrong Font type.");
|
|
|
|
}
|
2019-07-21 13:26:02 +00:00
|
|
|
if (fontOld) {
|
|
|
|
SelectFont(hdc, pfm->hfont);
|
|
|
|
} else {
|
|
|
|
fontOld = SelectFont(hdc, pfm->hfont);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
int SurfaceGDI::LogPixelsY() {
|
2021-02-21 04:53:09 +00:00
|
|
|
return logPixelsY;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int SurfaceGDI::PixelDivisions() {
|
|
|
|
// Win32 uses device pixels.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
int SurfaceGDI::DeviceHeightFont(int points) {
|
2009-04-24 23:35:41 +00:00
|
|
|
return ::MulDiv(points, LogPixelsY(), 72);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::LineDraw(Point start, Point end, Stroke stroke) {
|
|
|
|
PenColour(stroke.colour, stroke.width);
|
|
|
|
::MoveToEx(hdc, std::lround(std::floor(start.x)), std::lround(std::floor(start.y)), nullptr);
|
|
|
|
::LineTo(hdc, std::lround(std::floor(end.x)), std::lround(std::floor(end.y)));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
|
|
|
|
PLATFORM_ASSERT(npts > 1);
|
|
|
|
if (npts <= 1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
PenColour(stroke.colour, stroke.width);
|
|
|
|
std::vector<POINT> outline;
|
|
|
|
std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
|
|
|
|
::Polyline(hdc, outline.data(), static_cast<int>(npts));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
|
|
|
|
PenColour(fillStroke.stroke.colour.WithoutAlpha(), fillStroke.stroke.width);
|
|
|
|
BrushColour(fillStroke.fill.colour.WithoutAlpha());
|
2013-08-28 00:44:27 +00:00
|
|
|
std::vector<POINT> outline;
|
2021-02-21 04:53:09 +00:00
|
|
|
std::transform(pts, pts + npts, std::back_inserter(outline), POINTFromPoint);
|
|
|
|
::Polygon(hdc, outline.data(), static_cast<int>(npts));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
|
|
|
|
RectangleFrame(rc, fillStroke.stroke);
|
|
|
|
FillRectangle(rc.Inset(fillStroke.stroke.width), fillStroke.fill.colour);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::RectangleFrame(PRectangle rc, Stroke stroke) {
|
|
|
|
BrushColour(stroke.colour);
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
2022-01-04 23:07:50 +00:00
|
|
|
::FrameRect(hdc, &rcw, brush);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::FillRectangle(PRectangle rc, Fill fill) {
|
|
|
|
if (fill.colour.IsOpaque()) {
|
|
|
|
// Using ExtTextOut rather than a FillRect ensures that no dithering occurs.
|
|
|
|
// There is no need to allocate a brush either.
|
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
|
|
|
::SetBkColor(hdc, fill.colour.OpaqueRGB());
|
|
|
|
::ExtTextOut(hdc, rcw.left, rcw.top, ETO_OPAQUE, &rcw, TEXT(""), 0, nullptr);
|
|
|
|
} else {
|
|
|
|
AlphaRectangle(rc, 0, FillStroke(fill.colour));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::FillRectangleAligned(PRectangle rc, Fill fill) {
|
|
|
|
FillRectangle(PixelAlign(rc, 1), fill);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceGDI::FillRectangle(PRectangle rc, Surface &surfacePattern) {
|
2009-04-24 23:35:41 +00:00
|
|
|
HBRUSH br;
|
2021-02-21 04:53:09 +00:00
|
|
|
if (SurfaceGDI *psgdi = dynamic_cast<SurfaceGDI *>(&surfacePattern); psgdi && psgdi->bitmap) {
|
|
|
|
br = ::CreatePatternBrush(psgdi->bitmap);
|
|
|
|
} else { // Something is wrong so display in red
|
2009-04-24 23:35:41 +00:00
|
|
|
br = ::CreateSolidBrush(RGB(0xff, 0, 0));
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
2009-04-24 23:35:41 +00:00
|
|
|
::FillRect(hdc, &rcw, br);
|
|
|
|
::DeleteObject(br);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
|
|
|
|
PenColour(fillStroke.stroke.colour, fillStroke.stroke.width);
|
|
|
|
BrushColour(fillStroke.fill.colour);
|
2015-06-07 21:19:26 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
2009-04-24 23:35:41 +00:00
|
|
|
::RoundRect(hdc,
|
2015-06-07 21:19:26 +00:00
|
|
|
rcw.left + 1, rcw.top,
|
|
|
|
rcw.right - 1, rcw.bottom,
|
2009-04-24 23:35:41 +00:00
|
|
|
8, 8);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
namespace {
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
constexpr DWORD dwordFromBGRA(byte b, byte g, byte r, byte a) noexcept {
|
|
|
|
return (a << 24) | (r << 16) | (g << 8) | b;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
constexpr byte AlphaScaled(unsigned char component, unsigned int alpha) noexcept {
|
|
|
|
return static_cast<byte>(component * alpha / 255);
|
2010-07-12 22:19:51 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr DWORD dwordMultiplied(ColourRGBA colour) noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
return dwordFromBGRA(
|
2022-01-04 23:07:50 +00:00
|
|
|
AlphaScaled(colour.GetBlue(), colour.GetAlpha()),
|
|
|
|
AlphaScaled(colour.GetGreen(), colour.GetAlpha()),
|
|
|
|
AlphaScaled(colour.GetRed(), colour.GetAlpha()),
|
|
|
|
colour.GetAlpha());
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
class DIBSection {
|
|
|
|
HDC hMemDC {};
|
|
|
|
HBITMAP hbmMem {};
|
|
|
|
HBITMAP hbmOld {};
|
|
|
|
SIZE size {};
|
|
|
|
DWORD *pixels = nullptr;
|
|
|
|
public:
|
|
|
|
DIBSection(HDC hdc, SIZE size_) noexcept;
|
|
|
|
// Deleted so DIBSection objects can not be copied.
|
|
|
|
DIBSection(const DIBSection&) = delete;
|
|
|
|
DIBSection(DIBSection&&) = delete;
|
|
|
|
DIBSection &operator=(const DIBSection&) = delete;
|
|
|
|
DIBSection &operator=(DIBSection&&) = delete;
|
|
|
|
~DIBSection() noexcept;
|
|
|
|
operator bool() const noexcept {
|
|
|
|
return hMemDC && hbmMem && pixels;
|
|
|
|
}
|
|
|
|
DWORD *Pixels() const noexcept {
|
|
|
|
return pixels;
|
|
|
|
}
|
|
|
|
unsigned char *Bytes() const noexcept {
|
|
|
|
return reinterpret_cast<unsigned char *>(pixels);
|
|
|
|
}
|
|
|
|
HDC DC() const noexcept {
|
|
|
|
return hMemDC;
|
|
|
|
}
|
|
|
|
void SetPixel(LONG x, LONG y, DWORD value) noexcept {
|
|
|
|
PLATFORM_ASSERT(x >= 0);
|
|
|
|
PLATFORM_ASSERT(y >= 0);
|
|
|
|
PLATFORM_ASSERT(x < size.cx);
|
|
|
|
PLATFORM_ASSERT(y < size.cy);
|
|
|
|
pixels[y * size.cx + x] = value;
|
|
|
|
}
|
|
|
|
void SetSymmetric(LONG x, LONG y, DWORD value) noexcept;
|
|
|
|
};
|
|
|
|
|
|
|
|
DIBSection::DIBSection(HDC hdc, SIZE size_) noexcept {
|
|
|
|
hMemDC = ::CreateCompatibleDC(hdc);
|
|
|
|
if (!hMemDC) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size = size_;
|
|
|
|
|
|
|
|
// -size.y makes bitmap start from top
|
|
|
|
const BITMAPINFO bpih = { {sizeof(BITMAPINFOHEADER), size.cx, -size.cy, 1, 32, BI_RGB, 0, 0, 0, 0, 0},
|
|
|
|
{{0, 0, 0, 0}} };
|
|
|
|
void *image = nullptr;
|
|
|
|
hbmMem = CreateDIBSection(hMemDC, &bpih, DIB_RGB_COLORS, &image, {}, 0);
|
|
|
|
if (!hbmMem || !image) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
pixels = static_cast<DWORD *>(image);
|
|
|
|
hbmOld = SelectBitmap(hMemDC, hbmMem);
|
|
|
|
}
|
|
|
|
|
|
|
|
DIBSection::~DIBSection() noexcept {
|
|
|
|
if (hbmOld) {
|
|
|
|
SelectBitmap(hMemDC, hbmOld);
|
|
|
|
hbmOld = {};
|
|
|
|
}
|
|
|
|
if (hbmMem) {
|
|
|
|
::DeleteObject(hbmMem);
|
|
|
|
hbmMem = {};
|
|
|
|
}
|
|
|
|
if (hMemDC) {
|
|
|
|
::DeleteDC(hMemDC);
|
|
|
|
hMemDC = {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DIBSection::SetSymmetric(LONG x, LONG y, DWORD value) noexcept {
|
|
|
|
// Plot a point symmetrically to all 4 quadrants
|
|
|
|
const LONG xSymmetric = size.cx - 1 - x;
|
|
|
|
const LONG ySymmetric = size.cy - 1 - y;
|
|
|
|
SetPixel(x, y, value);
|
|
|
|
SetPixel(xSymmetric, y, value);
|
|
|
|
SetPixel(x, ySymmetric, value);
|
|
|
|
SetPixel(xSymmetric, ySymmetric, value);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ColourRGBA GradientValue(const std::vector<ColourStop> &stops, XYPOSITION proportion) noexcept {
|
2021-02-21 04:53:09 +00:00
|
|
|
for (size_t stop = 0; stop < stops.size() - 1; stop++) {
|
|
|
|
// Loop through each pair of stops
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION positionStart = stops[stop].position;
|
|
|
|
const XYPOSITION positionEnd = stops[stop + 1].position;
|
2021-02-21 04:53:09 +00:00
|
|
|
if ((proportion >= positionStart) && (proportion <= positionEnd)) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION proportionInPair = (proportion - positionStart) /
|
2021-02-21 04:53:09 +00:00
|
|
|
(positionEnd - positionStart);
|
2022-01-04 23:07:50 +00:00
|
|
|
return stops[stop].colour.MixedWith(stops[stop + 1].colour, proportionInPair);
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Loop should always find a value
|
2022-01-04 23:07:50 +00:00
|
|
|
return ColourRGBA();
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
constexpr SIZE SizeOfRect(RECT rc) noexcept {
|
|
|
|
return { rc.right - rc.left, rc.bottom - rc.top };
|
|
|
|
}
|
|
|
|
|
|
|
|
constexpr BLENDFUNCTION mergeAlpha = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
|
|
|
|
// TODO: Implement strokeWidth
|
2015-06-07 21:19:26 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
2021-02-21 04:53:09 +00:00
|
|
|
const SIZE size = SizeOfRect(rcw);
|
|
|
|
|
|
|
|
if (size.cx > 0) {
|
|
|
|
|
|
|
|
DIBSection section(hdc, size);
|
|
|
|
|
|
|
|
if (section) {
|
|
|
|
|
|
|
|
// Ensure not distorted too much by corners when small
|
2022-01-04 23:07:50 +00:00
|
|
|
const LONG corner = std::min(static_cast<LONG>(cornerSize), (std::min(size.cx, size.cy) / 2) - 2);
|
2021-02-21 04:53:09 +00:00
|
|
|
|
|
|
|
constexpr DWORD valEmpty = dwordFromBGRA(0,0,0,0);
|
2022-01-04 23:07:50 +00:00
|
|
|
const DWORD valFill = dwordMultiplied(fillStroke.fill.colour);
|
|
|
|
const DWORD valOutline = dwordMultiplied(fillStroke.stroke.colour);
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
// Draw a framed rectangle
|
|
|
|
for (int y=0; y<size.cy; y++) {
|
|
|
|
for (int x=0; x<size.cx; x++) {
|
|
|
|
if ((x==0) || (x==size.cx-1) || (y == 0) || (y == size.cy -1)) {
|
|
|
|
section.SetPixel(x, y, valOutline);
|
2013-08-28 00:44:27 +00:00
|
|
|
} else {
|
2021-02-21 04:53:09 +00:00
|
|
|
section.SetPixel(x, y, valFill);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
|
|
|
|
// Make the corners transparent
|
|
|
|
for (LONG c=0; c<corner; c++) {
|
|
|
|
for (LONG x=0; x<c+1; x++) {
|
|
|
|
section.SetSymmetric(x, c - x, valEmpty);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
// Draw the corner frame pieces
|
|
|
|
for (LONG x=1; x<corner; x++) {
|
|
|
|
section.SetSymmetric(x, corner - x, valOutline);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2023-05-31 23:11:12 +00:00
|
|
|
GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
2022-01-04 23:07:50 +00:00
|
|
|
BrushColour(fillStroke.stroke.colour);
|
2009-04-24 23:35:41 +00:00
|
|
|
FrameRect(hdc, &rcw, brush);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SurfaceGDI::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
|
|
|
|
|
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
|
|
|
const SIZE size = SizeOfRect(rcw);
|
|
|
|
|
|
|
|
DIBSection section(hdc, size);
|
|
|
|
|
|
|
|
if (section) {
|
|
|
|
|
|
|
|
if (options == GradientOptions::topToBottom) {
|
|
|
|
for (LONG y = 0; y < size.cy; y++) {
|
|
|
|
// Find y/height proportional colour
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION proportion = y / (rc.Height() - 1.0f);
|
|
|
|
const ColourRGBA mixed = GradientValue(stops, proportion);
|
|
|
|
const DWORD valFill = dwordMultiplied(mixed);
|
2021-02-21 04:53:09 +00:00
|
|
|
for (LONG x = 0; x < size.cx; x++) {
|
|
|
|
section.SetPixel(x, y, valFill);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (LONG x = 0; x < size.cx; x++) {
|
|
|
|
// Find x/width proportional colour
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION proportion = x / (rc.Width() - 1.0f);
|
|
|
|
const ColourRGBA mixed = GradientValue(stops, proportion);
|
|
|
|
const DWORD valFill = dwordMultiplied(mixed);
|
2021-02-21 04:53:09 +00:00
|
|
|
for (LONG y = 0; y < size.cy; y++) {
|
|
|
|
section.SetPixel(x, y, valFill);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-31 23:11:12 +00:00
|
|
|
GdiAlphaBlend(hdc, rcw.left, rcw.top, size.cx, size.cy, section.DC(), 0, 0, size.cx, size.cy, mergeAlpha);
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceGDI::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
|
2019-05-04 18:14:48 +00:00
|
|
|
if (rc.Width() > 0) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (rc.Width() > width)
|
2019-07-21 13:26:02 +00:00
|
|
|
rc.left += std::floor((rc.Width() - width) / 2);
|
2013-08-28 00:44:27 +00:00
|
|
|
rc.right = rc.left + width;
|
|
|
|
if (rc.Height() > height)
|
2019-07-21 13:26:02 +00:00
|
|
|
rc.top += std::floor((rc.Height() - height) / 2);
|
2013-08-28 00:44:27 +00:00
|
|
|
rc.bottom = rc.top + height;
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
const SIZE size { width, height };
|
|
|
|
DIBSection section(hdc, size);
|
|
|
|
if (section) {
|
2022-01-04 23:07:50 +00:00
|
|
|
RGBAImage::BGRAFromRGBA(section.Bytes(), pixelsImage, static_cast<size_t>(width) * height);
|
2023-05-31 23:11:12 +00:00
|
|
|
GdiAlphaBlend(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
|
2021-02-21 04:53:09 +00:00
|
|
|
static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), section.DC(),
|
|
|
|
0, 0, width, height, mergeAlpha);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::Ellipse(PRectangle rc, FillStroke fillStroke) {
|
|
|
|
PenColour(fillStroke.stroke.colour, fillStroke.stroke.width);
|
|
|
|
BrushColour(fillStroke.fill.colour);
|
2015-06-07 21:19:26 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
|
|
|
::Ellipse(hdc, rcw.left, rcw.top, rcw.right, rcw.bottom);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::Stadium(PRectangle rc, FillStroke fillStroke, [[maybe_unused]] Ends ends) {
|
|
|
|
// TODO: Implement properly - the rectangle is just a placeholder
|
|
|
|
RectangleDraw(rc, fillStroke);
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceGDI::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
|
2009-04-24 23:35:41 +00:00
|
|
|
::BitBlt(hdc,
|
2015-06-07 21:19:26 +00:00
|
|
|
static_cast<int>(rc.left), static_cast<int>(rc.top),
|
|
|
|
static_cast<int>(rc.Width()), static_cast<int>(rc.Height()),
|
2021-02-21 04:53:09 +00:00
|
|
|
dynamic_cast<SurfaceGDI &>(surfaceSource).hdc,
|
2015-06-07 21:19:26 +00:00
|
|
|
static_cast<int>(from.x), static_cast<int>(from.y), SRCCOPY);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
std::unique_ptr<IScreenLineLayout> SurfaceGDI::Layout(const IScreenLine *) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
typedef VarBuffer<int, stackBufferLength> TextPositionsI;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
|
|
|
const int x = static_cast<int>(rc.left);
|
2015-06-07 21:19:26 +00:00
|
|
|
const int yBaseInt = static_cast<int>(ybase);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
if (mode.codePage == CpUtf8) {
|
|
|
|
const TextWide tbuf(text, mode.codePage);
|
2019-05-04 18:14:48 +00:00
|
|
|
::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
2019-05-04 18:14:48 +00:00
|
|
|
::ExtTextOutA(hdc, x, yBaseInt, fuOptions, &rcw, text.data(), static_cast<UINT>(text.length()), nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
::SetTextColor(hdc, fore.OpaqueRGB());
|
|
|
|
::SetBkColor(hdc, back.OpaqueRGB());
|
2019-05-04 18:14:48 +00:00
|
|
|
DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
::SetTextColor(hdc, fore.OpaqueRGB());
|
|
|
|
::SetBkColor(hdc, back.OpaqueRGB());
|
2019-05-04 18:14:48 +00:00
|
|
|
DrawTextCommon(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
2009-04-24 23:35:41 +00:00
|
|
|
// Avoid drawing spaces in transparent mode
|
2021-02-21 04:53:09 +00:00
|
|
|
for (const char ch : text) {
|
|
|
|
if (ch != ' ') {
|
2022-01-04 23:07:50 +00:00
|
|
|
::SetTextColor(hdc, fore.OpaqueRGB());
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetBkMode(hdc, TRANSPARENT);
|
2019-05-04 18:14:48 +00:00
|
|
|
DrawTextCommon(rc, font_, ybase, text, 0);
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetBkMode(hdc, OPAQUE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
|
2019-05-04 18:14:48 +00:00
|
|
|
// Zero positions to avoid random behaviour on failure.
|
|
|
|
std::fill(positions, positions + text.length(), 0.0f);
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
2022-01-04 23:07:50 +00:00
|
|
|
SIZE sz = { 0,0 };
|
2009-04-24 23:35:41 +00:00
|
|
|
int fit = 0;
|
2019-05-04 18:14:48 +00:00
|
|
|
int i = 0;
|
|
|
|
const int len = static_cast<int>(text.length());
|
2022-01-04 23:07:50 +00:00
|
|
|
if (mode.codePage == CpUtf8) {
|
|
|
|
const TextWide tbuf(text, mode.codePage);
|
2013-08-28 00:44:27 +00:00
|
|
|
TextPositionsI poses(tbuf.tlen);
|
2009-04-25 23:38:15 +00:00
|
|
|
if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
|
2019-05-04 18:14:48 +00:00
|
|
|
// Failure
|
|
|
|
return;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2009-04-25 23:38:15 +00:00
|
|
|
// Map the widths given for UTF-16 characters back onto the UTF-8 input string
|
2019-05-04 18:14:48 +00:00
|
|
|
for (int ui = 0; ui < fit; ui++) {
|
|
|
|
const unsigned char uch = text[i];
|
|
|
|
const unsigned int byteCount = UTF8BytesOfLead[uch];
|
|
|
|
if (byteCount == 4) { // Non-BMP
|
2009-04-24 23:35:41 +00:00
|
|
|
ui++;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) {
|
2015-06-07 21:19:26 +00:00
|
|
|
positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
} else {
|
2019-05-04 18:14:48 +00:00
|
|
|
TextPositionsI poses(len);
|
|
|
|
if (!::GetTextExtentExPointA(hdc, text.data(), len, maxWidthMeasure, &fit, poses.buffer, &sz)) {
|
|
|
|
// Eeek - a NULL DC or other foolishness could cause this.
|
|
|
|
return;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
while (i < fit) {
|
2019-05-04 18:14:48 +00:00
|
|
|
positions[i] = static_cast<XYPOSITION>(poses.buffer[i]);
|
|
|
|
i++;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
// If any positions not filled in then use the last position for them
|
|
|
|
const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
|
2022-01-04 23:07:50 +00:00
|
|
|
std::fill(positions + i, positions + text.length(), lastPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceGDI::WidthText(const Font *font_, std::string_view text) {
|
|
|
|
SetFont(font_);
|
|
|
|
SIZE sz = { 0,0 };
|
|
|
|
if (!(mode.codePage == CpUtf8)) {
|
|
|
|
::GetTextExtentPoint32A(hdc, text.data(), std::min(static_cast<int>(text.length()), maxLenText), &sz);
|
|
|
|
} else {
|
|
|
|
const TextWide tbuf(text, mode.codePage);
|
|
|
|
::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
|
|
|
|
}
|
|
|
|
return static_cast<XYPOSITION>(sz.cx);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::DrawTextCommonUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, UINT fuOptions) {
|
|
|
|
SetFont(font_);
|
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
|
|
|
const int x = static_cast<int>(rc.left);
|
|
|
|
const int yBaseInt = static_cast<int>(ybase);
|
|
|
|
|
|
|
|
const TextWide tbuf(text, CpUtf8);
|
|
|
|
::ExtTextOutW(hdc, x, yBaseInt, fuOptions, &rcw, tbuf.buffer, tbuf.tlen, nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
::SetTextColor(hdc, fore.OpaqueRGB());
|
|
|
|
::SetBkColor(hdc, back.OpaqueRGB());
|
|
|
|
DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
::SetTextColor(hdc, fore.OpaqueRGB());
|
|
|
|
::SetBkColor(hdc, back.OpaqueRGB());
|
|
|
|
DrawTextCommonUTF8(rc, font_, ybase, text, ETO_OPAQUE | ETO_CLIPPED);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
|
|
|
// Avoid drawing spaces in transparent mode
|
|
|
|
for (const char ch : text) {
|
|
|
|
if (ch != ' ') {
|
|
|
|
::SetTextColor(hdc, fore.OpaqueRGB());
|
|
|
|
::SetBkMode(hdc, TRANSPARENT);
|
|
|
|
DrawTextCommonUTF8(rc, font_, ybase, text, 0);
|
|
|
|
::SetBkMode(hdc, OPAQUE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceGDI::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
|
|
|
|
// Zero positions to avoid random behaviour on failure.
|
|
|
|
std::fill(positions, positions + text.length(), 0.0f);
|
|
|
|
SetFont(font_);
|
|
|
|
SIZE sz = { 0,0 };
|
|
|
|
int fit = 0;
|
|
|
|
int i = 0;
|
|
|
|
const int len = static_cast<int>(text.length());
|
|
|
|
const TextWide tbuf(text, CpUtf8);
|
|
|
|
TextPositionsI poses(tbuf.tlen);
|
|
|
|
if (!::GetTextExtentExPointW(hdc, tbuf.buffer, tbuf.tlen, maxWidthMeasure, &fit, poses.buffer, &sz)) {
|
|
|
|
// Failure
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Map the widths given for UTF-16 characters back onto the UTF-8 input string
|
|
|
|
for (int ui = 0; ui < fit; ui++) {
|
|
|
|
const unsigned char uch = text[i];
|
|
|
|
const unsigned int byteCount = UTF8BytesOfLead[uch];
|
|
|
|
if (byteCount == 4) { // Non-BMP
|
|
|
|
ui++;
|
|
|
|
}
|
|
|
|
for (unsigned int bytePos = 0; (bytePos < byteCount) && (i < len); bytePos++) {
|
|
|
|
positions[i++] = static_cast<XYPOSITION>(poses.buffer[ui]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If any positions not filled in then use the last position for them
|
|
|
|
const XYPOSITION lastPos = (fit > 0) ? positions[fit - 1] : 0.0f;
|
|
|
|
std::fill(positions + i, positions + text.length(), lastPos);
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceGDI::WidthTextUTF8(const Font *font_, std::string_view text) {
|
|
|
|
SetFont(font_);
|
|
|
|
SIZE sz = { 0,0 };
|
|
|
|
const TextWide tbuf(text, CpUtf8);
|
|
|
|
::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &sz);
|
|
|
|
return static_cast<XYPOSITION>(sz.cx);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceGDI::Ascent(const Font *font_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
|
|
|
TEXTMETRIC tm;
|
|
|
|
::GetTextMetrics(hdc, &tm);
|
2015-06-07 21:19:26 +00:00
|
|
|
return static_cast<XYPOSITION>(tm.tmAscent);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceGDI::Descent(const Font *font_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
|
|
|
TEXTMETRIC tm;
|
|
|
|
::GetTextMetrics(hdc, &tm);
|
2015-06-07 21:19:26 +00:00
|
|
|
return static_cast<XYPOSITION>(tm.tmDescent);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceGDI::InternalLeading(const Font *font_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
|
|
|
TEXTMETRIC tm;
|
|
|
|
::GetTextMetrics(hdc, &tm);
|
2015-06-07 21:19:26 +00:00
|
|
|
return static_cast<XYPOSITION>(tm.tmInternalLeading);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceGDI::Height(const Font *font_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
|
|
|
TEXTMETRIC tm;
|
|
|
|
::GetTextMetrics(hdc, &tm);
|
2015-06-07 21:19:26 +00:00
|
|
|
return static_cast<XYPOSITION>(tm.tmHeight);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceGDI::AverageCharWidth(const Font *font_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
SetFont(font_);
|
|
|
|
TEXTMETRIC tm;
|
|
|
|
::GetTextMetrics(hdc, &tm);
|
2015-06-07 21:19:26 +00:00
|
|
|
return static_cast<XYPOSITION>(tm.tmAveCharWidth);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceGDI::SetClip(PRectangle rc) {
|
2022-01-04 23:07:50 +00:00
|
|
|
::SaveDC(hdc);
|
2015-06-07 21:19:26 +00:00
|
|
|
::IntersectClipRect(hdc, static_cast<int>(rc.left), static_cast<int>(rc.top),
|
|
|
|
static_cast<int>(rc.right), static_cast<int>(rc.bottom));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::PopClip() {
|
|
|
|
::RestoreDC(hdc, -1);
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceGDI::FlushCachedState() {
|
2021-02-21 04:53:09 +00:00
|
|
|
pen = {};
|
|
|
|
brush = {};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceGDI::FlushDrawing() {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
#if defined(USE_D2D)
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
constexpr D2D1_RECT_F RectangleFromPRectangle(PRectangle rc) noexcept {
|
|
|
|
return {
|
|
|
|
static_cast<FLOAT>(rc.left),
|
|
|
|
static_cast<FLOAT>(rc.top),
|
|
|
|
static_cast<FLOAT>(rc.right),
|
|
|
|
static_cast<FLOAT>(rc.bottom)
|
|
|
|
};
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr D2D1_POINT_2F DPointFromPoint(Point point) noexcept {
|
|
|
|
return { static_cast<FLOAT>(point.x), static_cast<FLOAT>(point.y) };
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr Supports SupportsD2D[] = {
|
|
|
|
Supports::LineDrawsFinal,
|
|
|
|
Supports::FractionalStrokeWidth,
|
|
|
|
Supports::TranslucentStroke,
|
|
|
|
Supports::PixelModification,
|
|
|
|
Supports::ThreadSafeMeasureWidths,
|
|
|
|
};
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr D2D_COLOR_F ColorFromColourAlpha(ColourRGBA colour) noexcept {
|
|
|
|
return D2D_COLOR_F{
|
|
|
|
colour.GetRedComponent(),
|
|
|
|
colour.GetGreenComponent(),
|
|
|
|
colour.GetBlueComponent(),
|
|
|
|
colour.GetAlphaComponent()
|
|
|
|
};
|
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
constexpr D2D1_RECT_F RectangleInset(D2D1_RECT_F rect, FLOAT inset) noexcept {
|
|
|
|
return D2D1_RECT_F{
|
|
|
|
rect.left + inset,
|
|
|
|
rect.top + inset,
|
|
|
|
rect.right - inset,
|
|
|
|
rect.bottom - inset };
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
class BlobInline;
|
|
|
|
|
2022-04-13 11:10:12 +00:00
|
|
|
class SurfaceD2D : public Surface, public ISetRenderingParams {
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceMode mode;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ID2D1RenderTarget *pRenderTarget = nullptr;
|
|
|
|
ID2D1BitmapRenderTarget *pBitmapRenderTarget = nullptr;
|
|
|
|
bool ownRenderTarget = false;
|
|
|
|
int clipsActive = 0;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ID2D1SolidColorBrush *pBrush = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-04-13 11:10:12 +00:00
|
|
|
static constexpr FontQuality invalidFontQuality = FontQuality::QualityMask;
|
|
|
|
FontQuality fontQuality = invalidFontQuality;
|
2022-01-04 23:07:50 +00:00
|
|
|
int logPixelsY = USER_DEFAULT_SCREEN_DPI;
|
2022-12-10 12:35:16 +00:00
|
|
|
int deviceScaleFactor = 1;
|
2022-04-13 11:10:12 +00:00
|
|
|
std::shared_ptr<RenderingParams> renderingParams;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void Clear() noexcept;
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetFontQuality(FontQuality extraFontFlag);
|
2021-02-21 04:53:09 +00:00
|
|
|
HRESULT GetBitmap(ID2D1Bitmap **ppBitmap);
|
2022-12-10 12:35:16 +00:00
|
|
|
void SetDeviceScaleFactor(const ID2D1RenderTarget *const pRenderTarget) noexcept;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
|
|
|
public:
|
2019-05-04 18:14:48 +00:00
|
|
|
SurfaceD2D() noexcept;
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept;
|
2019-05-04 18:14:48 +00:00
|
|
|
// Deleted so SurfaceD2D objects can not be copied.
|
|
|
|
SurfaceD2D(const SurfaceD2D &) = delete;
|
|
|
|
SurfaceD2D(SurfaceD2D &&) = delete;
|
|
|
|
SurfaceD2D &operator=(const SurfaceD2D &) = delete;
|
|
|
|
SurfaceD2D &operator=(SurfaceD2D &&) = delete;
|
2022-01-04 23:07:50 +00:00
|
|
|
~SurfaceD2D() noexcept override;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SetScale(WindowID wid) noexcept;
|
2019-05-04 18:14:48 +00:00
|
|
|
void Init(WindowID wid) override;
|
|
|
|
void Init(SurfaceID sid, WindowID wid) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> AllocatePixMap(int width, int height) override;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetMode(SurfaceMode mode_) override;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Release() noexcept override;
|
|
|
|
int SupportsFeature(Supports feature) noexcept override;
|
|
|
|
bool Initialised() override;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void D2DPenColourAlpha(ColourRGBA fore) noexcept;
|
2019-05-04 18:14:48 +00:00
|
|
|
int LogPixelsY() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
int PixelDivisions() override;
|
2019-05-04 18:14:48 +00:00
|
|
|
int DeviceHeightFont(int points) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void LineDraw(Point start, Point end, Stroke stroke) override;
|
|
|
|
ID2D1PathGeometry *Geometry(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept;
|
|
|
|
void PolyLine(const Point *pts, size_t npts, Stroke stroke) override;
|
|
|
|
void Polygon(const Point *pts, size_t npts, FillStroke fillStroke) override;
|
|
|
|
void RectangleDraw(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void RectangleFrame(PRectangle rc, Stroke stroke) override;
|
|
|
|
void FillRectangle(PRectangle rc, Fill fill) override;
|
|
|
|
void FillRectangleAligned(PRectangle rc, Fill fill) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void FillRectangle(PRectangle rc, Surface &surfacePattern) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void RoundedRectangle(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) override;
|
|
|
|
void DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void Ellipse(PRectangle rc, FillStroke fillStroke) override;
|
|
|
|
void Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void Copy(PRectangle rc, Point from, Surface &surfaceSource) override;
|
|
|
|
|
|
|
|
std::unique_ptr<IScreenLineLayout> Layout(const IScreenLine *screenLine) override;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, int codePageOverride, UINT fuOptions);
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
|
|
|
|
void MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) override;
|
|
|
|
XYPOSITION WidthText(const Font *font_, std::string_view text) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore, ColourRGBA back) override;
|
|
|
|
void DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, ColourRGBA fore) override;
|
|
|
|
void MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) override;
|
|
|
|
XYPOSITION WidthTextUTF8(const Font *font_, std::string_view text) override;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION Ascent(const Font *font_) override;
|
|
|
|
XYPOSITION Descent(const Font *font_) override;
|
|
|
|
XYPOSITION InternalLeading(const Font *font_) override;
|
|
|
|
XYPOSITION Height(const Font *font_) override;
|
|
|
|
XYPOSITION AverageCharWidth(const Font *font_) override;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetClip(PRectangle rc) override;
|
|
|
|
void PopClip() override;
|
|
|
|
void FlushCachedState() override;
|
|
|
|
void FlushDrawing() override;
|
2022-04-13 11:10:12 +00:00
|
|
|
|
|
|
|
void SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
};
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceD2D::SurfaceD2D() noexcept {
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceD2D::SurfaceD2D(ID2D1RenderTarget *pRenderTargetCompatible, int width, int height, SurfaceMode mode_, int logPixelsY_) noexcept {
|
|
|
|
const D2D1_SIZE_F desiredSize = D2D1::SizeF(static_cast<float>(width), static_cast<float>(height));
|
|
|
|
D2D1_PIXEL_FORMAT desiredFormat;
|
|
|
|
#ifdef __MINGW32__
|
|
|
|
desiredFormat.format = DXGI_FORMAT_UNKNOWN;
|
|
|
|
#else
|
|
|
|
desiredFormat = pRenderTargetCompatible->GetPixelFormat();
|
|
|
|
#endif
|
|
|
|
desiredFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE;
|
|
|
|
const HRESULT hr = pRenderTargetCompatible->CreateCompatibleRenderTarget(
|
|
|
|
&desiredSize, nullptr, &desiredFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &pBitmapRenderTarget);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
pRenderTarget = pBitmapRenderTarget;
|
2022-12-10 12:35:16 +00:00
|
|
|
SetDeviceScaleFactor(pRenderTarget);
|
2022-01-04 23:07:50 +00:00
|
|
|
pRenderTarget->BeginDraw();
|
|
|
|
ownRenderTarget = true;
|
|
|
|
}
|
|
|
|
mode = mode_;
|
|
|
|
logPixelsY = logPixelsY_;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
SurfaceD2D::~SurfaceD2D() noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
Clear();
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void SurfaceD2D::Clear() noexcept {
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pBrush);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
|
|
|
while (clipsActive) {
|
|
|
|
pRenderTarget->PopAxisAlignedClip();
|
|
|
|
clipsActive--;
|
|
|
|
}
|
|
|
|
if (ownRenderTarget) {
|
2021-02-21 04:53:09 +00:00
|
|
|
pRenderTarget->EndDraw();
|
|
|
|
ReleaseUnknown(pRenderTarget);
|
|
|
|
ownRenderTarget = false;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
pRenderTarget = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
pBitmapRenderTarget = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::Release() noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
Clear();
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SurfaceD2D::SetScale(WindowID wid) noexcept {
|
2022-04-13 11:10:12 +00:00
|
|
|
fontQuality = invalidFontQuality;
|
2021-02-21 04:53:09 +00:00
|
|
|
logPixelsY = DpiForWindow(wid);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int SurfaceD2D::SupportsFeature(Supports feature) noexcept {
|
|
|
|
for (const Supports f : SupportsD2D) {
|
|
|
|
if (f == feature)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
bool SurfaceD2D::Initialised() {
|
|
|
|
return pRenderTarget != nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SurfaceD2D::Init(WindowID wid) {
|
2013-08-28 00:44:27 +00:00
|
|
|
Release();
|
2021-02-21 04:53:09 +00:00
|
|
|
SetScale(wid);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void SurfaceD2D::Init(SurfaceID sid, WindowID wid) {
|
2013-08-28 00:44:27 +00:00
|
|
|
Release();
|
2021-02-21 04:53:09 +00:00
|
|
|
SetScale(wid);
|
2019-05-04 18:14:48 +00:00
|
|
|
pRenderTarget = static_cast<ID2D1RenderTarget *>(sid);
|
2022-12-10 12:35:16 +00:00
|
|
|
SetDeviceScaleFactor(pRenderTarget);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> SurfaceD2D::AllocatePixMap(int width, int height) {
|
2022-04-13 11:10:12 +00:00
|
|
|
std::unique_ptr<SurfaceD2D> surf = std::make_unique<SurfaceD2D>(pRenderTarget, width, height, mode, logPixelsY);
|
|
|
|
surf->SetRenderingParams(renderingParams);
|
|
|
|
return surf;
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceD2D::SetMode(SurfaceMode mode_) {
|
|
|
|
mode = mode_;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
HRESULT SurfaceD2D::GetBitmap(ID2D1Bitmap **ppBitmap) {
|
|
|
|
PLATFORM_ASSERT(pBitmapRenderTarget);
|
|
|
|
return pBitmapRenderTarget->GetBitmap(ppBitmap);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::D2DPenColourAlpha(ColourRGBA fore) noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const D2D_COLOR_F col = ColorFromColourAlpha(fore);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pBrush) {
|
|
|
|
pBrush->SetColor(col);
|
|
|
|
} else {
|
2019-05-04 18:14:48 +00:00
|
|
|
const HRESULT hr = pRenderTarget->CreateSolidColorBrush(col, &pBrush);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!SUCCEEDED(hr)) {
|
|
|
|
ReleaseUnknown(pBrush);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::SetFontQuality(FontQuality extraFontFlag) {
|
2022-04-13 11:10:12 +00:00
|
|
|
if ((fontQuality != extraFontFlag) && renderingParams) {
|
2022-01-04 23:07:50 +00:00
|
|
|
fontQuality = extraFontFlag;
|
|
|
|
const D2D1_TEXT_ANTIALIAS_MODE aaMode = DWriteMapFontQuality(extraFontFlag);
|
2022-04-13 11:10:12 +00:00
|
|
|
if (aaMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && renderingParams->customRenderingParams) {
|
|
|
|
pRenderTarget->SetTextRenderingParams(renderingParams->customRenderingParams.get());
|
|
|
|
} else if (renderingParams->defaultRenderingParams) {
|
|
|
|
pRenderTarget->SetTextRenderingParams(renderingParams->defaultRenderingParams.get());
|
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
pRenderTarget->SetTextAntialiasMode(aaMode);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int SurfaceD2D::LogPixelsY() {
|
|
|
|
return logPixelsY;
|
|
|
|
}
|
|
|
|
|
2022-12-10 12:35:16 +00:00
|
|
|
void SurfaceD2D::SetDeviceScaleFactor(const ID2D1RenderTarget *const pD2D1RenderTarget) noexcept {
|
|
|
|
FLOAT dpiX = 0.f;
|
|
|
|
FLOAT dpiY = 0.f;
|
|
|
|
pD2D1RenderTarget->GetDpi(&dpiX, &dpiY);
|
|
|
|
deviceScaleFactor = static_cast<int>(dpiX / 96.f);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
int SurfaceD2D::PixelDivisions() {
|
2022-12-10 12:35:16 +00:00
|
|
|
return deviceScaleFactor;
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
int SurfaceD2D::DeviceHeightFont(int points) {
|
|
|
|
return ::MulDiv(points, LogPixelsY(), 72);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::LineDraw(Point start, Point end, Stroke stroke) {
|
|
|
|
D2DPenColourAlpha(stroke.colour);
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
D2D1_STROKE_STYLE_PROPERTIES strokeProps {};
|
|
|
|
strokeProps.startCap = D2D1_CAP_STYLE_SQUARE;
|
|
|
|
strokeProps.endCap = D2D1_CAP_STYLE_SQUARE;
|
|
|
|
strokeProps.dashCap = D2D1_CAP_STYLE_FLAT;
|
|
|
|
strokeProps.lineJoin = D2D1_LINE_JOIN_MITER;
|
|
|
|
strokeProps.miterLimit = 4.0f;
|
|
|
|
strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID;
|
|
|
|
strokeProps.dashOffset = 0;
|
|
|
|
|
|
|
|
// get the stroke style to apply
|
|
|
|
ID2D1StrokeStyle *pStrokeStyle = nullptr;
|
|
|
|
const HRESULT hr = pD2DFactory->CreateStrokeStyle(
|
|
|
|
strokeProps, nullptr, 0, &pStrokeStyle);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
pRenderTarget->DrawLine(
|
|
|
|
DPointFromPoint(start),
|
|
|
|
DPointFromPoint(end), pBrush, stroke.WidthF(), pStrokeStyle);
|
|
|
|
}
|
|
|
|
|
|
|
|
ReleaseUnknown(pStrokeStyle);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ID2D1PathGeometry *SurfaceD2D::Geometry(const Point *pts, size_t npts, D2D1_FIGURE_BEGIN figureBegin) noexcept {
|
|
|
|
ID2D1PathGeometry *geometry = nullptr;
|
|
|
|
HRESULT hr = pD2DFactory->CreatePathGeometry(&geometry);
|
|
|
|
if (SUCCEEDED(hr) && geometry) {
|
|
|
|
ID2D1GeometrySink *sink = nullptr;
|
|
|
|
hr = geometry->Open(&sink);
|
|
|
|
if (SUCCEEDED(hr) && sink) {
|
|
|
|
sink->BeginFigure(DPointFromPoint(pts[0]), figureBegin);
|
|
|
|
for (size_t i = 1; i < npts; i++) {
|
|
|
|
sink->AddLine(DPointFromPoint(pts[i]));
|
|
|
|
}
|
|
|
|
sink->EndFigure((figureBegin == D2D1_FIGURE_BEGIN_FILLED) ?
|
|
|
|
D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN);
|
|
|
|
sink->Close();
|
|
|
|
ReleaseUnknown(sink);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
return geometry;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::PolyLine(const Point *pts, size_t npts, Stroke stroke) {
|
|
|
|
PLATFORM_ASSERT(pRenderTarget && (npts > 1));
|
|
|
|
if (!pRenderTarget || (npts <= 1)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ID2D1PathGeometry *geometry = Geometry(pts, npts, D2D1_FIGURE_BEGIN_HOLLOW);
|
|
|
|
PLATFORM_ASSERT(geometry);
|
|
|
|
if (!geometry) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
D2DPenColourAlpha(stroke.colour);
|
|
|
|
D2D1_STROKE_STYLE_PROPERTIES strokeProps {};
|
|
|
|
strokeProps.startCap = D2D1_CAP_STYLE_ROUND;
|
|
|
|
strokeProps.endCap = D2D1_CAP_STYLE_ROUND;
|
|
|
|
strokeProps.dashCap = D2D1_CAP_STYLE_FLAT;
|
|
|
|
strokeProps.lineJoin = D2D1_LINE_JOIN_MITER;
|
|
|
|
strokeProps.miterLimit = 4.0f;
|
|
|
|
strokeProps.dashStyle = D2D1_DASH_STYLE_SOLID;
|
|
|
|
strokeProps.dashOffset = 0;
|
|
|
|
|
|
|
|
// get the stroke style to apply
|
|
|
|
ID2D1StrokeStyle *pStrokeStyle = nullptr;
|
|
|
|
const HRESULT hr = pD2DFactory->CreateStrokeStyle(
|
|
|
|
strokeProps, nullptr, 0, &pStrokeStyle);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
pRenderTarget->DrawGeometry(geometry, pBrush, stroke.WidthF(), pStrokeStyle);
|
|
|
|
}
|
|
|
|
ReleaseUnknown(pStrokeStyle);
|
|
|
|
ReleaseUnknown(geometry);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceD2D::Polygon(const Point *pts, size_t npts, FillStroke fillStroke) {
|
2021-02-21 04:53:09 +00:00
|
|
|
PLATFORM_ASSERT(pRenderTarget && (npts > 2));
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
ID2D1PathGeometry *geometry = Geometry(pts, npts, D2D1_FIGURE_BEGIN_FILLED);
|
2021-02-21 04:53:09 +00:00
|
|
|
PLATFORM_ASSERT(geometry);
|
2022-01-04 23:07:50 +00:00
|
|
|
if (geometry) {
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillGeometry(geometry, pBrush);
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawGeometry(geometry, pBrush, fillStroke.stroke.WidthF());
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(geometry);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::RectangleDraw(PRectangle rc, FillStroke fillStroke) {
|
|
|
|
if (!pRenderTarget)
|
|
|
|
return;
|
|
|
|
const D2D1_RECT_F rect = RectangleFromPRectangle(rc);
|
|
|
|
const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF());
|
|
|
|
const float halfStroke = fillStroke.stroke.WidthF() / 2.0f;
|
|
|
|
const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke);
|
|
|
|
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillRectangle(&rectFill, pBrush);
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawRectangle(&rectOutline, pBrush, fillStroke.stroke.WidthF());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceD2D::RectangleFrame(PRectangle rc, Stroke stroke) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION halfStroke = stroke.width / 2.0f;
|
|
|
|
const D2D1_RECT_F rectangle1 = RectangleFromPRectangle(rc.Inset(halfStroke));
|
|
|
|
D2DPenColourAlpha(stroke.colour);
|
|
|
|
pRenderTarget->DrawRectangle(&rectangle1, pBrush, stroke.WidthF());
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::FillRectangle(PRectangle rc, Fill fill) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
D2DPenColourAlpha(fill.colour);
|
|
|
|
const D2D1_RECT_F rectangle = RectangleFromPRectangle(rc);
|
|
|
|
pRenderTarget->FillRectangle(&rectangle, pBrush);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::FillRectangleAligned(PRectangle rc, Fill fill) {
|
2022-12-10 12:35:16 +00:00
|
|
|
FillRectangle(PixelAlign(rc, PixelDivisions()), fill);
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceD2D::FillRectangle(PRectangle rc, Surface &surfacePattern) {
|
2021-02-21 04:53:09 +00:00
|
|
|
SurfaceD2D *psurfOther = dynamic_cast<SurfaceD2D *>(&surfacePattern);
|
|
|
|
PLATFORM_ASSERT(psurfOther);
|
2022-01-04 23:07:50 +00:00
|
|
|
if (!psurfOther) {
|
|
|
|
throw std::runtime_error("SurfaceD2D::FillRectangle: wrong Surface type.");
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
ID2D1Bitmap *pBitmap = nullptr;
|
2021-02-21 04:53:09 +00:00
|
|
|
HRESULT hr = psurfOther->GetBitmap(&pBitmap);
|
|
|
|
if (SUCCEEDED(hr) && pBitmap) {
|
2019-05-04 18:14:48 +00:00
|
|
|
ID2D1BitmapBrush *pBitmapBrush = nullptr;
|
|
|
|
const D2D1_BITMAP_BRUSH_PROPERTIES brushProperties =
|
2013-08-28 00:44:27 +00:00
|
|
|
D2D1::BitmapBrushProperties(D2D1_EXTEND_MODE_WRAP, D2D1_EXTEND_MODE_WRAP,
|
|
|
|
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR);
|
|
|
|
// Create the bitmap brush.
|
|
|
|
hr = pRenderTarget->CreateBitmapBrush(pBitmap, brushProperties, &pBitmapBrush);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pBitmap);
|
|
|
|
if (SUCCEEDED(hr) && pBitmapBrush) {
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->FillRectangle(
|
2022-01-04 23:07:50 +00:00
|
|
|
RectangleFromPRectangle(rc),
|
2013-08-28 00:44:27 +00:00
|
|
|
pBitmapBrush);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pBitmapBrush);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::RoundedRectangle(PRectangle rc, FillStroke fillStroke) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-08-27 07:35:52 +00:00
|
|
|
const FLOAT minDimension = static_cast<FLOAT>(std::min(rc.Width(), rc.Height())) / 2.0f;
|
|
|
|
const FLOAT radius = std::min(4.0f, minDimension);
|
|
|
|
if (fillStroke.fill.colour == fillStroke.stroke.colour) {
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRectFill = {
|
2022-08-27 07:35:52 +00:00
|
|
|
RectangleFromPRectangle(rc),
|
|
|
|
radius, radius };
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
|
|
|
|
} else {
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRectFill = {
|
2022-08-27 07:35:52 +00:00
|
|
|
RectangleFromPRectangle(rc.Inset(1.0)),
|
|
|
|
radius-1, radius-1 };
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRect = {
|
2022-08-27 07:35:52 +00:00
|
|
|
RectangleFromPRectangle(rc.Inset(0.5)),
|
|
|
|
radius, radius };
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.WidthF());
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::AlphaRectangle(PRectangle rc, XYPOSITION cornerSize, FillStroke fillStroke) {
|
|
|
|
const D2D1_RECT_F rect = RectangleFromPRectangle(rc);
|
|
|
|
const D2D1_RECT_F rectFill = RectangleInset(rect, fillStroke.stroke.WidthF());
|
|
|
|
const float halfStroke = fillStroke.stroke.WidthF() / 2.0f;
|
|
|
|
const D2D1_RECT_F rectOutline = RectangleInset(rect, halfStroke);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
|
|
|
if (cornerSize == 0) {
|
|
|
|
// When corner size is zero, draw square rectangle to prevent blurry pixels at corners
|
2022-01-04 23:07:50 +00:00
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->FillRectangle(rectFill, pBrush);
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawRectangle(rectOutline, pBrush, fillStroke.stroke.WidthF());
|
2013-08-28 00:44:27 +00:00
|
|
|
} else {
|
|
|
|
const float cornerSizeF = static_cast<float>(cornerSize);
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRectFill = {
|
2022-01-04 23:07:50 +00:00
|
|
|
rectFill, cornerSizeF - 1.0f, cornerSizeF - 1.0f };
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
|
|
|
|
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRect = {
|
2022-01-04 23:07:50 +00:00
|
|
|
rectOutline, cornerSizeF, cornerSizeF};
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.WidthF());
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void SurfaceD2D::GradientRectangle(PRectangle rc, const std::vector<ColourStop> &stops, GradientOptions options) {
|
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgbp {
|
|
|
|
DPointFromPoint(Point(rc.left, rc.top)), {}
|
|
|
|
};
|
2019-05-04 18:14:48 +00:00
|
|
|
switch (options) {
|
|
|
|
case GradientOptions::leftToRight:
|
2022-01-04 23:07:50 +00:00
|
|
|
lgbp.endPoint = DPointFromPoint(Point(rc.right, rc.top));
|
2019-05-04 18:14:48 +00:00
|
|
|
break;
|
|
|
|
case GradientOptions::topToBottom:
|
|
|
|
default:
|
2022-01-04 23:07:50 +00:00
|
|
|
lgbp.endPoint = DPointFromPoint(Point(rc.left, rc.bottom));
|
2019-05-04 18:14:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<D2D1_GRADIENT_STOP> gradientStops;
|
|
|
|
for (const ColourStop &stop : stops) {
|
2022-01-04 23:07:50 +00:00
|
|
|
gradientStops.push_back({ static_cast<FLOAT>(stop.position), ColorFromColourAlpha(stop.colour) });
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
ID2D1GradientStopCollection *pGradientStops = nullptr;
|
|
|
|
HRESULT hr = pRenderTarget->CreateGradientStopCollection(
|
|
|
|
gradientStops.data(), static_cast<UINT32>(gradientStops.size()), &pGradientStops);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (FAILED(hr) || !pGradientStops) {
|
2019-05-04 18:14:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
ID2D1LinearGradientBrush *pBrushLinear = nullptr;
|
|
|
|
hr = pRenderTarget->CreateLinearGradientBrush(
|
|
|
|
lgbp, pGradientStops, &pBrushLinear);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (SUCCEEDED(hr) && pBrushLinear) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const D2D1_RECT_F rectangle = RectangleFromPRectangle(PRectangle(
|
|
|
|
std::round(rc.left), rc.top, std::round(rc.right), rc.bottom));
|
2019-05-04 18:14:48 +00:00
|
|
|
pRenderTarget->FillRectangle(&rectangle, pBrushLinear);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pBrushLinear);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pGradientStops);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void SurfaceD2D::DrawRGBAImage(PRectangle rc, int width, int height, const unsigned char *pixelsImage) {
|
|
|
|
if (pRenderTarget) {
|
|
|
|
if (rc.Width() > width)
|
2019-07-21 13:26:02 +00:00
|
|
|
rc.left += std::floor((rc.Width() - width) / 2);
|
2013-08-28 00:44:27 +00:00
|
|
|
rc.right = rc.left + width;
|
|
|
|
if (rc.Height() > height)
|
2019-07-21 13:26:02 +00:00
|
|
|
rc.top += std::floor((rc.Height() - height) / 2);
|
2013-08-28 00:44:27 +00:00
|
|
|
rc.bottom = rc.top + height;
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
std::vector<unsigned char> image(RGBAImage::bytesPerPixel * height * width);
|
|
|
|
RGBAImage::BGRAFromRGBA(image.data(), pixelsImage, static_cast<ptrdiff_t>(height) * width);
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
ID2D1Bitmap *bitmap = nullptr;
|
|
|
|
const D2D1_SIZE_U size = D2D1::SizeU(width, height);
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_BITMAP_PROPERTIES props = {{DXGI_FORMAT_B8G8R8A8_UNORM,
|
2013-08-28 00:44:27 +00:00
|
|
|
D2D1_ALPHA_MODE_PREMULTIPLIED}, 72.0, 72.0};
|
2021-02-21 04:53:09 +00:00
|
|
|
const HRESULT hr = pRenderTarget->CreateBitmap(size, image.data(),
|
2013-08-28 00:44:27 +00:00
|
|
|
width * 4, &props, &bitmap);
|
|
|
|
if (SUCCEEDED(hr)) {
|
2021-02-21 04:53:09 +00:00
|
|
|
const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc);
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->DrawBitmap(bitmap, rcDestination);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(bitmap);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::Ellipse(PRectangle rc, FillStroke fillStroke) {
|
|
|
|
if (!pRenderTarget)
|
|
|
|
return;
|
|
|
|
const D2D1_POINT_2F centre = DPointFromPoint(rc.Centre());
|
|
|
|
|
|
|
|
const FLOAT radiusFill = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width);
|
|
|
|
const D2D1_ELLIPSE ellipseFill = { centre, radiusFill, radiusFill };
|
|
|
|
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillEllipse(ellipseFill, pBrush);
|
|
|
|
|
|
|
|
const FLOAT radiusOutline = static_cast<FLOAT>(rc.Width() / 2.0f - fillStroke.stroke.width / 2.0f);
|
|
|
|
const D2D1_ELLIPSE ellipseOutline = { centre, radiusOutline, radiusOutline };
|
|
|
|
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawEllipse(ellipseOutline, pBrush, fillStroke.stroke.WidthF());
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::Stadium(PRectangle rc, FillStroke fillStroke, Ends ends) {
|
|
|
|
if (!pRenderTarget)
|
|
|
|
return;
|
|
|
|
if (rc.Width() < rc.Height()) {
|
|
|
|
// Can't draw nice ends so just draw a rectangle
|
|
|
|
RectangleDraw(rc, fillStroke);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const FLOAT radius = static_cast<FLOAT>(rc.Height() / 2.0);
|
|
|
|
const FLOAT radiusFill = radius - fillStroke.stroke.WidthF();
|
|
|
|
const FLOAT halfStroke = fillStroke.stroke.WidthF() / 2.0f;
|
|
|
|
if (ends == Surface::Ends::semiCircles) {
|
|
|
|
const D2D1_RECT_F rect = RectangleFromPRectangle(rc);
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRectFill = { RectangleInset(rect, fillStroke.stroke.WidthF()),
|
2022-01-04 23:07:50 +00:00
|
|
|
radiusFill, radiusFill };
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillRoundedRectangle(roundedRectFill, pBrush);
|
|
|
|
|
2023-05-31 23:11:12 +00:00
|
|
|
const D2D1_ROUNDED_RECT roundedRect = { RectangleInset(rect, halfStroke),
|
2022-01-04 23:07:50 +00:00
|
|
|
radius, radius };
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawRoundedRectangle(roundedRect, pBrush, fillStroke.stroke.WidthF());
|
|
|
|
} else {
|
|
|
|
const Ends leftSide = static_cast<Ends>(static_cast<int>(ends) & 0xf);
|
|
|
|
const Ends rightSide = static_cast<Ends>(static_cast<int>(ends) & 0xf0);
|
|
|
|
PRectangle rcInner = rc;
|
|
|
|
rcInner.left += radius;
|
|
|
|
rcInner.right -= radius;
|
|
|
|
ID2D1PathGeometry *pathGeometry = nullptr;
|
|
|
|
const HRESULT hrGeometry = pD2DFactory->CreatePathGeometry(&pathGeometry);
|
|
|
|
if (FAILED(hrGeometry) || !pathGeometry)
|
|
|
|
return;
|
|
|
|
ID2D1GeometrySink *pSink = nullptr;
|
|
|
|
const HRESULT hrSink = pathGeometry->Open(&pSink);
|
|
|
|
if (SUCCEEDED(hrSink) && pSink) {
|
|
|
|
switch (leftSide) {
|
|
|
|
case Ends::leftFlat:
|
|
|
|
pSink->BeginFigure(DPointFromPoint(Point(rc.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED);
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.bottom - halfStroke)));
|
|
|
|
break;
|
|
|
|
case Ends::leftAngle:
|
|
|
|
pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED);
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rc.left + halfStroke, rc.Centre().y)));
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke)));
|
|
|
|
break;
|
|
|
|
case Ends::semiCircles:
|
|
|
|
default: {
|
|
|
|
pSink->BeginFigure(DPointFromPoint(Point(rcInner.left + halfStroke, rc.top + halfStroke)), D2D1_FIGURE_BEGIN_FILLED);
|
|
|
|
D2D1_ARC_SEGMENT segment{};
|
|
|
|
segment.point = DPointFromPoint(Point(rcInner.left + halfStroke, rc.bottom - halfStroke));
|
|
|
|
segment.size = D2D1::SizeF(radiusFill, radiusFill);
|
|
|
|
segment.rotationAngle = 0.0f;
|
|
|
|
segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
|
|
|
|
segment.arcSize = D2D1_ARC_SIZE_SMALL;
|
|
|
|
pSink->AddArc(segment);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (rightSide) {
|
|
|
|
case Ends::rightFlat:
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.bottom - halfStroke)));
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.top + halfStroke)));
|
|
|
|
break;
|
|
|
|
case Ends::rightAngle:
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke)));
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rc.right - halfStroke, rc.Centre().y)));
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke)));
|
|
|
|
break;
|
|
|
|
case Ends::semiCircles:
|
|
|
|
default: {
|
|
|
|
pSink->AddLine(DPointFromPoint(Point(rcInner.right - halfStroke, rc.bottom - halfStroke)));
|
|
|
|
D2D1_ARC_SEGMENT segment{};
|
|
|
|
segment.point = DPointFromPoint(Point(rcInner.right - halfStroke, rc.top + halfStroke));
|
|
|
|
segment.size = D2D1::SizeF(radiusFill, radiusFill);
|
|
|
|
segment.rotationAngle = 0.0f;
|
|
|
|
segment.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
|
|
|
|
segment.arcSize = D2D1_ARC_SIZE_SMALL;
|
|
|
|
pSink->AddArc(segment);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
|
|
|
|
|
|
|
|
pSink->Close();
|
|
|
|
}
|
|
|
|
ReleaseUnknown(pSink);
|
|
|
|
D2DPenColourAlpha(fillStroke.fill.colour);
|
|
|
|
pRenderTarget->FillGeometry(pathGeometry, pBrush);
|
|
|
|
D2DPenColourAlpha(fillStroke.stroke.colour);
|
|
|
|
pRenderTarget->DrawGeometry(pathGeometry, pBrush, fillStroke.stroke.WidthF());
|
|
|
|
ReleaseUnknown(pathGeometry);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceD2D::Copy(PRectangle rc, Point from, Surface &surfaceSource) {
|
2021-02-21 04:53:09 +00:00
|
|
|
SurfaceD2D &surfOther = dynamic_cast<SurfaceD2D &>(surfaceSource);
|
2019-05-04 18:14:48 +00:00
|
|
|
ID2D1Bitmap *pBitmap = nullptr;
|
2022-01-04 23:07:50 +00:00
|
|
|
const HRESULT hr = surfOther.GetBitmap(&pBitmap);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (SUCCEEDED(hr) && pBitmap) {
|
|
|
|
const D2D1_RECT_F rcDestination = RectangleFromPRectangle(rc);
|
2022-01-04 23:07:50 +00:00
|
|
|
const D2D1_RECT_F rcSource = RectangleFromPRectangle(PRectangle(
|
|
|
|
from.x, from.y, from.x + rc.Width(), from.y + rc.Height()));
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->DrawBitmap(pBitmap, rcDestination, 1.0f,
|
|
|
|
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, rcSource);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pBitmap);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-31 23:11:12 +00:00
|
|
|
class BlobInline final : public IDWriteInlineObject {
|
2019-05-04 18:14:48 +00:00
|
|
|
XYPOSITION width;
|
|
|
|
|
|
|
|
// IUnknown
|
|
|
|
STDMETHODIMP QueryInterface(REFIID riid, PVOID *ppv) override;
|
|
|
|
STDMETHODIMP_(ULONG)AddRef() override;
|
|
|
|
STDMETHODIMP_(ULONG)Release() override;
|
|
|
|
|
|
|
|
// IDWriteInlineObject
|
2021-02-21 04:53:09 +00:00
|
|
|
COM_DECLSPEC_NOTHROW STDMETHODIMP Draw(
|
2019-05-04 18:14:48 +00:00
|
|
|
void *clientDrawingContext,
|
|
|
|
IDWriteTextRenderer *renderer,
|
|
|
|
FLOAT originX,
|
|
|
|
FLOAT originY,
|
|
|
|
BOOL isSideways,
|
|
|
|
BOOL isRightToLeft,
|
|
|
|
IUnknown *clientDrawingEffect
|
|
|
|
) override;
|
2021-02-21 04:53:09 +00:00
|
|
|
COM_DECLSPEC_NOTHROW STDMETHODIMP GetMetrics(DWRITE_INLINE_OBJECT_METRICS *metrics) override;
|
|
|
|
COM_DECLSPEC_NOTHROW STDMETHODIMP GetOverhangMetrics(DWRITE_OVERHANG_METRICS *overhangs) override;
|
|
|
|
COM_DECLSPEC_NOTHROW STDMETHODIMP GetBreakConditions(
|
2019-05-04 18:14:48 +00:00
|
|
|
DWRITE_BREAK_CONDITION *breakConditionBefore,
|
|
|
|
DWRITE_BREAK_CONDITION *breakConditionAfter) override;
|
|
|
|
public:
|
|
|
|
BlobInline(XYPOSITION width_=0.0f) noexcept : width(width_) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/// Implement IUnknown
|
|
|
|
STDMETHODIMP BlobInline::QueryInterface(REFIID riid, PVOID *ppv) {
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!ppv)
|
|
|
|
return E_POINTER;
|
2019-05-04 18:14:48 +00:00
|
|
|
// Never called so not checked.
|
|
|
|
*ppv = nullptr;
|
|
|
|
if (riid == IID_IUnknown)
|
2023-05-31 23:11:12 +00:00
|
|
|
*ppv = this;
|
2019-05-04 18:14:48 +00:00
|
|
|
if (riid == __uuidof(IDWriteInlineObject))
|
2023-05-31 23:11:12 +00:00
|
|
|
*ppv = this;
|
2019-05-04 18:14:48 +00:00
|
|
|
if (!*ppv)
|
|
|
|
return E_NOINTERFACE;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
STDMETHODIMP_(ULONG) BlobInline::AddRef() {
|
|
|
|
// Lifetime tied to Platform methods so ignore any reference operations.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
STDMETHODIMP_(ULONG) BlobInline::Release() {
|
|
|
|
// Lifetime tied to Platform methods so ignore any reference operations.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Implement IDWriteInlineObject
|
2021-02-21 04:53:09 +00:00
|
|
|
COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::Draw(
|
2019-05-04 18:14:48 +00:00
|
|
|
void*,
|
|
|
|
IDWriteTextRenderer*,
|
|
|
|
FLOAT,
|
|
|
|
FLOAT,
|
|
|
|
BOOL,
|
|
|
|
BOOL,
|
|
|
|
IUnknown*) {
|
|
|
|
// Since not performing drawing, not necessary to implement
|
|
|
|
// Could be implemented by calling back into platform-independent code.
|
|
|
|
// This would allow more of the drawing to be mediated through DirectWrite.
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetMetrics(
|
2019-05-04 18:14:48 +00:00
|
|
|
DWRITE_INLINE_OBJECT_METRICS *metrics
|
|
|
|
) {
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!metrics)
|
|
|
|
return E_POINTER;
|
2022-01-04 23:07:50 +00:00
|
|
|
metrics->width = static_cast<FLOAT>(width);
|
2019-05-04 18:14:48 +00:00
|
|
|
metrics->height = 2;
|
|
|
|
metrics->baseline = 1;
|
|
|
|
metrics->supportsSideways = FALSE;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetOverhangMetrics(
|
2019-05-04 18:14:48 +00:00
|
|
|
DWRITE_OVERHANG_METRICS *overhangs
|
|
|
|
) {
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!overhangs)
|
|
|
|
return E_POINTER;
|
2019-05-04 18:14:48 +00:00
|
|
|
overhangs->left = 0;
|
|
|
|
overhangs->top = 0;
|
|
|
|
overhangs->right = 0;
|
|
|
|
overhangs->bottom = 0;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE BlobInline::GetBreakConditions(
|
2019-05-04 18:14:48 +00:00
|
|
|
DWRITE_BREAK_CONDITION *breakConditionBefore,
|
|
|
|
DWRITE_BREAK_CONDITION *breakConditionAfter
|
|
|
|
) {
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!breakConditionBefore || !breakConditionAfter)
|
|
|
|
return E_POINTER;
|
2019-05-04 18:14:48 +00:00
|
|
|
// Since not performing 2D layout, not necessary to implement
|
|
|
|
*breakConditionBefore = DWRITE_BREAK_CONDITION_NEUTRAL;
|
|
|
|
*breakConditionAfter = DWRITE_BREAK_CONDITION_NEUTRAL;
|
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
class ScreenLineLayout : public IScreenLineLayout {
|
|
|
|
IDWriteTextLayout *textLayout = nullptr;
|
|
|
|
std::string text;
|
|
|
|
std::wstring buffer;
|
|
|
|
std::vector<BlobInline> blobs;
|
|
|
|
static void FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs);
|
|
|
|
static std::wstring ReplaceRepresentation(std::string_view text);
|
|
|
|
static size_t GetPositionInLayout(std::string_view text, size_t position);
|
|
|
|
public:
|
|
|
|
ScreenLineLayout(const IScreenLine *screenLine);
|
|
|
|
// Deleted so ScreenLineLayout objects can not be copied
|
|
|
|
ScreenLineLayout(const ScreenLineLayout &) = delete;
|
|
|
|
ScreenLineLayout(ScreenLineLayout &&) = delete;
|
|
|
|
ScreenLineLayout &operator=(const ScreenLineLayout &) = delete;
|
|
|
|
ScreenLineLayout &operator=(ScreenLineLayout &&) = delete;
|
2022-01-04 23:07:50 +00:00
|
|
|
~ScreenLineLayout() noexcept override;
|
2019-05-04 18:14:48 +00:00
|
|
|
size_t PositionFromX(XYPOSITION xDistance, bool charPosition) override;
|
|
|
|
XYPOSITION XFromPosition(size_t caretPosition) override;
|
|
|
|
std::vector<Interval> FindRangeIntervals(size_t start, size_t end) override;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Each char can have its own style, so we fill the textLayout with the textFormat of each char
|
|
|
|
|
|
|
|
void ScreenLineLayout::FillTextLayoutFormats(const IScreenLine *screenLine, IDWriteTextLayout *textLayout, std::vector<BlobInline> &blobs) {
|
|
|
|
// Reserve enough entries up front so they are not moved and the pointers handed
|
|
|
|
// to textLayout remain valid.
|
|
|
|
const ptrdiff_t numRepresentations = screenLine->RepresentationCount();
|
|
|
|
std::string_view text = screenLine->Text();
|
|
|
|
const ptrdiff_t numTabs = std::count(std::begin(text), std::end(text), '\t');
|
|
|
|
blobs.reserve(numRepresentations + numTabs);
|
|
|
|
|
|
|
|
UINT32 layoutPosition = 0;
|
|
|
|
|
|
|
|
for (size_t bytePosition = 0; bytePosition < screenLine->Length();) {
|
|
|
|
const unsigned char uch = screenLine->Text()[bytePosition];
|
|
|
|
const unsigned int byteCount = UTF8BytesOfLead[uch];
|
2021-02-21 04:53:09 +00:00
|
|
|
const UINT32 codeUnits = UTF16LengthFromUTF8ByteCount(byteCount);
|
2019-05-04 18:14:48 +00:00
|
|
|
const DWRITE_TEXT_RANGE textRange = { layoutPosition, codeUnits };
|
|
|
|
|
|
|
|
XYPOSITION representationWidth = screenLine->RepresentationWidth(bytePosition);
|
|
|
|
if ((representationWidth == 0.0f) && (screenLine->Text()[bytePosition] == '\t')) {
|
2022-01-04 23:07:50 +00:00
|
|
|
D2D1_POINT_2F realPt {};
|
2021-02-21 04:53:09 +00:00
|
|
|
DWRITE_HIT_TEST_METRICS realCaretMetrics {};
|
2019-05-04 18:14:48 +00:00
|
|
|
textLayout->HitTestTextPosition(
|
|
|
|
layoutPosition,
|
|
|
|
false, // trailing if false, else leading edge
|
|
|
|
&realPt.x,
|
|
|
|
&realPt.y,
|
|
|
|
&realCaretMetrics
|
|
|
|
);
|
|
|
|
|
|
|
|
const XYPOSITION nextTab = screenLine->TabPositionAfter(realPt.x);
|
|
|
|
representationWidth = nextTab - realPt.x;
|
|
|
|
}
|
|
|
|
if (representationWidth > 0.0f) {
|
|
|
|
blobs.push_back(BlobInline(representationWidth));
|
|
|
|
textLayout->SetInlineObject(&blobs.back(), textRange);
|
|
|
|
};
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
const FontDirectWrite *pfm =
|
|
|
|
dynamic_cast<const FontDirectWrite *>(screenLine->FontOfPosition(bytePosition));
|
|
|
|
if (!pfm) {
|
|
|
|
throw std::runtime_error("FillTextLayoutFormats: wrong Font type.");
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
const unsigned int fontFamilyNameSize = pfm->pTextFormat->GetFontFamilyNameLength();
|
2021-02-21 04:53:09 +00:00
|
|
|
std::wstring fontFamilyName(fontFamilyNameSize, 0);
|
|
|
|
const HRESULT hrFamily = pfm->pTextFormat->GetFontFamilyName(fontFamilyName.data(), fontFamilyNameSize + 1);
|
|
|
|
if (SUCCEEDED(hrFamily)) {
|
|
|
|
textLayout->SetFontFamilyName(fontFamilyName.c_str(), textRange);
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
textLayout->SetFontSize(pfm->pTextFormat->GetFontSize(), textRange);
|
|
|
|
textLayout->SetFontWeight(pfm->pTextFormat->GetFontWeight(), textRange);
|
|
|
|
textLayout->SetFontStyle(pfm->pTextFormat->GetFontStyle(), textRange);
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
const unsigned int localeNameSize = pfm->pTextFormat->GetLocaleNameLength();
|
|
|
|
std::wstring localeName(localeNameSize, 0);
|
|
|
|
const HRESULT hrLocale = pfm->pTextFormat->GetLocaleName(localeName.data(), localeNameSize + 1);
|
|
|
|
if (SUCCEEDED(hrLocale)) {
|
|
|
|
textLayout->SetLocaleName(localeName.c_str(), textRange);
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
textLayout->SetFontStretch(pfm->pTextFormat->GetFontStretch(), textRange);
|
|
|
|
|
|
|
|
IDWriteFontCollection *fontCollection = nullptr;
|
|
|
|
if (SUCCEEDED(pfm->pTextFormat->GetFontCollection(&fontCollection))) {
|
|
|
|
textLayout->SetFontCollection(fontCollection, textRange);
|
|
|
|
}
|
|
|
|
|
|
|
|
bytePosition += byteCount;
|
|
|
|
layoutPosition += codeUnits;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Convert to a wide character string and replace tabs with X to stop DirectWrite tab expansion */
|
|
|
|
|
|
|
|
std::wstring ScreenLineLayout::ReplaceRepresentation(std::string_view text) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const TextWide wideText(text, CpUtf8);
|
2019-05-04 18:14:48 +00:00
|
|
|
std::wstring ws(wideText.buffer, wideText.tlen);
|
|
|
|
std::replace(ws.begin(), ws.end(), L'\t', L'X');
|
|
|
|
return ws;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finds the position in the wide character version of the text.
|
|
|
|
|
|
|
|
size_t ScreenLineLayout::GetPositionInLayout(std::string_view text, size_t position) {
|
|
|
|
const std::string_view textUptoPosition = text.substr(0, position);
|
|
|
|
return UTF16Length(textUptoPosition);
|
|
|
|
}
|
|
|
|
|
|
|
|
ScreenLineLayout::ScreenLineLayout(const IScreenLine *screenLine) {
|
|
|
|
// If the text is empty, then no need to go through this function
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!screenLine || !screenLine->Length())
|
2019-05-04 18:14:48 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
text = screenLine->Text();
|
|
|
|
|
|
|
|
// Get textFormat
|
2022-01-04 23:07:50 +00:00
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(screenLine->FontOfPosition(0));
|
|
|
|
if (!pfm->pTextFormat) {
|
2019-05-04 18:14:48 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the string to wstring and replace the original control characters with their representative chars.
|
|
|
|
buffer = ReplaceRepresentation(screenLine->Text());
|
|
|
|
|
|
|
|
// Create a text layout
|
2022-01-04 23:07:50 +00:00
|
|
|
const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(
|
|
|
|
buffer.c_str(),
|
|
|
|
static_cast<UINT32>(buffer.length()),
|
|
|
|
pfm->pTextFormat,
|
|
|
|
static_cast<FLOAT>(screenLine->Width()),
|
|
|
|
static_cast<FLOAT>(screenLine->Height()),
|
|
|
|
&textLayout);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (!SUCCEEDED(hrCreate)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fill the textLayout chars with their own formats
|
|
|
|
FillTextLayoutFormats(screenLine, textLayout, blobs);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ScreenLineLayout::~ScreenLineLayout() noexcept {
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(textLayout);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the position from the provided x
|
|
|
|
|
|
|
|
size_t ScreenLineLayout::PositionFromX(XYPOSITION xDistance, bool charPosition) {
|
|
|
|
if (!textLayout) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the text position corresponding to the mouse x,y.
|
|
|
|
// If hitting the trailing side of a cluster, return the
|
|
|
|
// leading edge of the following text position.
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
BOOL isTrailingHit = FALSE;
|
|
|
|
BOOL isInside = FALSE;
|
|
|
|
DWRITE_HIT_TEST_METRICS caretMetrics {};
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
textLayout->HitTestPoint(
|
2022-01-04 23:07:50 +00:00
|
|
|
static_cast<FLOAT>(xDistance),
|
2019-05-04 18:14:48 +00:00
|
|
|
0.0f,
|
|
|
|
&isTrailingHit,
|
|
|
|
&isInside,
|
|
|
|
&caretMetrics
|
|
|
|
);
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
DWRITE_HIT_TEST_METRICS hitTestMetrics {};
|
2019-05-04 18:14:48 +00:00
|
|
|
if (isTrailingHit) {
|
|
|
|
FLOAT caretX = 0.0f;
|
|
|
|
FLOAT caretY = 0.0f;
|
|
|
|
|
|
|
|
// Uses hit-testing to align the current caret position to a whole cluster,
|
|
|
|
// rather than residing in the middle of a base character + diacritic,
|
|
|
|
// surrogate pair, or character + UVS.
|
|
|
|
|
|
|
|
// Align the caret to the nearest whole cluster.
|
|
|
|
textLayout->HitTestTextPosition(
|
|
|
|
caretMetrics.textPosition,
|
|
|
|
false,
|
|
|
|
&caretX,
|
|
|
|
&caretY,
|
|
|
|
&hitTestMetrics
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t pos;
|
|
|
|
if (charPosition) {
|
|
|
|
pos = isTrailingHit ? hitTestMetrics.textPosition : caretMetrics.textPosition;
|
|
|
|
} else {
|
2022-01-04 23:07:50 +00:00
|
|
|
pos = isTrailingHit ? static_cast<size_t>(hitTestMetrics.textPosition) + hitTestMetrics.length : caretMetrics.textPosition;
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the character position in original string
|
|
|
|
return UTF8PositionFromUTF16Position(text, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finds the point of the caret position
|
|
|
|
|
|
|
|
XYPOSITION ScreenLineLayout::XFromPosition(size_t caretPosition) {
|
|
|
|
if (!textLayout) {
|
|
|
|
return 0.0;
|
|
|
|
}
|
|
|
|
// Convert byte positions to wchar_t positions
|
|
|
|
const size_t position = GetPositionInLayout(text, caretPosition);
|
|
|
|
|
|
|
|
// Translate text character offset to point x,y.
|
2021-02-21 04:53:09 +00:00
|
|
|
DWRITE_HIT_TEST_METRICS caretMetrics {};
|
2022-01-04 23:07:50 +00:00
|
|
|
D2D1_POINT_2F pt {};
|
2019-05-04 18:14:48 +00:00
|
|
|
|
|
|
|
textLayout->HitTestTextPosition(
|
|
|
|
static_cast<UINT32>(position),
|
|
|
|
false, // trailing if false, else leading edge
|
|
|
|
&pt.x,
|
|
|
|
&pt.y,
|
|
|
|
&caretMetrics
|
|
|
|
);
|
|
|
|
|
|
|
|
return pt.x;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the selection range rectangles
|
|
|
|
|
|
|
|
std::vector<Interval> ScreenLineLayout::FindRangeIntervals(size_t start, size_t end) {
|
|
|
|
std::vector<Interval> ret;
|
|
|
|
|
|
|
|
if (!textLayout || (start == end)) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert byte positions to wchar_t positions
|
|
|
|
const size_t startPos = GetPositionInLayout(text, start);
|
|
|
|
const size_t endPos = GetPositionInLayout(text, end);
|
|
|
|
|
|
|
|
// Find selection range length
|
|
|
|
const size_t rangeLength = (endPos > startPos) ? (endPos - startPos) : (startPos - endPos);
|
|
|
|
|
|
|
|
// Determine actual number of hit-test ranges
|
|
|
|
UINT32 actualHitTestCount = 0;
|
|
|
|
|
|
|
|
// First try with 2 elements and if more needed, allocate.
|
|
|
|
std::vector<DWRITE_HIT_TEST_METRICS> hitTestMetrics(2);
|
|
|
|
textLayout->HitTestTextRange(
|
|
|
|
static_cast<UINT32>(startPos),
|
|
|
|
static_cast<UINT32>(rangeLength),
|
|
|
|
0, // x
|
|
|
|
0, // y
|
|
|
|
hitTestMetrics.data(),
|
|
|
|
static_cast<UINT32>(hitTestMetrics.size()),
|
|
|
|
&actualHitTestCount
|
|
|
|
);
|
|
|
|
|
|
|
|
if (actualHitTestCount == 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hitTestMetrics.size() < actualHitTestCount) {
|
|
|
|
// Allocate enough room to return all hit-test metrics.
|
|
|
|
hitTestMetrics.resize(actualHitTestCount);
|
|
|
|
textLayout->HitTestTextRange(
|
|
|
|
static_cast<UINT32>(startPos),
|
|
|
|
static_cast<UINT32>(rangeLength),
|
|
|
|
0, // x
|
|
|
|
0, // y
|
|
|
|
hitTestMetrics.data(),
|
|
|
|
static_cast<UINT32>(hitTestMetrics.size()),
|
|
|
|
&actualHitTestCount
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the selection ranges behind the text.
|
|
|
|
|
|
|
|
for (size_t i = 0; i < actualHitTestCount; ++i) {
|
|
|
|
// Store selection rectangle
|
|
|
|
const DWRITE_HIT_TEST_METRICS &htm = hitTestMetrics[i];
|
2022-01-04 23:07:50 +00:00
|
|
|
const Interval selectionInterval { htm.left, htm.left + htm.width };
|
2019-05-04 18:14:48 +00:00
|
|
|
ret.push_back(selectionInterval);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<IScreenLineLayout> SurfaceD2D::Layout(const IScreenLine *screenLine) {
|
|
|
|
return std::make_unique<ScreenLineLayout>(screenLine);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextCommon(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text, int codePageOverride, UINT fuOptions) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
if (pfm->pTextFormat && pRenderTarget && pBrush) {
|
|
|
|
// Use Unicode calls
|
|
|
|
const int codePageDraw = codePageOverride ? codePageOverride : pfm->CodePageText(mode.codePage);
|
|
|
|
const TextWide tbuf(text, codePageDraw);
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
SetFontQuality(pfm->extraFontFlag);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (fuOptions & ETO_CLIPPED) {
|
2021-02-21 04:53:09 +00:00
|
|
|
const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc);
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Explicitly creating a text layout appears a little faster
|
2021-02-21 04:53:09 +00:00
|
|
|
IDWriteTextLayout *pTextLayout = nullptr;
|
2022-01-04 23:07:50 +00:00
|
|
|
const HRESULT hr = pIDWriteFactory->CreateTextLayout(
|
|
|
|
tbuf.buffer,
|
|
|
|
tbuf.tlen,
|
|
|
|
pfm->pTextFormat,
|
|
|
|
static_cast<FLOAT>(rc.Width()),
|
|
|
|
static_cast<FLOAT>(rc.Height()),
|
|
|
|
&pTextLayout);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (SUCCEEDED(hr)) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const D2D1_POINT_2F origin = DPointFromPoint(Point(rc.left, ybase - pfm->yAscent));
|
2019-07-21 13:26:02 +00:00
|
|
|
pRenderTarget->DrawTextLayout(origin, pTextLayout, pBrush, d2dDrawTextOptions);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pTextLayout);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fuOptions & ETO_CLIPPED) {
|
|
|
|
pRenderTarget->PopAxisAlignedClip();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextNoClip(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
FillRectangleAligned(rc, back);
|
|
|
|
D2DPenColourAlpha(fore);
|
|
|
|
DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextClipped(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
FillRectangleAligned(rc, back);
|
|
|
|
D2DPenColourAlpha(fore);
|
|
|
|
DrawTextCommon(rc, font_, ybase, text, 0, ETO_OPAQUE | ETO_CLIPPED);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextTransparent(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
2013-08-28 00:44:27 +00:00
|
|
|
// Avoid drawing spaces in transparent mode
|
2021-02-21 04:53:09 +00:00
|
|
|
for (const char ch : text) {
|
|
|
|
if (ch != ' ') {
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pRenderTarget) {
|
2022-01-04 23:07:50 +00:00
|
|
|
D2DPenColourAlpha(fore);
|
|
|
|
DrawTextCommon(rc, font_, ybase, text, 0, 0);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-27 07:35:52 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
HRESULT MeasurePositions(TextPositions &poses, const TextWide &tbuf, IDWriteTextFormat *pTextFormat) {
|
|
|
|
if (!pTextFormat) {
|
|
|
|
// Unexpected failure like no access to DirectWrite so give up.
|
|
|
|
return E_FAIL;
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
2022-08-27 07:35:52 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
// Initialize poses for safety.
|
|
|
|
std::fill(poses.buffer, poses.buffer + tbuf.tlen, 0.0f);
|
|
|
|
// Create a layout
|
|
|
|
IDWriteTextLayout *pTextLayout = nullptr;
|
2022-08-27 07:35:52 +00:00
|
|
|
const HRESULT hrCreate = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pTextFormat, 10000.0, 1000.0, &pTextLayout);
|
|
|
|
if (!SUCCEEDED(hrCreate)) {
|
|
|
|
return hrCreate;
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
2022-08-27 07:35:52 +00:00
|
|
|
if (!pTextLayout) {
|
|
|
|
return E_FAIL;
|
|
|
|
}
|
|
|
|
VarBuffer<DWRITE_CLUSTER_METRICS, stackBufferLength> cm(tbuf.tlen);
|
2013-08-28 00:44:27 +00:00
|
|
|
UINT32 count = 0;
|
2022-08-27 07:35:52 +00:00
|
|
|
const HRESULT hrGetCluster = pTextLayout->GetClusterMetrics(cm.buffer, tbuf.tlen, &count);
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pTextLayout);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (!SUCCEEDED(hrGetCluster)) {
|
2022-08-27 07:35:52 +00:00
|
|
|
return hrGetCluster;
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
2022-08-27 07:35:52 +00:00
|
|
|
const DWRITE_CLUSTER_METRICS * const clusterMetrics = cm.buffer;
|
2019-05-04 18:14:48 +00:00
|
|
|
// A cluster may be more than one WCHAR, such as for "ffi" which is a ligature in the Candara font
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION position = 0.0;
|
2021-02-21 04:53:09 +00:00
|
|
|
int ti=0;
|
|
|
|
for (unsigned int ci=0; ci<count; ci++) {
|
|
|
|
for (unsigned int inCluster=0; inCluster<clusterMetrics[ci].length; inCluster++) {
|
2019-05-04 18:14:48 +00:00
|
|
|
poses.buffer[ti++] = position + clusterMetrics[ci].width * (inCluster + 1) / clusterMetrics[ci].length;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
position += clusterMetrics[ci].width;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
PLATFORM_ASSERT(ti == tbuf.tlen);
|
2022-08-27 07:35:52 +00:00
|
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceD2D::MeasureWidths(const Font *font_, std::string_view text, XYPOSITION *positions) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
const int codePageText = pfm->CodePageText(mode.codePage);
|
|
|
|
const TextWide tbuf(text, codePageText);
|
|
|
|
TextPositions poses(tbuf.tlen);
|
|
|
|
if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat))) {
|
|
|
|
return;
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
if (codePageText == CpUtf8) {
|
2013-08-28 00:44:27 +00:00
|
|
|
// Map the widths given for UTF-16 characters back onto the UTF-8 input string
|
2022-01-04 23:07:50 +00:00
|
|
|
size_t i = 0;
|
|
|
|
for (int ui = 0; ui < tbuf.tlen; ui++) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const unsigned char uch = text[i];
|
|
|
|
const unsigned int byteCount = UTF8BytesOfLead[uch];
|
|
|
|
if (byteCount == 4) { // Non-BMP
|
2013-08-28 00:44:27 +00:00
|
|
|
ui++;
|
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui<tbuf.tlen); bytePos++) {
|
2013-08-28 00:44:27 +00:00
|
|
|
positions[i++] = poses.buffer[ui];
|
|
|
|
}
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0;
|
2019-05-04 18:14:48 +00:00
|
|
|
while (i<text.length()) {
|
2013-08-28 00:44:27 +00:00
|
|
|
positions[i++] = lastPos;
|
|
|
|
}
|
2019-07-21 13:26:02 +00:00
|
|
|
} else if (!IsDBCSCodePage(codePageText)) {
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
// One char per position
|
|
|
|
PLATFORM_ASSERT(text.length() == static_cast<size_t>(tbuf.tlen));
|
|
|
|
for (int kk=0; kk<tbuf.tlen; kk++) {
|
2013-08-28 00:44:27 +00:00
|
|
|
positions[kk] = poses.buffer[kk];
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
// May be one or two bytes per position
|
|
|
|
int ui = 0;
|
|
|
|
for (size_t i=0; i<text.length() && ui<tbuf.tlen;) {
|
|
|
|
positions[i] = poses.buffer[ui];
|
|
|
|
if (DBCSIsLeadByte(codePageText, text[i])) {
|
|
|
|
positions[i+1] = poses.buffer[ui];
|
2013-08-28 00:44:27 +00:00
|
|
|
i += 2;
|
|
|
|
} else {
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
ui++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
XYPOSITION SurfaceD2D::WidthText(const Font *font_, std::string_view text) {
|
|
|
|
FLOAT width = 1.0;
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
if (pfm->pTextFormat) {
|
|
|
|
const TextWide tbuf(text, pfm->CodePageText(mode.codePage));
|
|
|
|
// Create a layout
|
|
|
|
IDWriteTextLayout *pTextLayout = nullptr;
|
|
|
|
const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pfm->pTextFormat, 1000.0, 1000.0, &pTextLayout);
|
|
|
|
if (SUCCEEDED(hr) && pTextLayout) {
|
|
|
|
DWRITE_TEXT_METRICS textMetrics;
|
|
|
|
if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
|
|
|
|
width = textMetrics.widthIncludingTrailingWhitespace;
|
|
|
|
ReleaseUnknown(pTextLayout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return width;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextNoClipUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
if (pRenderTarget) {
|
|
|
|
FillRectangleAligned(rc, back);
|
|
|
|
D2DPenColourAlpha(fore);
|
|
|
|
DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE);
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextClippedUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore, ColourRGBA back) {
|
|
|
|
if (pRenderTarget) {
|
|
|
|
FillRectangleAligned(rc, back);
|
|
|
|
D2DPenColourAlpha(fore);
|
|
|
|
DrawTextCommon(rc, font_, ybase, text, CpUtf8, ETO_OPAQUE | ETO_CLIPPED);
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::DrawTextTransparentUTF8(PRectangle rc, const Font *font_, XYPOSITION ybase, std::string_view text,
|
|
|
|
ColourRGBA fore) {
|
|
|
|
// Avoid drawing spaces in transparent mode
|
|
|
|
for (const char ch : text) {
|
|
|
|
if (ch != ' ') {
|
|
|
|
if (pRenderTarget) {
|
|
|
|
D2DPenColourAlpha(fore);
|
|
|
|
DrawTextCommon(rc, font_, ybase, text, CpUtf8, 0);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::MeasureWidthsUTF8(const Font *font_, std::string_view text, XYPOSITION *positions) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
const TextWide tbuf(text, CpUtf8);
|
|
|
|
TextPositions poses(tbuf.tlen);
|
2022-08-27 07:35:52 +00:00
|
|
|
if (FAILED(MeasurePositions(poses, tbuf, pfm->pTextFormat))) {
|
2022-01-04 23:07:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Map the widths given for UTF-16 characters back onto the UTF-8 input string
|
|
|
|
size_t i = 0;
|
|
|
|
for (int ui = 0; ui < tbuf.tlen; ui++) {
|
|
|
|
const unsigned char uch = text[i];
|
|
|
|
const unsigned int byteCount = UTF8BytesOfLead[uch];
|
|
|
|
if (byteCount == 4) { // Non-BMP
|
|
|
|
ui++;
|
2022-08-27 07:35:52 +00:00
|
|
|
PLATFORM_ASSERT(ui < tbuf.tlen);
|
2022-01-04 23:07:50 +00:00
|
|
|
}
|
|
|
|
for (unsigned int bytePos=0; (bytePos<byteCount) && (i<text.length()) && (ui < tbuf.tlen); bytePos++) {
|
|
|
|
positions[i++] = poses.buffer[ui];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const XYPOSITION lastPos = (i > 0) ? positions[i - 1] : 0.0;
|
|
|
|
while (i < text.length()) {
|
|
|
|
positions[i++] = lastPos;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceD2D::WidthTextUTF8(const Font * font_, std::string_view text) {
|
2013-08-28 00:44:27 +00:00
|
|
|
FLOAT width = 1.0;
|
2022-01-04 23:07:50 +00:00
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
if (pfm->pTextFormat) {
|
|
|
|
const TextWide tbuf(text, CpUtf8);
|
|
|
|
// Create a layout
|
|
|
|
IDWriteTextLayout *pTextLayout = nullptr;
|
|
|
|
const HRESULT hr = pIDWriteFactory->CreateTextLayout(tbuf.buffer, tbuf.tlen, pfm->pTextFormat, 1000.0, 1000.0, &pTextLayout);
|
|
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
DWRITE_TEXT_METRICS textMetrics;
|
|
|
|
if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
|
|
|
|
width = textMetrics.widthIncludingTrailingWhitespace;
|
|
|
|
ReleaseUnknown(pTextLayout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return width;
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceD2D::Ascent(const Font *font_) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
return std::ceil(pfm->yAscent);
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceD2D::Descent(const Font *font_) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
return std::ceil(pfm->yDescent);
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceD2D::InternalLeading(const Font *font_) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
return std::floor(pfm->yInternalLeading);
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceD2D::Height(const Font *font_) {
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
return std::ceil(pfm->yAscent) + std::ceil(pfm->yDescent);
|
|
|
|
}
|
|
|
|
|
|
|
|
XYPOSITION SurfaceD2D::AverageCharWidth(const Font *font_) {
|
|
|
|
FLOAT width = 1.0;
|
|
|
|
const FontDirectWrite *pfm = FontDirectWrite::Cast(font_);
|
|
|
|
if (pfm->pTextFormat) {
|
2013-08-28 00:44:27 +00:00
|
|
|
// Create a layout
|
2019-05-04 18:14:48 +00:00
|
|
|
IDWriteTextLayout *pTextLayout = nullptr;
|
2022-01-04 23:07:50 +00:00
|
|
|
static constexpr WCHAR wszAllAlpha[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
2015-06-07 21:19:26 +00:00
|
|
|
const size_t lenAllAlpha = wcslen(wszAllAlpha);
|
2019-05-04 18:14:48 +00:00
|
|
|
const HRESULT hr = pIDWriteFactory->CreateTextLayout(wszAllAlpha, static_cast<UINT32>(lenAllAlpha),
|
2022-01-04 23:07:50 +00:00
|
|
|
pfm->pTextFormat, 1000.0, 1000.0, &pTextLayout);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (SUCCEEDED(hr) && pTextLayout) {
|
2013-08-28 00:44:27 +00:00
|
|
|
DWRITE_TEXT_METRICS textMetrics;
|
|
|
|
if (SUCCEEDED(pTextLayout->GetMetrics(&textMetrics)))
|
2015-06-07 21:19:26 +00:00
|
|
|
width = textMetrics.width / lenAllAlpha;
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pTextLayout);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return width;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SurfaceD2D::SetClip(PRectangle rc) {
|
|
|
|
if (pRenderTarget) {
|
2021-02-21 04:53:09 +00:00
|
|
|
const D2D1_RECT_F rcClip = RectangleFromPRectangle(rc);
|
2013-08-28 00:44:27 +00:00
|
|
|
pRenderTarget->PushAxisAlignedClip(rcClip, D2D1_ANTIALIAS_MODE_ALIASED);
|
|
|
|
clipsActive++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::PopClip() {
|
|
|
|
if (pRenderTarget) {
|
|
|
|
PLATFORM_ASSERT(clipsActive > 0);
|
|
|
|
pRenderTarget->PopAxisAlignedClip();
|
|
|
|
clipsActive--;
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::FlushCachedState() {
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void SurfaceD2D::FlushDrawing() {
|
|
|
|
if (pRenderTarget) {
|
|
|
|
pRenderTarget->Flush();
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2022-04-13 11:10:12 +00:00
|
|
|
void SurfaceD2D::SetRenderingParams(std::shared_ptr<RenderingParams> renderingParams_) {
|
2023-11-05 17:24:41 +00:00
|
|
|
renderingParams = std::move(renderingParams_);
|
2022-04-13 11:10:12 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
#endif
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<Surface> Surface::Allocate(Technology technology) {
|
2013-08-28 00:44:27 +00:00
|
|
|
#if defined(USE_D2D)
|
2022-01-04 23:07:50 +00:00
|
|
|
if (technology == Technology::Default)
|
|
|
|
return std::make_unique<SurfaceGDI>();
|
2013-08-28 00:44:27 +00:00
|
|
|
else
|
2022-01-04 23:07:50 +00:00
|
|
|
return std::make_unique<SurfaceD2D>();
|
2013-08-28 00:44:27 +00:00
|
|
|
#else
|
2022-01-04 23:07:50 +00:00
|
|
|
return std::make_unique<SurfaceGDI>();
|
2013-08-28 00:44:27 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
Window::~Window() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Window::Destroy() noexcept {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid)
|
2019-05-04 18:14:48 +00:00
|
|
|
::DestroyWindow(HwndFromWindowID(wid));
|
|
|
|
wid = nullptr;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
PRectangle Window::GetPosition() const {
|
2009-04-24 23:35:41 +00:00
|
|
|
RECT rc;
|
2019-05-04 18:14:48 +00:00
|
|
|
::GetWindowRect(HwndFromWindowID(wid), &rc);
|
2015-06-07 21:19:26 +00:00
|
|
|
return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Window::SetPosition(PRectangle rc) {
|
2019-05-04 18:14:48 +00:00
|
|
|
::SetWindowPos(HwndFromWindowID(wid),
|
2015-06-07 21:19:26 +00:00
|
|
|
0, static_cast<int>(rc.left), static_cast<int>(rc.top),
|
|
|
|
static_cast<int>(rc.Width()), static_cast<int>(rc.Height()), SWP_NOZORDER | SWP_NOACTIVATE);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
namespace {
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
RECT RectFromMonitor(HMONITOR hMonitor) noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
MONITORINFO mi = {};
|
|
|
|
mi.cbSize = sizeof(mi);
|
|
|
|
if (GetMonitorInfo(hMonitor, &mi)) {
|
|
|
|
return mi.rcWork;
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
RECT rc = {0, 0, 0, 0};
|
|
|
|
if (::SystemParametersInfoA(SPI_GETWORKAREA, 0, &rc, 0) == 0) {
|
|
|
|
rc.left = 0;
|
|
|
|
rc.top = 0;
|
|
|
|
rc.right = 0;
|
|
|
|
rc.bottom = 0;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) {
|
2021-02-21 04:53:09 +00:00
|
|
|
const DWORD style = GetWindowStyle(HwndFromWindowID(wid));
|
2009-04-24 23:35:41 +00:00
|
|
|
if (style & WS_POPUP) {
|
2011-03-22 00:16:49 +00:00
|
|
|
POINT ptOther = {0, 0};
|
2021-02-21 04:53:09 +00:00
|
|
|
::ClientToScreen(HwndFromWindow(*relativeTo), &ptOther);
|
2015-06-07 21:19:26 +00:00
|
|
|
rc.Move(static_cast<XYPOSITION>(ptOther.x), static_cast<XYPOSITION>(ptOther.y));
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcMonitor = RectFromPRectangle(rc);
|
2010-08-21 23:59:56 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
HMONITOR hMonitor = MonitorFromRect(&rcMonitor, MONITOR_DEFAULTTONEAREST);
|
2010-08-21 23:59:56 +00:00
|
|
|
// If hMonitor is NULL, that's just the main screen anyways.
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcWork = RectFromMonitor(hMonitor);
|
2013-08-28 00:44:27 +00:00
|
|
|
|
|
|
|
if (rcWork.left < rcWork.right) {
|
|
|
|
// Now clamp our desired rectangle to fit inside the work area
|
|
|
|
// This way, the menu will fit wholly on one screen. An improvement even
|
|
|
|
// if you don't have a second monitor on the left... Menu's appears half on
|
|
|
|
// one screen and half on the other are just U.G.L.Y.!
|
|
|
|
if (rc.right > rcWork.right)
|
|
|
|
rc.Move(rcWork.right - rc.right, 0);
|
|
|
|
if (rc.bottom > rcWork.bottom)
|
|
|
|
rc.Move(0, rcWork.bottom - rc.bottom);
|
|
|
|
if (rc.left < rcWork.left)
|
|
|
|
rc.Move(rcWork.left - rc.left, 0);
|
|
|
|
if (rc.top < rcWork.top)
|
|
|
|
rc.Move(0, rcWork.top - rc.top);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
SetPosition(rc);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
PRectangle Window::GetClientPosition() const {
|
2009-04-24 23:35:41 +00:00
|
|
|
RECT rc={0,0,0,0};
|
2009-08-23 02:24:48 +00:00
|
|
|
if (wid)
|
2019-05-04 18:14:48 +00:00
|
|
|
::GetClientRect(HwndFromWindowID(wid), &rc);
|
2015-06-07 21:19:26 +00:00
|
|
|
return PRectangle::FromInts(rc.left, rc.top, rc.right, rc.bottom);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Window::Show(bool show) {
|
|
|
|
if (show)
|
2019-05-04 18:14:48 +00:00
|
|
|
::ShowWindow(HwndFromWindowID(wid), SW_SHOWNOACTIVATE);
|
2009-04-24 23:35:41 +00:00
|
|
|
else
|
2019-05-04 18:14:48 +00:00
|
|
|
::ShowWindow(HwndFromWindowID(wid), SW_HIDE);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Window::InvalidateAll() {
|
2019-07-21 13:26:02 +00:00
|
|
|
::InvalidateRect(HwndFromWindowID(wid), nullptr, FALSE);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Window::InvalidateRectangle(PRectangle rc) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcw = RectFromPRectangle(rc);
|
|
|
|
::InvalidateRect(HwndFromWindowID(wid), &rcw, FALSE);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
HCURSOR LoadReverseArrowCursor(UINT dpi) noexcept {
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
class CursorHelper {
|
|
|
|
public:
|
|
|
|
ICONINFO info{};
|
|
|
|
BITMAP bmp{};
|
|
|
|
bool HasBitmap() const noexcept {
|
|
|
|
return bmp.bmWidth > 0;
|
|
|
|
}
|
2011-03-22 00:16:49 +00:00
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
CursorHelper(const HCURSOR cursor) noexcept {
|
|
|
|
Init(cursor);
|
|
|
|
}
|
|
|
|
~CursorHelper() {
|
|
|
|
CleanUp();
|
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
CursorHelper &operator=(const HCURSOR cursor) noexcept {
|
|
|
|
CleanUp();
|
|
|
|
Init(cursor);
|
|
|
|
return *this;
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
2011-03-22 00:16:49 +00:00
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
bool MatchesSize(const int width, const int height) noexcept {
|
|
|
|
return bmp.bmWidth == width && bmp.bmHeight == height;
|
|
|
|
}
|
|
|
|
|
|
|
|
HCURSOR CreateFlippedCursor() noexcept {
|
|
|
|
if (info.hbmMask)
|
|
|
|
FlipBitmap(info.hbmMask, bmp.bmWidth, bmp.bmHeight);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (info.hbmColor)
|
2021-02-21 04:53:09 +00:00
|
|
|
FlipBitmap(info.hbmColor, bmp.bmWidth, bmp.bmHeight);
|
|
|
|
info.xHotspot = bmp.bmWidth - 1 - info.xHotspot;
|
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
return ::CreateIconIndirect(&info);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void Init(const HCURSOR &cursor) noexcept {
|
|
|
|
if (::GetIconInfo(cursor, &info)) {
|
|
|
|
::GetObject(info.hbmMask, sizeof(bmp), &bmp);
|
|
|
|
PLATFORM_ASSERT(HasBitmap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CleanUp() noexcept {
|
|
|
|
if (info.hbmMask)
|
|
|
|
::DeleteObject(info.hbmMask);
|
|
|
|
if (info.hbmColor)
|
|
|
|
::DeleteObject(info.hbmColor);
|
|
|
|
info = {};
|
|
|
|
bmp = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
static void FlipBitmap(const HBITMAP bitmap, const int width, const int height) noexcept {
|
|
|
|
HDC hdc = ::CreateCompatibleDC({});
|
|
|
|
if (hdc) {
|
|
|
|
HBITMAP prevBmp = SelectBitmap(hdc, bitmap);
|
|
|
|
::StretchBlt(hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY);
|
|
|
|
SelectBitmap(hdc, prevBmp);
|
|
|
|
::DeleteDC(hdc);
|
|
|
|
}
|
2011-03-22 00:16:49 +00:00
|
|
|
}
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
HCURSOR reverseArrowCursor {};
|
2021-02-21 04:53:09 +00:00
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
const int width = SystemMetricsForDpi(SM_CXCURSOR, dpi);
|
|
|
|
const int height = SystemMetricsForDpi(SM_CYCURSOR, dpi);
|
|
|
|
|
|
|
|
DPI_AWARENESS_CONTEXT oldContext = nullptr;
|
|
|
|
if (fnAreDpiAwarenessContextsEqual && fnAreDpiAwarenessContextsEqual(fnGetThreadDpiAwarenessContext(), DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)) {
|
|
|
|
oldContext = fnSetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
|
|
|
PLATFORM_ASSERT(oldContext != nullptr);
|
2011-03-22 00:16:49 +00:00
|
|
|
}
|
|
|
|
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
const HCURSOR cursor = static_cast<HCURSOR>(::LoadImage({}, IDC_ARROW, IMAGE_CURSOR, width, height, LR_SHARED));
|
|
|
|
if (cursor) {
|
|
|
|
CursorHelper cursorHelper(cursor);
|
|
|
|
|
|
|
|
if (cursorHelper.HasBitmap() && !cursorHelper.MatchesSize(width, height)) {
|
|
|
|
const HCURSOR copy = static_cast<HCURSOR>(::CopyImage(cursor, IMAGE_CURSOR, width, height, LR_COPYFROMRESOURCE | LR_COPYRETURNORG));
|
|
|
|
if (copy) {
|
|
|
|
cursorHelper = copy;
|
|
|
|
::DestroyCursor(copy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursorHelper.HasBitmap()) {
|
|
|
|
reverseArrowCursor = cursorHelper.CreateFlippedCursor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (oldContext) {
|
|
|
|
fnSetThreadDpiAwarenessContext(oldContext);
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
Update: Scintilla 5.3.6 and Lexilla 5.2.6
update to Scinitlla Release 5.3.6 (https://www.scintilla.org/scintilla536.zip)
Released 26 July 2023.
Redraw calltip after showing as didn't update when size of new text exactly same as previous. Feature #1486.
On Win32 fix reverse arrow cursor when scaled. Bug #2382.
On Win32 hide cursor when typing if that system preference has been chosen. Bug #2333.
On Win32 and Qt, stop aligning IME candidate window to target. It is now always aligned to start of composition string. This undoes part of feature #1300. Feature #1488, Bug #2391, Feature #1300.
On Qt, for IMEs, update micro focus when selection changes. This may move the location of IME popups to align with the caret.
On Qt, implement replacement for IMEs which may help with actions like reconversion. This is similar to delete-surrounding on GTK.
and Lexilla Release 5.2.6 (https://www.scintilla.org/lexilla526.zip)
Released 26 July 2023.
Include empty word list names in value returned by DescribeWordListSets and SCI_DESCRIBEKEYWORDSETS. Issue #175, Pull request #176.
Bash: style here-doc end delimiters as SCE_SH_HERE_DELIM instead of SCE_SH_HERE_Q. Issue #177.
Bash: allow '$' as last character in string. Issue #180, Pull request #181.
Bash: fix state after expansion. Highlight all numeric and file test operators. Don't highlight dash in long option as operator. Issue #182, Pull request #183.
Bash: strict checking of special parameters ($*, $@, $$, ...) with property lexer.bash.special.parameter to specify valid parameters. Issue #184, Pull request #186.
Bash: recognize keyword before redirection operators (< and >). Issue #188, Pull request #189.
Errorlist: recognize Bash diagnostic messages.
HTML: allow ASP block to terminate inside line comment. Issue #185.
HTML: fix folding with JSP/ASP.NET <%-- comment. Issue #191.
HTML: fix incremental styling of multi-line ASP.NET directive. Issue #191.
Matlab: improve arguments blocks. Add support for multiple arguments blocks. Prevent "arguments" from being keyword in function declaration line. Fix semicolon handling. Pull request #179.
Visual Prolog: add support for embedded syntax with SCE_VISUALPROLOG_EMBEDDED and SCE_VISUALPROLOG_PLACEHOLDER.
Styling of string literals changed with no differentiation between literals with quotes and those that are prefixed with "@". Quote characters are in a separate style (SCE_VISUALPROLOG_STRING_QUOTE) to contents (SCE_VISUALPROLOG_STRING).
SCE_VISUALPROLOG_CHARACTER, SCE_VISUALPROLOG_CHARACTER_TOO_MANY, SCE_VISUALPROLOG_CHARACTER_ESCAPE_ERROR, SCE_VISUALPROLOG_STRING_EOL_OPEN, and SCE_VISUALPROLOG_STRING_VERBATIM_SPECIAL were removed (replaced with SCE_VISUALPROLOG_UNUSED[1-5]). Pull request #178.
Fix #13901, fix #13911, fix #13943, close #13940
2023-07-27 17:57:12 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
return reverseArrowCursor;
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
void Window::SetCursor(Cursor curs) {
|
|
|
|
switch (curs) {
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::text:
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_IBEAM));
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::up:
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_UPARROW));
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::wait:
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_WAIT));
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::horizontal:
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_SIZEWE));
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::vertical:
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_SIZENS));
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::hand:
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_HAND));
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
case Cursor::reverseArrow:
|
|
|
|
case Cursor::arrow:
|
|
|
|
case Cursor::invalid: // Should not occur, but just in case.
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetCursor(::LoadCursor(NULL,IDC_ARROW));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Returns rectangle of monitor pt is on, both rect and pt are in Window's
|
|
|
|
coordinates */
|
|
|
|
PRectangle Window::GetMonitorRect(Point pt) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const PRectangle rcPosition = GetPosition();
|
2023-05-31 23:11:12 +00:00
|
|
|
const POINT ptDesktop = {static_cast<LONG>(pt.x + rcPosition.left),
|
2013-08-28 00:44:27 +00:00
|
|
|
static_cast<LONG>(pt.y + rcPosition.top)};
|
2019-05-04 18:14:48 +00:00
|
|
|
HMONITOR hMonitor = MonitorFromPoint(ptDesktop, MONITOR_DEFAULTTONEAREST);
|
2013-08-28 00:44:27 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rcWork = RectFromMonitor(hMonitor);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (rcWork.left < rcWork.right) {
|
2009-04-24 23:35:41 +00:00
|
|
|
PRectangle rcMonitor(
|
2013-08-28 00:44:27 +00:00
|
|
|
rcWork.left - rcPosition.left,
|
|
|
|
rcWork.top - rcPosition.top,
|
|
|
|
rcWork.right - rcPosition.left,
|
|
|
|
rcWork.bottom - rcPosition.top);
|
2009-04-24 23:35:41 +00:00
|
|
|
return rcMonitor;
|
2010-08-21 23:59:56 +00:00
|
|
|
} else {
|
|
|
|
return PRectangle();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2010-08-21 23:59:56 +00:00
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
struct ListItemData {
|
|
|
|
const char *text;
|
|
|
|
int pixId;
|
|
|
|
};
|
|
|
|
|
|
|
|
class LineToItem {
|
2013-08-28 00:44:27 +00:00
|
|
|
std::vector<char> words;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
std::vector<ListItemData> data;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
public:
|
2019-05-04 18:14:48 +00:00
|
|
|
void Clear() noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
words.clear();
|
|
|
|
data.clear();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
ListItemData Get(size_t index) const noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
if (index < data.size()) {
|
2009-04-24 23:35:41 +00:00
|
|
|
return data[index];
|
|
|
|
} else {
|
|
|
|
ListItemData missing = {"", -1};
|
|
|
|
return missing;
|
|
|
|
}
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
int Count() const noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
return static_cast<int>(data.size());
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void AllocItem(const char *text, int pixId) {
|
2023-05-31 23:11:12 +00:00
|
|
|
const ListItemData lid = { text, pixId };
|
2013-08-28 00:44:27 +00:00
|
|
|
data.push_back(lid);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
char *SetWords(const char *s) {
|
|
|
|
words = std::vector<char>(s, s+strlen(s)+1);
|
|
|
|
return &words[0];
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const TCHAR ListBoxX_ClassName[] = TEXT("ListBoxX");
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
ListBox::ListBox() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ListBox::~ListBox() noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ListBoxX : public ListBox {
|
|
|
|
int lineHeight;
|
2022-01-04 23:07:50 +00:00
|
|
|
HFONT fontCopy;
|
|
|
|
Technology technology;
|
2013-08-28 00:44:27 +00:00
|
|
|
RGBAImageSet images;
|
2009-04-24 23:35:41 +00:00
|
|
|
LineToItem lti;
|
|
|
|
HWND lb;
|
|
|
|
bool unicodeMode;
|
|
|
|
int desiredVisibleRows;
|
|
|
|
unsigned int maxItemCharacters;
|
|
|
|
unsigned int aveCharWidth;
|
|
|
|
Window *parent;
|
|
|
|
int ctrlID;
|
2021-02-21 04:53:09 +00:00
|
|
|
UINT dpi;
|
2019-05-04 18:14:48 +00:00
|
|
|
IListBoxDelegate *delegate;
|
2009-04-24 23:35:41 +00:00
|
|
|
const char *widestItem;
|
|
|
|
unsigned int maxCharWidth;
|
2019-05-04 18:14:48 +00:00
|
|
|
WPARAM resizeHit;
|
2009-04-24 23:35:41 +00:00
|
|
|
PRectangle rcPreSize;
|
|
|
|
Point dragOffset;
|
|
|
|
Point location; // Caret location at which the list is opened
|
2022-12-10 12:35:16 +00:00
|
|
|
MouseWheelDelta wheelDelta;
|
2022-01-04 23:07:50 +00:00
|
|
|
ListOptions options;
|
|
|
|
DWORD frameStyle = WS_THICKFRAME;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
HWND GetHWND() const noexcept;
|
2013-08-28 00:44:27 +00:00
|
|
|
void AppendListItem(const char *text, const char *numword);
|
2022-01-04 23:07:50 +00:00
|
|
|
void AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
int ItemHeight() const;
|
2021-02-21 04:53:09 +00:00
|
|
|
int MinClientWidth() const noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
int TextOffset() const;
|
2021-02-21 04:53:09 +00:00
|
|
|
POINT GetClientExtent() const noexcept;
|
2010-07-12 22:19:51 +00:00
|
|
|
POINT MinTrackSize() const;
|
|
|
|
POINT MaxTrackSize() const;
|
2021-02-21 04:53:09 +00:00
|
|
|
void SetRedraw(bool on) noexcept;
|
2009-04-24 23:35:41 +00:00
|
|
|
void OnDoubleClick();
|
2019-05-04 18:14:48 +00:00
|
|
|
void OnSelChange();
|
2009-04-24 23:35:41 +00:00
|
|
|
void ResizeToCursor();
|
|
|
|
void StartResize(WPARAM);
|
2015-06-07 21:19:26 +00:00
|
|
|
LRESULT NcHitTest(WPARAM, LPARAM) const;
|
|
|
|
void CentreItem(int n);
|
2022-01-04 23:07:50 +00:00
|
|
|
void Paint(HDC);
|
2009-04-24 23:35:41 +00:00
|
|
|
static LRESULT PASCAL ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
static constexpr Point ItemInset {0, 0}; // Padding around whole item
|
|
|
|
static constexpr Point TextInset {2, 0}; // Padding around text
|
|
|
|
static constexpr Point ImageInset {1, 0}; // Padding around image
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
public:
|
2022-01-04 23:07:50 +00:00
|
|
|
ListBoxX() : lineHeight(10), fontCopy{}, technology(Technology::Default), lb{}, unicodeMode(false),
|
2019-05-04 18:14:48 +00:00
|
|
|
desiredVisibleRows(9), maxItemCharacters(0), aveCharWidth(8),
|
2021-02-21 04:53:09 +00:00
|
|
|
parent(nullptr), ctrlID(0), dpi(USER_DEFAULT_SCREEN_DPI),
|
2019-05-04 18:14:48 +00:00
|
|
|
delegate(nullptr),
|
2022-12-10 12:35:16 +00:00
|
|
|
widestItem(nullptr), maxCharWidth(1), resizeHit(0) {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
ListBoxX(const ListBoxX &) = delete;
|
|
|
|
ListBoxX(ListBoxX &&) = delete;
|
|
|
|
ListBoxX &operator=(const ListBoxX &) = delete;
|
|
|
|
ListBoxX &operator=(ListBoxX &&) = delete;
|
|
|
|
~ListBoxX() noexcept override {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (fontCopy) {
|
|
|
|
::DeleteObject(fontCopy);
|
|
|
|
fontCopy = 0;
|
|
|
|
}
|
|
|
|
}
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetFont(const Font *font) override;
|
|
|
|
void Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void SetAverageCharWidth(int width) override;
|
|
|
|
void SetVisibleRows(int rows) override;
|
|
|
|
int GetVisibleRows() const override;
|
|
|
|
PRectangle GetDesiredRect() override;
|
|
|
|
int CaretFromEdge() override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void Clear() noexcept override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void Append(char *s, int type = -1) override;
|
|
|
|
int Length() override;
|
|
|
|
void Select(int n) override;
|
|
|
|
int GetSelection() override;
|
|
|
|
int Find(const char *prefix) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
std::string GetValue(int n) override;
|
2019-05-04 18:14:48 +00:00
|
|
|
void RegisterImage(int type, const char *xpm_data) override;
|
|
|
|
void RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) override;
|
|
|
|
void ClearRegisteredImages() override;
|
|
|
|
void SetDelegate(IListBoxDelegate *lbDelegate) override;
|
|
|
|
void SetList(const char *list, char separator, char typesep) override;
|
2022-01-04 23:07:50 +00:00
|
|
|
void SetOptions(ListOptions options_) override;
|
2009-04-24 23:35:41 +00:00
|
|
|
void Draw(DRAWITEMSTRUCT *pDrawItem);
|
|
|
|
LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
|
|
|
|
static LRESULT PASCAL StaticWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
|
|
|
|
};
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::unique_ptr<ListBox> ListBox::Allocate() {
|
|
|
|
return std::make_unique<ListBoxX>();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::Create(Window &parent_, int ctrlID_, Point location_, int lineHeight_, bool unicodeMode_, Technology technology_) {
|
2009-04-24 23:35:41 +00:00
|
|
|
parent = &parent_;
|
|
|
|
ctrlID = ctrlID_;
|
|
|
|
location = location_;
|
|
|
|
lineHeight = lineHeight_;
|
|
|
|
unicodeMode = unicodeMode_;
|
2013-08-28 00:44:27 +00:00
|
|
|
technology = technology_;
|
2021-02-21 04:53:09 +00:00
|
|
|
HWND hwndParent = HwndFromWindow(*parent);
|
2009-04-24 23:35:41 +00:00
|
|
|
HINSTANCE hinstanceParent = GetWindowInstance(hwndParent);
|
|
|
|
// Window created as popup so not clipped within parent client area
|
2009-08-23 02:24:48 +00:00
|
|
|
wid = ::CreateWindowEx(
|
2009-04-24 23:35:41 +00:00
|
|
|
WS_EX_WINDOWEDGE, ListBoxX_ClassName, TEXT(""),
|
2022-01-04 23:07:50 +00:00
|
|
|
WS_POPUP | frameStyle,
|
2009-04-24 23:35:41 +00:00
|
|
|
100,100, 150,80, hwndParent,
|
|
|
|
NULL,
|
|
|
|
hinstanceParent,
|
|
|
|
this);
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
dpi = DpiForWindow(hwndParent);
|
|
|
|
POINT locationw = POINTFromPoint(location);
|
2013-08-28 00:44:27 +00:00
|
|
|
::MapWindowPoints(hwndParent, NULL, &locationw, 1);
|
2021-02-21 04:53:09 +00:00
|
|
|
location = PointFromPOINT(locationw);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::SetFont(const Font *font) {
|
|
|
|
const FontWin *pfm = dynamic_cast<const FontWin *>(font);
|
|
|
|
if (pfm) {
|
2009-04-24 23:35:41 +00:00
|
|
|
if (fontCopy) {
|
|
|
|
::DeleteObject(fontCopy);
|
|
|
|
fontCopy = 0;
|
|
|
|
}
|
2013-08-28 00:44:27 +00:00
|
|
|
fontCopy = pfm->HFont();
|
2021-02-21 04:53:09 +00:00
|
|
|
SetWindowFont(lb, fontCopy, 0);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::SetAverageCharWidth(int width) {
|
|
|
|
aveCharWidth = width;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::SetVisibleRows(int rows) {
|
|
|
|
desiredVisibleRows = rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::GetVisibleRows() const {
|
|
|
|
return desiredVisibleRows;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
HWND ListBoxX::GetHWND() const noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
return HwndFromWindowID(GetID());
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
PRectangle ListBoxX::GetDesiredRect() {
|
|
|
|
PRectangle rcDesired = GetPosition();
|
|
|
|
|
|
|
|
int rows = Length();
|
|
|
|
if ((rows == 0) || (rows > desiredVisibleRows))
|
|
|
|
rows = desiredVisibleRows;
|
|
|
|
rcDesired.bottom = rcDesired.top + ItemHeight() * rows;
|
|
|
|
|
|
|
|
int width = MinClientWidth();
|
|
|
|
HDC hdc = ::GetDC(lb);
|
|
|
|
HFONT oldFont = SelectFont(hdc, fontCopy);
|
|
|
|
SIZE textSize = {0, 0};
|
2015-06-07 21:19:26 +00:00
|
|
|
int len = 0;
|
|
|
|
if (widestItem) {
|
|
|
|
len = static_cast<int>(strlen(widestItem));
|
|
|
|
if (unicodeMode) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const TextWide tbuf(widestItem, CpUtf8);
|
2015-06-07 21:19:26 +00:00
|
|
|
::GetTextExtentPoint32W(hdc, tbuf.buffer, tbuf.tlen, &textSize);
|
|
|
|
} else {
|
|
|
|
::GetTextExtentPoint32A(hdc, widestItem, len, &textSize);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
TEXTMETRIC tm;
|
|
|
|
::GetTextMetrics(hdc, &tm);
|
|
|
|
maxCharWidth = tm.tmMaxCharWidth;
|
|
|
|
SelectFont(hdc, oldFont);
|
|
|
|
::ReleaseDC(lb, hdc);
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
const int widthDesired = std::max(textSize.cx, (len + 1) * tm.tmAveCharWidth);
|
2009-04-24 23:35:41 +00:00
|
|
|
if (width < widthDesired)
|
|
|
|
width = widthDesired;
|
|
|
|
|
|
|
|
rcDesired.right = rcDesired.left + TextOffset() + width + (TextInset.x * 2);
|
|
|
|
if (Length() > rows)
|
2021-02-21 04:53:09 +00:00
|
|
|
rcDesired.right += SystemMetricsForDpi(SM_CXVSCROLL, dpi);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
AdjustWindowRect(&rcDesired, dpi);
|
2009-04-24 23:35:41 +00:00
|
|
|
return rcDesired;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::TextOffset() const {
|
2019-05-04 18:14:48 +00:00
|
|
|
const int pixWidth = images.GetWidth();
|
2015-06-07 21:19:26 +00:00
|
|
|
return static_cast<int>(pixWidth == 0 ? ItemInset.x : ItemInset.x + pixWidth + (ImageInset.x * 2));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::CaretFromEdge() {
|
|
|
|
PRectangle rc;
|
2021-02-21 04:53:09 +00:00
|
|
|
AdjustWindowRect(&rc, dpi);
|
2015-06-07 21:19:26 +00:00
|
|
|
return TextOffset() + static_cast<int>(TextInset.x + (0 - rc.left) - 1);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::Clear() noexcept {
|
2021-02-21 04:53:09 +00:00
|
|
|
ListBox_ResetContent(lb);
|
2009-04-24 23:35:41 +00:00
|
|
|
maxItemCharacters = 0;
|
2019-05-04 18:14:48 +00:00
|
|
|
widestItem = nullptr;
|
2009-04-24 23:35:41 +00:00
|
|
|
lti.Clear();
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void ListBoxX::Append(char *, int) {
|
|
|
|
// This method is no longer called in Scintilla
|
|
|
|
PLATFORM_ASSERT(false);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::Length() {
|
|
|
|
return lti.Count();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::Select(int n) {
|
|
|
|
// We are going to scroll to centre on the new selection and then select it, so disable
|
|
|
|
// redraw to avoid flicker caused by a painting new selection twice in unselected and then
|
|
|
|
// selected states
|
|
|
|
SetRedraw(false);
|
|
|
|
CentreItem(n);
|
2021-02-21 04:53:09 +00:00
|
|
|
ListBox_SetCurSel(lb, n);
|
2019-05-04 18:14:48 +00:00
|
|
|
OnSelChange();
|
2009-04-24 23:35:41 +00:00
|
|
|
SetRedraw(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::GetSelection() {
|
2021-02-21 04:53:09 +00:00
|
|
|
return ListBox_GetCurSel(lb);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is not actually called at present
|
|
|
|
int ListBoxX::Find(const char *) {
|
|
|
|
return LB_ERR;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
std::string ListBoxX::GetValue(int n) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const ListItemData item = lti.Get(n);
|
2022-01-04 23:07:50 +00:00
|
|
|
return item.text;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::RegisterImage(int type, const char *xpm_data) {
|
2013-08-28 00:44:27 +00:00
|
|
|
XPM xpmImage(xpm_data);
|
2022-01-04 23:07:50 +00:00
|
|
|
images.AddImage(type, std::make_unique<RGBAImage>(xpmImage));
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::RegisterRGBAImage(int type, int width, int height, const unsigned char *pixelsImage) {
|
2022-01-04 23:07:50 +00:00
|
|
|
images.AddImage(type, std::make_unique<RGBAImage>(width, height, 1.0f, pixelsImage));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::ClearRegisteredImages() {
|
2013-08-28 00:44:27 +00:00
|
|
|
images.Clear();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
int ColourOfElement(std::optional<ColourRGBA> colour, int nIndex) {
|
|
|
|
if (colour.has_value()) {
|
|
|
|
return colour.value().OpaqueRGB();
|
|
|
|
} else {
|
|
|
|
return ::GetSysColor(nIndex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FillRectColour(HDC hdc, const RECT *lprc, int colour) noexcept {
|
|
|
|
const HBRUSH brush = ::CreateSolidBrush(colour);
|
|
|
|
::FillRect(hdc, lprc, brush);
|
|
|
|
::DeleteObject(brush);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
void ListBoxX::Draw(DRAWITEMSTRUCT *pDrawItem) {
|
|
|
|
if ((pDrawItem->itemAction == ODA_SELECT) || (pDrawItem->itemAction == ODA_DRAWENTIRE)) {
|
|
|
|
RECT rcBox = pDrawItem->rcItem;
|
|
|
|
rcBox.left += TextOffset();
|
|
|
|
if (pDrawItem->itemState & ODS_SELECTED) {
|
|
|
|
RECT rcImage = pDrawItem->rcItem;
|
|
|
|
rcImage.right = rcBox.left;
|
|
|
|
// The image is not highlighted
|
2022-01-04 23:07:50 +00:00
|
|
|
FillRectColour(pDrawItem->hDC, &rcImage, ColourOfElement(options.back, COLOR_WINDOW));
|
|
|
|
FillRectColour(pDrawItem->hDC, &rcBox, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT));
|
|
|
|
::SetBkColor(pDrawItem->hDC, ColourOfElement(options.backSelected, COLOR_HIGHLIGHT));
|
|
|
|
::SetTextColor(pDrawItem->hDC, ColourOfElement(options.foreSelected, COLOR_HIGHLIGHTTEXT));
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
2022-01-04 23:07:50 +00:00
|
|
|
FillRectColour(pDrawItem->hDC, &pDrawItem->rcItem, ColourOfElement(options.back, COLOR_WINDOW));
|
|
|
|
::SetBkColor(pDrawItem->hDC, ColourOfElement(options.back, COLOR_WINDOW));
|
|
|
|
::SetTextColor(pDrawItem->hDC, ColourOfElement(options.fore, COLOR_WINDOWTEXT));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
const ListItemData item = lti.Get(pDrawItem->itemID);
|
|
|
|
const int pixId = item.pixId;
|
2009-04-24 23:35:41 +00:00
|
|
|
const char *text = item.text;
|
2019-05-04 18:14:48 +00:00
|
|
|
const int len = static_cast<int>(strlen(text));
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
RECT rcText = rcBox;
|
2015-06-07 21:19:26 +00:00
|
|
|
::InsetRect(&rcText, static_cast<int>(TextInset.x), static_cast<int>(TextInset.y));
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
if (unicodeMode) {
|
2022-01-04 23:07:50 +00:00
|
|
|
const TextWide tbuf(text, CpUtf8);
|
2009-04-25 23:38:15 +00:00
|
|
|
::DrawTextW(pDrawItem->hDC, tbuf.buffer, tbuf.tlen, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
|
2009-04-24 23:35:41 +00:00
|
|
|
} else {
|
|
|
|
::DrawTextA(pDrawItem->hDC, text, len, &rcText, DT_NOPREFIX|DT_END_ELLIPSIS|DT_SINGLELINE|DT_NOCLIP);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Draw the image, if any
|
2019-05-04 18:14:48 +00:00
|
|
|
const RGBAImage *pimage = images.Get(pixId);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (pimage) {
|
2019-05-04 18:14:48 +00:00
|
|
|
std::unique_ptr<Surface> surfaceItem(Surface::Allocate(technology));
|
2022-01-04 23:07:50 +00:00
|
|
|
if (technology == Technology::Default) {
|
2019-05-04 18:14:48 +00:00
|
|
|
surfaceItem->Init(pDrawItem->hDC, pDrawItem->hwndItem);
|
|
|
|
const long left = pDrawItem->rcItem.left + static_cast<int>(ItemInset.x + ImageInset.x);
|
|
|
|
const PRectangle rcImage = PRectangle::FromInts(left, pDrawItem->rcItem.top,
|
|
|
|
left + images.GetWidth(), pDrawItem->rcItem.bottom);
|
|
|
|
surfaceItem->DrawRGBAImage(rcImage,
|
|
|
|
pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
|
|
|
|
::SetTextAlign(pDrawItem->hDC, TA_TOP);
|
|
|
|
} else {
|
2013-08-28 00:44:27 +00:00
|
|
|
#if defined(USE_D2D)
|
2019-05-04 18:14:48 +00:00
|
|
|
const D2D1_RENDER_TARGET_PROPERTIES props = D2D1::RenderTargetProperties(
|
|
|
|
D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
|
|
|
D2D1::PixelFormat(
|
|
|
|
DXGI_FORMAT_B8G8R8A8_UNORM,
|
|
|
|
D2D1_ALPHA_MODE_IGNORE),
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
D2D1_RENDER_TARGET_USAGE_NONE,
|
|
|
|
D2D1_FEATURE_LEVEL_DEFAULT
|
|
|
|
);
|
|
|
|
ID2D1DCRenderTarget *pDCRT = nullptr;
|
|
|
|
HRESULT hr = pD2DFactory->CreateDCRenderTarget(&props, &pDCRT);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (SUCCEEDED(hr) && pDCRT) {
|
2022-10-12 18:45:40 +00:00
|
|
|
const long left = pDrawItem->rcItem.left + static_cast<long>(ItemInset.x + ImageInset.x);
|
|
|
|
|
|
|
|
RECT rcItem = pDrawItem->rcItem;
|
|
|
|
rcItem.left = left;
|
|
|
|
rcItem.right = rcItem.left + images.GetWidth();
|
|
|
|
|
|
|
|
hr = pDCRT->BindDC(pDrawItem->hDC, &rcItem);
|
2013-08-28 00:44:27 +00:00
|
|
|
if (SUCCEEDED(hr)) {
|
2019-05-04 18:14:48 +00:00
|
|
|
surfaceItem->Init(pDCRT, pDrawItem->hwndItem);
|
|
|
|
pDCRT->BeginDraw();
|
2022-10-12 18:45:40 +00:00
|
|
|
const PRectangle rcImage = PRectangle::FromInts(0, 0, images.GetWidth(), rcItem.bottom - rcItem.top);
|
2019-05-04 18:14:48 +00:00
|
|
|
surfaceItem->DrawRGBAImage(rcImage,
|
|
|
|
pimage->GetWidth(), pimage->GetHeight(), pimage->Pixels());
|
|
|
|
pDCRT->EndDraw();
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pDCRT);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
#endif
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
void ListBoxX::AppendListItem(const char *text, const char *numword) {
|
|
|
|
int pixId = -1;
|
2009-04-24 23:35:41 +00:00
|
|
|
if (numword) {
|
2013-08-28 00:44:27 +00:00
|
|
|
pixId = 0;
|
2009-04-24 23:35:41 +00:00
|
|
|
char ch;
|
|
|
|
while ((ch = *++numword) != '\0') {
|
|
|
|
pixId = 10 * pixId + (ch - '0');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-28 00:44:27 +00:00
|
|
|
lti.AllocItem(text, pixId);
|
2019-05-04 18:14:48 +00:00
|
|
|
const unsigned int len = static_cast<unsigned int>(strlen(text));
|
2009-04-24 23:35:41 +00:00
|
|
|
if (maxItemCharacters < len) {
|
|
|
|
maxItemCharacters = len;
|
2013-08-28 00:44:27 +00:00
|
|
|
widestItem = text;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void ListBoxX::SetDelegate(IListBoxDelegate *lbDelegate) {
|
|
|
|
delegate = lbDelegate;
|
|
|
|
}
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
void ListBoxX::SetList(const char *list, char separator, char typesep) {
|
|
|
|
// Turn off redraw while populating the list - this has a significant effect, even if
|
|
|
|
// the listbox is not visible.
|
|
|
|
SetRedraw(false);
|
|
|
|
Clear();
|
2019-05-04 18:14:48 +00:00
|
|
|
const size_t size = strlen(list);
|
2013-08-28 00:44:27 +00:00
|
|
|
char *words = lti.SetWords(list);
|
2009-08-23 02:24:48 +00:00
|
|
|
char *startword = words;
|
2019-05-04 18:14:48 +00:00
|
|
|
char *numword = nullptr;
|
2013-08-28 00:44:27 +00:00
|
|
|
for (size_t i=0; i < size; i++) {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (words[i] == separator) {
|
|
|
|
words[i] = '\0';
|
2009-04-24 23:35:41 +00:00
|
|
|
if (numword)
|
|
|
|
*numword = '\0';
|
|
|
|
AppendListItem(startword, numword);
|
2009-08-23 02:24:48 +00:00
|
|
|
startword = words + i + 1;
|
2019-05-04 18:14:48 +00:00
|
|
|
numword = nullptr;
|
2009-08-23 02:24:48 +00:00
|
|
|
} else if (words[i] == typesep) {
|
|
|
|
numword = words + i;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2009-08-23 02:24:48 +00:00
|
|
|
}
|
|
|
|
if (startword) {
|
|
|
|
if (numword)
|
|
|
|
*numword = '\0';
|
|
|
|
AppendListItem(startword, numword);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
// Finally populate the listbox itself with the correct number of items
|
2019-05-04 18:14:48 +00:00
|
|
|
const int count = lti.Count();
|
2009-08-23 02:24:48 +00:00
|
|
|
::SendMessage(lb, LB_INITSTORAGE, count, 0);
|
2021-02-21 04:53:09 +00:00
|
|
|
for (intptr_t j=0; j<count; j++) {
|
|
|
|
ListBox_AddItemData(lb, j+1);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
SetRedraw(true);
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::SetOptions(ListOptions options_) {
|
|
|
|
options = options_;
|
|
|
|
frameStyle = FlagSet(options.options, AutoCompleteOption::FixedSize) ? WS_BORDER : WS_THICKFRAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::AdjustWindowRect(PRectangle *rc, UINT dpiAdjust) const noexcept {
|
2013-08-28 00:44:27 +00:00
|
|
|
RECT rcw = RectFromPRectangle(*rc);
|
2021-02-21 04:53:09 +00:00
|
|
|
if (fnAdjustWindowRectExForDpi) {
|
2022-01-04 23:07:50 +00:00
|
|
|
fnAdjustWindowRectExForDpi(&rcw, frameStyle, false, WS_EX_WINDOWEDGE, dpiAdjust);
|
2021-02-21 04:53:09 +00:00
|
|
|
} else {
|
2022-01-04 23:07:50 +00:00
|
|
|
::AdjustWindowRectEx(&rcw, frameStyle, false, WS_EX_WINDOWEDGE);
|
2021-02-21 04:53:09 +00:00
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
*rc = PRectangle::FromInts(rcw.left, rcw.top, rcw.right, rcw.bottom);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int ListBoxX::ItemHeight() const {
|
2015-06-07 21:19:26 +00:00
|
|
|
int itemHeight = lineHeight + (static_cast<int>(TextInset.y) * 2);
|
2019-05-04 18:14:48 +00:00
|
|
|
const int pixHeight = images.GetHeight() + (static_cast<int>(ImageInset.y) * 2);
|
2009-04-24 23:35:41 +00:00
|
|
|
if (itemHeight < pixHeight) {
|
|
|
|
itemHeight = pixHeight;
|
|
|
|
}
|
|
|
|
return itemHeight;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
int ListBoxX::MinClientWidth() const noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
return 12 * (aveCharWidth+aveCharWidth/3);
|
|
|
|
}
|
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
POINT ListBoxX::MinTrackSize() const {
|
2015-06-07 21:19:26 +00:00
|
|
|
PRectangle rc = PRectangle::FromInts(0, 0, MinClientWidth(), ItemHeight());
|
2021-02-21 04:53:09 +00:00
|
|
|
AdjustWindowRect(&rc, dpi);
|
2013-08-28 00:44:27 +00:00
|
|
|
POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
|
2010-07-12 22:19:51 +00:00
|
|
|
return ret;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2010-07-12 22:19:51 +00:00
|
|
|
POINT ListBoxX::MaxTrackSize() const {
|
2015-06-07 21:19:26 +00:00
|
|
|
PRectangle rc = PRectangle::FromInts(0, 0,
|
2019-05-04 18:14:48 +00:00
|
|
|
std::max(static_cast<unsigned int>(MinClientWidth()),
|
2015-06-07 21:19:26 +00:00
|
|
|
maxCharWidth * maxItemCharacters + static_cast<int>(TextInset.x) * 2 +
|
2021-02-21 04:53:09 +00:00
|
|
|
TextOffset() + SystemMetricsForDpi(SM_CXVSCROLL, dpi)),
|
2009-04-24 23:35:41 +00:00
|
|
|
ItemHeight() * lti.Count());
|
2021-02-21 04:53:09 +00:00
|
|
|
AdjustWindowRect(&rc, dpi);
|
2013-08-28 00:44:27 +00:00
|
|
|
POINT ret = {static_cast<LONG>(rc.Width()), static_cast<LONG>(rc.Height())};
|
2010-07-12 22:19:51 +00:00
|
|
|
return ret;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void ListBoxX::SetRedraw(bool on) noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
::SendMessage(lb, WM_SETREDRAW, on, 0);
|
2009-04-24 23:35:41 +00:00
|
|
|
if (on)
|
2019-07-21 13:26:02 +00:00
|
|
|
::InvalidateRect(lb, nullptr, TRUE);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::ResizeToCursor() {
|
|
|
|
PRectangle rc = GetPosition();
|
2013-08-28 00:44:27 +00:00
|
|
|
POINT ptw;
|
|
|
|
::GetCursorPos(&ptw);
|
2021-02-21 04:53:09 +00:00
|
|
|
const Point pt = PointFromPOINT(ptw) + dragOffset;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
switch (resizeHit) {
|
|
|
|
case HTLEFT:
|
|
|
|
rc.left = pt.x;
|
|
|
|
break;
|
|
|
|
case HTRIGHT:
|
|
|
|
rc.right = pt.x;
|
|
|
|
break;
|
|
|
|
case HTTOP:
|
|
|
|
rc.top = pt.y;
|
|
|
|
break;
|
|
|
|
case HTTOPLEFT:
|
|
|
|
rc.top = pt.y;
|
|
|
|
rc.left = pt.x;
|
|
|
|
break;
|
|
|
|
case HTTOPRIGHT:
|
|
|
|
rc.top = pt.y;
|
|
|
|
rc.right = pt.x;
|
|
|
|
break;
|
|
|
|
case HTBOTTOM:
|
|
|
|
rc.bottom = pt.y;
|
|
|
|
break;
|
|
|
|
case HTBOTTOMLEFT:
|
|
|
|
rc.bottom = pt.y;
|
|
|
|
rc.left = pt.x;
|
|
|
|
break;
|
|
|
|
case HTBOTTOMRIGHT:
|
|
|
|
rc.bottom = pt.y;
|
|
|
|
rc.right = pt.x;
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
default:
|
|
|
|
break;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
const POINT ptMin = MinTrackSize();
|
|
|
|
const POINT ptMax = MaxTrackSize();
|
2009-04-24 23:35:41 +00:00
|
|
|
// We don't allow the left edge to move at present, but just in case
|
2019-05-04 18:14:48 +00:00
|
|
|
rc.left = std::clamp(rc.left, rcPreSize.right - ptMax.x, rcPreSize.right - ptMin.x);
|
|
|
|
rc.top = std::clamp(rc.top, rcPreSize.bottom - ptMax.y, rcPreSize.bottom - ptMin.y);
|
|
|
|
rc.right = std::clamp(rc.right, rcPreSize.left + ptMin.x, rcPreSize.left + ptMax.x);
|
|
|
|
rc.bottom = std::clamp(rc.bottom, rcPreSize.top + ptMin.y, rcPreSize.top + ptMax.y);
|
2009-04-24 23:35:41 +00:00
|
|
|
|
|
|
|
SetPosition(rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::StartResize(WPARAM hitCode) {
|
|
|
|
rcPreSize = GetPosition();
|
|
|
|
POINT cursorPos;
|
|
|
|
::GetCursorPos(&cursorPos);
|
|
|
|
|
|
|
|
switch (hitCode) {
|
|
|
|
case HTRIGHT:
|
|
|
|
case HTBOTTOM:
|
|
|
|
case HTBOTTOMRIGHT:
|
|
|
|
dragOffset.x = rcPreSize.right - cursorPos.x;
|
|
|
|
dragOffset.y = rcPreSize.bottom - cursorPos.y;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case HTTOPRIGHT:
|
|
|
|
dragOffset.x = rcPreSize.right - cursorPos.x;
|
|
|
|
dragOffset.y = rcPreSize.top - cursorPos.y;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// Note that the current hit test code prevents the left edge cases ever firing
|
2021-02-21 04:53:09 +00:00
|
|
|
// as we don't want the left edge to be movable
|
2009-04-24 23:35:41 +00:00
|
|
|
case HTLEFT:
|
|
|
|
case HTTOP:
|
|
|
|
case HTTOPLEFT:
|
|
|
|
dragOffset.x = rcPreSize.left - cursorPos.x;
|
|
|
|
dragOffset.y = rcPreSize.top - cursorPos.y;
|
|
|
|
break;
|
|
|
|
case HTBOTTOMLEFT:
|
|
|
|
dragOffset.x = rcPreSize.left - cursorPos.x;
|
|
|
|
dragOffset.y = rcPreSize.bottom - cursorPos.y;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
::SetCapture(GetHWND());
|
2019-05-04 18:14:48 +00:00
|
|
|
resizeHit = hitCode;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2015-06-07 21:19:26 +00:00
|
|
|
LRESULT ListBoxX::NcHitTest(WPARAM wParam, LPARAM lParam) const {
|
2019-05-04 18:14:48 +00:00
|
|
|
const PRectangle rc = GetPosition();
|
|
|
|
|
2015-06-07 21:19:26 +00:00
|
|
|
LRESULT hit = ::DefWindowProc(GetHWND(), WM_NCHITTEST, wParam, lParam);
|
2009-04-24 23:35:41 +00:00
|
|
|
// There is an apparent bug in the DefWindowProc hit test code whereby it will
|
|
|
|
// return HTTOPXXX if the window in question is shorter than the default
|
|
|
|
// window caption height + frame, even if one is hovering over the bottom edge of
|
|
|
|
// the frame, so workaround that here
|
|
|
|
if (hit >= HTTOP && hit <= HTTOPRIGHT) {
|
2021-02-21 04:53:09 +00:00
|
|
|
const int minHeight = SystemMetricsForDpi(SM_CYMINTRACK, dpi);
|
2019-05-04 18:14:48 +00:00
|
|
|
const int yPos = GET_Y_LPARAM(lParam);
|
2009-04-24 23:35:41 +00:00
|
|
|
if ((rc.Height() < minHeight) && (yPos > ((rc.top + rc.bottom)/2))) {
|
|
|
|
hit += HTBOTTOM - HTTOP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
// Never permit resizing that moves the left edge. Allow movement of top or bottom edge
|
2009-04-24 23:35:41 +00:00
|
|
|
// depending on whether the list is above or below the caret
|
|
|
|
switch (hit) {
|
|
|
|
case HTLEFT:
|
|
|
|
case HTTOPLEFT:
|
|
|
|
case HTBOTTOMLEFT:
|
|
|
|
hit = HTERROR;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case HTTOP:
|
|
|
|
case HTTOPRIGHT: {
|
|
|
|
// Valid only if caret below list
|
|
|
|
if (location.y < rc.top)
|
|
|
|
hit = HTERROR;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case HTBOTTOM:
|
|
|
|
case HTBOTTOMRIGHT: {
|
|
|
|
// Valid only if caret above list
|
2019-05-04 18:14:48 +00:00
|
|
|
if (rc.bottom <= location.y)
|
2009-04-24 23:35:41 +00:00
|
|
|
hit = HTERROR;
|
|
|
|
}
|
|
|
|
break;
|
2022-01-04 23:07:50 +00:00
|
|
|
default:
|
|
|
|
break;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return hit;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::OnDoubleClick() {
|
2019-05-04 18:14:48 +00:00
|
|
|
if (delegate) {
|
|
|
|
ListBoxEvent event(ListBoxEvent::EventType::doubleClick);
|
|
|
|
delegate->ListNotify(&event);
|
|
|
|
}
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
void ListBoxX::OnSelChange() {
|
|
|
|
if (delegate) {
|
|
|
|
ListBoxEvent event(ListBoxEvent::EventType::selectionChange);
|
|
|
|
delegate->ListNotify(&event);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
POINT ListBoxX::GetClientExtent() const noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
RECT rc;
|
|
|
|
::GetWindowRect(HwndFromWindowID(wid), &rc);
|
|
|
|
POINT ret { rc.right - rc.left, rc.bottom - rc.top };
|
2015-06-07 21:19:26 +00:00
|
|
|
return ret;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ListBoxX::CentreItem(int n) {
|
|
|
|
// If below mid point, scroll up to centre, but with more items below if uneven
|
|
|
|
if (n >= 0) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const POINT extent = GetClientExtent();
|
|
|
|
const int visible = extent.y/ItemHeight();
|
2009-04-24 23:35:41 +00:00
|
|
|
if (visible < Length()) {
|
2021-02-21 04:53:09 +00:00
|
|
|
const int top = ListBox_GetTopIndex(lb);
|
2019-05-04 18:14:48 +00:00
|
|
|
const int half = (visible - 1) / 2;
|
2009-04-24 23:35:41 +00:00
|
|
|
if (n > (top + half))
|
2021-02-21 04:53:09 +00:00
|
|
|
ListBox_SetTopIndex(lb, n - half);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Performs a double-buffered paint operation to avoid flicker
|
2022-01-04 23:07:50 +00:00
|
|
|
void ListBoxX::Paint(HDC hDC) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const POINT extent = GetClientExtent();
|
2009-04-24 23:35:41 +00:00
|
|
|
HBITMAP hBitmap = ::CreateCompatibleBitmap(hDC, extent.x, extent.y);
|
|
|
|
HDC bitmapDC = ::CreateCompatibleDC(hDC);
|
|
|
|
HBITMAP hBitmapOld = SelectBitmap(bitmapDC, hBitmap);
|
|
|
|
// The list background is mainly erased during painting, but can be a small
|
|
|
|
// unpainted area when at the end of a non-integrally sized list with a
|
|
|
|
// vertical scroll bar
|
2019-05-04 18:14:48 +00:00
|
|
|
const RECT rc = { 0, 0, extent.x, extent.y };
|
2022-01-04 23:07:50 +00:00
|
|
|
FillRectColour(bitmapDC, &rc, ColourOfElement(options.back, COLOR_WINDOWTEXT));
|
2009-04-24 23:35:41 +00:00
|
|
|
// Paint the entire client area and vertical scrollbar
|
|
|
|
::SendMessage(lb, WM_PRINT, reinterpret_cast<WPARAM>(bitmapDC), PRF_CLIENT|PRF_NONCLIENT);
|
|
|
|
::BitBlt(hDC, 0, 0, extent.x, extent.y, bitmapDC, 0, 0, SRCCOPY);
|
|
|
|
// Select a stock brush to prevent warnings from BoundsChecker
|
2019-05-04 18:14:48 +00:00
|
|
|
SelectBrush(bitmapDC, GetStockBrush(WHITE_BRUSH));
|
2009-04-24 23:35:41 +00:00
|
|
|
SelectBitmap(bitmapDC, hBitmapOld);
|
|
|
|
::DeleteDC(bitmapDC);
|
|
|
|
::DeleteObject(hBitmap);
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
LRESULT PASCAL ListBoxX::ControlWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
|
2009-08-23 02:24:48 +00:00
|
|
|
try {
|
2019-05-04 18:14:48 +00:00
|
|
|
ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(::GetParent(hWnd)));
|
|
|
|
switch (iMessage) {
|
2009-08-23 02:24:48 +00:00
|
|
|
case WM_ERASEBKGND:
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_PAINT: {
|
|
|
|
PAINTSTRUCT ps;
|
|
|
|
HDC hDC = ::BeginPaint(hWnd, &ps);
|
2019-05-04 18:14:48 +00:00
|
|
|
if (lbx) {
|
2009-08-23 02:24:48 +00:00
|
|
|
lbx->Paint(hDC);
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
2009-08-23 02:24:48 +00:00
|
|
|
::EndPaint(hWnd, &ps);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2009-08-23 02:24:48 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
case WM_MOUSEACTIVATE:
|
|
|
|
// This prevents the view activating when the scrollbar is clicked
|
|
|
|
return MA_NOACTIVATE;
|
|
|
|
|
|
|
|
case WM_LBUTTONDOWN: {
|
|
|
|
// We must take control of selection to prevent the ListBox activating
|
|
|
|
// the popup
|
2019-05-04 18:14:48 +00:00
|
|
|
const LRESULT lResult = ::SendMessage(hWnd, LB_ITEMFROMPOINT, 0, lParam);
|
2022-01-04 23:07:50 +00:00
|
|
|
if (HIWORD(lResult) == 0) {
|
|
|
|
ListBox_SetCurSel(hWnd, LOWORD(lResult));
|
2019-05-04 18:14:48 +00:00
|
|
|
if (lbx) {
|
|
|
|
lbx->OnSelChange();
|
|
|
|
}
|
2009-08-23 02:24:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
case WM_LBUTTONUP:
|
|
|
|
return 0;
|
2009-04-24 23:35:41 +00:00
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
case WM_LBUTTONDBLCLK: {
|
|
|
|
if (lbx) {
|
|
|
|
lbx->OnDoubleClick();
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2009-08-23 02:24:48 +00:00
|
|
|
return 0;
|
2013-08-28 00:44:27 +00:00
|
|
|
|
|
|
|
case WM_MBUTTONDOWN:
|
|
|
|
// disable the scroll wheel button click action
|
|
|
|
return 0;
|
2022-01-04 23:07:50 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2009-08-23 02:24:48 +00:00
|
|
|
WNDPROC prevWndProc = reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
|
|
|
|
if (prevWndProc) {
|
2019-05-04 18:14:48 +00:00
|
|
|
return ::CallWindowProc(prevWndProc, hWnd, iMessage, wParam, lParam);
|
2009-08-23 02:24:48 +00:00
|
|
|
} else {
|
2019-05-04 18:14:48 +00:00
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
2009-08-23 02:24:48 +00:00
|
|
|
}
|
|
|
|
} catch (...) {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
2019-05-04 18:14:48 +00:00
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LRESULT ListBoxX::WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
|
|
|
|
switch (iMessage) {
|
|
|
|
case WM_CREATE: {
|
2021-02-21 04:53:09 +00:00
|
|
|
HINSTANCE hinstanceParent = GetWindowInstance(HwndFromWindow(*parent));
|
2009-04-24 23:35:41 +00:00
|
|
|
// Note that LBS_NOINTEGRALHEIGHT is specified to fix cosmetic issue when resizing the list
|
|
|
|
// but has useful side effect of speeding up list population significantly
|
|
|
|
lb = ::CreateWindowEx(
|
|
|
|
0, TEXT("listbox"), TEXT(""),
|
|
|
|
WS_CHILD | WS_VSCROLL | WS_VISIBLE |
|
|
|
|
LBS_OWNERDRAWFIXED | LBS_NODATA | LBS_NOINTEGRALHEIGHT,
|
|
|
|
0, 0, 150,80, hWnd,
|
2019-05-04 18:14:48 +00:00
|
|
|
reinterpret_cast<HMENU>(static_cast<ptrdiff_t>(ctrlID)),
|
2009-04-24 23:35:41 +00:00
|
|
|
hinstanceParent,
|
|
|
|
0);
|
2019-05-04 18:14:48 +00:00
|
|
|
WNDPROC prevWndProc = SubclassWindow(lb, ControlWndProc);
|
2009-04-24 23:35:41 +00:00
|
|
|
::SetWindowLongPtr(lb, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(prevWndProc));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_SIZE:
|
|
|
|
if (lb) {
|
|
|
|
SetRedraw(false);
|
|
|
|
::SetWindowPos(lb, 0, 0,0, LOWORD(lParam), HIWORD(lParam), SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOMOVE);
|
|
|
|
// Ensure the selection remains visible
|
|
|
|
CentreItem(GetSelection());
|
|
|
|
SetRedraw(true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_PAINT: {
|
|
|
|
PAINTSTRUCT ps;
|
|
|
|
::BeginPaint(hWnd, &ps);
|
|
|
|
::EndPaint(hWnd, &ps);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_COMMAND:
|
|
|
|
// This is not actually needed now - the registered double click action is used
|
|
|
|
// directly to action a choice from the list.
|
2021-02-21 04:53:09 +00:00
|
|
|
::SendMessage(HwndFromWindow(*parent), iMessage, wParam, lParam);
|
2009-04-24 23:35:41 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_MEASUREITEM: {
|
|
|
|
MEASUREITEMSTRUCT *pMeasureItem = reinterpret_cast<MEASUREITEMSTRUCT *>(lParam);
|
2019-05-04 18:14:48 +00:00
|
|
|
pMeasureItem->itemHeight = ItemHeight();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_DRAWITEM:
|
|
|
|
Draw(reinterpret_cast<DRAWITEMSTRUCT *>(lParam));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_DESTROY:
|
|
|
|
lb = 0;
|
2021-02-21 04:53:09 +00:00
|
|
|
SetWindowPointer(hWnd, nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
|
|
|
|
|
|
|
case WM_ERASEBKGND:
|
|
|
|
// To reduce flicker we can elide background erasure since this window is
|
|
|
|
// completely covered by its child.
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
case WM_GETMINMAXINFO: {
|
|
|
|
MINMAXINFO *minMax = reinterpret_cast<MINMAXINFO*>(lParam);
|
2010-07-12 22:19:51 +00:00
|
|
|
minMax->ptMaxTrackSize = MaxTrackSize();
|
|
|
|
minMax->ptMinTrackSize = MinTrackSize();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_MOUSEACTIVATE:
|
|
|
|
return MA_NOACTIVATE;
|
|
|
|
|
|
|
|
case WM_NCHITTEST:
|
|
|
|
return NcHitTest(wParam, lParam);
|
|
|
|
|
|
|
|
case WM_NCLBUTTONDOWN:
|
|
|
|
// We have to implement our own window resizing because the DefWindowProc
|
|
|
|
// implementation insists on activating the resized window
|
|
|
|
StartResize(wParam);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case WM_MOUSEMOVE: {
|
|
|
|
if (resizeHit == 0) {
|
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
|
|
|
} else {
|
|
|
|
ResizeToCursor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM_LBUTTONUP:
|
|
|
|
case WM_CANCELMODE:
|
|
|
|
if (resizeHit != 0) {
|
|
|
|
resizeHit = 0;
|
|
|
|
::ReleaseCapture();
|
|
|
|
}
|
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
2013-08-28 00:44:27 +00:00
|
|
|
case WM_MOUSEWHEEL:
|
2022-12-10 12:35:16 +00:00
|
|
|
if (wheelDelta.Accumulate(wParam)) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const int nRows = GetVisibleRows();
|
2022-01-04 23:07:50 +00:00
|
|
|
int linesToScroll = std::clamp(nRows - 1, 1, 3);
|
2022-12-10 12:35:16 +00:00
|
|
|
linesToScroll *= wheelDelta.Actions();
|
2021-02-21 04:53:09 +00:00
|
|
|
int top = ListBox_GetTopIndex(lb) + linesToScroll;
|
2013-08-28 00:44:27 +00:00
|
|
|
if (top < 0) {
|
|
|
|
top = 0;
|
|
|
|
}
|
2021-02-21 04:53:09 +00:00
|
|
|
ListBox_SetTopIndex(lb, top);
|
2013-08-28 00:44:27 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2009-04-24 23:35:41 +00:00
|
|
|
default:
|
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
LRESULT PASCAL ListBoxX::StaticWndProc(
|
|
|
|
HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) {
|
|
|
|
if (iMessage == WM_CREATE) {
|
|
|
|
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT *>(lParam);
|
|
|
|
SetWindowPointer(hWnd, pCreate->lpCreateParams);
|
|
|
|
}
|
|
|
|
// Find C++ object associated with window.
|
2019-05-04 18:14:48 +00:00
|
|
|
ListBoxX *lbx = static_cast<ListBoxX *>(PointerFromWindow(hWnd));
|
2009-04-24 23:35:41 +00:00
|
|
|
if (lbx) {
|
|
|
|
return lbx->WndProc(hWnd, iMessage, wParam, lParam);
|
|
|
|
} else {
|
|
|
|
return ::DefWindowProc(hWnd, iMessage, wParam, lParam);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
bool ListBoxX_Register() noexcept {
|
2019-07-21 13:26:02 +00:00
|
|
|
WNDCLASSEX wndclassc {};
|
2009-04-24 23:35:41 +00:00
|
|
|
wndclassc.cbSize = sizeof(wndclassc);
|
|
|
|
// We need CS_HREDRAW and CS_VREDRAW because of the ellipsis that might be drawn for
|
|
|
|
// truncated items in the list and the appearance/disappearance of the vertical scroll bar.
|
|
|
|
// The list repaint is double-buffered to avoid the flicker this would otherwise cause.
|
|
|
|
wndclassc.style = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW;
|
|
|
|
wndclassc.cbWndExtra = sizeof(ListBoxX *);
|
|
|
|
wndclassc.hInstance = hinstPlatformRes;
|
|
|
|
wndclassc.lpfnWndProc = ListBoxX::StaticWndProc;
|
|
|
|
wndclassc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
|
|
|
|
wndclassc.lpszClassName = ListBoxX_ClassName;
|
|
|
|
|
|
|
|
return ::RegisterClassEx(&wndclassc) != 0;
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void ListBoxX_Unregister() noexcept {
|
|
|
|
if (hinstPlatformRes) {
|
|
|
|
::UnregisterClass(ListBoxX_ClassName, hinstPlatformRes);
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2019-05-04 18:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Menu::Menu() noexcept : mid{} {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Menu::CreatePopUp() {
|
|
|
|
Destroy();
|
2009-08-23 02:24:48 +00:00
|
|
|
mid = ::CreatePopupMenu();
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Menu::Destroy() noexcept {
|
2009-08-23 02:24:48 +00:00
|
|
|
if (mid)
|
2019-05-04 18:14:48 +00:00
|
|
|
::DestroyMenu(static_cast<HMENU>(mid));
|
2009-08-23 02:24:48 +00:00
|
|
|
mid = 0;
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Menu::Show(Point pt, const Window &w) {
|
2019-05-04 18:14:48 +00:00
|
|
|
::TrackPopupMenu(static_cast<HMENU>(mid),
|
2015-06-07 21:19:26 +00:00
|
|
|
TPM_RIGHTBUTTON, static_cast<int>(pt.x - 4), static_cast<int>(pt.y), 0,
|
2021-02-21 04:53:09 +00:00
|
|
|
HwndFromWindow(w), nullptr);
|
2009-04-24 23:35:41 +00:00
|
|
|
Destroy();
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ColourRGBA Platform::Chrome() {
|
|
|
|
return ColourRGBA::FromRGB(static_cast<int>(::GetSysColor(COLOR_3DFACE)));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
ColourRGBA Platform::ChromeHighlight() {
|
|
|
|
return ColourRGBA::FromRGB(static_cast<int>(::GetSysColor(COLOR_3DHIGHLIGHT)));
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *Platform::DefaultFont() {
|
|
|
|
return "Verdana";
|
|
|
|
}
|
|
|
|
|
|
|
|
int Platform::DefaultFontSize() {
|
|
|
|
return 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Platform::DoubleClickTime() {
|
|
|
|
return ::GetDoubleClickTime();
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::DebugDisplay(const char *s) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
::OutputDebugStringA(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
//#define TRACE
|
|
|
|
|
|
|
|
#ifdef TRACE
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::DebugPrintf(const char *format, ...) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
char buffer[2000];
|
|
|
|
va_list pArguments;
|
|
|
|
va_start(pArguments, format);
|
Update scintilla 5.3.4 and lexilla 5.2.4 with:
https://www.scintilla.org/scintilla534.zip
Released 8 March 2023.
Add multithreaded wrap to significantly improve performance of wrapping large files.
More typesafe bindings of *Full APIs in ScintillaCall. Feature #1477.
Fix overlapping of text with line end wrap marker. Bug #2378.
Fix clipping of line end wrap symbol for SC_WRAPVISUALFLAGLOC_END_BY_TEXT.
Where a multi-byte character contains multiple styles, display each byte as a representation. This makes it easier to see and fix lexers that change styles mid-character, commonly because they use fixed size buffers.
Fix a potential crash with autocompletion list fill-ups where a SCN_CHARADDED handler retriggered an autocompletion list, but with no items that match the typed character.
lexilla523
Released 8 March 2023.
Add scripts/PromoteNew.bat script to promote .new files after checking.
Makefile: Remove 1024-byte line length limit..
Ruby: Add new lexical classes for % literals SCE_RB_STRING_W (%w non-interpolable string array), SCE_RB_STRING_I (%i non-interpolable symbol array), SCE_RB_STRING_QI (%I interpolable symbol array), and SCE_RB_STRING_QS (%s symbol). Issue #124.
Ruby: Disambiguate %= which may be a quote or modulo assignment. Issue #124, Bug #1255, Bug #2182.
Ruby: Fix additional fold level for single character in SCE_RB_STRING_QW. Issue #132.
Ruby: Set SCE_RB_HERE_QQ for unquoted and double-quoted heredocs and SCE_RB_HERE_QX for backticks-quoted heredocs. Issue #134.
Ruby: Recognise #{} inside SCE_RB_HERE_QQ and SCE_RB_HERE_QX. Issue #134.
Ruby: Improve regex and heredoc recognition. Issue #136.
Ruby: Highlight #@, #@@ and #$ style interpolation. Issue #140.
Ruby: Fix folding for multiple heredocs started on one line. Fix folding when there is a space after heredoc opening delimiter. Issue #135.
YAML: Remove 1024-byte line length limit.
https://www.scintilla.org/lexilla524.zip
Released 13 March 2023.
C++: Fix failure to recognize keywords containing upper case. Issue #149.
GDScript: Support % and $ node paths. Issue #145, Pull request #146.
Close #13338
2023-03-10 02:37:21 +00:00
|
|
|
vsnprintf(buffer, std::size(buffer), format, pArguments);
|
2009-04-24 23:35:41 +00:00
|
|
|
va_end(pArguments);
|
|
|
|
Platform::DebugDisplay(buffer);
|
|
|
|
}
|
|
|
|
#else
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::DebugPrintf(const char *, ...) noexcept {
|
2009-04-24 23:35:41 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static bool assertionPopUps = true;
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
bool Platform::ShowAssertionPopUps(bool assertionPopUps_) noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
const bool ret = assertionPopUps;
|
2009-04-24 23:35:41 +00:00
|
|
|
assertionPopUps = assertionPopUps_;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-01-04 23:07:50 +00:00
|
|
|
void Platform::Assert(const char *c, const char *file, int line) noexcept {
|
2021-02-21 04:53:09 +00:00
|
|
|
char buffer[2000] {};
|
Update scintilla 5.3.4 and lexilla 5.2.4 with:
https://www.scintilla.org/scintilla534.zip
Released 8 March 2023.
Add multithreaded wrap to significantly improve performance of wrapping large files.
More typesafe bindings of *Full APIs in ScintillaCall. Feature #1477.
Fix overlapping of text with line end wrap marker. Bug #2378.
Fix clipping of line end wrap symbol for SC_WRAPVISUALFLAGLOC_END_BY_TEXT.
Where a multi-byte character contains multiple styles, display each byte as a representation. This makes it easier to see and fix lexers that change styles mid-character, commonly because they use fixed size buffers.
Fix a potential crash with autocompletion list fill-ups where a SCN_CHARADDED handler retriggered an autocompletion list, but with no items that match the typed character.
lexilla523
Released 8 March 2023.
Add scripts/PromoteNew.bat script to promote .new files after checking.
Makefile: Remove 1024-byte line length limit..
Ruby: Add new lexical classes for % literals SCE_RB_STRING_W (%w non-interpolable string array), SCE_RB_STRING_I (%i non-interpolable symbol array), SCE_RB_STRING_QI (%I interpolable symbol array), and SCE_RB_STRING_QS (%s symbol). Issue #124.
Ruby: Disambiguate %= which may be a quote or modulo assignment. Issue #124, Bug #1255, Bug #2182.
Ruby: Fix additional fold level for single character in SCE_RB_STRING_QW. Issue #132.
Ruby: Set SCE_RB_HERE_QQ for unquoted and double-quoted heredocs and SCE_RB_HERE_QX for backticks-quoted heredocs. Issue #134.
Ruby: Recognise #{} inside SCE_RB_HERE_QQ and SCE_RB_HERE_QX. Issue #134.
Ruby: Improve regex and heredoc recognition. Issue #136.
Ruby: Highlight #@, #@@ and #$ style interpolation. Issue #140.
Ruby: Fix folding for multiple heredocs started on one line. Fix folding when there is a space after heredoc opening delimiter. Issue #135.
YAML: Remove 1024-byte line length limit.
https://www.scintilla.org/lexilla524.zip
Released 13 March 2023.
C++: Fix failure to recognize keywords containing upper case. Issue #149.
GDScript: Support % and $ node paths. Issue #145, Pull request #146.
Close #13338
2023-03-10 02:37:21 +00:00
|
|
|
snprintf(buffer, std::size(buffer), "Assertion [%s] failed at %s %d%s", c, file, line, assertionPopUps ? "" : "\r\n");
|
2009-04-24 23:35:41 +00:00
|
|
|
if (assertionPopUps) {
|
2019-05-04 18:14:48 +00:00
|
|
|
const int idButton = ::MessageBoxA(0, buffer, "Assertion failure",
|
2009-04-24 23:35:41 +00:00
|
|
|
MB_ABORTRETRYIGNORE|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL);
|
|
|
|
if (idButton == IDRETRY) {
|
|
|
|
::DebugBreak();
|
|
|
|
} else if (idButton == IDIGNORE) {
|
|
|
|
// all OK
|
|
|
|
} else {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Platform::DebugDisplay(buffer);
|
|
|
|
::DebugBreak();
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void Platform_Initialise(void *hInstance) noexcept {
|
2019-05-04 18:14:48 +00:00
|
|
|
hinstPlatformRes = static_cast<HINSTANCE>(hInstance);
|
2021-02-21 04:53:09 +00:00
|
|
|
LoadDpiForWindow();
|
2009-04-24 23:35:41 +00:00
|
|
|
ListBoxX_Register();
|
|
|
|
}
|
|
|
|
|
2021-02-21 04:53:09 +00:00
|
|
|
void Platform_Finalise(bool fromDllMain) noexcept {
|
2015-06-07 21:19:26 +00:00
|
|
|
#if defined(USE_D2D)
|
|
|
|
if (!fromDllMain) {
|
2021-02-21 04:53:09 +00:00
|
|
|
ReleaseUnknown(pIDWriteFactory);
|
|
|
|
ReleaseUnknown(pD2DFactory);
|
2015-06-07 21:19:26 +00:00
|
|
|
if (hDLLDWrite) {
|
|
|
|
FreeLibrary(hDLLDWrite);
|
2021-02-21 04:53:09 +00:00
|
|
|
hDLLDWrite = {};
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
|
|
|
if (hDLLD2D) {
|
|
|
|
FreeLibrary(hDLLD2D);
|
2021-02-21 04:53:09 +00:00
|
|
|
hDLLD2D = {};
|
2015-06-07 21:19:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2021-02-21 04:53:09 +00:00
|
|
|
if (!fromDllMain && hDLLShcore) {
|
|
|
|
FreeLibrary(hDLLShcore);
|
|
|
|
hDLLShcore = {};
|
|
|
|
}
|
2009-04-24 23:35:41 +00:00
|
|
|
ListBoxX_Unregister();
|
|
|
|
}
|
2015-06-07 21:19:26 +00:00
|
|
|
|
|
|
|
}
|