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 
isRecursiveKeyEventGuard() const98 	bool isRecursiveKeyEventGuard () const { return hasBit (flags, BitRecursiveKeyGuard); }
isBlinkToggle() const99 	bool isBlinkToggle () const { return hasBit (flags, BitBlinkToggle); }
isCursorSet() const100 	bool isCursorSet () const { return hasBit (flags, BitCursorIsSet); }
cursorSizesValid() const101 	bool cursorSizesValid () const { return hasBit (flags, BitCursorSizesValid); }
notifyTextChange() const102 	bool notifyTextChange () const { return hasBit (flags, BitNotifyTextChange); }
103 
setRecursiveKeyEventGuard(bool state)104 	void setRecursiveKeyEventGuard (bool state) { setBit (flags, BitRecursiveKeyGuard, state); }
setBlinkToggle(bool state)105 	void setBlinkToggle (bool state) { setBit (flags, BitBlinkToggle, state); }
setCursorIsSet(bool state)106 	void setCursorIsSet (bool state) { setBit (flags, BitCursorIsSet, state); }
setCursorSizesValid(bool state)107 	void setCursorSizesValid (bool state) { setBit (flags, BitCursorSizesValid, state); }
setNotifyTextChange(bool 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 //-----------------------------------------------------------------------------
GenericTextEdit(IPlatformTextEditCallback * callback)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 	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 //-----------------------------------------------------------------------------
~GenericTextEdit()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 //-----------------------------------------------------------------------------
getText()213 UTF8String GenericTextEdit::getText ()
214 {
215 	return impl->view->getText ();
216 }
217 
218 //-----------------------------------------------------------------------------
setText(const UTF8String & text)219 bool GenericTextEdit::setText (const UTF8String& text)
220 {
221 	impl->view->setText (text);
222 	return true;
223 }
224 
225 //-----------------------------------------------------------------------------
updateSize()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 //-----------------------------------------------------------------------------
STBTextEditView(IPlatformTextEditCallback * callback)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>
callSTB(Proc 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 //-----------------------------------------------------------------------------
onKeyDown(const VstKeyCode & code,CFrame * frame)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 //-----------------------------------------------------------------------------
onKeyUp(const VstKeyCode & code,CFrame * frame)345 int32_t STBTextEditView::onKeyUp (const VstKeyCode& code, CFrame* frame)
346 {
347 	return -1;
348 }
349 
350 //-----------------------------------------------------------------------------
onMouseDown(CFrame * frame,const CPoint & _where,const CButtonState & buttons)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 		/* SURGE CHANGE to remove this translate. See github #1104 */
359 		/* parent-> */ translateToLocal (where);
360 		if (buttons.isLeftButton () && hitTest (where, buttons))
361 		{
362 			CPoint where2 (where);
363 			where2.x -= getViewSize ().left;
364 			where2.y -= getViewSize ().top;
365 			callSTB ([&]() {
366 				stb_textedit_click (this, &editState, static_cast<float> (where2.x),
367 									static_cast<float> (where2.y));
368 			});
369 			return kMouseEventHandled;
370 		}
371 	}
372 	return kMouseEventNotHandled;
373 }
374 
375 //-----------------------------------------------------------------------------
onMouseMoved(CFrame * frame,const CPoint & _where,const CButtonState & buttons)376 CMouseEventResult STBTextEditView::onMouseMoved (CFrame* frame,
377 												 const CPoint& _where,
378 												 const CButtonState& buttons)
379 {
380 	auto where = _where;
381 	if (auto parent = getParentView ())
382 	{
383 		parent->translateToLocal (where);
384 		if (buttons.isLeftButton () && hitTest (where, buttons))
385 		{
386 			CPoint where2 (where);
387 			where2.x -= getViewSize ().left;
388 			where2.y -= getViewSize ().top;
389 			callSTB ([&]() {
390 				stb_textedit_drag (this, &editState, static_cast<float> (where2.x),
391 								   static_cast<float> (where2.y));
392 			});
393 			return kMouseEventHandled;
394 		}
395 	}
396 	return kMouseEventNotHandled;
397 }
398 
399 //-----------------------------------------------------------------------------
onMouseEntered(CView * view,CFrame * frame)400 void STBTextEditView::onMouseEntered (CView* view, CFrame* frame)
401 {
402 	if (view == this)
403 	{
404 		setCursorIsSet (true);
405 		getFrame ()->setCursor (kCursorIBeam);
406 	}
407 }
408 
409 //-----------------------------------------------------------------------------
onMouseExited(CView * view,CFrame * frame)410 void STBTextEditView::onMouseExited (CView* view, CFrame* frame)
411 {
412 	if (view == this)
413 	{
414 		setCursorIsSet (false);
415 		getFrame ()->setCursor (kCursorDefault);
416 	}
417 }
418 
419 //-----------------------------------------------------------------------------
attached(CView * parent)420 bool STBTextEditView::attached (CView* parent)
421 {
422 	if (auto frame = parent->getFrame ())
423 	{
424 		frame->registerMouseObserver (this);
425 		frame->registerKeyboardHook (this);
426 		selectionColor = frame->getFocusColor ();
427 		drawStyleChanged ();
428 	}
429 	return CTextLabel::attached (parent);
430 }
431 
432 //-----------------------------------------------------------------------------
removed(CView * parent)433 bool STBTextEditView::removed (CView* parent)
434 {
435 	if (auto frame = getFrame ())
436 	{
437 		blinkTimer = nullptr;
438 		frame->unregisterMouseObserver (this);
439 		frame->unregisterKeyboardHook (this);
440 		if (isCursorSet ())
441 			frame->setCursor (kCursorDefault);
442 	}
443 	return CTextLabel::removed (parent);
444 }
445 
446 //-----------------------------------------------------------------------------
drawStyleChanged()447 void STBTextEditView::drawStyleChanged ()
448 {
449 	setCursorSizesValid (false);
450 	charWidthCache.clear ();
451 	CTextLabel::drawStyleChanged ();
452 }
453 
454 //-----------------------------------------------------------------------------
selectAll()455 void STBTextEditView::selectAll ()
456 {
457 	editState.select_start = 0;
458 	editState.select_end = static_cast<int> (getText ().length ());
459 	onStateChanged ();
460 }
461 
462 //-----------------------------------------------------------------------------
doCut()463 bool STBTextEditView::doCut ()
464 {
465 	if (doCopy ())
466 	{
467 		callSTB ([&]() { stb_textedit_cut (this, &editState); });
468 		return true;
469 	}
470 	return false;
471 }
472 
473 //-----------------------------------------------------------------------------
doCopy()474 bool STBTextEditView::doCopy ()
475 {
476 	if (editState.select_start == editState.select_end)
477 		return false;
478 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
479 	auto txt = StringConvert{}.to_bytes (uString.data () + editState.select_start,
480 										 uString.data () + editState.select_end);
481 	auto dataPackage = CDropSource::create (txt.data (), txt.size (), IDataPackage::kText);
482 #else
483 	auto dataPackage =
484 		CDropSource::create (getText ().data (), getText ().length (), IDataPackage::kText);
485 #endif
486 	getFrame ()->getPlatformFrame ()->setClipboard (dataPackage);
487 	return true;
488 }
489 
490 //-----------------------------------------------------------------------------
doPaste()491 bool STBTextEditView::doPaste ()
492 {
493 	if (auto clipboard = getFrame ()->getPlatformFrame ()->getClipboard ())
494 	{
495 		auto count = clipboard->getCount ();
496 		for (auto i = 0u; i < count; ++i)
497 		{
498 			const void* buffer;
499 			IDataPackage::Type dataType;
500 			auto size = clipboard->getData (i, buffer, dataType);
501 			if (dataType == IDataPackage::kText)
502 			{
503 				auto text = reinterpret_cast<const char*> (buffer);
504 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
505 				auto uText = StringConvert{}.from_bytes (text, text + size);
506 				callSTB (
507 					[&]() { stb_textedit_paste (this, &editState, uText.data (), uText.size ()); });
508 #else
509 				callSTB ([&]() { stb_textedit_paste (this, &editState, text, size); });
510 #endif
511 				return true;
512 			}
513 		}
514 	}
515 	return false;
516 }
517 
518 //-----------------------------------------------------------------------------
onStateChanged()519 void STBTextEditView::onStateChanged ()
520 {
521 	setBlinkToggle (true);
522 	if (isAttached ())
523 	{
524 		blinkTimer = makeOwned<CVSTGUITimer> (
525 			[&](CVSTGUITimer* timer) {
526 				setBlinkToggle (!isBlinkToggle ());
527 				if (editState.select_start == editState.select_end)
528 					invalid ();
529 			},
530 			500);
531 	}
532 	invalid ();
533 }
534 
535 //-----------------------------------------------------------------------------
setText(const UTF8String & txt)536 void STBTextEditView::setText (const UTF8String& txt)
537 {
538 	charWidthCache.clear ();
539 	CTextLabel::setText (txt);
540 	if (editState.select_start != editState.select_end)
541 		selectAll ();
542 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
543 	uString = StringConvert{}.from_bytes (CTextLabel::getText ().getString ());
544 #endif
545 }
546 
547 //-----------------------------------------------------------------------------
getCharWidth(STB_CharT c,STB_CharT pc) const548 CCoord STBTextEditView::getCharWidth (STB_CharT c, STB_CharT pc) const
549 {
550 	auto platformFont = getFont ()->getPlatformFont ();
551 	assert (platformFont);
552 	auto fontPainter = platformFont->getPainter ();
553 	assert (fontPainter);
554 
555 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
556 	if (pc)
557 	{
558 		UTF8String str (StringConvert{}.to_bytes (pc));
559 		auto pcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
560 		str += StringConvert{}.to_bytes (c);
561 		auto tcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
562 		return tcWidth - pcWidth;
563 	}
564 	UTF8String str (StringConvert{}.to_bytes (c));
565 	return fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
566 #else
567 	if (pc)
568 	{
569 		UTF8String str (std::string (1, pc));
570 		auto pcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
571 		str += std::string (1, c);
572 		auto tcWidth = fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
573 		return tcWidth - pcWidth;
574 	}
575 
576 	UTF8String str (std::string (1, c));
577 	return fontPainter->getStringWidth (nullptr, str.getPlatformString (), true);
578 #endif
579 }
580 
581 //-----------------------------------------------------------------------------
fillCharWidthCache()582 void STBTextEditView::fillCharWidthCache ()
583 {
584 	if (!charWidthCache.empty ())
585 		return;
586 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
587 	auto num = uString.size ();
588 	charWidthCache.resize (num);
589 	for (auto i = 0u; i < num; ++i)
590 		charWidthCache[i] = getCharWidth (uString[i], i == 0 ? 0 : uString[i - 1]);
591 #else
592 	auto num = getText ().length ();
593 	charWidthCache.resize (num);
594 	const auto& str = getText ().getString ();
595 	for (auto i = 0u; i < num; ++i)
596 		charWidthCache[i] = getCharWidth (str[i], i == 0 ? 0 : str[i - 1]);
597 #endif
598 }
599 
600 //-----------------------------------------------------------------------------
calcCursorSizes()601 void STBTextEditView::calcCursorSizes ()
602 {
603 	if (cursorSizesValid ())
604 		return;
605 
606 	auto platformFont = getFont ()->getPlatformFont ();
607 	assert (platformFont);
608 
609 	cursorHeight = platformFont->getAscent () + platformFont->getDescent ();
610 	auto viewHeight = getViewSize ().getHeight ();
611 	cursorOffset = (viewHeight / 2. - cursorHeight / 2.);
612 	setCursorSizesValid (true);
613 }
614 
615 //-----------------------------------------------------------------------------
draw(CDrawContext * context)616 void STBTextEditView::draw (CDrawContext* context)
617 {
618 	fillCharWidthCache ();
619 	calcCursorSizes ();
620 
621 	drawBack (context, nullptr);
622 	drawPlatformText (context, getText ().getPlatformString ());
623 
624 	if (!isBlinkToggle () || editState.select_start != editState.select_end)
625 		return;
626 
627 	// draw cursor
628 	StbTexteditRow row{};
629 	layout (&row, this, 0);
630 
631 	context->setFillColor (getFontColor ());
632 	context->setDrawMode (kAntiAliasing);
633 	CRect r = getViewSize ();
634 	r.setHeight (cursorHeight);
635 	r.offset (row.x0, cursorOffset);
636 	r.setWidth (1);
637 	for (auto i = 0; i < editState.cursor; ++i)
638 		r.offset (charWidthCache[i], 0);
639 	r.offset (-0.5, 0);
640 	context->drawRect (r, kDrawFilled);
641 }
642 
643 //-----------------------------------------------------------------------------
drawBack(CDrawContext * context,CBitmap * newBack)644 void STBTextEditView::drawBack (CDrawContext* context, CBitmap* newBack)
645 {
646 	CTextLabel::drawBack (context, newBack);
647 
648 	auto selStart = editState.select_start;
649 	auto selEnd = editState.select_end;
650 	if (selStart > selEnd)
651 		std::swap (selStart, selEnd);
652 
653 	if (selStart != selEnd)
654 	{
655 		StbTexteditRow row{};
656 		layout (&row, this, 0);
657 
658 		// draw selection
659 		CRect selection = getViewSize ();
660 		selection.setHeight (cursorHeight);
661 		selection.offset (row.x0, cursorOffset);
662 		selection.setWidth (0);
663 		auto index = 0;
664 		for (; index < selStart; ++index)
665 			selection.offset (charWidthCache[index], 0);
666 		for (; index < selEnd; ++index)
667 			selection.right += charWidthCache[index];
668 		context->setFillColor (selectionColor);
669 		context->drawRect (selection, kDrawFilled);
670 	}
671 }
672 
673 //-----------------------------------------------------------------------------
onTextChange()674 void STBTextEditView::onTextChange ()
675 {
676 	if (notifyTextChange ())
677 		return;
678 	if (auto frame = getFrame ())
679 	{
680 		if (frame->inEventProcessing ())
681 		{
682 			setNotifyTextChange (true);
683 			auto self = shared (this);
684 			frame->doAfterEventProcessing ([self]() {
685 				self->setNotifyTextChange (false);
686 				self->callback->platformTextDidChange ();
687 			});
688 		}
689 	}
690 }
691 
692 //-----------------------------------------------------------------------------
deleteChars(STBTextEditView * self,size_t pos,size_t num)693 int STBTextEditView::deleteChars (STBTextEditView* self, size_t pos, size_t num)
694 {
695 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
696 	self->uString.erase (pos, num);
697 	self->setText (StringConvert{}.to_bytes (self->uString));
698 	self->onTextChange ();
699 	return true;
700 #else
701 	auto str = self->text.getString ();
702 	str.erase (pos, num);
703 	self->setText (str.data ());
704 	self->onTextChange ();
705 	return true; // success
706 #endif
707 }
708 
709 //-----------------------------------------------------------------------------
insertChars(STBTextEditView * self,size_t pos,const STB_CharT * text,size_t num)710 int STBTextEditView::insertChars (STBTextEditView* self,
711 								  size_t pos,
712 								  const STB_CharT* text,
713 								  size_t num)
714 {
715 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
716 	self->uString.insert (pos, text, num);
717 	self->setText (StringConvert{}.to_bytes (self->uString));
718 	self->onTextChange ();
719 	return true;
720 #else
721 	auto str = self->text.getString ();
722 	str.insert (pos, text, num);
723 	self->setText (str.data ());
724 	self->onTextChange ();
725 	return true; // success
726 #endif
727 }
728 
729 //-----------------------------------------------------------------------------
getChar(STBTextEditView * self,int pos)730 STB_CharT STBTextEditView::getChar (STBTextEditView* self, int pos)
731 {
732 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
733 	return self->uString[pos];
734 #else
735 	return self->getText ().getString ()[pos];
736 #endif
737 }
738 
739 //-----------------------------------------------------------------------------
getLength(STBTextEditView * self)740 int STBTextEditView::getLength (STBTextEditView* self)
741 {
742 #if VSTGUI_STB_TEXTEDIT_USE_UNICODE
743 	return self->uString.size ();
744 #else
745 	return static_cast<int> (self->getText ().length ());
746 #endif
747 }
748 
749 //-----------------------------------------------------------------------------
layout(StbTexteditRow * row,STBTextEditView * self,int start_i)750 void STBTextEditView::layout (StbTexteditRow* row, STBTextEditView* self, int start_i)
751 {
752 	assert (start_i == 0);
753 
754 	self->fillCharWidthCache ();
755 	auto textWidth = static_cast<float> (
756 		std::accumulate (self->charWidthCache.begin (), self->charWidthCache.end (), 0.));
757 
758 	row->num_chars = static_cast<int> (self->getText ().length ());
759 	row->baseline_y_delta = 1.25;
760 	row->ymin = 0.f;
761 	row->ymax = static_cast<float> (self->getFont ()->getSize ());
762 	switch (self->getHoriAlign ())
763 	{
764 		case kLeftText:
765 		{
766 			row->x0 = static_cast<float> (self->getTextInset ().x);
767 			row->x1 = row->x0 + textWidth;
768 			break;
769 		}
770 		case kCenterText:
771 		{
772 			row->x0 =
773 				static_cast<float> ((self->getViewSize ().getWidth () / 2.) - (textWidth / 2.));
774 			row->x1 = row->x0 + textWidth;
775 			break;
776 		}
777 		default:
778 		{
779 			vstgui_assert (false, "Not Implemented !");
780 			break;
781 		}
782 	}
783 }
784 
785 //-----------------------------------------------------------------------------
getCharWidth(STBTextEditView * self,int n,int i)786 float STBTextEditView::getCharWidth (STBTextEditView* self, int n, int i)
787 {
788 	self->fillCharWidthCache ();
789 	return static_cast<float> (self->charWidthCache[i]);
790 }
791 
792 //-----------------------------------------------------------------------------
793 } // VSTGUI
794