1 ///
2 module nanogui.textbox;
3 
4 /*
5 	nanogui.textbox.d -- Fancy text box with builtin regular
6 	expression-based validation
7 
8 	The text box widget was contributed by Christian Schueller.
9 
10 	NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
11 	The widget drawing code is based on the NanoVG demo application
12 	by Mikko Mononen.
13 
14 	All rights reserved. Use of this source code is governed by a
15 	BSD-style license that can be found in the LICENSE.txt file.
16 */
17 
18 import std.array : replaceInPlace;
19 import std.algorithm : swap;
20 import std.math : abs;
21 
22 import arsd.nanovega;
23 import nanogui.widget : Widget;
24 import nanogui.theme : Theme;
25 import nanogui.common : Vector2i, MouseAction, MouseButton, Cursor, 
26     Color, boxGradient, fillColor, Vector2f, Key, KeyAction, KeyMod;
27 
28 private auto squeezeGlyphs(T)(T[] glyphs_buffer, T[] glyphs)
29 {
30     import std.algorithm : uniq;
31     size_t i;
32     foreach(e; glyphs.uniq!"a.x==b.x")
33         glyphs_buffer[i++] = e;
34 
35     return glyphs_buffer[0..i];
36 }
37 
38 /**
39  * Fancy text box with builtin regular expression-based validation.
40  *
41  * Remark:
42  *     This class overrides `nanogui.Widget.mIconExtraScale` to be `0.8f`,
43  *     which affects all subclasses of this Widget.  Subclasses must explicitly
44  *     set a different value if needed (e.g., in their constructor).
45  */
46 class TextBox : Widget
47 {
48 public:
49 	/// How to align the text in the text box.
50 	enum Alignment {
51 		Left,
52 		Center,
53 		Right
54 	};
55 
56 	this(Widget parent, string value = "Untitled")
57 	{
58 		super(parent);
59 		mEditable  = false;
60 		mSpinnable = false;
61 		mCommitted = true;
62 		mValue     = value;
63 		mDefaultValue = "";
64 		mAlignment = Alignment.Center;
65 		mUnits = "";
66 		mFormat = "";
67 		mUnitsImage = NVGImage();
68 		mValidFormat = true;
69 		mValueTemp = value;
70 		mCursorPos = -1;
71 		mSelectionPos = -1;
72 		mMousePos = Vector2i(-1,-1);
73 		mMouseDownPos = Vector2i(-1,-1);
74 		mMouseDragPos = Vector2i(-1,-1);
75 		mMouseDownModifier = 0;
76 		mTextOffset = 0;
77 		mLastClick = 0;
78 		if (mTheme) mFontSize = mTheme.mTextBoxFontSize;
79 		mIconExtraScale = 0.8f;// widget override
80 	}
81 
82 	bool editable() const { return mEditable; }
83 	final void editable(bool editable)
84 	{
85 		mEditable = editable;
86 		cursor = editable ? Cursor.IBeam : Cursor.Arrow;
87 	}
88 
89 	final bool spinnable() const { return mSpinnable; }
90 	final void spinnable(bool spinnable) { mSpinnable = spinnable; }
91 
92 	final string value() const { return mValue; }
93 	final void value(string value) { mValue = value; }
94 
95 	final string defaultValue() const { return mDefaultValue; }
96 	final void defaultValue(string defaultValue) { mDefaultValue = defaultValue; }
97 
98 	final Alignment alignment() const { return mAlignment; }
99 	final void alignment(Alignment al) { mAlignment = al; }
100 
101 	final string units() const { return mUnits; }
102 	final void units(string units) { mUnits = units; }
103 
104 	final auto unitsImage() const { return mUnitsImage; }
105 	final void unitsImage(NVGImage image) { mUnitsImage = image; }
106 
107 	/// Return the underlying regular expression specifying valid formats
108 	final string format() const { return mFormat; }
109 	/// Specify a regular expression specifying valid formats
110 	final void format(string format) { mFormat = format; }
111 
112 	/// Return the placeholder text to be displayed while the text box is empty.
113 	final string placeholder() const { return mPlaceholder; }
114 	/// Specify a placeholder text to be displayed while the text box is empty.
115 	final void placeholder(string placeholder) { mPlaceholder = placeholder; }
116 
117 	/// Set the `Theme` used to draw this widget
118 	override void theme(Theme theme)
119 	{
120 		Widget.theme(theme);
121 		if (mTheme)
122 			mFontSize = mTheme.mTextBoxFontSize;
123 	}
124 
125 	/// The callback to execute when the value of this TextBox has changed.
126 	final bool delegate(string str) callback() const { return mCallback; }
127 
128 	/// Sets the callback to execute when the value of this TextBox has changed.
129 	final void callback(bool delegate(string str) callback) { mCallback = callback; }
130 
131 	override bool mouseButtonEvent(Vector2i p, MouseButton button, bool down, int modifiers)
132     {
133         if (button == MouseButton.Left && down && !mFocused)
134         {
135             if (!mSpinnable || spinArea(p) == SpinArea.None) /* not on scrolling arrows */
136                 requestFocus();
137         }
138 
139         if (mEditable && focused)
140         {
141             if (down)
142             {
143                 mMouseDownPos = p;
144                 mMouseDownModifier = modifiers;
145 
146 // double time = glfwGetTime();
147 // if (time - mLastClick < 0.25) {
148 //     /* Double-click: select all text */
149 //     mSelectionPos = 0;
150 //     mCursorPos = (int) mValueTemp.size();
151 //     mMouseDownPos = Vector2i(-1, -1);
152 // }
153 // mLastClick = time;
154             }
155             else
156             {
157                 mMouseDownPos = Vector2i(-1, -1);
158                 mMouseDragPos = Vector2i(-1, -1);
159             }
160             return true;
161         }
162         else if (mSpinnable && !focused)
163         {
164             if (down)
165             {
166                 if (spinArea(p) == SpinArea.None)
167                 {
168                     mMouseDownPos = p;
169                     mMouseDownModifier = modifiers;
170 
171 // double time = glfwGetTime();
172 // if (time - mLastClick < 0.25) {
173 //     /* Double-click: reset to default value */
174 //     mValue = mDefaultValue;
175 //     if (mCallback)
176 //         mCallback(mValue);
177 
178 //     mMouseDownPos = Vector2i(-1, -1);
179 // }
180 // mLastClick = time;
181                 }
182                 else
183                 {
184                     mMouseDownPos = Vector2i(-1, -1);
185                     mMouseDragPos = Vector2i(-1, -1);
186                 }
187             }
188             else
189             {
190                 mMouseDownPos = Vector2i(-1, -1);
191                 mMouseDragPos = Vector2i(-1, -1);
192             }
193             return true;
194         }
195 
196         return false;
197     }
198 
199 	override bool mouseMotionEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
200     {
201         mMousePos = p;
202 
203         if (!mEditable)
204             cursor = Cursor.Arrow;
205         else if (mSpinnable && !focused && spinArea(mMousePos) != SpinArea.None) /* scrolling arrows */
206             cursor = Cursor.Hand;
207         else
208             cursor = Cursor.IBeam;
209 
210         if (mEditable && focused)
211         {
212             return true;
213         }
214         return false;
215     }
216 
217 	override bool mouseDragEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
218     {
219         mMousePos = p;
220         mMouseDragPos = p;
221 
222         if (mEditable && focused)
223             return true;
224 
225         return false;
226     }
227 
228 	override bool focusEvent(bool focused)
229     {
230         super.focusEvent(focused);
231 
232         string backup = mValue;
233 
234         if (mEditable)
235         {
236             if (focused)
237             {
238                 mValueTemp = mValue;
239                 mCommitted = false;
240                 mCursorPos = 0;
241             }
242             else
243             {
244                 if (mValidFormat)
245                 {
246                     if (mValueTemp == "")
247                         mValue = mDefaultValue;
248                     else
249                         mValue = mValueTemp;
250                 }
251 
252                 if (mCallback && !mCallback(mValue))
253                     mValue = backup;
254 
255                 mValidFormat = true;
256                 mCommitted = true;
257                 mCursorPos = -1;
258                 mSelectionPos = -1;
259                 mTextOffset = 0;
260             }
261 
262             mValidFormat = (mValueTemp == "") || checkFormat(mValueTemp, mFormat);
263         }
264 
265         return true;
266     }
267 
268 	override bool keyboardEvent(int key, int scancode, KeyAction action, int modifiers)
269     {
270         if (mEditable && focused)
271         {
272             if (action == KeyAction.Press || action == KeyAction.Repeat)
273             {
274                 import std.uni : byGrapheme;
275                 import std.range : walkLength;
276 
277                 if (key == Key.Left)
278                 {
279                     if (modifiers == KeyMod.Shift)
280                     {
281                         if (mSelectionPos == -1)
282                             mSelectionPos = mCursorPos;
283                     }
284                     else
285                         mSelectionPos = -1;
286 
287                     if (mCursorPos > 0)
288                         mCursorPos--;
289                 }
290                 else if (key == Key.Right)
291                 {
292                     if (modifiers == KeyMod.Shift)
293                     {
294                         if (mSelectionPos == -1)
295                             mSelectionPos = mCursorPos;
296                     }
297                     else
298                         mSelectionPos = -1;
299 
300                     if (mCursorPos < cast(int) mValueTemp.byGrapheme.walkLength)
301                         mCursorPos++;
302                 }
303                 else if (key == Key.Home)
304                 {
305                     if (modifiers == KeyMod.Shift)
306                     {
307                         if (mSelectionPos == -1)
308                             mSelectionPos = mCursorPos;
309                     }
310                     else
311                         mSelectionPos = -1;
312 
313                     mCursorPos = 0;
314                 }
315                 else if (key == Key.End)
316                 {
317                     if (modifiers == KeyMod.Shift)
318                     {
319                         if (mSelectionPos == -1)
320                             mSelectionPos = mCursorPos;
321                     }
322                     else
323                         mSelectionPos = -1;
324 
325                     mCursorPos = cast(int) mValueTemp.byGrapheme.walkLength;
326                 }
327                 else if (key == Key.Backspace)
328                 {
329                     if (!deleteSelection())
330                     {
331                         if (mCursorPos > 0)
332                         {
333                             auto begin = symbolLengthToBytes(mValueTemp, mCursorPos - 1);
334                             auto end   = symbolLengthToBytes(mValueTemp, mCursorPos);
335                             mValueTemp.replaceInPlace(begin, end, (char[]).init);
336                             mCursorPos--;
337                         }
338                     }
339                 }
340                 else if (key == Key.Delete)
341                 {
342                     if (!deleteSelection())
343                     {
344                         if (mCursorPos < cast(int) mValueTemp.byGrapheme.walkLength)
345                         {
346                             auto begin = symbolLengthToBytes(mValueTemp, mCursorPos);
347                             auto end   = symbolLengthToBytes(mValueTemp, mCursorPos+1);
348                             mValueTemp.replaceInPlace(begin, end, (char[]).init);
349                         }
350                     }
351                 }
352                 else if (key == Key.Enter)
353                 {
354                     if (!mCommitted)
355                         focusEvent(false);
356                 }
357                 else if (key == Key.A && modifiers == KeyMod.Ctrl)
358                 {
359                     mCursorPos = cast(int) mValueTemp.byGrapheme.walkLength;
360                     mSelectionPos = 0;
361                 }
362                 else if (key == Key.X && modifiers == KeyMod.Ctrl)
363                 {
364                     copySelection();
365                     deleteSelection();
366                 }
367                 else if (key == Key.C && modifiers == KeyMod.Ctrl)
368                 {
369                     copySelection();
370                 }
371                 else if (key == Key.V && modifiers == KeyMod.Ctrl)
372                 {
373                     deleteSelection();
374                     pasteFromClipboard();
375                 }
376 
377                 mValidFormat =
378                     (mValueTemp == "") || checkFormat(mValueTemp, mFormat);
379             }
380 
381             return true;
382         }
383 
384         return false;
385     }
386 
387 	/// converts length in symbols to length in bytes
388     private auto symbolLengthToBytes(string txt, size_t count)
389     {
390         import std.uni : graphemeStride;
391         size_t pos; // current position in string
392         size_t total_len; // length of pos symbols in bytes
393         while(total_len != txt.length && pos < count)
394         {
395             assert(total_len <= txt.length);
396             // get length of the current graphem in bytes
397             auto len = graphemeStride(txt, total_len);
398             total_len += len;
399             pos++;
400         }
401         return total_len;
402     }
403 
404 	override bool keyboardCharacterEvent(dchar codepoint)
405     {
406         if (mEditable && focused)
407         {
408             deleteSelection();
409             import std.conv : to;
410             auto replacement = codepoint.to!string;
411             auto pos = symbolLengthToBytes(mValueTemp, mCursorPos);
412             mValueTemp = mValueTemp[0..pos] ~ 
413                          replacement ~ 
414                          mValueTemp[pos..$];
415             mCursorPos++;
416 
417             mValidFormat = (mValueTemp == "") || checkFormat(mValueTemp, mFormat);
418 
419             return true;
420         }
421 
422         return false;
423     }
424 
425 	override Vector2i preferredSize(NVGContext nvg) const
426 	{
427 		Vector2i size = Vector2i(0, cast(int) (fontSize * 1.4f));
428 
429 		float uw = 0;
430 		if (mUnitsImage.valid) 
431 		{
432 			int w, h;
433 			nvg.imageSize(mUnitsImage, w, h);
434 			float uh = size[1] * 0.4f;
435 			uw = w * uh / h;
436 		} else if (mUnits.length)
437 		{
438 			uw = nvg.textBounds(0, 0, mUnits, null);
439 		}
440 		float sw = 0;
441 		if (mSpinnable)
442 			sw = 14.0f;
443 
444 		float ts = nvg.textBounds(0, 0, mValue, null);
445 		size[0] = size[1] + cast(int)(ts + uw + sw);
446 		return size;
447 	}
448 
449 	override void draw(NVGContext nvg)
450     {
451         super.draw(nvg);
452 
453         NVGPaint bg = nvg.boxGradient(
454             mPos.x + 1, mPos.y + 1 + 1.0f, mSize.x - 2, mSize.y - 2,
455             3, 4, Color(255, 255, 255, 32), Color(32, 32, 32, 32));
456         NVGPaint fg1 = nvg.boxGradient(
457             mPos.x + 1, mPos.y + 1 + 1.0f, mSize.x - 2, mSize.y - 2,
458             3, 4, Color(150, 150, 150, 32), Color(32, 32, 32, 32));
459         NVGPaint fg2 = nvg.boxGradient(
460             mPos.x + 1, mPos.y + 1 + 1.0f, mSize.x - 2, mSize.y - 2,
461             3, 4, Color(255, 0, 0, 100), Color(255, 0, 0, 50));
462 
463         nvg.beginPath;
464         nvg.roundedRect(mPos.x + 1, mPos.y + 1 + 1.0f, mSize.x - 2,
465                     mSize.y - 2, 3);
466 
467         if (mEditable && focused)
468             mValidFormat ? nvg.fillPaint(fg1) : nvg.fillPaint(fg2);
469         else if (mSpinnable && mMouseDownPos.x != -1)
470             nvg.fillPaint(fg1);
471         else
472             nvg.fillPaint(bg);
473 
474         nvg.fill;
475 
476         nvg.beginPath;
477         nvg.roundedRect(mPos.x + 0.5f, mPos.y + 0.5f, mSize.x - 1,
478                     mSize.y - 1, 2.5f);
479         nvg.strokeColor(NVGColor(0, 0, 0, 48));
480         nvg.stroke;
481 
482         nvg.fontSize(fontSize());
483         if (mTheme !is null)
484             nvg.fontFaceId(mTheme.mFontNormal);
485         else
486             nvg.fontFace("sans");
487 
488         auto draw_pos = Vector2i(mPos.x, cast(int) (mPos.y + mSize.y * 0.5f + 1));
489 
490         float xSpacing = mSize.y * 0.3f;
491 
492         float unitWidth = 0;
493 
494         if (mUnitsImage.valid)
495         {
496             int w, h;
497             nvg.imageSize(mUnitsImage, w, h);
498             float unitHeight = mSize.y * 0.4f;
499             unitWidth = w * unitHeight / h;
500             NVGPaint imgPaint = nvg.imagePattern(
501                 mPos.x + mSize.x - xSpacing - unitWidth,
502                 draw_pos.y - unitHeight * 0.5f, unitWidth, unitHeight, 0,
503                 mUnitsImage, mEnabled ? 0.7f : 0.35f);
504             nvg.beginPath;
505             nvg.rect(mPos.x + mSize.x - xSpacing - unitWidth,
506                     draw_pos.y - unitHeight * 0.5f, unitWidth, unitHeight);
507             nvg.fillPaint(imgPaint);
508             nvg.fill;
509             unitWidth += 2;
510         }
511         else if (mUnits.length)
512         {
513             unitWidth = nvg.textBounds(0, 0, mUnits, null);
514             nvg.fillColor(Color(255, 255, 255, mEnabled ? 64 : 32));
515             NVGTextAlign algn;
516 			algn.right = true;
517 			algn.middle = true;
518 			nvg.textAlign(algn);
519             nvg.text(mPos.x + mSize.x - xSpacing, draw_pos.y,
520                     mUnits);
521             unitWidth += 2;
522         }
523 
524         float spinArrowsWidth = 0.0f;
525 
526         if (mSpinnable && !focused()) {
527             spinArrowsWidth = 14.0f;
528 
529             nvg.fontFace("icons");
530             nvg.fontSize(((mFontSize < 0) ? mTheme.mButtonFontSize : mFontSize) * icon_scale());
531 
532             bool spinning = mMouseDownPos.x != -1;
533 
534             /* up button */ {
535                 bool hover = mMouseFocus && spinArea(mMousePos) == SpinArea.Top;
536                 nvg.fillColor((mEnabled && (hover || spinning)) ? mTheme.mTextColor : mTheme.mDisabledTextColor);
537                 auto icon = mTheme.mTextBoxUpIcon;
538                 NVGTextAlign algn;
539                 algn.left = true;
540                 algn.middle = true;
541                 nvg.textAlign(algn);
542                 auto iconPos = Vector2f(mPos.x + 4.0f,
543                                 mPos.y + mSize.y/2.0f - xSpacing/2.0f);
544                 nvg.text(iconPos.x, iconPos.y, [icon]);
545             }
546 
547             /* down button */ {
548                 bool hover = mMouseFocus && spinArea(mMousePos) == SpinArea.Bottom;
549                 nvg.fillColor((mEnabled && (hover || spinning)) ? mTheme.mTextColor : mTheme.mDisabledTextColor);
550                 auto icon = mTheme.mTextBoxDownIcon;
551                 NVGTextAlign algn;
552                 algn.left = true;
553                 algn.middle = true;
554                 nvg.textAlign(algn);
555                 auto iconPos = Vector2f(mPos.x + 4.0f,
556                                 mPos.y + mSize.y/2.0f + xSpacing/2.0f + 1.5f);
557                 nvg.text(iconPos.x, iconPos.y, [icon]);
558             }
559 
560             nvg.fontSize(fontSize());
561             nvg.fontFace("sans");
562         }
563 
564         final switch (mAlignment) {
565             case Alignment.Left:
566                 NVGTextAlign algn;
567                 algn.left = true;
568                 algn.middle = true;
569                 nvg.textAlign(algn);
570                 draw_pos.x += cast(int)(xSpacing + spinArrowsWidth);
571                 break;
572             case Alignment.Right:
573                 NVGTextAlign algn;
574                 algn.right = true;
575                 algn.middle = true;
576                 nvg.textAlign(algn);
577                 draw_pos.x += cast(int)(mSize.x - unitWidth - xSpacing);
578                 break;
579             case Alignment.Center:
580                 NVGTextAlign algn;
581                 algn.center = true;
582                 algn.middle = true;
583                 nvg.textAlign(algn);
584                 draw_pos.x += cast(int)(mSize.x * 0.5f);
585                 break;
586         }
587 
588         nvg.fontSize(fontSize());
589         nvg.fillColor(mEnabled && (!mCommitted || mValue.length) ?
590             mTheme.mTextColor :
591             mTheme.mDisabledTextColor);
592 
593         // clip visible text area
594         float clipX = mPos.x + xSpacing + spinArrowsWidth - 1.0f;
595         float clipY = mPos.y + 1.0f;
596         float clipWidth = mSize.x - unitWidth - spinArrowsWidth - 2 * xSpacing + 2.0f;
597         float clipHeight = mSize.y - 3.0f;
598 
599         nvg.save;
600         nvg.intersectScissor(clipX, clipY, clipWidth, clipHeight);
601 
602         auto old_draw_pos = Vector2i(draw_pos);
603         draw_pos.x += cast(int) mTextOffset;
604 
605         if (mCommitted) {
606             nvg.text(draw_pos.x, draw_pos.y,
607                 !mValue.length ? mPlaceholder : mValue);
608         } else {
609             const int maxGlyphs = 1024;
610             NVGGlyphPosition[maxGlyphs] glyphs_buffer;
611             float[4] textBound;
612             nvg.textBounds(draw_pos.x, draw_pos.y, mValueTemp,
613                         textBound);
614             float lineh = textBound[3] - textBound[1];
615 
616             // find cursor positions
617             auto glyphs =
618                 nvg.textGlyphPositions(draw_pos.x, draw_pos.y,
619                                     mValueTemp, glyphs_buffer);
620             glyphs = squeezeGlyphs(glyphs_buffer[], glyphs);
621             updateCursor(nvg, textBound[2], glyphs);
622 
623             // compute text offset
624             int prevCPos = mCursorPos > 0 ? mCursorPos - 1 : 0;
625             int len = cast(int) glyphs.length;
626             int nextCPos = mCursorPos < len ? mCursorPos + 1 : len;
627             float prevCX = cursorIndex2Position(prevCPos, textBound[2], glyphs);
628             float nextCX = cursorIndex2Position(nextCPos, textBound[2], glyphs);
629 
630             if (nextCX > clipX + clipWidth)
631                 mTextOffset -= nextCX - (clipX + clipWidth) + 1;
632             if (prevCX < clipX)
633                 mTextOffset += clipX - prevCX + 1;
634 
635             draw_pos.x = cast(int) (old_draw_pos.x + mTextOffset);
636 
637             // draw text with offset
638             nvg.text(draw_pos.x, draw_pos.y, mValueTemp);
639             nvg.textBounds(draw_pos.x, draw_pos.y, mValueTemp, textBound);
640 
641             // recompute cursor positions
642             glyphs = nvg.textGlyphPositions(draw_pos.x, draw_pos.y,
643                     mValueTemp, glyphs_buffer);
644             glyphs = squeezeGlyphs(glyphs_buffer[], glyphs);
645 
646             if (mCursorPos > -1) {
647                 if (mSelectionPos > -1) {
648                     float caretx = cursorIndex2Position(mCursorPos, textBound[2],
649                                                         glyphs);
650                     float selx = cursorIndex2Position(mSelectionPos, textBound[2],
651                                                     glyphs);
652 
653                     if (caretx > selx)
654                     {
655                         swap(caretx, selx);
656                     }
657 
658                     // draw selection
659                     nvg.beginPath;
660                     nvg.fillColor(Color(255, 255, 255, 80));
661                     nvg.rect(caretx, draw_pos.y - lineh * 0.5f, selx - caretx,
662                             lineh);
663                     nvg.fill;
664                 }
665 
666                 float caretx = cursorIndex2Position(mCursorPos, textBound[2], glyphs);
667 
668                 // draw cursor
669                 nvg.beginPath;
670                 nvg.moveTo(caretx, draw_pos.y - lineh * 0.5f);
671                 nvg.lineTo(caretx, draw_pos.y + lineh * 0.5f);
672                 nvg.strokeColor(nvgRGBA(255, 192, 0, 255));
673                 nvg.strokeWidth(1.0f);
674                 nvg.stroke;
675             }
676         }
677         nvg.restore;
678     }
679 
680 // override void save(Serializer &s) const;
681 // override bool load(Serializer &s);
682 protected:
683 
684     // hide method
685     override void cursor(Cursor value)
686     {
687         super.cursor(value);
688     }
689 
690 	bool checkFormat(string input, string format)
691     {
692         if (!format.length)
693             return true;
694         // try
695         // {
696             import std.regex : regex, matchAll;
697             import std.range : walkLength;
698             auto r = regex(format);
699             auto ma = input.matchAll(r);
700             return ma.walkLength == 1;
701         // }
702         // catch (RegexException)
703         // {
704         //     throw;
705         // }
706     }
707 
708 	bool copySelection()
709     {
710         import nanogui.screen : Screen;
711         if (mSelectionPos > -1) {
712             Screen sc = cast(Screen) (window.parent);
713             if (!sc)
714                 return false;
715 
716             int begin = mCursorPos;
717             int end = mSelectionPos;
718 
719             if (begin > end)
720                 swap(begin, end);
721 
722 // glfwSetClipboardString(sc->glfwWindow(),
723 //                     mValueTemp.substr(begin, end).c_str());
724             return true;
725         }
726 
727         return false;
728     }
729 
730 	void pasteFromClipboard()
731     {
732         import nanogui.screen : Screen;
733         Screen sc = cast(Screen) (window.parent);
734         if (!sc)
735             return;
736         // const char* cbstr = glfwGetClipboardString(sc->glfwWindow());
737         // if (cbstr)
738         //     mValueTemp.insert(mCursorPos, std::string(cbstr));
739     }
740 	bool deleteSelection()
741     {
742         if (mSelectionPos > -1) {
743             size_t begin = symbolLengthToBytes(mValueTemp, mCursorPos);
744             size_t end = symbolLengthToBytes(mValueTemp, mSelectionPos);
745 
746             if (begin > end)
747                 swap(begin, end);
748 
749             if (begin == end - 1)
750                 mValueTemp.replaceInPlace(begin, begin+1, (char[]).init);
751             else
752                 mValueTemp.replaceInPlace(begin, end, (char[]).init);
753 
754             import std.utf : count;
755             mCursorPos = cast(int) mValueTemp[0..begin].count;
756             mSelectionPos = -1;
757             return true;
758         }
759 
760         return false;
761     }
762 
763 	void updateCursor(NVGContext nvg, float lastx,
764 					  const(NVGGlyphPosition)[] glyphs)
765     {
766         // handle mouse cursor events
767         if (mMouseDownPos.x != -1) {
768             if (mMouseDownModifier == KeyMod.Shift)
769             {
770                 if (mSelectionPos == -1)
771                     mSelectionPos = mCursorPos;
772             } else
773                 mSelectionPos = -1;
774 
775             mCursorPos =
776                 position2CursorIndex(mMouseDownPos.x, lastx, glyphs);
777 
778             mMouseDownPos = Vector2i(-1, -1);
779         } else if (mMouseDragPos.x != -1) {
780             if (mSelectionPos == -1)
781                 mSelectionPos = mCursorPos;
782 
783             mCursorPos =
784                 position2CursorIndex(mMouseDragPos.x, lastx, glyphs);
785         } else {
786             // set cursor to last character
787             if (mCursorPos == -2)
788                 mCursorPos = cast(int) glyphs.length;
789         }
790 
791         if (mCursorPos == mSelectionPos)
792             mSelectionPos = -1;
793     }
794 	float cursorIndex2Position(int index, float lastx,
795 							   const(NVGGlyphPosition)[] glyphs)
796     {
797         float pos = 0;
798         if (index == glyphs.length)
799             pos = lastx; // last character
800         else
801             pos = glyphs[index].x;
802 
803         return pos;
804     }
805 
806 	int position2CursorIndex(float posx, float lastx,
807 							 const(NVGGlyphPosition)[] glyphs)
808     {
809         int mCursorId = 0;
810         float caretx = glyphs[mCursorId].x;
811         for (int j = 1; j < glyphs.length; j++) {
812             if (abs(caretx - posx) > abs(glyphs[j].x - posx)) {
813                 mCursorId = j;
814                 caretx = glyphs[mCursorId].x;
815             }
816         }
817         if (abs(caretx - posx) > abs(lastx - posx))
818             mCursorId = cast(int) glyphs.length;
819 
820         return mCursorId;
821     }
822 
823 	/// The location (if any) for the spin area.
824 	enum SpinArea { None, Top, Bottom }
825 	SpinArea spinArea(Vector2i pos)
826     {
827         if (0 <= pos.x - mPos.x && pos.x - mPos.x < 14.0f) { /* on scrolling arrows */
828             if (mSize.y >= pos.y - mPos.y && pos.y - mPos.y <= mSize.y / 2.0f) { /* top part */
829                 return SpinArea.Top;
830             } else if (0.0f <= pos.y - mPos.y && pos.y - mPos.y > mSize.y / 2.0f) { /* bottom part */
831                 return SpinArea.Bottom;
832             }
833         }
834         return SpinArea.None;
835     }
836 
837 	bool mEditable;
838 	bool mSpinnable;
839 	bool mCommitted;
840 	string mValue;
841 	string mDefaultValue;
842 	Alignment mAlignment;
843 	string mUnits;
844 	string mFormat;
845 	NVGImage mUnitsImage;
846 	bool delegate(string str) mCallback;
847 	bool mValidFormat;
848 	string mValueTemp;
849 	string mPlaceholder;
850 	int mCursorPos;
851 	int mSelectionPos;
852 	Vector2i mMousePos;
853 	Vector2i mMouseDownPos;
854 	Vector2i mMouseDragPos;
855 	int mMouseDownModifier;
856 	float mTextOffset;
857 	double mLastClick;
858 }
859 
860 // /**
861 //  * \class IntBox textbox.h nanogui/textbox.h
862 //  *
863 //  * \brief A specialization of TextBox for representing integral values.
864 //  *
865 //  * Template parameters should be integral types, e.g. `int`, `long`,
866 //  * `uint32_t`, etc.
867 //  */
868 // template <typename Scalar>
869 // class IntBox : public TextBox {
870 // public:
871 // 	IntBox(Widget *parent, Scalar value = (Scalar) 0) : TextBox(parent) {
872 // 		setDefaultValue("0");
873 // 		setFormat(std::is_signed<Scalar>::value ? "[-]?[0-9]*" : "[0-9]*");
874 // 		setValueIncrement(1);
875 // 		setMinMaxValues(std::numeric_limits<Scalar>::lowest(), std::numeric_limits<Scalar>::max());
876 // 		setValue(value);
877 // 		setSpinnable(false);
878 // 	}
879 
880 // 	Scalar value() const {
881 // 		std::istringstream iss(TextBox::value());
882 // 		Scalar value = 0;
883 // 		iss >> value;
884 // 		return value;
885 // 	}
886 
887 // 	void setValue(Scalar value) {
888 // 		Scalar clampedValue = std::min(std::max(value, mMinValue),mMaxValue);
889 // 		TextBox::setValue(std::to_string(clampedValue));
890 // 	}
891 
892 // 	void setCallback(const std::function<void(Scalar)> &cb) {
893 // 		TextBox::setCallback(
894 // 			[cb, this](const string &str) {
895 // 				std::istringstream iss(str);
896 // 				Scalar value = 0;
897 // 				iss >> value;
898 // 				setValue(value);
899 // 				cb(value);
900 // 				return true;
901 // 			}
902 // 		);
903 // 	}
904 
905 // 	void setValueIncrement(Scalar incr) {
906 // 		mValueIncrement = incr;
907 // 	}
908 // 	void setMinValue(Scalar minValue) {
909 // 		mMinValue = minValue;
910 // 	}
911 // 	void setMaxValue(Scalar maxValue) {
912 // 		mMaxValue = maxValue;
913 // 	}
914 // 	void setMinMaxValues(Scalar minValue, Scalar maxValue) {
915 // 		setMinValue(minValue);
916 // 		setMaxValue(maxValue);
917 // 	}
918 
919 // 	virtual bool mouseButtonEvent(Vector2i p, int button, bool down, int modifiers) override {
920 // 		if ((mEditable || mSpinnable) && down)
921 // 			mMouseDownValue = value();
922 
923 // 		SpinArea area = spinArea(p);
924 // 		if (mSpinnable && area != SpinArea.None && down && !focused()) {
925 // 			if (area == SpinArea.Top) {
926 // 				setValue(value() + mValueIncrement);
927 // 				if (mCallback)
928 // 					mCallback(mValue);
929 // 			} else if (area == SpinArea.Bottom) {
930 // 				setValue(value() - mValueIncrement);
931 // 				if (mCallback)
932 // 					mCallback(mValue);
933 // 			}
934 // 			return true;
935 // 		}
936 
937 // 		return TextBox::mouseButtonEvent(p, button, down, modifiers);
938 // 	}
939 // 	virtual bool mouseDragEvent(Vector2i p, Vector2i rel, int button, int modifiers) override {
940 // 		if (TextBox::mouseDragEvent(p, rel, button, modifiers)) {
941 // 			return true;
942 // 		}
943 // 		if (mSpinnable && !focused() && button == 2 /* 1 << GLFW_MOUSE_BUTTON_2 */ && mMouseDownPos.x != -1) {
944 // 				int valueDelta = static_cast<int>((p.x - mMouseDownPos.x) / float(10));
945 // 				setValue(mMouseDownValue + valueDelta * mValueIncrement);
946 // 				if (mCallback)
947 // 					mCallback(mValue);
948 // 				return true;
949 // 		}
950 // 		return false;
951 // 	}
952 // 	virtual bool scrollEvent(Vector2i p, const Vector2f &rel) override {
953 // 		if (Widget::scrollEvent(p, rel)) {
954 // 			return true;
955 // 		}
956 // 		if (mSpinnable && !focused()) {
957 // 			  int valueDelta = (rel.y > 0) ? 1 : -1;
958 // 			  setValue(value() + valueDelta*mValueIncrement);
959 // 			  if (mCallback)
960 // 				  mCallback(mValue);
961 // 			  return true;
962 // 		}
963 // 		return false;
964 // 	}
965 // private:
966 // 	Scalar mMouseDownValue;
967 // 	Scalar mValueIncrement;
968 // 	Scalar mMinValue, mMaxValue;
969 // public:
970 // 	EIGEN_MAKE_ALIGNED_OPERATOR_NEW
971 // };
972 
973 /**
974  * \class FloatBox textbox.d nanogui/textbox.d
975  *
976  * \brief A specialization of TextBox representing floating point values.
977 
978  * Template parameters should be float types, e.g. `float`, `double`,
979  * `float64_t`, etc.
980  */
981 class FloatBox(Scalar) : TextBox {
982 public:
983 	this(Widget parent, Scalar v = cast(Scalar) 0.0f)
984 	{
985 		super(parent);
986 		mNumberFormat = Scalar.sizeof == float.sizeof ? "%.4g" : "%.7g";
987 		defaultValue("0");
988 		format("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?");valueIncrement(cast(Scalar) 0.1);
989 		minMaxValues(Scalar.min_exp, Scalar.max);
990 		value(v);
991 		spinnable(false);
992 	}
993 
994 	final string numberFormat() const { return mNumberFormat; }
995 	final void numberFormat(string format) { mNumberFormat = format; }
996 
997 	final Scalar value() const {
998 		import std.conv : to;
999 		return TextBox.value.to!Scalar;
1000 	}
1001 
1002 	final void value(Scalar v) {
1003 		import std.algorithm : min, max;
1004 		import std..string : fromStringz;
1005 		import core.stdc.stdio : snprintf;
1006 
1007 		Scalar clampedValue = min(max(v, mMinValue), mMaxValue);
1008 		char[50] buffer;
1009 		snprintf(buffer.ptr, 50, mNumberFormat.ptr, clampedValue);
1010 		TextBox.value(buffer.ptr.fromStringz.dup);
1011 	}
1012 
1013 	final void callback(void delegate(Scalar) cb) {
1014 		TextBox.callback((string str) {
1015 			import std.conv : to;
1016 			Scalar scalar = str.to!Scalar;
1017 			value(scalar);
1018 			cb(scalar);
1019 			return true;
1020 		});
1021 	}
1022 
1023 	final void valueIncrement(Scalar value) {
1024 		mValueIncrement = value;
1025 	}
1026 
1027 	final void minValue(Scalar value) {
1028 		mMinValue = value;
1029 	}
1030 
1031 	final void maxValue(Scalar value) {
1032 		mMaxValue = value;
1033 	}
1034 
1035 	final void minMaxValues(Scalar min_value, Scalar max_value) {
1036 		minValue(min_value);
1037 		maxValue(max_value);
1038 	}
1039 
1040 	override bool mouseButtonEvent(Vector2i p, MouseButton button, bool down, int modifiers)
1041 	{
1042 		if ((mEditable || mSpinnable) && down)
1043 			mMouseDownValue = value();
1044 
1045 		SpinArea area = spinArea(p);
1046 		if (mSpinnable && area != SpinArea.None && down && !focused()) {
1047 			if (area == SpinArea.Top) {
1048 				value(value() + mValueIncrement);
1049 				if (mCallback)
1050 					mCallback(mValue);
1051 			} else if (area == SpinArea.Bottom) {
1052 				value(value() - mValueIncrement);
1053 				if (mCallback)
1054 					mCallback(mValue);
1055 			}
1056 			return true;
1057 		}
1058 
1059 		return TextBox.mouseButtonEvent(p, button, down, modifiers);
1060 	}
1061 
1062 	override bool mouseDragEvent(Vector2i p, Vector2i rel, MouseButton button, int modifiers)
1063 	{
1064 		if (TextBox.mouseDragEvent(p, rel, button, modifiers)) {
1065 			return true;
1066 		}
1067 		if (mSpinnable && !focused() && button == 2 /* 1 << GLFW_MOUSE_BUTTON_2 */ && mMouseDownPos.x != -1) {
1068 			int valueDelta = cast(int)((p.x - mMouseDownPos.x) / float(10));
1069 			value(mMouseDownValue + valueDelta * mValueIncrement);
1070 			if (mCallback)
1071 				mCallback(mValue);
1072 			return true;
1073 		}
1074 		return false;
1075 	}
1076 
1077 	override bool scrollEvent(Vector2i p, Vector2f rel)
1078 	{
1079 		if (Widget.scrollEvent(p, rel)) {
1080 			return true;
1081 		}
1082 		if (mSpinnable && !focused()) {
1083 			const valueDelta = (rel.y > 0) ? 1 : -1;
1084 			value(value() + valueDelta*mValueIncrement);
1085 			if (mCallback)
1086 				mCallback(mValue);
1087 			return true;
1088 		}
1089 		return false;
1090 	}
1091 
1092 private:
1093 	string mNumberFormat;
1094 	Scalar mMouseDownValue;
1095 	Scalar mValueIncrement;
1096 	Scalar mMinValue, mMaxValue;
1097 }