iPlug 2: ITextEntryControl.cpp Source File

1

2

3

4

5

6

7

8

9

10

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

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

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()

64{

65 stb_textedit_initialize_state(&mEditState, true);

66

67 SetActionFunction([&](IControl* pCaller) {

68

69 mDrawCursor = true;

70

71 SetAnimation([&](IControl* pCaller) {

73

74 if(progress > 0.5) {

75 mDrawCursor = 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

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();

139 return true;

140 }

141

142 return false;

143}

144

146{

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

174 }

175}

176

178{

179 if (mod.L)

180 {

181 CallSTB([&]() {

182 stb_textedit_drag(this, &mEditState, x, y);

183 });

184 }

185}

186

188{

189 SelectAll();

190}

191

193{

194 if (mod.L)

195 {

196 CallSTB([&]() {

197 stb_textedit_drag(this, &mEditState, x, y);

198 });

199

201 }

202}

203

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

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)

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);

338 }

339}

340

341void ITextEntryControl::Paste()

342{

343 WDL_String fromClipboard;

344 if (GetUI()->GetTextFromClipboard(fromClipboard))

345 {

346 CallSTB([&] {

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

370int ITextEntryControl::DeleteChars(ITextEntryControl* _this, size_t pos, size_t num)

371{

372 _this->mEditString.erase(pos, num);

374 _this->OnTextChange();

375 return true;

376}

377

378

379int ITextEntryControl::InsertChars(ITextEntryControl* _this, size_t pos, const char16_t* text, size_t num)

380{

381 _this->mEditString.insert(pos, text, num);

383 _this->OnTextChange();

384 return true;

385}

386

387

388char16_t ITextEntryControl::GetChar(ITextEntryControl* _this, int pos)

389{

390 return _this->mEditString[pos];

391}

392

393

395{

396 return static_cast<int>(_this->mEditString.size());

397}

398

399

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

459float ITextEntryControl::GetCharWidth(ITextEntryControl* _this, int n, int i)

460{

461 _this->FillCharWidthCache();

462 return _this->mCharWidths.Get()[i];

463}

464

465void ITextEntryControl::OnStateChanged()

466{

468}

469

470void ITextEntryControl::OnTextChange()

471{

472 mCharWidths.Resize(0, false);

473 FillCharWidthCache();

474}

475

476void ITextEntryControl::FillCharWidthCache()

477{

478

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

493}

494

495

496

497

498

499

500

501float ITextEntryControl::MeasureCharWidth(char16_t c, char16_t nc)

502{

504

505 if (nc)

506 {

508 float ncWidth = GetUI()->MeasureText(mText, str.c_str(), bounds);

510 float tcWidth = GetUI()->MeasureText(mText, str.c_str(), bounds);

511 return tcWidth - ncWidth;

512 }

513

516}

517

518void ITextEntryControl::CreateTextEntry(int paramIdx, const IText& text, const IRECT& bounds, int length, const char* str)

519{

522 mText.mFGColor = mText.mTextEntryFGColor;

523 SetStr(str);

524 SelectAll();

525 mEditState.cursor = 0;

526 OnTextChange();

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);

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.

const IRECT & GetRECT() const

Get the rectangular draw area for this control, within the graphics context.

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.

double GetAnimationProgress() const

Get the progress in a control's animation, in the range 0-1.

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...

const IText & GetText() const

Get the Text object for the control.

void SetTargetAndDrawRECTs(const IRECT &bounds)

Set BOTH the draw rect and the target area, within the graphics context for this control.

virtual void SetDirty(bool triggerAction=true, int valIdx=kNoValIdx)

Mark the control as dirty, i.e.

The lowest level base class of an IGraphics context.

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.

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.

void ClearInTextEntryControl()

Called when the text entry is dismissed, to reset mInTextEntry.

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.

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...

void SetAllControlsDirty()

Calls SetDirty() on every control.

IControl * GetControlInTextEntry()

virtual float MeasureText(const IText &text, const char *str, IRECT &bounds) const

Measure the rectangular region that some text will occupy.

EParamType Type() const

Get the parameter's type.

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...

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.

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,...