1 // This file is part of VSTGUI. It is subject to the license terms
2 // in the LICENSE file found in the top-level directory of this
3 // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
4 
5 #include "generictextedit.h"
6 #include "../iplatformfont.h"
7 #include "../iplatformframe.h"
8 #include "../../controls/ctextlabel.h"
9 #include "../../cframe.h"
10 #include "../../cvstguitimer.h"
11 #include "../../cdropsource.h"
12 #include <numeric>
13 #include <string>
14 #include <codecvt>
15 #include <locale>
16 
17 //-----------------------------------------------------------------------------
18 namespace VSTGUI {
19 
20 #define VSTGUI_STB_TEXTEDIT_USE_UNICODE 1
21 
22 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
23 using STB_CharT = char16_t;
24 using StringConvert = std::wstring_convert<std::codecvt_utf8_utf16<STB_CharT>, STB_CharT>;
25 #else
26 using STB_CharT = char;
27 #endif
28 #define STB_TEXTEDIT_CHARTYPE STB_CharT
29 #define STB_TEXTEDIT_POSITIONTYPE int
30 #define STB_TEXTEDIT_STRING STBTextEditView
31 #define STB_TEXTEDIT_KEYTYPE uint32_t
32 
33 #include "stb_textedit.h"
34 
35 //-----------------------------------------------------------------------------
36 class STBTextEditView
37 	: public CTextLabel
38 	, public IKeyboardHook
39 	, public IMouseObserver
40 {
41 public:
42 	STBTextEditView (IPlatformTextEditCallback* callback);
43 
44 	void draw (CDrawContext* pContext) override;
45 	void drawBack (CDrawContext* pContext, CBitmap* newBack = nullptr) override;
46 	void setText (const UTF8String& txt) override;
47 
48 	int32_t onKeyDown (const VstKeyCode& code, CFrame* frame) override;
49 	int32_t onKeyUp (const VstKeyCode& code, CFrame* frame) override;
50 
51 	void onMouseEntered (CView* view, CFrame* frame) override;
52 	void onMouseExited (CView* view, CFrame* frame) override;
53 	CMouseEventResult onMouseMoved (CFrame* frame,
54 									const CPoint& where,
55 									const CButtonState& buttons) override;
56 	CMouseEventResult onMouseDown (CFrame* frame,
57 								   const CPoint& where,
58 								   const CButtonState& buttons) override;
59 
60 	bool attached (CView* parent) override;
61 	bool removed (CView* parent) override;
62 	void drawStyleChanged () override;
63 
64 	void selectAll ();
65 	bool doCut ();
66 	bool doCopy ();
67 	bool doPaste ();
68 
69 	static int deleteChars (STBTextEditView* self, size_t pos, size_t num);
70 	static int insertChars (STBTextEditView* self, size_t pos, const STB_CharT* text, size_t num);
71 	static void layout (StbTexteditRow* row, STBTextEditView* self, int start_i);
72 	static float getCharWidth (STBTextEditView* self, int n, int i);
73 	static STB_CharT getChar (STBTextEditView* self, int pos);
74 	static int getLength (STBTextEditView* self);
75 
76 private:
77 	using CTextLabel::onKeyDown;
78 	using CTextLabel::onKeyUp;
79 	using CTextLabel::onMouseEntered;
80 	using CTextLabel::onMouseExited;
81 	using CTextLabel::onMouseMoved;
82 	using CTextLabel::onMouseDown;
83 
84 	template<typename Proc>
85 	bool callSTB (Proc proc);
86 	void onStateChanged ();
87 	void onTextChange ();
88 	void fillCharWidthCache ();
89 	void calcCursorSizes ();
90 	CCoord getCharWidth (STB_CharT c, STB_CharT pc) const;
91 
92 	static constexpr auto BitRecursiveKeyGuard = 1 << 0;
93 	static constexpr auto BitBlinkToggle = 1 << 1;
94 	static constexpr auto BitCursorIsSet = 1 << 2;
95 	static constexpr auto BitCursorSizesValid = 1 << 3;
96 	static constexpr auto BitNotifyTextChange = 1 << 4;
97 
98 	bool isRecursiveKeyEventGuard () const { return hasBit (flags, BitRecursiveKeyGuard); }
99 	bool isBlinkToggle () const { return hasBit (flags, BitBlinkToggle); }
100 	bool isCursorSet () const { return hasBit (flags, BitCursorIsSet); }
101 	bool cursorSizesValid () const { return hasBit (flags, BitCursorSizesValid); }
102 	bool notifyTextChange () const { return hasBit (flags, BitNotifyTextChange); }
103 
104 	void setRecursiveKeyEventGuard (bool state) { setBit (flags, BitRecursiveKeyGuard, state); }
105 	void setBlinkToggle (bool state) { setBit (flags, BitBlinkToggle, state); }
106 	void setCursorIsSet (bool state) { setBit (flags, BitCursorIsSet, state); }
107 	void setCursorSizesValid (bool state) { setBit (flags, BitCursorSizesValid, state); }
108 	void setNotifyTextChange (bool state) { setBit (flags, BitNotifyTextChange, state); }
109 
110 	SharedPointer<CVSTGUITimer> blinkTimer;
111 	IPlatformTextEditCallback* callback;
112 	STB_TexteditState editState;
113 	std::vector<CCoord> charWidthCache;
114 	CColor selectionColor{kBlueCColor};
115 	CCoord cursorOffset{0.};
116 	CCoord cursorHeight{0.};
117 	uint32_t flags{0};
118 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
119 	std::u16string uString;
120 #endif
121 };
122 
123 //-----------------------------------------------------------------------------
124 #define VIRTUAL_KEY_BIT 0x80000000
125 #define STB_TEXTEDIT_K_SHIFT 0x40000000
126 #define STB_TEXTEDIT_K_CONTROL 0x20000000
127 #define STB_TEXTEDIT_K_ALT 0x10000000
128 // key-bindings
129 #define STB_TEXTEDIT_K_LEFT (VIRTUAL_KEY_BIT | VKEY_LEFT)
130 #define STB_TEXTEDIT_K_RIGHT (VIRTUAL_KEY_BIT | VKEY_RIGHT)
131 #define STB_TEXTEDIT_K_UP (VIRTUAL_KEY_BIT | VKEY_UP)
132 #define STB_TEXTEDIT_K_DOWN (VIRTUAL_KEY_BIT | VKEY_DOWN)
133 #if MAC
134 #	define STB_TEXTEDIT_K_LINESTART (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_LEFT)
135 #	define STB_TEXTEDIT_K_LINEEND (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_RIGHT)
136 #	define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_ALT | STB_TEXTEDIT_K_LEFT)
137 #	define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_ALT | STB_TEXTEDIT_K_RIGHT)
138 #	define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_UP)
139 #	define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_DOWN)
140 #else
141 #	define STB_TEXTEDIT_K_LINESTART (VIRTUAL_KEY_BIT | VKEY_HOME)
142 #	define STB_TEXTEDIT_K_LINEEND (VIRTUAL_KEY_BIT | VKEY_END)
143 #	define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_CONTROL)
144 #	define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_CONTROL)
145 #	define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_CONTROL)
146 #	define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_CONTROL)
147 #endif
148 #define STB_TEXTEDIT_K_DELETE (VIRTUAL_KEY_BIT | VKEY_DELETE)
149 #define STB_TEXTEDIT_K_BACKSPACE (VIRTUAL_KEY_BIT | VKEY_BACK)
150 #define STB_TEXTEDIT_K_UNDO (STB_TEXTEDIT_K_CONTROL | 'z')
151 #define STB_TEXTEDIT_K_REDO (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_SHIFT | 'z')
152 #define STB_TEXTEDIT_K_INSERT (VIRTUAL_KEY_BIT | VKEY_INSERT)
153 #define STB_TEXTEDIT_K_PGUP (VIRTUAL_KEY_BIT | VKEY_PAGEUP)
154 #define STB_TEXTEDIT_K_PGDOWN (VIRTUAL_KEY_BIT | VKEY_PAGEDOWN)
155 // functions
156 #define STB_TEXTEDIT_STRINGLEN(tc) STBTextEditView::getLength (tc)
157 #define STB_TEXTEDIT_LAYOUTROW STBTextEditView::layout
158 #define STB_TEXTEDIT_GETWIDTH(tc, n, i) STBTextEditView::getCharWidth (tc, n, i)
159 #define STB_TEXTEDIT_KEYTOTEXT(key)                                                                \
160 	((key & VIRTUAL_KEY_BIT) ? 0 : ((key & STB_TEXTEDIT_K_CONTROL) ? 0 : (key & (~0xF0000000))));
161 #define STB_TEXTEDIT_GETCHAR(tc, i) STBTextEditView::getChar (tc, i)
162 #define STB_TEXTEDIT_NEWLINE '\n'
163 #define STB_TEXTEDIT_IS_SPACE(ch) isSpace (ch)
164 #define STB_TEXTEDIT_DELETECHARS STBTextEditView::deleteChars
165 #define STB_TEXTEDIT_INSERTCHARS STBTextEditView::insertChars
166 
167 #define STB_TEXTEDIT_IMPLEMENTATION
168 #include "stb_textedit.h"
169 
170 //-----------------------------------------------------------------------------
171 struct GenericTextEdit::Impl
172 {
173 	STBTextEditView* view;
174 };
175 
176 //-----------------------------------------------------------------------------
177 GenericTextEdit::GenericTextEdit (IPlatformTextEditCallback* callback)
178 	: IPlatformTextEdit (callback)
179 {
180 	impl = std::unique_ptr<Impl> (new Impl);
181 	impl->view = new STBTextEditView (callback);
182 	auto view = dynamic_cast<CView*> (callback);
183 	vstgui_assert (view);
184 	view->getParentView ()->asViewContainer ()->addView (impl->view);
185 
186 	auto font = shared (callback->platformGetFont ());
187 	auto fontSize = font->getSize () / impl->view->getGlobalTransform ().m11;
188 	if (fontSize != font->getSize ())
189 	{
190 		font = makeOwned<CFontDesc> (*font);
191 		font->setSize (fontSize);
192 	}
193 	impl->view->setFont (font);
194 	impl->view->setFontColor (callback->platformGetFontColor ());
195 	impl->view->setTextInset (callback->platformGetTextInset ());
196 	impl->view->setHoriAlign (callback->platformGetHoriTxtAlign ());
197 	impl->view->setText (callback->platformGetText ());
198 	impl->view->selectAll ();
199 
200 	updateSize ();
201 }
202 
203 //-----------------------------------------------------------------------------
204 GenericTextEdit::~GenericTextEdit () noexcept
205 {
206 	if (impl->view->isAttached ())
207 		impl->view->getParentView ()->asViewContainer ()->removeView (impl->view);
208 	else
209 		impl->view->forget ();
210 }
211 
212 //-----------------------------------------------------------------------------
213 UTF8String GenericTextEdit::getText ()
214 {
215 	return impl->view->getText ();
216 }
217 
218 //-----------------------------------------------------------------------------
219 bool GenericTextEdit::setText (const UTF8String& text)
220 {
221 	impl->view->setText (text);
222 	return true;
223 }
224 
225 //-----------------------------------------------------------------------------
226 bool GenericTextEdit::updateSize ()
227 {
228 	auto r = textEdit->platformGetVisibleSize ();
229 	r = impl->view->translateToLocal (r);
230 	impl->view->setViewSize (r);
231 	impl->view->setMouseableArea (r);
232 	return true;
233 }
234 
235 //-----------------------------------------------------------------------------
236 STBTextEditView::STBTextEditView (IPlatformTextEditCallback* callback)
237 	: CTextLabel ({}), callback (callback)
238 {
239 	stb_textedit_initialize_state (&editState, true);
240 	setTransparency (true);
241 }
242 
243 //-----------------------------------------------------------------------------
244 template<typename Proc>
245 bool STBTextEditView::callSTB (Proc proc)
246 {
247 	auto oldState = editState;
248 	proc ();
249 	if (memcmp (&oldState, &editState, sizeof (STB_TexteditState)) != 0)
250 	{
251 		onStateChanged ();
252 		return true;
253 	}
254 	return false;
255 }
256 
257 //-----------------------------------------------------------------------------
258 int32_t STBTextEditView::onKeyDown (const VstKeyCode& code, CFrame* frame)
259 {
260 	if (isRecursiveKeyEventGuard ())
261 		return -1;
262 	auto selfGuard = SharedPointer<CBaseObject> (this);
263 	BitScopeToggleT<uint32_t, uint32_t> br (flags, BitRecursiveKeyGuard);
264 	if (callback->platformOnKeyDown (code))
265 		return 1;
266 
267 	if (code.character == 0 && code.virt == 0)
268 		return -1;
269 
270 	if (code.modifier == MODIFIER_CONTROL)
271 	{
272 		switch (code.character)
273 		{
274 			case 'a':
275 			{
276 				selectAll ();
277 				return 1;
278 			}
279 			case 'x':
280 			{
281 				if (doCut ())
282 					return 1;
283 				return -1;
284 			}
285 			case 'c':
286 			{
287 				if (doCopy ())
288 					return 1;
289 				return -1;
290 			}
291 			case 'v':
292 			{
293 				if (doPaste ())
294 					return 1;
295 				return -1;
296 			}
297 		}
298 	}
299 
300 	auto key = code.character;
301 	if (key)
302 	{
303 		if (auto text = getFrame ()->getPlatformFrame ()->convertCurrentKeyEventToText ())
304 		{
305 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
306 			auto tmp = StringConvert{}.from_bytes (text->getString ());
307 			key = tmp[0];
308 #else
309 			if (text->length () != 1)
310 				return -1;
311 			key = text->getString ()[0];
312 #endif
313 		}
314 	}
315 	if (code.virt)
316 	{
317 		switch (code.virt)
318 		{
319 			case VKEY_SPACE:
320 			{
321 				key = 0x20;
322 				break;
323 			}
324 			case VKEY_TAB:
325 			{
326 				return -1;
327 			}
328 			default:
329 			{
330 				key = code.virt | VIRTUAL_KEY_BIT;
331 				break;
332 			}
333 		}
334 	}
335 	if (code.modifier & MODIFIER_CONTROL)
336 		key |= STB_TEXTEDIT_K_CONTROL;
337 	if (code.modifier & MODIFIER_ALTERNATE)
338 		key |= STB_TEXTEDIT_K_ALT;
339 	if (code.modifier & MODIFIER_SHIFT)
340 		key |= STB_TEXTEDIT_K_SHIFT;
341 	return callSTB ([&]() { stb_textedit_key (this, &editState, key); }) ? 1 : -1;
342 }
343 
344 //-----------------------------------------------------------------------------
345 int32_t STBTextEditView::onKeyUp (const VstKeyCode& code, CFrame* frame)
346 {
347 	return -1;
348 }
349 
350 //-----------------------------------------------------------------------------
351 CMouseEventResult STBTextEditView::onMouseDown (CFrame* frame,
352 												const CPoint& _where,
353 												const CButtonState& buttons)
354 {
355 	auto where = _where;
356 	if (auto parent = getParentView ())
357 	{
358 		parent->translateToLocal (where);
359 		if (buttons.isLeftButton () && hitTest (where, buttons))
360 		{
361 			CPoint where2 (where);
362 			where2.x -= getViewSize ().left;
363 			where2.y -= getViewSize ().top;
364 			callSTB ([&]() {
365 				stb_textedit_click (this, &editState, static_cast<float> (where2.x),
366 									static_cast<float> (where2.y));
367 			});
368 			return kMouseEventHandled;
369 		}
370 	}
371 	return kMouseEventNotHandled;
372 }
373 
374 //-----------------------------------------------------------------------------
375 CMouseEventResult STBTextEditView::onMouseMoved (CFrame* frame,
376 												 const CPoint& _where,
377 												 const CButtonState& buttons)
378 {
379 	auto where = _where;
380 	if (auto parent = getParentView ())
381 	{
382 		parent->translateToLocal (where);
383 		if (buttons.isLeftButton () && hitTest (where, buttons))
384 		{
385 			CPoint where2 (where);
386 			where2.x -= getViewSize ().left;
387 			where2.y -= getViewSize ().top;
388 			callSTB ([&]() {
389 				stb_textedit_drag (this, &editState, static_cast<float> (where2.x),
390 								   static_cast<float> (where2.y));
391 			});
392 			return kMouseEventHandled;
393 		}
394 	}
395 	return kMouseEventNotHandled;
396 }
397 
398 //-----------------------------------------------------------------------------
399 void STBTextEditView::onMouseEntered (CView* view, CFrame* frame)
400 {
401 	if (view == this)
402 	{
403 		setCursorIsSet (true);
404 		getFrame ()->setCursor (kCursorIBeam);
405 	}
406 }
407 
408 //-----------------------------------------------------------------------------
409 void STBTextEditView::onMouseExited (CView* view, CFrame* frame)
410 {
411 	if (view == this)
412 	{
413 		setCursorIsSet (false);
414 		getFrame ()->setCursor (kCursorDefault);
415 	}
416 }
417 
418 //-----------------------------------------------------------------------------
419 bool STBTextEditView::attached (CView* parent)
420 {
421 	if (auto frame = parent->getFrame ())
422 	{
423 		frame->registerMouseObserver (this);
424 		frame->registerKeyboardHook (this);
425 		selectionColor = frame->getFocusColor ();
426 		drawStyleChanged ();
427 	}
428 	return CTextLabel::attached (parent);
429 }
430 
431 //-----------------------------------------------------------------------------
432 bool STBTextEditView::removed (CView* parent)
433 {
434 	if (auto frame = getFrame ())
435 	{
436 		blinkTimer = nullptr;
437 		frame->unregisterMouseObserver (this);
438 		frame->unregisterKeyboardHook (this);
439 		if (isCursorSet ())
440 			frame->setCursor (kCursorDefault);
441 	}
442 	return CTextLabel::removed (parent);
443 }
444 
445 //-----------------------------------------------------------------------------
446 void STBTextEditView::drawStyleChanged ()
447 {
448 	setCursorSizesValid (false);
449 	charWidthCache.clear ();
450 	CTextLabel::drawStyleChanged ();
451 }
452 
453 //-----------------------------------------------------------------------------
454 void STBTextEditView::selectAll ()
455 {
456 	editState.select_start = 0;
457 	editState.select_end = static_cast<int> (getText ().length ());
458 	onStateChanged ();
459 }
460 
461 //-----------------------------------------------------------------------------
462 bool STBTextEditView::doCut ()
463 {
464 	if (doCopy ())
465 	{
466 		callSTB ([&]() { stb_textedit_cut (this, &editState); });
467 		return true;
468 	}
469 	return false;
470 }
471 
472 //-----------------------------------------------------------------------------
473 bool STBTextEditView::doCopy ()
474 {
475 	if (editState.select_start == editState.select_end)
476 		return false;
477 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
478 	auto txt = StringConvert{}.to_bytes (uString.data () + editState.select_start,
479 										 uString.data () + editState.select_end);
480 	auto dataPackage = CDropSource::create (txt.data (), txt.size (), IDataPackage::kText);
481 #else
482 	auto dataPackage =
483 		CDropSource::create (getText ().data (), getText ().length (), IDataPackage::kText);
484 #endif
485 	getFrame ()->getPlatformFrame ()->setClipboard (dataPackage);
486 	return true;
487 }
488 
489 //-----------------------------------------------------------------------------
490 bool STBTextEditView::doPaste ()
491 {
492 	if (auto clipboard = getFrame ()->getPlatformFrame ()->getClipboard ())
493 	{
494 		auto count = clipboard->getCount ();
495 		for (auto i = 0u; i < count; ++i)
496 		{
497 			const void* buffer;
498 			IDataPackage::Type dataType;
499 			auto size = clipboard->getData (i, buffer, dataType);
500 			if (dataType == IDataPackage::kText)
501 			{
502 				auto text = reinterpret_cast<const char*> (buffer);
503 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
504 				auto uText = StringConvert{}.from_bytes (text, text + size);
505 				callSTB (
506 					[&]() { stb_textedit_paste (this, &editState, uText.data (), uText.size ()); });
507 #else
508 				callSTB ([&]() { stb_textedit_paste (this, &editState, text, size); });
509 #endif
510 				return true;
511 			}
512 		}
513 	}
514 	return false;
515 }
516 
517 //-----------------------------------------------------------------------------
518 void STBTextEditView::onStateChanged ()
519 {
520 	setBlinkToggle (true);
521 	if (isAttached ())
522 	{
523 		blinkTimer = makeOwned<CVSTGUITimer> (
524 			[&](CVSTGUITimer* timer) {
525 				setBlinkToggle (!isBlinkToggle ());
526 				if (editState.select_start == editState.select_end)
527 					invalid ();
528 			},
529 			500);
530 	}
531 	invalid ();
532 }
533 
534 //-----------------------------------------------------------------------------
535 void STBTextEditView::setText (const UTF8String& txt)
536 {
537 	charWidthCache.clear ();
538 	CTextLabel::setText (txt);
539 	if (editState.select_start != editState.select_end)
540 		selectAll ();
541 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
542 	uString = StringConvert{}.from_bytes (CTextLabel::getText ().getString ());
543 #endif
544 }
545 
546 //-----------------------------------------------------------------------------
547 CCoord STBTextEditView::getCharWidth (STB_CharT c, STB_CharT pc) const
548 {
549 	auto platformFont = getFont ()->getPlatformFont ();
550 	vstgui_assert (platformFont);
551 	auto fontPainter = platformFont->getPainter ();
552 	vstgui_assert (fontPainter);
553 
554 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
555 	if (pc)
556 	{
557 		UTF8String str (StringConvert{}.to_bytes (pc));
558 		auto pcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
559 		str += StringConvert{}.to_bytes (c);
560 		auto tcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
561 		return tcWidth - pcWidth;
562 	}
563 	UTF8String str (StringConvert{}.to_bytes (c));
564 	return fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
565 #else
566 	if (pc)
567 	{
568 		UTF8String str (std::string (1, pc));
569 		auto pcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
570 		str += std::string (1, c);
571 		auto tcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
572 		return tcWidth - pcWidth;
573 	}
574 
575 	UTF8String str (std::string (1, c));
576 	return fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
577 #endif
578 }
579 
580 //-----------------------------------------------------------------------------
581 void STBTextEditView::fillCharWidthCache ()
582 {
583 	if (!charWidthCache.empty ())
584 		return;
585 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
586 	auto num = uString.size ();
587 	charWidthCache.resize (num);
588 	for (auto i = 0u; i < num; ++i)
589 		charWidthCache[i] = getCharWidth (uString[i], i == 0 ? 0 : uString[i - 1]);
590 #else
591 	auto num = getText ().length ();
592 	charWidthCache.resize (num);
593 	const auto& str = getText ().getString ();
594 	for (auto i = 0u; i < num; ++i)
595 		charWidthCache[i] = getCharWidth (str[i], i == 0 ? 0 : str[i - 1]);
596 #endif
597 }
598 
599 //-----------------------------------------------------------------------------
600 void STBTextEditView::calcCursorSizes ()
601 {
602 	if (cursorSizesValid ())
603 		return;
604 
605 	auto platformFont = getFont ()->getPlatformFont ();
606 	vstgui_assert (platformFont);
607 
608 	cursorHeight = platformFont->getAscent () + platformFont->getDescent ();
609 	auto viewHeight = getViewSize ().getHeight ();
610 	cursorOffset = (viewHeight / 2. - cursorHeight / 2.);
611 	setCursorSizesValid (true);
612 }
613 
614 //-----------------------------------------------------------------------------
615 void STBTextEditView::draw (CDrawContext* context)
616 {
617 	fillCharWidthCache ();
618 	calcCursorSizes ();
619 
620 	drawBack (context, nullptr);
621 	drawPlatformText (context, getText ().getPlatformString ());
622 
623 	if (!isBlinkToggle () || editState.select_start != editState.select_end)
624 		return;
625 
626 	// draw cursor
627 	StbTexteditRow row{};
628 	layout (&row, this, 0);
629 
630 	context->setFillColor (getFontColor ());
631 	context->setDrawMode (kAntiAliasing);
632 	CRect r = getViewSize ();
633 	r.setHeight (cursorHeight);
634 	r.offset (row.x0, cursorOffset);
635 	r.setWidth (1);
636 	for (auto i = 0; i < editState.cursor; ++i)
637 		r.offset (charWidthCache[i], 0);
638 	r.offset (-0.5, 0);
639 	context->drawRect (r, kDrawFilled);
640 }
641 
642 //-----------------------------------------------------------------------------
643 void STBTextEditView::drawBack (CDrawContext* context, CBitmap* newBack)
644 {
645 	CTextLabel::drawBack (context, newBack);
646 
647 	auto selStart = editState.select_start;
648 	auto selEnd = editState.select_end;
649 	if (selStart > selEnd)
650 		std::swap (selStart, selEnd);
651 
652 	if (selStart != selEnd)
653 	{
654 		StbTexteditRow row{};
655 		layout (&row, this, 0);
656 
657 		// draw selection
658 		CRect selection = getViewSize ();
659 		selection.setHeight (cursorHeight);
660 		selection.offset (row.x0, cursorOffset);
661 		selection.setWidth (0);
662 		auto index = 0;
663 		for (; index < selStart; ++index)
664 			selection.offset (charWidthCache[index], 0);
665 		for (; index < selEnd; ++index)
666 			selection.right += charWidthCache[index];
667 		context->setFillColor (selectionColor);
668 		context->drawRect (selection, kDrawFilled);
669 	}
670 }
671 
672 //-----------------------------------------------------------------------------
673 void STBTextEditView::onTextChange ()
674 {
675 	if (notifyTextChange ())
676 		return;
677 	if (auto frame = getFrame ())
678 	{
679 		if (frame->inEventProcessing ())
680 		{
681 			setNotifyTextChange (true);
682 			auto self = shared (this);
683 			frame->doAfterEventProcessing ([self]() {
684 				self->setNotifyTextChange (false);
685 				self->callback->platformTextDidChange ();
686 			});
687 		}
688 	}
689 }
690 
691 //-----------------------------------------------------------------------------
692 int STBTextEditView::deleteChars (STBTextEditView* self, size_t pos, size_t num)
693 {
694 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
695 	self->uString.erase (pos, num);
696 	self->setText (StringConvert{}.to_bytes (self->uString));
697 	self->onTextChange ();
698 	return true;
699 #else
700 	auto str = self->text.getString ();
701 	str.erase (pos, num);
702 	self->setText (str.data ());
703 	self->onTextChange ();
704 	return true; // success
705 #endif
706 }
707 
708 //-----------------------------------------------------------------------------
709 int STBTextEditView::insertChars (STBTextEditView* self,
710 								  size_t pos,
711 								  const STB_CharT* text,
712 								  size_t num)
713 {
714 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
715 	self->uString.insert (pos, text, num);
716 	self->setText (StringConvert{}.to_bytes (self->uString));
717 	self->onTextChange ();
718 	return true;
719 #else
720 	auto str = self->text.getString ();
721 	str.insert (pos, text, num);
722 	self->setText (str.data ());
723 	self->onTextChange ();
724 	return true; // success
725 #endif
726 }
727 
728 //-----------------------------------------------------------------------------
729 STB_CharT STBTextEditView::getChar (STBTextEditView* self, int pos)
730 {
731 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
732 	return self->uString[pos];
733 #else
734 	return self->getText ().getString ()[pos];
735 #endif
736 }
737 
738 //-----------------------------------------------------------------------------
739 int STBTextEditView::getLength (STBTextEditView* self)
740 {
741 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
742 	return self->uString.size ();
743 #else
744 	return static_cast<int> (self->getText ().length ());
745 #endif
746 }
747 
748 //-----------------------------------------------------------------------------
749 void STBTextEditView::layout (StbTexteditRow* row, STBTextEditView* self, int start_i)
750 {
751 	vstgui_assert (start_i == 0);
752 
753 	self->fillCharWidthCache ();
754 	auto textWidth = static_cast<float> (
755 		std::accumulate (self->charWidthCache.begin (), self->charWidthCache.end (), 0.));
756 
757 	row->num_chars = static_cast<int> (self->getText ().length ());
758 	row->baseline_y_delta = 1.25;
759 	row->ymin = 0.f;
760 	row->ymax = static_cast<float> (self->getFont ()->getSize ());
761 	switch (self->getHoriAlign ())
762 	{
763 		case kLeftText:
764 		{
765 			row->x0 = static_cast<float> (self->getTextInset ().x);
766 			row->x1 = row->x0 + textWidth;
767 			break;
768 		}
769 		case kCenterText:
770 		{
771 			row->x0 =
772 				static_cast<float> ((self->getViewSize ().getWidth () / 2.) - (textWidth / 2.));
773 			row->x1 = row->x0 + textWidth;
774 			break;
775 		}
776 		default:
777 		{
778 			vstgui_assert (false, "Not Implemented !");
779 			break;
780 		}
781 	}
782 }
783 
784 //-----------------------------------------------------------------------------
785 float STBTextEditView::getCharWidth (STBTextEditView* self, int n, int i)
786 {
787 	self->fillCharWidthCache ();
788 	return static_cast<float> (self->charWidthCache[i]);
789 }
790 
791 //-----------------------------------------------------------------------------
792 } // VSTGUI
793