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