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