1 /*
2     nanogui/textbox.h -- 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 /** \file */
15 
16 #pragma once
17 
18 #include <nanogui/compat.h>
19 #include <nanogui/widget.h>
20 #include <sstream>
21 
NAMESPACE_BEGIN(nanogui)22 NAMESPACE_BEGIN(nanogui)
23 
24 /**
25  * \class TextBox textbox.h nanogui/textbox.h
26  *
27  * \brief Fancy text box with builtin regular expression-based validation.
28  */
29 class NANOGUI_EXPORT TextBox : public Widget {
30 public:
31     /// How to align the text in the text box.
32     enum class Alignment {
33         Left,
34         Center,
35         Right
36     };
37 
38     TextBox(Widget *parent, const std::string &value = "Untitled");
39 
40     bool editable() const { return mEditable; }
41     void setEditable(bool editable);
42 
43     bool spinnable() const { return mSpinnable; }
44     void setSpinnable(bool spinnable) { mSpinnable = spinnable; }
45 
46     const std::string &value() const { return mValue; }
47     void setValue(const std::string &value) { mValue = value; }
48 
49     const std::string &defaultValue() const { return mDefaultValue; }
50     void setDefaultValue(const std::string &defaultValue) { mDefaultValue = defaultValue; }
51 
52     Alignment alignment() const { return mAlignment; }
53     void setAlignment(Alignment align) { mAlignment = align; }
54 
55     const std::string &units() const { return mUnits; }
56     void setUnits(const std::string &units) { mUnits = units; }
57 
58     int unitsImage() const { return mUnitsImage; }
59     void setUnitsImage(int image) { mUnitsImage = image; }
60 
61     /// Return the underlying regular expression specifying valid formats
62     const std::string &format() const { return mFormat; }
63     /// Specify a regular expression specifying valid formats
64     void setFormat(const std::string &format) { mFormat = format; }
65 
66     /// Set the \ref Theme used to draw this widget
67     virtual void setTheme(Theme *theme) override;
68 
69     /// Set the change callback
70     std::function<bool(const std::string& str)> callback() const { return mCallback; }
71     void setCallback(const std::function<bool(const std::string& str)> &callback) { mCallback = callback; }
72 
73     virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override;
74     virtual bool mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override;
75     virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override;
76     virtual bool focusEvent(bool focused) override;
77     virtual bool keyboardEvent(int key, int scancode, int action, int modifiers) override;
78     virtual bool keyboardCharacterEvent(unsigned int codepoint) override;
79 
80     virtual Vector2i preferredSize(NVGcontext *ctx) const override;
81     virtual void draw(NVGcontext* ctx) override;
82     virtual void save(Serializer &s) const override;
83     virtual bool load(Serializer &s) override;
84 protected:
85     bool checkFormat(const std::string& input,const std::string& format);
86     bool copySelection();
87     void pasteFromClipboard();
88     bool deleteSelection();
89 
90     void updateCursor(NVGcontext *ctx, float lastx,
91                       const NVGglyphPosition *glyphs, int size);
92     float cursorIndex2Position(int index, float lastx,
93                                const NVGglyphPosition *glyphs, int size);
94     int position2CursorIndex(float posx, float lastx,
95                              const NVGglyphPosition *glyphs, int size);
96 
97     /// The location (if any) for the spin area.
98     enum class SpinArea { None, Top, Bottom };
99     SpinArea spinArea(const Vector2i & pos);
100 
101 protected:
102     bool mEditable;
103     bool mSpinnable;
104     bool mCommitted;
105     std::string mValue;
106     std::string mDefaultValue;
107     Alignment mAlignment;
108     std::string mUnits;
109     std::string mFormat;
110     int mUnitsImage;
111     std::function<bool(const std::string& str)> mCallback;
112     bool mValidFormat;
113     std::string mValueTemp;
114     int mCursorPos;
115     int mSelectionPos;
116     Vector2i mMousePos;
117     Vector2i mMouseDownPos;
118     Vector2i mMouseDragPos;
119     int mMouseDownModifier;
120     float mTextOffset;
121     double mLastClick;
122 };
123 
124 /**
125  * \class IntBox textbox.h nanogui/textbox.h
126  *
127  * \brief A specialization of TextBox for representing integral values.
128  *
129  * Template parameters should be integral types, e.g. ``int``, ``long``,
130  * ``uint32_t``, etc.
131  */
132 template <typename Scalar>
133 class IntBox : public TextBox {
134 public:
TextBox(parent)135     IntBox(Widget *parent, Scalar value = (Scalar) 0) : TextBox(parent) {
136         setDefaultValue("0");
137         setFormat(std::is_signed<Scalar>::value ? "[-]?[0-9]*" : "[0-9]*");
138         setValueIncrement(1);
139         setMinMaxValues(std::numeric_limits<Scalar>::lowest(), std::numeric_limits<Scalar>::max());
140         setValue(value);
141         setSpinnable(false);
142     }
143 
value()144     Scalar value() const {
145         std::istringstream iss(TextBox::value());
146         Scalar value = 0;
147         iss >> value;
148         return value;
149     }
150 
setValue(Scalar value)151     void setValue(Scalar value) {
152         Scalar clampedValue = std::min(std::max(value, mMinValue),mMaxValue);
153         TextBox::setValue(std::to_string(clampedValue));
154     }
155 
setCallback(const std::function<void (Scalar)> & cb)156     void setCallback(const std::function<void(Scalar)> &cb) {
157         TextBox::setCallback(
158             [cb, this](const std::string &str) {
159                 std::istringstream iss(str);
160                 Scalar value = 0;
161                 iss >> value;
162                 setValue(value);
163                 cb(value);
164                 return true;
165             }
166         );
167     }
168 
setValueIncrement(Scalar incr)169     void setValueIncrement(Scalar incr) {
170         mValueIncrement = incr;
171     }
setMinValue(Scalar minValue)172     void setMinValue(Scalar minValue) {
173         mMinValue = minValue;
174     }
setMaxValue(Scalar maxValue)175     void setMaxValue(Scalar maxValue) {
176         mMaxValue = maxValue;
177     }
setMinMaxValues(Scalar minValue,Scalar maxValue)178     void setMinMaxValues(Scalar minValue, Scalar maxValue) {
179         setMinValue(minValue);
180         setMaxValue(maxValue);
181     }
182 
mouseButtonEvent(const Vector2i & p,int button,bool down,int modifiers)183     virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override {
184         if ((mEditable || mSpinnable) && down)
185             mMouseDownValue = value();
186 
187         SpinArea area = spinArea(p);
188         if (mSpinnable && area != SpinArea::None && down && !focused()) {
189             if (area == SpinArea::Top) {
190                 setValue(value() + mValueIncrement);
191                 if (mCallback)
192                     mCallback(mValue);
193             } else if (area == SpinArea::Bottom) {
194                 setValue(value() - mValueIncrement);
195                 if (mCallback)
196                     mCallback(mValue);
197             }
198             return true;
199         }
200 
201         return TextBox::mouseButtonEvent(p, button, down, modifiers);
202     }
mouseDragEvent(const Vector2i & p,const Vector2i & rel,int button,int modifiers)203     virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override {
204         if (TextBox::mouseDragEvent(p, rel, button, modifiers)) {
205             return true;
206         }
207         if (mSpinnable && !focused() && button == 2 /* 1 << GLFW_MOUSE_BUTTON_2 */ && mMouseDownPos.x() != -1) {
208                 int valueDelta = static_cast<int>((p.x() - mMouseDownPos.x()) / float(10));
209                 setValue(mMouseDownValue + valueDelta * mValueIncrement);
210                 if (mCallback)
211                     mCallback(mValue);
212                 return true;
213         }
214         return false;
215     }
scrollEvent(const Vector2i & p,const Vector2f & rel)216     virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel) override {
217         if (Widget::scrollEvent(p, rel)) {
218             return true;
219         }
220         if (mSpinnable && !focused()) {
221               int valueDelta = (rel.y() > 0) ? 1 : -1;
222               setValue(value() + valueDelta*mValueIncrement);
223               if (mCallback)
224                   mCallback(mValue);
225               return true;
226         }
227         return false;
228     }
229 private:
230     Scalar mMouseDownValue;
231     Scalar mValueIncrement;
232     Scalar mMinValue, mMaxValue;
233 };
234 
235 /**
236  * \class FloatBox textbox.h nanogui/textbox.h
237  *
238  * \brief A specialization of TextBox representing floating point values.
239 
240  * Template parameters should be float types, e.g. ``float``, ``double``,
241  * ``float64_t``, etc.
242  */
243 template <typename Scalar>
244 class FloatBox : public TextBox {
245 public:
TextBox(parent)246     FloatBox(Widget *parent, Scalar value = (Scalar) 0.f) : TextBox(parent) {
247         mNumberFormat = sizeof(Scalar) == sizeof(float) ? "%.4g" : "%.7g";
248         setDefaultValue("0");
249         setFormat("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?");
250         setValueIncrement((Scalar) 0.1);
251         setMinMaxValues(std::numeric_limits<Scalar>::lowest(), std::numeric_limits<Scalar>::max());
252         setValue(value);
253         setSpinnable(false);
254     }
255 
numberFormat()256     std::string numberFormat() const { return mNumberFormat; }
numberFormat(const std::string & format)257     void numberFormat(const std::string &format) { mNumberFormat = format; }
258 
value()259     Scalar value() const {
260         return (Scalar) std::stod(TextBox::value());
261     }
262 
setValue(Scalar value)263     void setValue(Scalar value) {
264         Scalar clampedValue = std::min(std::max(value, mMinValue),mMaxValue);
265         char buffer[50];
266         NANOGUI_SNPRINTF(buffer, 50, mNumberFormat.c_str(), clampedValue);
267         TextBox::setValue(buffer);
268     }
269 
setCallback(const std::function<void (Scalar)> & cb)270     void setCallback(const std::function<void(Scalar)> &cb) {
271         TextBox::setCallback([cb, this](const std::string &str) {
272             Scalar scalar = (Scalar) std::stod(str);
273             setValue(scalar);
274             cb(scalar);
275             return true;
276         });
277     }
278 
setValueIncrement(Scalar incr)279     void setValueIncrement(Scalar incr) {
280         mValueIncrement = incr;
281     }
setMinValue(Scalar minValue)282     void setMinValue(Scalar minValue) {
283         mMinValue = minValue;
284     }
setMaxValue(Scalar maxValue)285     void setMaxValue(Scalar maxValue) {
286         mMaxValue = maxValue;
287     }
setMinMaxValues(Scalar minValue,Scalar maxValue)288     void setMinMaxValues(Scalar minValue, Scalar maxValue) {
289         setMinValue(minValue);
290         setMaxValue(maxValue);
291     }
292 
mouseButtonEvent(const Vector2i & p,int button,bool down,int modifiers)293     virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override {
294         if ((mEditable || mSpinnable) && down)
295             mMouseDownValue = value();
296 
297         SpinArea area = spinArea(p);
298         if (mSpinnable && area != SpinArea::None && down && !focused()) {
299             if (area == SpinArea::Top) {
300                 setValue(value() + mValueIncrement);
301                 if (mCallback)
302                     mCallback(mValue);
303             } else if (area == SpinArea::Bottom) {
304                 setValue(value() - mValueIncrement);
305                 if (mCallback)
306                     mCallback(mValue);
307             }
308             return true;
309         }
310 
311         return TextBox::mouseButtonEvent(p, button, down, modifiers);
312     }
mouseDragEvent(const Vector2i & p,const Vector2i & rel,int button,int modifiers)313     virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override {
314         if (TextBox::mouseDragEvent(p, rel, button, modifiers)) {
315             return true;
316         }
317         if (mSpinnable && !focused() && button == 2 /* 1 << GLFW_MOUSE_BUTTON_2 */ && mMouseDownPos.x() != -1) {
318             int valueDelta = static_cast<int>((p.x() - mMouseDownPos.x()) / float(10));
319             setValue(mMouseDownValue + valueDelta * mValueIncrement);
320             if (mCallback)
321                 mCallback(mValue);
322             return true;
323         }
324         return false;
325     }
scrollEvent(const Vector2i & p,const Vector2f & rel)326     virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel) override {
327         if (Widget::scrollEvent(p, rel)) {
328             return true;
329         }
330         if (mSpinnable && !focused()) {
331             int valueDelta = (rel.y() > 0) ? 1 : -1;
332             setValue(value() + valueDelta*mValueIncrement);
333             if (mCallback)
334                 mCallback(mValue);
335             return true;
336         }
337         return false;
338     }
339 
340 private:
341     std::string mNumberFormat;
342     Scalar mMouseDownValue;
343     Scalar mValueIncrement;
344     Scalar mMinValue, mMaxValue;
345 };
346 
347 NAMESPACE_END(nanogui)
348