天天看點

使用C++實作的簡單文本編輯器(direct2d)

在學習Direct2D的時候,意外地在windows-classic-samples裡面翻到了一個PadWrite,其實作了一個基本的文本編輯器,遂參考其實作了一個簡單的文本編輯器。

使用VS2017開發,能夠在C++"控制台應用程式"/“Windows桌面應用程式”項目中運作

支援中文及滾動(未添加滾動條),效果如圖

使用C++實作的簡單文本編輯器(direct2d)

下面是單檔案源碼,另附多檔案版及exe連結: 源代碼

#pragma once
#include<Windows.h>
#include<d2d1.h>
#include<dwrite.h>
#include<string>
#include<vector>

#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 <typename InterfaceType>
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<UINT32>(text.length()),
		textFormat,
		width, height,
		&textLayout
	);

	SafeRelease<IDWriteTextFormat>(&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<MyWindow*>(reinterpret_cast<void*>(GetWindowLongPtr(hWnd, 0)));
		if (window) {
			return window->messageProc(message, wParam, lParam);
		}
		else {
			if (message == WM_CREATE) {
				LPCREATESTRUCT cs = reinterpret_cast<LPCREATESTRUCT>(lParam);
				window = static_cast<MyWindow*>(cs->lpCreateParams);

				SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(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<UINT>(wp));
		return 0;
	case WM_CHAR:
		onChar(static_cast<UINT>(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<DWRITE_LINE_METRICS> 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<UINT32>(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<DWRITE_CLUSTER_METRICS> 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<wchar_t>(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<const wchar_t*>(memory);
			characterCount = static_cast<UINT32>(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::vector<DWRITE_HIT_TEST_METRICS>hitTestMetrics(actualHitTestCount);
		textLayout->HitTestTextRange(selectedRange.startPosition, selectedRange.length, 0, -scrollY, &hitTestMetrics[0], static_cast<UINT32>(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