在学习Direct2D的时候,意外地在windows-classic-samples里面翻到了一个PadWrite,其实现了一个基本的文本编辑器,遂参考其实现了一个简单的文本编辑器。
使用VS2017开发,能够在C++"控制台应用程序"/“Windows桌面应用程序”项目中运行
支持中文及滚动(未添加滚动条),效果如图
下面是单文件源码,另附多文件版及exe链接: 源代码
#pragma once
#include
#include
#include
#include
#include
#pragma comment(lib,"d2d1.lib")
#pragma comment(lib,"dwrite.lib")
#pragma comment(lib,"winmm.lib")
void start();
#ifdef WIN32
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
#else
int main()
#endif
{
start();
return 0;
}
//方便释放COM资源
template
inline void SafeRelease(InterfaceType** currentObject)
{
if (*currentObject != nullptr)
{
(*currentObject)->Release();
*currentObject = nullptr;
}
}
//自定义的窗口类
class MyWindow
{
public:
MyWindow();
~MyWindow();
void init();
void createWindow(const std::wstring& windowTitle, int left, int top, int width, int height);
void run();
//窗口事件,由子类负责实现
virtual void onResize(UINT32 width, UINT32 height) {}
virtual void onPaint() {}
virtual void onMouseEvent(UINT msg, WPARAM wp, LPARAM lp) {}
virtual void onScroll(short delta) {}
virtual void onKey(UINT32 vk) {}
virtual void onChar(UINT32 c) {}
protected:
//使用字符串创建用于绘制的文本布局
IDWriteTextLayout * createTextLayout(const std::wstring &text);
//窗口宽、高
UINT32 width;
UINT32 height;
ID2D1SolidColorBrush *brush;//画刷
ID2D1HwndRenderTarget * target;//用于呈现绘制结果
private:
void registerWindow();
void createD2DResource();
LRESULT messageProc(UINT msg, WPARAM wp, LPARAM lp);
bool isRunning;//窗口是否正在运行
HWND hWnd;//窗口句柄
//使用dwrite和direct2d不可缺少的
ID2D1Factory * d2dFactory;
IDWriteFactory* dwriteFactory;
};
//定义光标移动的方式
enum class SelectMode {
head, tile,
lastChar, nextChar,
lastWord, nextWord,
absoluteLeading, absoluteTrailing,
lastLine, nextLine,
all,
up, down,
};
class Editor :
public MyWindow {
public:
Editor();
~Editor();
private:
//重写基类函数以响应事件
void onResize(UINT32 width, UINT32 height)override;
void onPaint()override;
void onMouseEvent(UINT msg, WPARAM wp, LPARAM lp)override;
void onScroll(short delta)override;
void onKey(UINT32 vk)override;
void onChar(UINT32 c)override;
//使用指定方式移动光标
void select(SelectMode mode, bool moveAnchor = true);
//移动光标至指定点
void setSelectionFromPoint(float x, float y, bool moveAnchor);
//复制及粘贴
void copyToClipboard();
void pasteFromClipboard();
//得到选中文本的范围
DWRITE_TEXT_RANGE getSelectionRange();
//删除选中文本
void deleteSelection();
//检查 及 更新文本布局
void checkUpdate();
//判断两个字符是否是一组
bool isUnicodeUnit(wchar_t char1, wchar_t char2);
//三个绘制函数
void fillSelectedRange();
void drawCaret();
void drawText();
//被编辑的字符串
std::wstring text;
//需要更新文本布局的标记,每一帧都会检查这个值
bool needUpdate;
//Y轴最大滚动距离 及 当前滚动距离
float maxScrollY;
float scrollY;
//编辑状态时需要 保持 光标可见,此变量用于 判断 当前是否为滚轮在滚动,
bool isOnScroll;
//光标所在位置 及 锚点位置,两者之间的文本处于选中状态
UINT32 caretAnchor;
UINT32 caretPosition;
//呈现在窗口上的文本布局
IDWriteTextLayout * textLayout;
//在输入状态时,光标不应闪烁,
//使用此变量以记录上次的输入时间,绘制时比较当前时间与其差值以决定是否闪烁
float lastInputTime;
//记录上次点击时间,用于进行双击的判断
float lastClickTime;
//鼠标双击时,文本会被全选,
//但若当前选中不为空,则不进行全选
UINT32 lastSelectLength;
};
void start() {
Editor editor;
editor.init();
editor.createWindow(L"helloworld", 0, 0, 800, 500);
editor.run();
}
//进程句柄宏定义
#ifndef HINST_THISCOMPONENT
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
#define HINST_THISCOMPONET ((HINSTANCE)&__ImageBase)
#endif
MyWindow::MyWindow()
{
hWnd = 0;
width = 0;
height = 0;
isRunning = false;
d2dFactory = nullptr;
dwriteFactory = nullptr;
target = nullptr;
brush = nullptr;
}
MyWindow::~MyWindow()
{
SafeRelease(&brush);
SafeRelease(&target);
SafeRelease(&dwriteFactory);
SafeRelease(&d2dFactory);
UnregisterClassW(L"myWindow", HINST_THISCOMPONET);
}
void MyWindow::init()
{
registerWindow();
}
void MyWindow::createWindow(const std::wstring &windowTitle, int left, int top, int width, int height)
{
if (!CreateWindowW(L"myWindow", windowTitle.c_str(), WS_OVERLAPPEDWINDOW,
left, top,
width, height,
0, 0, HINST_THISCOMPONET, this))
throw std::exception("create window failed");
createD2DResource();
}
IDWriteTextLayout * MyWindow::createTextLayout(const std::wstring &text)
{
IDWriteTextLayout* textLayout = nullptr;
IDWriteTextFormat*textFormat = nullptr;
dwriteFactory->CreateTextFormat(L"", 0,
DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 16.0f, L"", &textFormat);
dwriteFactory->CreateTextLayout(
text.c_str(),
static_cast(text.length()),
textFormat,
width, height,
&textLayout
);
SafeRelease(&textFormat);
return textLayout;
}
void MyWindow::registerWindow()
{
WNDCLASSW wc = { 0 };
wc.hInstance = HINST_THISCOMPONET;
wc.lpszClassName = L"myWindow";
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.hIcon = LoadIconA(0, MAKEINTRESOURCEA(IDI_APPLICATION));
wc.hCursor = LoadCursorA(0, MAKEINTRESOURCEA(IDC_IBEAM));
wc.cbWndExtra = sizeof(void*);
wc.lpfnWndProc = [](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)->LRESULT {
MyWindow *window = static_cast(reinterpret_cast(GetWindowLongPtr(hWnd, 0)));
if (window) {
return window->messageProc(message, wParam, lParam);
}
else {
if (message == WM_CREATE) {
LPCREATESTRUCT cs = reinterpret_cast(lParam);
window = static_cast(cs->lpCreateParams);
SetWindowLongPtr(hWnd, 0, reinterpret_cast(window));
window->hWnd = hWnd;
return window->messageProc(message, wParam, lParam);
}
else return DefWindowProc(hWnd, message, wParam, lParam);
}
};
if (!RegisterClassW(&wc))
throw std::exception("register window failed");
}
void MyWindow::createD2DResource()
{
if (FAILED(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &d2dFactory)))
throw std::exception("create d2dFactory failed");
if (FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&dwriteFactory)))
throw std::exception("create dwrite factory failed");
if (FAILED(d2dFactory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, D2D1::SizeU((UINT32)0, (UINT32)0)), &target)))
throw std::exception("create hwndTarget failed");
//设置target的抗锯齿效果
target->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
//创建画刷用于后面的绘制,其颜色可以在后面更改
if (FAILED(target->CreateSolidColorBrush(D2D1::ColorF(0xffffff, 1.0f), &brush)))
throw std::exception("create brush failed");
}
LRESULT MyWindow::messageProc(UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
case WM_KEYDOWN:
onKey(static_cast(wp));
return 0;
case WM_CHAR:
onChar(static_cast(wp));
return 0;
case WM_MOUSEWHEEL:
onScroll(HIWORD(wp));
return 0;
case WM_LBUTTONDOWN:
SetFocus(hWnd);
SetCapture(hWnd);
onMouseEvent(msg, wp, lp);
return 0;
case WM_LBUTTONUP:
onMouseEvent(msg, wp, lp);
ReleaseCapture();
return 0;
case WM_MOUSEMOVE:
onMouseEvent(msg, wp, lp);
return 0;
case WM_PAINT:
target->BeginDraw();
target->Clear(D2D1::ColorF(D2D1::ColorF::White));
onPaint();
target->EndDraw();
ValidateRect(hWnd, 0);
return 0;
case WM_ERASEBKGND:
return 0;
case WM_SIZE:
{
RECT rect;
GetClientRect(hWnd, &rect);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
if (target) {
target->Resize(D2D1::SizeU(width, height));
}
onResize(width, height);
return 0;
}
case WM_DESTROY:
isRunning = false;
return 0;
}
return DefWindowProc(hWnd, msg, wp, lp);
}
void MyWindow::run()
{
//显示窗口
ShowWindow(hWnd, SW_SHOW);
UpdateWindow(hWnd);
MSG msg = { 0 };
isRunning = true;
while (isRunning) {
if (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else {
//hWndRenderTarget能自己控制帧率
target->BeginDraw();
target->Clear(D2D1::ColorF(D2D1::ColorF::White));
onPaint();
target->EndDraw();
}
}
}
namespace {
inline bool IsHighSurrogate(UINT32 ch) throw()
{
// 0xD800 <= ch <= 0xDBFF
return (ch & 0xFC00) == 0xD800;
}
inline bool IsLowSurrogate(UINT32 ch) throw()
{
// 0xDC00 <= ch <= 0xDFFF
return (ch & 0xFC00) == 0xDC00;
}
}
Editor::Editor()
{
maxScrollY = 0.f;
scrollY = 0.f;
lastInputTime = -1.f;
lastClickTime = -1.f;
lastSelectLength = 0;
isOnScroll = false;
textLayout = nullptr;
needUpdate = true;
caretAnchor = 0;
caretPosition = 0;
text = L"helloworld 北冥有鱼,其名为鲲。鲲之大,不知其几千里也。化而为鸟,其名为鹏。鹏之背,不知其几千里也。怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。";
}
Editor::~Editor()
{
SafeRelease(&textLayout);
}
void Editor::select(SelectMode mode, bool moveAnchor)
{
//以下代码使用了dwrite的api
switch (mode)
{
case SelectMode::up:
case SelectMode::down:
{
std::vector lineMetrics;
DWRITE_TEXT_METRICS textMetrics;
textLayout->GetMetrics(&textMetrics);
lineMetrics.resize(textMetrics.lineCount);
textLayout->GetLineMetrics(&lineMetrics.front(), textMetrics.lineCount, &textMetrics.lineCount);
UINT32 line = 0;
UINT32 linePosition = 0;
UINT32 nextLinePosition = 0;
UINT32 lineCount = static_cast(lineMetrics.size());
for (; line < lineCount; ++line)
{
linePosition = nextLinePosition;
nextLinePosition = linePosition + lineMetrics[line].length;
if (nextLinePosition > caretPosition) {
break;
}
}
if (line > lineCount - 1) {
line = lineCount - 1;
}
if (mode == SelectMode::up)
{
if (line <= 0)
break;
line--;
linePosition -= lineMetrics[line].length;
}
else
{
linePosition += lineMetrics[line].length;
line++;
if (line >= lineMetrics.size())
break;
}
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY, dummyX;
textLayout->HitTestTextPosition(
caretPosition,
false,
&caretX,
&caretY,
&hitTestMetrics
);
textLayout->HitTestTextPosition(
linePosition,
false,
&dummyX,
&caretY,
&hitTestMetrics
);
BOOL isInside, isTrailingHit;
textLayout->HitTestPoint(
caretX,
caretY,
&isTrailingHit,
&isInside,
&hitTestMetrics
);
caretPosition = hitTestMetrics.textPosition;
if (isTrailingHit) {
caretPosition += hitTestMetrics.length;
}
break;
}
case SelectMode::head:
caretPosition = 0;
break;
case SelectMode::tile:
caretPosition = text.length();
break;
case SelectMode::lastChar:
if (caretPosition > 0) {
UINT32 moveCount = 1;
if (caretPosition >= 2
&& caretPosition <= text.length())
{
if (isUnicodeUnit(text[caretPosition - 1], text[caretPosition - 2]))
{
moveCount = 2;
}
}
if (caretPosition < (UINT32)moveCount)
caretPosition = 0;
else caretPosition -= moveCount;
}
break;
case SelectMode::nextChar:
if (caretPosition < text.length()) {
UINT32 moveCount = 1;
if (caretPosition >= 0
&& caretPosition <= text.length() - 2)
{
wchar_t charBackOne = text[caretPosition];
wchar_t charBackTwo = text[caretPosition + 1];
if (isUnicodeUnit(text[caretPosition], text[caretPosition + 1]))
{
moveCount = 2;
}
}
if (caretPosition > text.length())
caretPosition = text.length();
else caretPosition += moveCount;
}
break;
case SelectMode::lastWord:
case SelectMode::nextWord: {
std::vector clusterMetrics;
UINT32 clusterCount;
textLayout->GetClusterMetrics(NULL, 0, &clusterCount);
if (clusterCount == 0)
break;
clusterMetrics.resize(clusterCount);
textLayout->GetClusterMetrics(&clusterMetrics.front(), clusterCount, &clusterCount);
UINT32 clusterPosition = 0;
UINT32 oldCaretPosition = caretPosition;
if (mode == SelectMode::lastWord) {
caretPosition = 0;
for (UINT32 cluster = 0; cluster < clusterCount; ++cluster) {
clusterPosition += clusterMetrics[cluster].length;
if (clusterMetrics[cluster].canWrapLineAfter) {
if (clusterPosition >= oldCaretPosition)
break;
caretPosition = clusterPosition;
}
}
}
else {
for (UINT32 cluster = 0; cluster < clusterCount; ++cluster) {
UINT32 clusterLength = clusterMetrics[cluster].length;
if (clusterPosition + clusterMetrics[cluster].length > oldCaretPosition && clusterMetrics[cluster].canWrapLineAfter) {
caretPosition = clusterPosition + clusterMetrics[cluster].length;
break;
}
clusterPosition += clusterLength;
caretPosition = clusterPosition;
}
}
break;
}
case SelectMode::absoluteLeading: {
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY;
textLayout->HitTestTextPosition(
caretPosition,
false,
&caretX,
&caretY,
&hitTestMetrics
);
caretPosition = hitTestMetrics.textPosition;
break;
}
case SelectMode::absoluteTrailing: {
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY;
textLayout->HitTestTextPosition(
caretPosition,
true,
&caretX,
&caretY,
&hitTestMetrics
);
caretPosition = hitTestMetrics.textPosition + hitTestMetrics.length;
break;
}
case SelectMode::all:
caretAnchor = 0;
caretPosition = text.length();
return;
default:
break;
}
if (moveAnchor)
caretAnchor = caretPosition;
}
void Editor::onResize(UINT32 width, UINT32 height)
{
needUpdate = true;
}
void Editor::onPaint()
{
checkUpdate();
if (textLayout) {
fillSelectedRange();
drawCaret();
drawText();
}
}
void Editor::onMouseEvent(UINT msg, WPARAM wp, LPARAM lp)
{
float x = (float)(short)LOWORD(lp);
float y = (float)(short)HIWORD(lp);
float time = timeGetTime() / 1000.f;
const float doubleClickInterval = 0.3f;
switch (msg)
{
case WM_LBUTTONDOWN:
isOnScroll = false;
lastSelectLength = getSelectionRange().length;
setSelectionFromPoint(x, y + scrollY, (GetKeyState(VK_SHIFT) & 0x80) == 0);
break;
case WM_LBUTTONUP:
if (time - lastClickTime < doubleClickInterval) {
if (lastSelectLength == 0)
select(SelectMode::all);
}
lastClickTime = time;
break;
case WM_MOUSEMOVE:
if ((wp & MK_LBUTTON) != 0)
setSelectionFromPoint(x, y + scrollY, false);
break;
default:
break;
}
}
void Editor::onScroll(short delta)
{
isOnScroll = true;
//滚动事件发生不意味着滚动值改变
float nextScroll = scrollY - delta;
if (nextScroll < 0)
nextScroll = 0;
else if (nextScroll > maxScrollY)
nextScroll = maxScrollY;
if (nextScroll != scrollY) {
scrollY = nextScroll;
needUpdate = true;
}
}
void Editor::onKey(UINT32 vk)
{
bool heldShift = (GetKeyState(VK_SHIFT) & 0x80) != 0;
bool heldControl = (GetKeyState(VK_CONTROL) & 0x80) != 0;
bool heldAlt = (GetKeyState(VK_MENU) & 0x80) != 0;
switch (vk)
{
case VK_RETURN:
deleteSelection();
wchar_t chars[3];
chars[0] = '\n';
chars[1] = 0;
text.insert(caretPosition, chars, 1);
caretPosition += 1;
caretAnchor = caretPosition;
needUpdate = true;
break;
case VK_BACK:
if (caretPosition != caretAnchor)
{
deleteSelection();
}
else if (caretPosition > 0)
{
UINT32 count = 1;
if (caretPosition >= 2
&& caretPosition <= text.length())
{
wchar_t charBackOne = text[caretPosition - 1];
wchar_t charBackTwo = text[caretPosition - 2];
if ((IsLowSurrogate(charBackOne) && IsHighSurrogate(charBackTwo))
|| (charBackOne == '\n' && charBackTwo == '\r'))
{
count = 2;
}
}
caretPosition -= count;
caretAnchor = caretPosition;
text.erase(caretPosition, count);
needUpdate = true;
}
break;
case VK_DELETE:
if (caretPosition != caretAnchor) {
deleteSelection();
}
else {
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY;
textLayout->HitTestTextPosition(
caretPosition,
false,
&caretX,
&caretY,
&hitTestMetrics
);
text.erase(hitTestMetrics.textPosition, hitTestMetrics.length);
needUpdate = true;
}
break;
case VK_TAB:
break;
case VK_LEFT:
if (!heldControl)
select(SelectMode::lastChar, !heldShift);
else
select(SelectMode::lastWord, !heldShift);
break;
case VK_RIGHT:
if (!heldControl)
select(SelectMode::nextChar, !heldShift);
else
select(SelectMode::nextWord, !heldShift);
break;
case VK_UP:
select(SelectMode::up);
break;
case VK_DOWN:
select(SelectMode::down);
break;
case VK_HOME:
select(SelectMode::head);
break;
case VK_END:
select(SelectMode::tile);
break;
case 'C':
if (heldControl)
copyToClipboard();
break;
case VK_INSERT:
if (heldControl)
copyToClipboard();
else if (heldShift) {
pasteFromClipboard();
}
break;
case 'V':
if (heldControl) {
pasteFromClipboard();
}
break;
case 'X':
//剪切文本,先复制再删除
if (heldControl) {
copyToClipboard();
deleteSelection();
}
break;
case 'A':
if (heldControl) {
select(SelectMode::all);
}
break;
default:
return;
}
isOnScroll = false;
lastInputTime = timeGetTime() / 1000.f;
}
void Editor::onChar(UINT32 c)
{
if (c >= 0x20 || c == 9)
{
deleteSelection();
UINT32 charsLength = 1;
wchar_t chars[2] = { static_cast(c), 0 };
if (c > 0xFFFF)
{
chars[0] = wchar_t(0xD800 + (c >> 10) - (0x10000 >> 10));
chars[1] = wchar_t(0xDC00 + (c & 0x3FF));
charsLength++;
}
text.insert(caretPosition, chars, charsLength);
caretPosition += charsLength;
caretAnchor = caretPosition;
needUpdate = true;
isOnScroll = false;
lastInputTime = timeGetTime() / 1000.f;
}
}
void Editor::copyToClipboard()
{
DWRITE_TEXT_RANGE selectionRange = getSelectionRange();
if (selectionRange.length <= 0)
return;
if (OpenClipboard(0)) {
if (EmptyClipboard()) {
size_t byteSize = sizeof(wchar_t) * (selectionRange.length + 1);
HGLOBAL hClipboardData = GlobalAlloc(GMEM_DDESHARE | GMEM_ZEROINIT, byteSize);
if (hClipboardData != NULL) {
void* memory = GlobalLock(hClipboardData);
if (memory != NULL) {
const wchar_t* ctext = text.c_str();
memcpy(memory, &ctext[selectionRange.startPosition], byteSize);
GlobalUnlock(hClipboardData);
if (SetClipboardData(CF_UNICODETEXT, hClipboardData) != NULL) {
hClipboardData = NULL;
}
}
GlobalFree(hClipboardData);
}
}
CloseClipboard();
}
}
void Editor::pasteFromClipboard() {
deleteSelection();
UINT32 characterCount = 0;
if (OpenClipboard(0)) {
HGLOBAL hClipboardData = GetClipboardData(CF_UNICODETEXT);
if (hClipboardData != NULL)
{
// Get text and size of text.
size_t byteSize = GlobalSize(hClipboardData);
void* memory = GlobalLock(hClipboardData); // [byteSize] in bytes
const wchar_t* ctext = reinterpret_cast(memory);
characterCount = static_cast(wcsnlen(ctext, byteSize / sizeof(wchar_t)));
if (memory != NULL)
{
// Insert the text at the current position.
text.insert(
caretPosition,
ctext,
characterCount
);
GlobalUnlock(hClipboardData);
}
}
CloseClipboard();
}
caretPosition += characterCount;
caretAnchor = caretPosition;
needUpdate = true;
}
void Editor::deleteSelection()
{
DWRITE_TEXT_RANGE range = getSelectionRange();
if (range.length <= 0)
return;
text.erase(range.startPosition, range.length);
caretPosition = range.startPosition;
caretAnchor = caretPosition;
needUpdate = true;
}
void Editor::setSelectionFromPoint(float x, float y, bool moveAnchor)
{
BOOL isTrailingHit;
BOOL isInside;
DWRITE_HIT_TEST_METRICS caretMetrics;
textLayout->HitTestPoint(
x, y,
&isTrailingHit,
&isInside,
&caretMetrics
);
if (isTrailingHit) {
caretPosition = caretMetrics.textPosition + caretMetrics.length;
}
else {
caretPosition = caretMetrics.textPosition;
}
if (moveAnchor)
caretAnchor = caretPosition;
return;
}
bool Editor::isUnicodeUnit(wchar_t char1, wchar_t char2)
{
return (IsLowSurrogate(char1) && IsHighSurrogate(char2))
|| (char1 == '\n' && char2 == '\r');
}
void Editor::fillSelectedRange()
{
UINT32 actualHitTestCount = 0;
auto selectedRange = getSelectionRange();
if (selectedRange.length > 0) {
textLayout->HitTestTextRange(selectedRange.startPosition, selectedRange.length, 0, 0, 0, 0, &actualHitTestCount);
std::vectorhitTestMetrics(actualHitTestCount);
textLayout->HitTestTextRange(selectedRange.startPosition, selectedRange.length, 0, -scrollY, &hitTestMetrics[0], static_cast(hitTestMetrics.size()), &actualHitTestCount);
//改变画刷为天蓝色
brush->SetColor(D2D1::ColorF(D2D1::ColorF::LightSkyBlue));
//遍历选中区域并进行填充
for (UINT32 i = 0; i < actualHitTestCount; i++) {
const DWRITE_HIT_TEST_METRICS& htm = hitTestMetrics[i];
D2D1_RECT_F highlightRect = {
htm.left,
htm.top,
(htm.left + htm.width),
(htm.top + htm.height)
};
target->FillRectangle(highlightRect, brush);
}
}
}
void Editor::drawCaret()
{
DWRITE_HIT_TEST_METRICS caretMetrics;
float caretX, caretY;
textLayout->HitTestTextPosition(caretPosition, false, &caretX, &caretY, &caretMetrics);
//若不处于滚动状态,则对光标位置进行判断修改,使其处于显示区域
if (!isOnScroll) {
if (caretY - scrollY + caretMetrics.height > height) {//光标超出窗口底部
scrollY = caretY - height + caretMetrics.height;
}
else if (caretY - scrollY < 0) {//光标在窗口上方
scrollY = caretY;
}
}
//使用sin函数决定是否绘制caret
if (sin((timeGetTime() / 1000.f - lastInputTime)*6.28f) > -0.1) {
//caret颜色为黑色
brush->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
target->DrawLine(D2D1::Point2F(caretX, caretY - scrollY)
, D2D1::Point2F(caretX, caretY + caretMetrics.height - scrollY), brush);
}
}
void Editor::drawText()
{
//文本为黑色
brush->SetColor(D2D1::ColorF(D2D1::ColorF::Black));
target->DrawTextLayout(D2D1::Point2F(0, -scrollY), textLayout, brush);
}
DWRITE_TEXT_RANGE Editor::getSelectionRange()
{
UINT32 caretBegin = caretAnchor;
UINT32 caretEnd = caretPosition;
if (caretBegin > caretEnd)
std::swap(caretBegin, caretEnd);
UINT32 textLength = (UINT32)(text.length());
if (caretBegin > textLength)
caretBegin = textLength;
if (caretEnd > textLength)
caretEnd = textLength;
return { caretBegin,caretEnd - caretBegin };
}
void Editor::checkUpdate()
{
if (needUpdate) {
IDWriteTextLayout * temp = createTextLayout(text);
if (temp) {
needUpdate = false;
SafeRelease(&textLayout);//释放上一个文本布局
textLayout = temp;
}
//获取文本区域的宽高
DWRITE_TEXT_METRICS metrics;
textLayout->GetMetrics(&metrics);
//修改更新后的滚动值
maxScrollY = max(metrics.height - height, 0);
if (scrollY > maxScrollY)
scrollY = maxScrollY;
}
}
asdfdg