1/*
2 src/textbox.cpp -- Fancy text box with builtin regular
3 expression-based validation
4
5 The text box widget was contributed by Christian Schueller.
6
7 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
8 The widget drawing code is based on the NanoVG demo application
9 by Mikko Mononen.
10
11 All rights reserved. Use of this source code is governed by a
12 BSD-style license that can be found in the LICENSE.txt file.
13*/
14
15#include <nanogui/window.h>
16#include <nanogui/screen.h>
17#include <nanogui/textbox.h>
18#include <nanogui/opengl.h>
19#include <nanogui/theme.h>
20#include <nanogui/serializer/core.h>
21#include <regex>
22#include <iostream>
23
24NAMESPACE_BEGIN(nanogui)
25
26TextBox::TextBox(Widget *parent,const std::string &value)
27 : Widget(parent),
28 mEditable(false),
29 mSpinnable(false),
30 mCommitted(true),
31 mValue(value),
32 mDefaultValue(""),
33 mAlignment(Alignment::Center),
34 mUnits(""),
35 mFormat(""),
36 mUnitsImage(-1),
37 mValidFormat(true),
38 mValueTemp(value),
39 mCursorPos(-1),
40 mSelectionPos(-1),
41 mMousePos(Vector2i(-1,-1)),
42 mMouseDownPos(Vector2i(-1,-1)),
43 mMouseDragPos(Vector2i(-1,-1)),
44 mMouseDownModifier(0),
45 mTextOffset(0),
46 mLastClick(0) {
47 if (mTheme) mFontSize = mTheme->mTextBoxFontSize;
48 mIconExtraScale = 0.8f;// widget override
49}
50
51void TextBox::setEditable(bool editable) {
52 mEditable = editable;
53 setCursor(editable ? Cursor::IBeam : Cursor::Arrow);
54}
55
56void TextBox::setTheme(Theme *theme) {
57 Widget::setTheme(theme);
58 if (mTheme)
59 mFontSize = mTheme->mTextBoxFontSize;
60}
61
62Vector2i TextBox::preferredSize(NVGcontext *ctx) const {
63 Vector2i size(0, fontSize() * 1.4f);
64
65 float uw = 0;
66 if (mUnitsImage > 0) {
67 int w, h;
68 nvgImageSize(ctx, mUnitsImage, &w, &h);
69 float uh = size(1) * 0.4f;
70 uw = w * uh / h;
71 } else if (!mUnits.empty()) {
72 uw = nvgTextBounds(ctx, 0, 0, mUnits.c_str(), nullptr, nullptr);
73 }
74 float sw = 0;
75 if (mSpinnable) {
76 sw = 14.f;
77 }
78
79 float ts = nvgTextBounds(ctx, 0, 0, mValue.c_str(), nullptr, nullptr);
80 size(0) = size(1) + ts + uw + sw;
81 return size;
82}
83
84void TextBox::draw(NVGcontext* ctx) {
85 Widget::draw(ctx);
86
87 NVGpaint bg = nvgBoxGradient(ctx,
88 mPos.x() + 1, mPos.y() + 1 + 1.0f, mSize.x() - 2, mSize.y() - 2,
89 3, 4, Color(255, 32), Color(32, 32));
90 NVGpaint fg1 = nvgBoxGradient(ctx,
91 mPos.x() + 1, mPos.y() + 1 + 1.0f, mSize.x() - 2, mSize.y() - 2,
92 3, 4, Color(150, 32), Color(32, 32));
93 NVGpaint fg2 = nvgBoxGradient(ctx,
94 mPos.x() + 1, mPos.y() + 1 + 1.0f, mSize.x() - 2, mSize.y() - 2,
95 3, 4, nvgRGBA(255, 0, 0, 100), nvgRGBA(255, 0, 0, 50));
96
97 nvgBeginPath(ctx);
98 nvgRoundedRect(ctx, mPos.x() + 1, mPos.y() + 1 + 1.0f, mSize.x() - 2,
99 mSize.y() - 2, 3);
100
101 if (mEditable && focused())
102 mValidFormat ? nvgFillPaint(ctx, fg1) : nvgFillPaint(ctx, fg2);
103 else if (mSpinnable && mMouseDownPos.x() != -1)
104 nvgFillPaint(ctx, fg1);
105 else
106 nvgFillPaint(ctx, bg);
107
108 nvgFill(ctx);
109
110 nvgBeginPath(ctx);
111 nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1,
112 mSize.y() - 1, 2.5f);
113 nvgStrokeColor(ctx, Color(0, 48));
114 nvgStroke(ctx);
115
116 nvgFontSize(ctx, fontSize());
117 nvgFontFace(ctx, "sans");
118 Vector2i drawPos(mPos.x(), mPos.y() + mSize.y() * 0.5f + 1);
119
120 float xSpacing = mSize.y() * 0.3f;
121
122 float unitWidth = 0;
123
124 if (mUnitsImage > 0) {
125 int w, h;
126 nvgImageSize(ctx, mUnitsImage, &w, &h);
127 float unitHeight = mSize.y() * 0.4f;
128 unitWidth = w * unitHeight / h;
129 NVGpaint imgPaint = nvgImagePattern(
130 ctx, mPos.x() + mSize.x() - xSpacing - unitWidth,
131 drawPos.y() - unitHeight * 0.5f, unitWidth, unitHeight, 0,
132 mUnitsImage, mEnabled ? 0.7f : 0.35f);
133 nvgBeginPath(ctx);
134 nvgRect(ctx, mPos.x() + mSize.x() - xSpacing - unitWidth,
135 drawPos.y() - unitHeight * 0.5f, unitWidth, unitHeight);
136 nvgFillPaint(ctx, imgPaint);
137 nvgFill(ctx);
138 unitWidth += 2;
139 } else if (!mUnits.empty()) {
140 unitWidth = nvgTextBounds(ctx, 0, 0, mUnits.c_str(), nullptr, nullptr);
141 nvgFillColor(ctx, Color(255, mEnabled ? 64 : 32));
142 nvgTextAlign(ctx, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
143 nvgText(ctx, mPos.x() + mSize.x() - xSpacing, drawPos.y(),
144 mUnits.c_str(), nullptr);
145 unitWidth += 2;
146 }
147
148 float spinArrowsWidth = 0.f;
149
150 if (mSpinnable && !focused()) {
151 spinArrowsWidth = 14.f;
152
153 nvgFontFace(ctx, "icons");
154 nvgFontSize(ctx, ((mFontSize < 0) ? mTheme->mButtonFontSize : mFontSize) * icon_scale());
155
156 bool spinning = mMouseDownPos.x() != -1;
157
158 /* up button */ {
159 bool hover = mMouseFocus && spinArea(mMousePos) == SpinArea::Top;
160 nvgFillColor(ctx, (mEnabled && (hover || spinning)) ? mTheme->mTextColor : mTheme->mDisabledTextColor);
161 auto icon = utf8(mTheme->mTextBoxUpIcon);
162 nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
163 Vector2f iconPos(mPos.x() + 4.f,
164 mPos.y() + mSize.y()/2.f - xSpacing/2.f);
165 nvgText(ctx, iconPos.x(), iconPos.y(), icon.data(), nullptr);
166 }
167
168 /* down button */ {
169 bool hover = mMouseFocus && spinArea(mMousePos) == SpinArea::Bottom;
170 nvgFillColor(ctx, (mEnabled && (hover || spinning)) ? mTheme->mTextColor : mTheme->mDisabledTextColor);
171 auto icon = utf8(mTheme->mTextBoxDownIcon);
172 nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
173 Vector2f iconPos(mPos.x() + 4.f,
174 mPos.y() + mSize.y()/2.f + xSpacing/2.f + 1.5f);
175 nvgText(ctx, iconPos.x(), iconPos.y(), icon.data(), nullptr);
176 }
177
178 nvgFontSize(ctx, fontSize());
179 nvgFontFace(ctx, "sans");
180 }
181
182 switch (mAlignment) {
183 case Alignment::Left:
184 nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
185 drawPos.x() += xSpacing + spinArrowsWidth;
186 break;
187 case Alignment::Right:
188 nvgTextAlign(ctx, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE);
189 drawPos.x() += mSize.x() - unitWidth - xSpacing;
190 break;
191 case Alignment::Center:
192 nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
193 drawPos.x() += mSize.x() * 0.5f;
194 break;
195 }
196
197 nvgFontSize(ctx, fontSize());
198 nvgFillColor(ctx, mEnabled && (!mCommitted || !mValue.empty()) ?
199 mTheme->mTextColor :
200 mTheme->mDisabledTextColor);
201
202 // clip visible text area
203 float clipX = mPos.x() + xSpacing + spinArrowsWidth - 1.0f;
204 float clipY = mPos.y() + 1.0f;
205 float clipWidth = mSize.x() - unitWidth - spinArrowsWidth - 2 * xSpacing + 2.0f;
206 float clipHeight = mSize.y() - 3.0f;
207
208 nvgSave(ctx);
209 nvgIntersectScissor(ctx, clipX, clipY, clipWidth, clipHeight);
210
211 Vector2i oldDrawPos(drawPos);
212 drawPos.x() += mTextOffset;
213
214 if (mCommitted) {
215 nvgText(ctx, drawPos.x(), drawPos.y(),
216 mValue.empty() ? mPlaceholder.c_str() : mValue.c_str(), nullptr);
217 } else {
218 const int maxGlyphs = 1024;
219 NVGglyphPosition glyphs[maxGlyphs];
220 float textBound[4];
221 nvgTextBounds(ctx, drawPos.x(), drawPos.y(), mValueTemp.c_str(),
222 nullptr, textBound);
223 float lineh = textBound[3] - textBound[1];
224
225 // find cursor positions
226 int nglyphs =
227 nvgTextGlyphPositions(ctx, drawPos.x(), drawPos.y(),
228 mValueTemp.c_str(), nullptr, glyphs, maxGlyphs);
229 updateCursor(ctx, textBound[2], glyphs, nglyphs);
230
231 // compute text offset
232 int prevCPos = mCursorPos > 0 ? mCursorPos - 1 : 0;
233 int nextCPos = mCursorPos < nglyphs ? mCursorPos + 1 : nglyphs;
234 float prevCX = cursorIndex2Position(prevCPos, textBound[2], glyphs, nglyphs);
235 float nextCX = cursorIndex2Position(nextCPos, textBound[2], glyphs, nglyphs);
236
237 if (nextCX > clipX + clipWidth)
238 mTextOffset -= nextCX - (clipX + clipWidth) + 1;
239 if (prevCX < clipX)
240 mTextOffset += clipX - prevCX + 1;
241
242 drawPos.x() = oldDrawPos.x() + mTextOffset;
243
244 // draw text with offset
245 nvgText(ctx, drawPos.x(), drawPos.y(), mValueTemp.c_str(), nullptr);
246 nvgTextBounds(ctx, drawPos.x(), drawPos.y(), mValueTemp.c_str(),
247 nullptr, textBound);
248
249 // recompute cursor positions
250 nglyphs = nvgTextGlyphPositions(ctx, drawPos.x(), drawPos.y(),
251 mValueTemp.c_str(), nullptr, glyphs, maxGlyphs);
252
253 if (mCursorPos > -1) {
254 if (mSelectionPos > -1) {
255 float caretx = cursorIndex2Position(mCursorPos, textBound[2],
256 glyphs, nglyphs);
257 float selx = cursorIndex2Position(mSelectionPos, textBound[2],
258 glyphs, nglyphs);
259
260 if (caretx > selx)
261 std::swap(caretx, selx);
262
263 // draw selection
264 nvgBeginPath(ctx);
265 nvgFillColor(ctx, nvgRGBA(255, 255, 255, 80));
266 nvgRect(ctx, caretx, drawPos.y() - lineh * 0.5f, selx - caretx,
267 lineh);
268 nvgFill(ctx);
269 }
270
271 float caretx = cursorIndex2Position(mCursorPos, textBound[2], glyphs, nglyphs);
272
273 // draw cursor
274 nvgBeginPath(ctx);
275 nvgMoveTo(ctx, caretx, drawPos.y() - lineh * 0.5f);
276 nvgLineTo(ctx, caretx, drawPos.y() + lineh * 0.5f);
277 nvgStrokeColor(ctx, nvgRGBA(255, 192, 0, 255));
278 nvgStrokeWidth(ctx, 1.0f);
279 nvgStroke(ctx);
280 }
281 }
282 nvgRestore(ctx);
283}
284
285bool TextBox::mouseButtonEvent(const Vector2i &p, int button, bool down,
286 int modifiers) {
287
288 if (button == GLFW_MOUSE_BUTTON_1 && down && !mFocused) {
289 if (!mSpinnable || spinArea(p) == SpinArea::None) /* not on scrolling arrows */
290 requestFocus();
291 }
292
293 if (mEditable && focused()) {
294 if (down) {
295 mMouseDownPos = p;
296 mMouseDownModifier = modifiers;
297
298 double time = glfwGetTime();
299 if (time - mLastClick < 0.25) {
300 /* Double-click: select all text */
301 mSelectionPos = 0;
302 mCursorPos = (int) mValueTemp.size();
303 mMouseDownPos = Vector2i(-1, -1);
304 }
305 mLastClick = time;
306 } else {
307 mMouseDownPos = Vector2i(-1, -1);
308 mMouseDragPos = Vector2i(-1, -1);
309 }
310 return true;
311 } else if (mSpinnable && !focused()) {
312 if (down) {
313 if (spinArea(p) == SpinArea::None) {
314 mMouseDownPos = p;
315 mMouseDownModifier = modifiers;
316
317 double time = glfwGetTime();
318 if (time - mLastClick < 0.25) {
319 /* Double-click: reset to default value */
320 mValue = mDefaultValue;
321 if (mCallback)
322 mCallback(mValue);
323
324 mMouseDownPos = Vector2i(-1, -1);
325 }
326 mLastClick = time;
327 } else {
328 mMouseDownPos = Vector2i(-1, -1);
329 mMouseDragPos = Vector2i(-1, -1);
330 }
331 } else {
332 mMouseDownPos = Vector2i(-1, -1);
333 mMouseDragPos = Vector2i(-1, -1);
334 }
335 return true;
336 }
337
338 return false;
339}
340
341bool TextBox::mouseMotionEvent(const Vector2i &p, const Vector2i & /* rel */,
342 int /* button */, int /* modifiers */) {
343 mMousePos = p;
344
345 if (!mEditable)
346 setCursor(Cursor::Arrow);
347 else if (mSpinnable && !focused() && spinArea(mMousePos) != SpinArea::None) /* scrolling arrows */
348 setCursor(Cursor::Hand);
349 else
350 setCursor(Cursor::IBeam);
351
352 if (mEditable && focused()) {
353 return true;
354 }
355 return false;
356}
357
358bool TextBox::mouseDragEvent(const Vector2i &p, const Vector2i &/* rel */,
359 int /* button */, int /* modifiers */) {
360 mMousePos = p;
361 mMouseDragPos = p;
362
363 if (mEditable && focused()) {
364 return true;
365 }
366 return false;
367}
368
369bool TextBox::focusEvent(bool focused) {
370 Widget::focusEvent(focused);
371
372 std::string backup = mValue;
373
374 if (mEditable) {
375 if (focused) {
376 mValueTemp = mValue;
377 mCommitted = false;
378 mCursorPos = 0;
379 } else {
380 if (mValidFormat) {
381 if (mValueTemp == "")
382 mValue = mDefaultValue;
383 else
384 mValue = mValueTemp;
385 }
386
387 if (mCallback && !mCallback(mValue))
388 mValue = backup;
389
390 mValidFormat = true;
391 mCommitted = true;
392 mCursorPos = -1;
393 mSelectionPos = -1;
394 mTextOffset = 0;
395 }
396
397 mValidFormat = (mValueTemp == "") || checkFormat(mValueTemp, mFormat);
398 }
399
400 return true;
401}
402
403bool TextBox::keyboardEvent(int key, int /* scancode */, int action, int modifiers) {
404 if (mEditable && focused()) {
405 if (action == GLFW_PRESS || action == GLFW_REPEAT) {
406 if (key == GLFW_KEY_LEFT) {
407 if (modifiers == GLFW_MOD_SHIFT) {
408 if (mSelectionPos == -1)
409 mSelectionPos = mCursorPos;
410 } else {
411 mSelectionPos = -1;
412 }
413
414 if (mCursorPos > 0)
415 mCursorPos--;
416 } else if (key == GLFW_KEY_RIGHT) {
417 if (modifiers == GLFW_MOD_SHIFT) {
418 if (mSelectionPos == -1)
419 mSelectionPos = mCursorPos;
420 } else {
421 mSelectionPos = -1;
422 }
423
424 if (mCursorPos < (int) mValueTemp.length())
425 mCursorPos++;
426 } else if (key == GLFW_KEY_HOME) {
427 if (modifiers == GLFW_MOD_SHIFT) {
428 if (mSelectionPos == -1)
429 mSelectionPos = mCursorPos;
430 } else {
431 mSelectionPos = -1;
432 }
433
434 mCursorPos = 0;
435 } else if (key == GLFW_KEY_END) {
436 if (modifiers == GLFW_MOD_SHIFT) {
437 if (mSelectionPos == -1)
438 mSelectionPos = mCursorPos;
439 } else {
440 mSelectionPos = -1;
441 }
442
443 mCursorPos = (int) mValueTemp.size();
444 } else if (key == GLFW_KEY_BACKSPACE) {
445 if (!deleteSelection()) {
446 if (mCursorPos > 0) {
447 mValueTemp.erase(mValueTemp.begin() + mCursorPos - 1);
448 mCursorPos--;
449 }
450 }
451 } else if (key == GLFW_KEY_DELETE) {
452 if (!deleteSelection()) {
453 if (mCursorPos < (int) mValueTemp.length())
454 mValueTemp.erase(mValueTemp.begin() + mCursorPos);
455 }
456 } else if (key == GLFW_KEY_ENTER) {
457 if (!mCommitted)
458 focusEvent(false);
459 } else if (key == GLFW_KEY_A && modifiers == SYSTEM_COMMAND_MOD) {
460 mCursorPos = (int) mValueTemp.length();
461 mSelectionPos = 0;
462 } else if (key == GLFW_KEY_X && modifiers == SYSTEM_COMMAND_MOD) {
463 copySelection();
464 deleteSelection();
465 } else if (key == GLFW_KEY_C && modifiers == SYSTEM_COMMAND_MOD) {
466 copySelection();
467 } else if (key == GLFW_KEY_V && modifiers == SYSTEM_COMMAND_MOD) {
468 deleteSelection();
469 pasteFromClipboard();
470 }
471
472 mValidFormat =
473 (mValueTemp == "") || checkFormat(mValueTemp, mFormat);
474 }
475
476 return true;
477 }
478
479 return false;
480}
481
482bool TextBox::keyboardCharacterEvent(unsigned int codepoint) {
483 if (mEditable && focused()) {
484 std::ostringstream convert;
485 convert << (char) codepoint;
486
487 deleteSelection();
488 mValueTemp.insert(mCursorPos, convert.str());
489 mCursorPos++;
490
491 mValidFormat = (mValueTemp == "") || checkFormat(mValueTemp, mFormat);
492
493 return true;
494 }
495
496 return false;
497}
498
499bool TextBox::checkFormat(const std::string &input, const std::string &format) {
500 if (format.empty())
501 return true;
502 try {
503 std::regex regex(format);
504 return regex_match(input, regex);
505 } catch (const std::regex_error &) {
506#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 9)
507 std::cerr << "Warning: cannot validate text field due to lacking regular expression support. please compile with GCC >= 4.9" << std::endl;
508 return true;
509#else
510 throw;
511#endif
512 }
513}
514
515bool TextBox::copySelection() {
516 if (mSelectionPos > -1) {
517 Screen *sc = dynamic_cast<Screen *>(this->window()->parent());
518 if (!sc)
519 return false;
520
521 int begin = mCursorPos;
522 int end = mSelectionPos;
523
524 if (begin > end)
525 std::swap(begin, end);
526
527 glfwSetClipboardString(sc->glfwWindow(),
528 mValueTemp.substr(begin, end).c_str());
529 return true;
530 }
531
532 return false;
533}
534
535void TextBox::pasteFromClipboard() {
536 Screen *sc = dynamic_cast<Screen *>(this->window()->parent());
537 if (!sc)
538 return;
539 const char* cbstr = glfwGetClipboardString(sc->glfwWindow());
540 if (cbstr)
541 mValueTemp.insert(mCursorPos, std::string(cbstr));
542}
543
544bool TextBox::deleteSelection() {
545 if (mSelectionPos > -1) {
546 int begin = mCursorPos;
547 int end = mSelectionPos;
548
549 if (begin > end)
550 std::swap(begin, end);
551
552 if (begin == end - 1)
553 mValueTemp.erase(mValueTemp.begin() + begin);
554 else
555 mValueTemp.erase(mValueTemp.begin() + begin,
556 mValueTemp.begin() + end);
557
558 mCursorPos = begin;
559 mSelectionPos = -1;
560 return true;
561 }
562
563 return false;
564}
565
566void TextBox::updateCursor(NVGcontext *, float lastx,
567 const NVGglyphPosition *glyphs, int size) {
568 // handle mouse cursor events
569 if (mMouseDownPos.x() != -1) {
570 if (mMouseDownModifier == GLFW_MOD_SHIFT) {
571 if (mSelectionPos == -1)
572 mSelectionPos = mCursorPos;
573 } else
574 mSelectionPos = -1;
575
576 mCursorPos =
577 position2CursorIndex(mMouseDownPos.x(), lastx, glyphs, size);
578
579 mMouseDownPos = Vector2i(-1, -1);
580 } else if (mMouseDragPos.x() != -1) {
581 if (mSelectionPos == -1)
582 mSelectionPos = mCursorPos;
583
584 mCursorPos =
585 position2CursorIndex(mMouseDragPos.x(), lastx, glyphs, size);
586 } else {
587 // set cursor to last character
588 if (mCursorPos == -2)
589 mCursorPos = size;
590 }
591
592 if (mCursorPos == mSelectionPos)
593 mSelectionPos = -1;
594}
595
596float TextBox::cursorIndex2Position(int index, float lastx,
597 const NVGglyphPosition *glyphs, int size) {
598 float pos = 0;
599 if (index == size)
600 pos = lastx; // last character
601 else
602 pos = glyphs[index].x;
603
604 return pos;
605}
606
607int TextBox::position2CursorIndex(float posx, float lastx,
608 const NVGglyphPosition *glyphs, int size) {
609 int mCursorId = 0;
610 float caretx = glyphs[mCursorId].x;
611 for (int j = 1; j < size; j++) {
612 if (std::abs(caretx - posx) > std::abs(glyphs[j].x - posx)) {
613 mCursorId = j;
614 caretx = glyphs[mCursorId].x;
615 }
616 }
617 if (std::abs(caretx - posx) > std::abs(lastx - posx))
618 mCursorId = size;
619
620 return mCursorId;
621}
622
623TextBox::SpinArea TextBox::spinArea(const Vector2i & pos) {
624 if (0 <= pos.x() - mPos.x() && pos.x() - mPos.x() < 14.f) { /* on scrolling arrows */
625 if (mSize.y() >= pos.y() - mPos.y() && pos.y() - mPos.y() <= mSize.y() / 2.f) { /* top part */
626 return SpinArea::Top;
627 } else if (0.f <= pos.y() - mPos.y() && pos.y() - mPos.y() > mSize.y() / 2.f) { /* bottom part */
628 return SpinArea::Bottom;
629 }
630 }
631 return SpinArea::None;
632}
633
634void TextBox::save(Serializer &s) const {
635 Widget::save(s);
636 s.set("editable", mEditable);
637 s.set("spinnable", mSpinnable);
638 s.set("committed", mCommitted);
639 s.set("value", mValue);
640 s.set("defaultValue", mDefaultValue);
641 s.set("alignment", (int) mAlignment);
642 s.set("units", mUnits);
643 s.set("format", mFormat);
644 s.set("unitsImage", mUnitsImage);
645 s.set("validFormat", mValidFormat);
646 s.set("valueTemp", mValueTemp);
647 s.set("cursorPos", mCursorPos);
648 s.set("selectionPos", mSelectionPos);
649}
650
651bool TextBox::load(Serializer &s) {
652 if (!Widget::load(s)) return false;
653 if (!s.get("editable", mEditable)) return false;
654 if (!s.get("spinnable", mSpinnable)) return false;
655 if (!s.get("committed", mCommitted)) return false;
656 if (!s.get("value", mValue)) return false;
657 if (!s.get("defaultValue", mDefaultValue)) return false;
658 if (!s.get("alignment", mAlignment)) return false;
659 if (!s.get("units", mUnits)) return false;
660 if (!s.get("format", mFormat)) return false;
661 if (!s.get("unitsImage", mUnitsImage)) return false;
662 if (!s.get("validFormat", mValidFormat)) return false;
663 if (!s.get("valueTemp", mValueTemp)) return false;
664 if (!s.get("cursorPos", mCursorPos)) return false;
665 if (!s.get("selectionPos", mSelectionPos)) return false;
666 mMousePos = mMouseDownPos = mMouseDragPos = Vector2i::Constant(-1);
667 mMouseDownModifier = mTextOffset = 0;
668 return true;
669}
670
671NAMESPACE_END(nanogui)
672