iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
ITextEntryControl.cpp
Go to the documentation of this file.
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9 */
10
17#include "ITextEntryControl.h"
18#include "IPlugPlatform.h"
19#include "IPlugUtilities.h"
20
21using namespace iplug;
22using namespace igraphics;
23
24#define VIRTUAL_KEY_BIT 0x80000000
25#define STB_TEXTEDIT_K_SHIFT 0x40000000
26#define STB_TEXTEDIT_K_CONTROL 0x20000000
27#define STB_TEXTEDIT_K_ALT 0x10000000
28// key-bindings
29#define STB_TEXTEDIT_K_LEFT (VIRTUAL_KEY_BIT | kVK_LEFT)
30#define STB_TEXTEDIT_K_RIGHT (VIRTUAL_KEY_BIT | kVK_RIGHT)
31#define STB_TEXTEDIT_K_UP (VIRTUAL_KEY_BIT | kVK_UP)
32#define STB_TEXTEDIT_K_DOWN (VIRTUAL_KEY_BIT | kVK_DOWN)
33#define STB_TEXTEDIT_K_LINESTART (VIRTUAL_KEY_BIT | kVK_HOME)
34#define STB_TEXTEDIT_K_LINEEND (VIRTUAL_KEY_BIT | kVK_END)
35#define STB_TEXTEDIT_K_WORDLEFT (STB_TEXTEDIT_K_LEFT | STB_TEXTEDIT_K_CONTROL)
36#define STB_TEXTEDIT_K_WORDRIGHT (STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_CONTROL)
37#define STB_TEXTEDIT_K_TEXTSTART (STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_CONTROL)
38#define STB_TEXTEDIT_K_TEXTEND (STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_CONTROL)
39#define STB_TEXTEDIT_K_DELETE (VIRTUAL_KEY_BIT | kVK_DELETE)
40#define STB_TEXTEDIT_K_BACKSPACE (VIRTUAL_KEY_BIT | kVK_BACK)
41#define STB_TEXTEDIT_K_UNDO (STB_TEXTEDIT_K_CONTROL | 'z')
42#define STB_TEXTEDIT_K_REDO (STB_TEXTEDIT_K_CONTROL | STB_TEXTEDIT_K_SHIFT | 'z')
43#define STB_TEXTEDIT_K_INSERT (VIRTUAL_KEY_BIT | kVK_INSERT)
44#define STB_TEXTEDIT_K_PGUP (VIRTUAL_KEY_BIT | kVK_PRIOR)
45#define STB_TEXTEDIT_K_PGDOWN (VIRTUAL_KEY_BIT | kVK_NEXT)
46// functions
47#define STB_TEXTEDIT_STRINGLEN(tc) ITextEntryControl::GetLength (tc)
48#define STB_TEXTEDIT_LAYOUTROW ITextEntryControl::Layout
49#define STB_TEXTEDIT_GETWIDTH(tc, n, i) ITextEntryControl::GetCharWidth (tc, n, i)
50#define STB_TEXTEDIT_KEYTOTEXT(key) \
51((key & VIRTUAL_KEY_BIT) ? 0 : ((key & STB_TEXTEDIT_K_CONTROL) ? 0 : (key & (~0xF0000000))));
52#define STB_TEXTEDIT_GETCHAR(tc, i) ITextEntryControl::GetChar (tc, i)
53#define STB_TEXTEDIT_NEWLINE '\n'
54#define STB_TEXTEDIT_IS_SPACE(ch) isspace(ch)
55#define STB_TEXTEDIT_DELETECHARS ITextEntryControl::DeleteChars
56#define STB_TEXTEDIT_INSERTCHARS ITextEntryControl::InsertChars
57
58#define STB_TEXTEDIT_IMPLEMENTATION
59#include "stb_textedit.h"
60
61
62ITextEntryControl::ITextEntryControl()
63: IControl(IRECT())
64{
65 stb_textedit_initialize_state(&mEditState, true);
66
67 SetActionFunction([&](IControl* pCaller) {
68
69 mDrawCursor = true;
70
71 SetAnimation([&](IControl* pCaller) {
72 auto progress = pCaller->GetAnimationProgress();
73
74 if(progress > 0.5) {
75 mDrawCursor = false;
76 pCaller->SetDirty(false);
77 }
78
79 if(progress > 1.) {
80 pCaller->OnEndAnimation();
81 return;
82 }
83
84 },
85 1000);
86 });
87}
88
90{
91 g.FillRect(mText.mTextEntryBGColor, mRECT);
92
93 StbTexteditRow row;
94 Layout(&row, this, 0);
95
96 const bool hasSelection = mEditState.select_start != mEditState.select_end;
97 if (hasSelection)
98 {
99 float selectionStart = row.x0, selectionEnd = row.x0;
100 const int start = std::min(mEditState.select_start, mEditState.select_end);
101 const int end = std::max(mEditState.select_start, mEditState.select_end);
102 for (int i = 0; i < mCharWidths.GetSize() && i < end; ++i)
103 {
104 if (i < start)
105 selectionStart += mCharWidths.Get()[i];
106
107 selectionEnd += mCharWidths.Get()[i];
108 }
109 IRECT selectionRect(selectionStart, mRECT.T + row.ymin, selectionEnd, mRECT.T + row.ymax);
110 selectionRect = selectionRect.GetVPadded(-mText.mSize*0.1f);
111 IBlend blend(EBlend::Default, 0.2f);
112 g.FillRect(mText.mTextEntryFGColor, selectionRect, &blend);
113 }
114
115 g.DrawText(mText, UTF16ToUTF8String(mEditString).c_str(), mRECT);
116
117 if (mDrawCursor && !hasSelection)
118 {
119 float cursorPos = row.x0;
120 for (int i = 0; i < mCharWidths.GetSize() && i < mEditState.cursor; ++i)
121 {
122 cursorPos += mCharWidths.Get()[i];
123 }
124 IRECT cursorRect(roundf(cursorPos-1), mRECT.T + row.ymin, roundf(cursorPos), mRECT.T + row.ymax);
125 cursorRect = cursorRect.GetVPadded(-mText.mSize*0.1f);
126 g.FillRect(mText.mTextEntryFGColor, cursorRect);
127 }
128}
129
130template<typename Proc>
131bool ITextEntryControl::CallSTB(Proc proc)
132{
133 auto oldState = mEditState;
134 proc();
135
136 if(memcmp(&oldState, &mEditState, sizeof (STB_TexteditState)) != 0)
137 {
138 OnStateChanged(); //TODO:
139 return true;
140 }
141
142 return false;
143}
144
145void ITextEntryControl::OnMouseDown(float x, float y, const IMouseMod& mod)
146{
147 if(!mRECT.Contains(x, y))
148 {
149 CommitEdit();
150 return;
151 }
152
153 if(mod.L)
154 {
155 CallSTB ([&]() {
156 stb_textedit_click(this, &mEditState, x, y);
157 });
158 }
159
160 if(mod.R)
161 {
162 static IPopupMenu menu {"", {"Cut", "Copy", "Paste"}, [&](IPopupMenu* pMenu) {
163 switch (pMenu->GetChosenItemIdx()) {
164 case 0: Cut(); break;
165 case 1: CopySelection(); break;
166 case 2: Paste(); break;
167 default:
168 break;
169 }
170 }
171 };
172
173 GetUI()->CreatePopupMenu(*this, menu, x, y);
174 }
175}
176
177void ITextEntryControl::OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod& mod)
178{
179 if (mod.L)
180 {
181 CallSTB([&]() {
182 stb_textedit_drag(this, &mEditState, x, y);
183 });
184 }
185}
186
187void ITextEntryControl::OnMouseDblClick(float x, float y, const IMouseMod& mod)
188{
189 SelectAll();
190}
191
192void ITextEntryControl::OnMouseUp(float x, float y, const IMouseMod& mod)
193{
194 if (mod.L)
195 {
196 CallSTB([&]() {
197 stb_textedit_drag(this, &mEditState, x, y);
198 });
199
200 SetDirty(true);
201 }
202}
203
204bool ITextEntryControl::OnKeyDown(float x, float y, const IKeyPress& key)
205{
206 if (key.C)
207 {
208 switch (key.VK)
209 {
210 case 'A':
211 {
212 SelectAll();
213 return true;
214 }
215 case 'X':
216 {
217 Cut();
218 return true;
219 }
220 case 'C':
221 {
222 CopySelection();
223 return true;
224 }
225 case 'V':
226 {
227 Paste();
228 return true;
229 }
230 case 'Z':
231 {
232 if (key.S)
233 CallSTB([&]() { stb_textedit_key(this, &mEditState, STB_TEXTEDIT_K_REDO); });
234 else
235 CallSTB([&]() { stb_textedit_key(this, &mEditState, STB_TEXTEDIT_K_UNDO); });
236 return true;
237 }
238
239 default:
240 break;
241 }
242 }
243
244 int stbKey;
245
246 wdl_utf8_parsechar(key.utf8, &stbKey);
247
248 switch (key.VK)
249 {
250 case kVK_SPACE: stbKey = ' '; break;
251 case kVK_TAB: return false;
252 case kVK_DELETE: stbKey = STB_TEXTEDIT_K_DELETE; break;
253 case kVK_BACK: stbKey = STB_TEXTEDIT_K_BACKSPACE; break;
254 case kVK_LEFT: stbKey = STB_TEXTEDIT_K_LEFT; break;
255 case kVK_RIGHT: stbKey = STB_TEXTEDIT_K_RIGHT; break;
256 case kVK_UP: stbKey = STB_TEXTEDIT_K_UP; break;
257 case kVK_DOWN: stbKey = STB_TEXTEDIT_K_DOWN; break;
258 case kVK_PRIOR: stbKey = STB_TEXTEDIT_K_PGUP; break;
259 case kVK_NEXT: stbKey = STB_TEXTEDIT_K_PGDOWN; break;
260 case kVK_HOME: stbKey = STB_TEXTEDIT_K_LINESTART; break;
261 case kVK_END: stbKey = STB_TEXTEDIT_K_LINEEND; break;
262 case kVK_RETURN: CommitEdit(); break;
263 case kVK_ESCAPE: DismissEdit(); break;
264 default:
265 {
266 // validate input based on param type
267 IControl* pControlInTextEntry = GetUI()->GetControlInTextEntry();
268
269 if(!pControlInTextEntry)
270 return false;
271
272 const IParam* pParam = pControlInTextEntry->GetParam();
273
274 if(pParam)
275 {
276 switch (pParam->Type())
277 {
278 case IParam::kTypeEnum:
279 case IParam::kTypeInt:
280 case IParam::kTypeBool:
281 {
282 if (key.VK >= '0' && key.VK <= '9' && !key.S)
283 break;
284 if (key.VK >= kVK_NUMPAD0 && key.VK <= kVK_NUMPAD9)
285 break;
286 if (stbKey == '+' || stbKey == '-')
287 break;
288 stbKey = 0;
289 break;
290 }
291 case IParam::kTypeDouble:
292 {
293 if (key.VK >= '0' && key.VK <= '9' && !key.S)
294 break;
295 if (key.VK >= kVK_NUMPAD0 && key.VK <= kVK_NUMPAD9)
296 break;
297 if (stbKey == '+' || stbKey == '-' || stbKey == '.')
298 break;
299 stbKey = 0;
300 break;
301 }
302 default:
303 break;
304 }
305 }
306
307 if (stbKey == 0)
308 {
309 stbKey = (key.VK) | VIRTUAL_KEY_BIT;
310 }
311 break;
312 }
313 }
314
315 if (key.C)
316 stbKey |= STB_TEXTEDIT_K_CONTROL;
317 if (key.A)
318 stbKey |= STB_TEXTEDIT_K_ALT;
319 if (key.S)
320 stbKey |= STB_TEXTEDIT_K_SHIFT;
321
322 return CallSTB([&]() { stb_textedit_key(this, &mEditState, stbKey); }) ? true : false;
323}
324
325void ITextEntryControl::OnEndAnimation()
326{
327 if(mEditing)
328 SetDirty(true);
329}
330
331void ITextEntryControl::CopySelection()
332{
333 if (mEditState.select_start != mEditState.select_end)
334 {
335 const int start = std::min(mEditState.select_start, mEditState.select_end);
336 const int end = std::max(mEditState.select_start, mEditState.select_end);
337 GetUI()->SetTextInClipboard(UTF16ToUTF8String(mEditString.data() + start, mEditString.data() + end).c_str());
338 }
339}
340
341void ITextEntryControl::Paste()
342{
343 WDL_String fromClipboard;
344 if (GetUI()->GetTextFromClipboard(fromClipboard))
345 {
346 CallSTB([&] {
347 auto uText = UTF8ToUTF16String(fromClipboard.Get());
348 stb_textedit_paste (this, &mEditState, uText.data(), (int) uText.size());
349 });
350 }
351}
352
353void ITextEntryControl::Cut()
354{
355 CopySelection();
356 CallSTB([&] {
357 stb_textedit_cut(this, &mEditState);
358 });
359}
360
361void ITextEntryControl::SelectAll()
362{
363 CallSTB([&]() {
364 mEditState.select_start = 0;
365 mEditState.select_end = static_cast<int>(mEditString.length());
366 });
367}
368
369//static
370int ITextEntryControl::DeleteChars(ITextEntryControl* _this, size_t pos, size_t num)
371{
372 _this->mEditString.erase(pos, num);
373 _this->SetStr(UTF16ToUTF8String(_this->mEditString).c_str());
374 _this->OnTextChange();
375 return true; // TODO: Error checking
376}
377
378//static
379int ITextEntryControl::InsertChars(ITextEntryControl* _this, size_t pos, const char16_t* text, size_t num)
380{
381 _this->mEditString.insert(pos, text, num);
382 _this->SetStr(UTF16ToUTF8String(_this->mEditString).c_str());
383 _this->OnTextChange();
384 return true;
385}
386
387//static
388char16_t ITextEntryControl::GetChar(ITextEntryControl* _this, int pos)
389{
390 return _this->mEditString[pos];
391}
392
393//static
394int ITextEntryControl::GetLength(ITextEntryControl* _this)
395{
396 return static_cast<int>(_this->mEditString.size());
397}
398
399//static
400void ITextEntryControl::Layout(StbTexteditRow* row, ITextEntryControl* _this, int start_i)
401{
402 assert (start_i == 0);
403
404 _this->FillCharWidthCache();
405 float textWidth = 0.;
406
407 for (int i = 0; i < _this->mCharWidths.GetSize(); i++)
408 {
409 textWidth += _this->mCharWidths.Get()[i];
410 }
411
412 row->num_chars = GetLength(_this);
413 row->baseline_y_delta = 1.25;
414
415 switch (_this->GetText().mAlign)
416 {
417 case EAlign::Near:
418 {
419 row->x0 = _this->GetRECT().L;
420 row->x1 = row->x0 + textWidth;
421 break;
422 }
423 case EAlign::Center:
424 {
425 row->x0 = _this->GetRECT().MW() - (textWidth * 0.5f);
426 row->x1 = row->x0 + textWidth;
427 break;
428 }
429 case EAlign::Far:
430 {
431 row->x0 = _this->GetRECT().R - textWidth;
432 row->x1 = row->x0 + textWidth;
433 }
434 }
435
436 switch (_this->GetText().mVAlign)
437 {
438 case EVAlign::Top:
439 {
440 row->ymin = 0;
441 break;
442 }
443 case EVAlign::Middle:
444 {
445 row->ymin = _this->GetRECT().H()*0.5f - _this->GetText().mSize * 0.5f;
446 break;
447 }
448 case EVAlign::Bottom:
449 {
450 row->ymin = _this->GetRECT().H() - _this->GetText().mSize;
451 break;
452 }
453 }
454
455 row->ymax = row->ymin + static_cast<float> (_this->GetText().mSize);
456}
457
458//static
459float ITextEntryControl::GetCharWidth(ITextEntryControl* _this, int n, int i)
460{
461 _this->FillCharWidthCache();
462 return _this->mCharWidths.Get()[i]; // TODO: n not used?
463}
464
465void ITextEntryControl::OnStateChanged()
466{
467 SetDirty(false);
468}
469
470void ITextEntryControl::OnTextChange()
471{
472 mCharWidths.Resize(0, false);
473 FillCharWidthCache();
474}
475
476void ITextEntryControl::FillCharWidthCache()
477{
478 // only calculate when empty
479 if (mCharWidths.GetSize())
480 return;
481
482 const int len = static_cast<int>(mEditString.size());
483 mCharWidths.Resize(len, false);
484 for (int i = 0; i < len; ++i)
485 {
486 mCharWidths.Get()[i] = MeasureCharWidth(mEditString[i], i == 0 ? 0 : mEditString[i - 1]);
487 }
488}
489
490void ITextEntryControl::CalcCursorSizes()
491{
492 //TODO: cache cursor size and location?
493}
494
495// the width of character 'c' should include the kerning between it and the next character,
496// so that when adding up character widths in the cache we can get to the beginning of the visible glyph,
497// which is important for cursor placement to look correct.
498// stb_textedit behavior for clicking in the text field is to place the cursor in front of a character
499// when the xpos is less than halfway into the width of the character and in front of the following character otherwise.
500// see: https://github.com/nothings/stb/issues/6
501float ITextEntryControl::MeasureCharWidth(char16_t c, char16_t nc)
502{
503 IRECT bounds;
504
505 if (nc)
506 {
507 std::string str (UTF16ToUTF8String(nc));
508 float ncWidth = GetUI()->MeasureText(mText, str.c_str(), bounds);
509 str += UTF16ToUTF8String(c);
510 float tcWidth = GetUI()->MeasureText(mText, str.c_str(), bounds);
511 return tcWidth - ncWidth;
512 }
513
514 std::string str (UTF16ToUTF8String(c));
515 return GetUI()->MeasureText(mText, str.c_str(), bounds);
516}
517
518void ITextEntryControl::CreateTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)
519{
520 SetTargetAndDrawRECTs(bounds);
521 SetText(text);
522 mText.mFGColor = mText.mTextEntryFGColor;
523 SetStr(str);
524 SelectAll();
525 mEditState.cursor = 0;
526 OnTextChange();
527 SetDirty(true);
528 mEditing = true;
529}
530
531void ITextEntryControl::DismissEdit()
532{
533 mEditing = false;
537}
538
539void ITextEntryControl::CommitEdit()
540{
541 mEditing = false;
545}
546
547void ITextEntryControl::SetStr(const char* str)
548{
549 mCharWidths.Resize(0, false);
550 mEditString = UTF8ToUTF16String(str);
551}
Include to get consistently named preprocessor macros for different platforms and logging functionali...
Utility functions and macros.
A Text entry widget drawn by IGraphics to optionally override platform text entries.
The lowest level base class of an IGraphics control.
Definition: IControl.h:49
IGraphics * GetUI()
Definition: IControl.h:472
const IRECT & GetRECT() const
Get the rectangular draw area for this control, within the graphics context.
Definition: IControl.h:316
virtual void SetText(const IText &txt)
Set the Text object typically used to determine font/layout/size etc of the main text in a control.
Definition: IControl.h:297
double GetAnimationProgress() const
Get the progress in a control's animation, in the range 0-1.
Definition: IControl.cpp:431
const IParam * GetParam(int valIdx=0) const
Get a const pointer to the IParam object (owned by the editor delegate class), associated with this c...
Definition: IControl.cpp:122
const IText & GetText() const
Get the Text object for the control.
Definition: IControl.h:293
void SetTargetAndDrawRECTs(const IRECT &bounds)
Set BOTH the draw rect and the target area, within the graphics context for this control.
Definition: IControl.h:332
virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)
Mark the control as dirty, i.e.
Definition: IControl.cpp:198
The lowest level base class of an IGraphics context.
Definition: IGraphics.h:86
void DrawText(const IText &text, const char *str, const IRECT &bounds, const IBlend *pBlend=0)
Draw some text to the graphics context in a specific rectangle.
Definition: IGraphics.cpp:683
void CreatePopupMenu(IControl &control, IPopupMenu &menu, const IRECT &bounds, int valIdx=0)
Shows a pop up/contextual menu in relation to a rectangular region of the graphics context.
Definition: IGraphics.cpp:1976
void ClearInTextEntryControl()
Called when the text entry is dismissed, to reset mInTextEntry.
Definition: IGraphics.h:1151
virtual bool SetTextInClipboard(const char *str)=0
Set text in the clipboard.
virtual void FillRect(const IColor &color, const IRECT &bounds, const IBlend *pBlend=0)
Fill a rectangular region of the graphics context with a color.
Definition: IGraphics.cpp:2587
void SetControlValueAfterTextEdit(const char *str)
Called by the platform class after returning from a text entry in order to update a control with a ne...
Definition: IGraphics.cpp:235
void SetAllControlsDirty()
Calls SetDirty() on every control.
Definition: IGraphics.cpp:595
IControl * GetControlInTextEntry()
Definition: IGraphics.h:1148
virtual float MeasureText(const IText &text, const char *str, IRECT &bounds) const
Measure the rectangular region that some text will occupy.
Definition: IGraphics.cpp:691
IPlug's parameter class.
EParamType Type() const
Get the parameter's type.
A class for setting the contents of a pop up menu.
A Text entry widget drawn by IGraphics.
void OnMouseDown(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse down event on this control.
void Draw(IGraphics &g) override
Draw the control to the graphics context.
void OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod &mod) override
Implement this method to respond to a mouse drag event on this control.
void OnMouseDblClick(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse double click event on this control.
bool OnKeyDown(float x, float y, const IKeyPress &key) override
Implement this method to respond to a key down event on this control.
void OnMouseUp(float x, float y, const IMouseMod &mod) override
Implement this method to respond to a mouse up event on this control.
static std::string UTF16ToUTF8String(const std::u16string &u16str)
Convert UTF-16 std::u16string to UTF-8 std::string using WDL functions.
static std::u16string UTF8ToUTF16String(const char *utf8)
Convert UTF-8 string to UTF-16 std::u16string using WDL functions.
Used to manage composite/blend operations, independent of draw class/platform.
Used for key press info, such as ASCII representation, virtual key (mapped to win32 codes) and modifi...
Definition: IPlugStructs.h:615
Used to manage mouse modifiers i.e.
Used to manage a rectangular area, independent of draw class/platform.
IRECT GetVPadded(float padding) const
Get a copy of this IRECT padded in the Y-axis N.B.
float H() const
float MW() const
bool Contains(const IRECT &rhs) const
Returns true if this IRECT completely contains rhs.
IText is used to manage font and text/text entry style for a piece of text on the UI,...