367 lines
12 KiB
C++
367 lines
12 KiB
C++
/*
|
|
nanogui/textbox.h -- Fancy text box with builtin regular
|
|
expression-based validation
|
|
|
|
The text box widget was contributed by Christian Schueller.
|
|
|
|
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
|
|
The widget drawing code is based on the NanoVG demo application
|
|
by Mikko Mononen.
|
|
|
|
All rights reserved. Use of this source code is governed by a
|
|
BSD-style license that can be found in the LICENSE.txt file.
|
|
*/
|
|
/** \file */
|
|
|
|
#pragma once
|
|
|
|
#include <nanogui/compat.h>
|
|
#include <nanogui/widget.h>
|
|
#include <sstream>
|
|
|
|
NAMESPACE_BEGIN(nanogui)
|
|
|
|
/**
|
|
* \class TextBox textbox.h nanogui/textbox.h
|
|
*
|
|
* \brief Fancy text box with builtin regular expression-based validation.
|
|
*
|
|
* \remark
|
|
* This class overrides \ref nanogui::Widget::mIconExtraScale to be ``0.8f``,
|
|
* which affects all subclasses of this Widget. Subclasses must explicitly
|
|
* set a different value if needed (e.g., in their constructor).
|
|
*/
|
|
class NANOGUI_EXPORT TextBox : public Widget {
|
|
public:
|
|
/// How to align the text in the text box.
|
|
enum class Alignment {
|
|
Left,
|
|
Center,
|
|
Right
|
|
};
|
|
|
|
TextBox(Widget *parent, const std::string &value = "Untitled");
|
|
|
|
bool editable() const { return mEditable; }
|
|
void setEditable(bool editable);
|
|
|
|
bool spinnable() const { return mSpinnable; }
|
|
void setSpinnable(bool spinnable) { mSpinnable = spinnable; }
|
|
|
|
const std::string &value() const { return mValue; }
|
|
void setValue(const std::string &value) { mValue = value; }
|
|
|
|
const std::string &defaultValue() const { return mDefaultValue; }
|
|
void setDefaultValue(const std::string &defaultValue) { mDefaultValue = defaultValue; }
|
|
|
|
Alignment alignment() const { return mAlignment; }
|
|
void setAlignment(Alignment align) { mAlignment = align; }
|
|
|
|
const std::string &units() const { return mUnits; }
|
|
void setUnits(const std::string &units) { mUnits = units; }
|
|
|
|
int unitsImage() const { return mUnitsImage; }
|
|
void setUnitsImage(int image) { mUnitsImage = image; }
|
|
|
|
/// Return the underlying regular expression specifying valid formats
|
|
const std::string &format() const { return mFormat; }
|
|
/// Specify a regular expression specifying valid formats
|
|
void setFormat(const std::string &format) { mFormat = format; }
|
|
|
|
/// Return the placeholder text to be displayed while the text box is empty.
|
|
const std::string &placeholder() const { return mPlaceholder; }
|
|
/// Specify a placeholder text to be displayed while the text box is empty.
|
|
void setPlaceholder(const std::string &placeholder) { mPlaceholder = placeholder; }
|
|
|
|
/// Set the \ref Theme used to draw this widget
|
|
virtual void setTheme(Theme *theme) override;
|
|
|
|
/// The callback to execute when the value of this TextBox has changed.
|
|
std::function<bool(const std::string& str)> callback() const { return mCallback; }
|
|
|
|
/// Sets the callback to execute when the value of this TextBox has changed.
|
|
void setCallback(const std::function<bool(const std::string& str)> &callback) { mCallback = callback; }
|
|
|
|
virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override;
|
|
virtual bool mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override;
|
|
virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override;
|
|
virtual bool focusEvent(bool focused) override;
|
|
virtual bool keyboardEvent(int key, int scancode, int action, int modifiers) override;
|
|
virtual bool keyboardCharacterEvent(unsigned int codepoint) override;
|
|
|
|
virtual Vector2i preferredSize(NVGcontext *ctx) const override;
|
|
virtual void draw(NVGcontext* ctx) override;
|
|
virtual void save(Serializer &s) const override;
|
|
virtual bool load(Serializer &s) override;
|
|
protected:
|
|
bool checkFormat(const std::string& input,const std::string& format);
|
|
bool copySelection();
|
|
void pasteFromClipboard();
|
|
bool deleteSelection();
|
|
|
|
void updateCursor(NVGcontext *ctx, float lastx,
|
|
const NVGglyphPosition *glyphs, int size);
|
|
float cursorIndex2Position(int index, float lastx,
|
|
const NVGglyphPosition *glyphs, int size);
|
|
int position2CursorIndex(float posx, float lastx,
|
|
const NVGglyphPosition *glyphs, int size);
|
|
|
|
/// The location (if any) for the spin area.
|
|
enum class SpinArea { None, Top, Bottom };
|
|
SpinArea spinArea(const Vector2i & pos);
|
|
|
|
protected:
|
|
bool mEditable;
|
|
bool mSpinnable;
|
|
bool mCommitted;
|
|
std::string mValue;
|
|
std::string mDefaultValue;
|
|
Alignment mAlignment;
|
|
std::string mUnits;
|
|
std::string mFormat;
|
|
int mUnitsImage;
|
|
std::function<bool(const std::string& str)> mCallback;
|
|
bool mValidFormat;
|
|
std::string mValueTemp;
|
|
std::string mPlaceholder;
|
|
int mCursorPos;
|
|
int mSelectionPos;
|
|
Vector2i mMousePos;
|
|
Vector2i mMouseDownPos;
|
|
Vector2i mMouseDragPos;
|
|
int mMouseDownModifier;
|
|
float mTextOffset;
|
|
double mLastClick;
|
|
public:
|
|
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
|
};
|
|
|
|
/**
|
|
* \class IntBox textbox.h nanogui/textbox.h
|
|
*
|
|
* \brief A specialization of TextBox for representing integral values.
|
|
*
|
|
* Template parameters should be integral types, e.g. ``int``, ``long``,
|
|
* ``uint32_t``, etc.
|
|
*/
|
|
template <typename Scalar>
|
|
class IntBox : public TextBox {
|
|
public:
|
|
IntBox(Widget *parent, Scalar value = (Scalar) 0) : TextBox(parent) {
|
|
setDefaultValue("0");
|
|
setFormat(std::is_signed<Scalar>::value ? "[-]?[0-9]*" : "[0-9]*");
|
|
setValueIncrement(1);
|
|
setMinMaxValues(std::numeric_limits<Scalar>::lowest(), std::numeric_limits<Scalar>::max());
|
|
setValue(value);
|
|
setSpinnable(false);
|
|
}
|
|
|
|
Scalar value() const {
|
|
std::istringstream iss(TextBox::value());
|
|
Scalar value = 0;
|
|
iss >> value;
|
|
return value;
|
|
}
|
|
|
|
void setValue(Scalar value) {
|
|
Scalar clampedValue = std::min(std::max(value, mMinValue),mMaxValue);
|
|
TextBox::setValue(std::to_string(clampedValue));
|
|
}
|
|
|
|
void setCallback(const std::function<void(Scalar)> &cb) {
|
|
TextBox::setCallback(
|
|
[cb, this](const std::string &str) {
|
|
std::istringstream iss(str);
|
|
Scalar value = 0;
|
|
iss >> value;
|
|
setValue(value);
|
|
cb(value);
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
void setValueIncrement(Scalar incr) {
|
|
mValueIncrement = incr;
|
|
}
|
|
void setMinValue(Scalar minValue) {
|
|
mMinValue = minValue;
|
|
}
|
|
void setMaxValue(Scalar maxValue) {
|
|
mMaxValue = maxValue;
|
|
}
|
|
void setMinMaxValues(Scalar minValue, Scalar maxValue) {
|
|
setMinValue(minValue);
|
|
setMaxValue(maxValue);
|
|
}
|
|
|
|
virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override {
|
|
if ((mEditable || mSpinnable) && down)
|
|
mMouseDownValue = value();
|
|
|
|
SpinArea area = spinArea(p);
|
|
if (mSpinnable && area != SpinArea::None && down && !focused()) {
|
|
if (area == SpinArea::Top) {
|
|
setValue(value() + mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
} else if (area == SpinArea::Bottom) {
|
|
setValue(value() - mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return TextBox::mouseButtonEvent(p, button, down, modifiers);
|
|
}
|
|
virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override {
|
|
if (TextBox::mouseDragEvent(p, rel, button, modifiers)) {
|
|
return true;
|
|
}
|
|
if (mSpinnable && !focused() && button == 2 /* 1 << GLFW_MOUSE_BUTTON_2 */ && mMouseDownPos.x() != -1) {
|
|
int valueDelta = static_cast<int>((p.x() - mMouseDownPos.x()) / float(10));
|
|
setValue(mMouseDownValue + valueDelta * mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel) override {
|
|
if (Widget::scrollEvent(p, rel)) {
|
|
return true;
|
|
}
|
|
if (mSpinnable && !focused()) {
|
|
int valueDelta = (rel.y() > 0) ? 1 : -1;
|
|
setValue(value() + valueDelta*mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
private:
|
|
Scalar mMouseDownValue;
|
|
Scalar mValueIncrement;
|
|
Scalar mMinValue, mMaxValue;
|
|
public:
|
|
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
|
};
|
|
|
|
/**
|
|
* \class FloatBox textbox.h nanogui/textbox.h
|
|
*
|
|
* \brief A specialization of TextBox representing floating point values.
|
|
|
|
* Template parameters should be float types, e.g. ``float``, ``double``,
|
|
* ``float64_t``, etc.
|
|
*/
|
|
template <typename Scalar>
|
|
class FloatBox : public TextBox {
|
|
public:
|
|
FloatBox(Widget *parent, Scalar value = (Scalar) 0.f) : TextBox(parent) {
|
|
mNumberFormat = sizeof(Scalar) == sizeof(float) ? "%.4g" : "%.7g";
|
|
setDefaultValue("0");
|
|
setFormat("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?");
|
|
setValueIncrement((Scalar) 0.1);
|
|
setMinMaxValues(std::numeric_limits<Scalar>::lowest(), std::numeric_limits<Scalar>::max());
|
|
setValue(value);
|
|
setSpinnable(false);
|
|
}
|
|
|
|
std::string numberFormat() const { return mNumberFormat; }
|
|
void numberFormat(const std::string &format) { mNumberFormat = format; }
|
|
|
|
Scalar value() const {
|
|
return (Scalar) std::stod(TextBox::value());
|
|
}
|
|
|
|
void setValue(Scalar value) {
|
|
Scalar clampedValue = std::min(std::max(value, mMinValue),mMaxValue);
|
|
char buffer[50];
|
|
NANOGUI_SNPRINTF(buffer, 50, mNumberFormat.c_str(), clampedValue);
|
|
TextBox::setValue(buffer);
|
|
}
|
|
|
|
void setCallback(const std::function<void(Scalar)> &cb) {
|
|
TextBox::setCallback([cb, this](const std::string &str) {
|
|
Scalar scalar = (Scalar) std::stod(str);
|
|
setValue(scalar);
|
|
cb(scalar);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void setValueIncrement(Scalar incr) {
|
|
mValueIncrement = incr;
|
|
}
|
|
void setMinValue(Scalar minValue) {
|
|
mMinValue = minValue;
|
|
}
|
|
void setMaxValue(Scalar maxValue) {
|
|
mMaxValue = maxValue;
|
|
}
|
|
void setMinMaxValues(Scalar minValue, Scalar maxValue) {
|
|
setMinValue(minValue);
|
|
setMaxValue(maxValue);
|
|
}
|
|
|
|
virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override {
|
|
if ((mEditable || mSpinnable) && down)
|
|
mMouseDownValue = value();
|
|
|
|
SpinArea area = spinArea(p);
|
|
if (mSpinnable && area != SpinArea::None && down && !focused()) {
|
|
if (area == SpinArea::Top) {
|
|
setValue(value() + mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
} else if (area == SpinArea::Bottom) {
|
|
setValue(value() - mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return TextBox::mouseButtonEvent(p, button, down, modifiers);
|
|
}
|
|
virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override {
|
|
if (TextBox::mouseDragEvent(p, rel, button, modifiers)) {
|
|
return true;
|
|
}
|
|
if (mSpinnable && !focused() && button == 2 /* 1 << GLFW_MOUSE_BUTTON_2 */ && mMouseDownPos.x() != -1) {
|
|
int valueDelta = static_cast<int>((p.x() - mMouseDownPos.x()) / float(10));
|
|
setValue(mMouseDownValue + valueDelta * mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool scrollEvent(const Vector2i &p, const Vector2f &rel) override {
|
|
if (Widget::scrollEvent(p, rel)) {
|
|
return true;
|
|
}
|
|
if (mSpinnable && !focused()) {
|
|
int valueDelta = (rel.y() > 0) ? 1 : -1;
|
|
setValue(value() + valueDelta*mValueIncrement);
|
|
if (mCallback)
|
|
mCallback(mValue);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private:
|
|
std::string mNumberFormat;
|
|
Scalar mMouseDownValue;
|
|
Scalar mValueIncrement;
|
|
Scalar mMinValue, mMaxValue;
|
|
public:
|
|
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
|
|
};
|
|
|
|
NAMESPACE_END(nanogui)
|