437 lines
13 KiB
C++
437 lines
13 KiB
C++
#include "StarTextBoxWidget.hpp"
|
|
#include "StarRoot.hpp"
|
|
#include "StarJsonExtra.hpp"
|
|
#include "StarAssets.hpp"
|
|
|
|
namespace Star {
|
|
|
|
TextBoxWidget::TextBoxWidget(String const& startingText, String const& hint, WidgetCallbackFunc callback)
|
|
: m_text(startingText), m_hint(hint), m_callback(callback) {
|
|
auto assets = Root::singleton().assets();
|
|
m_textHidden = false;
|
|
m_regex = ".*";
|
|
m_repeatKeyThreshold = 0;
|
|
m_repeatCode = SpecialRepeatKeyCodes::None;
|
|
m_isPressed = false;
|
|
m_isHover = false;
|
|
m_cursorOffset = startingText.size();
|
|
m_hAnchor = HorizontalAnchor::LeftAnchor;
|
|
m_vAnchor = VerticalAnchor::BottomAnchor;
|
|
m_drawBorder = false;
|
|
m_cursorHoriz = Vec2I();
|
|
m_cursorVert = Vec2I();
|
|
m_overfillMode = true;
|
|
|
|
m_maxWidth = assets->json("/interface.config:textBoxDefaultWidth").toInt();
|
|
auto fontConfig = assets->json("/interface.config:font");
|
|
m_fontSize = fontConfig.getInt("baseSize");
|
|
m_processingDirectives = fontConfig.getString("defaultDirectives");
|
|
m_font = fontConfig.queryString("defaultFont", "");
|
|
m_color = Color::rgb(jsonToVec3B(fontConfig.getArray("defaultColor")));
|
|
|
|
// Meh, padding is hard-coded here
|
|
setSize({m_maxWidth + 6, m_fontSize + 2});
|
|
m_cursorHoriz = jsonToVec2I(assets->json("/interface.config:textboxCursorHorizontal"));
|
|
m_cursorVert = jsonToVec2I(assets->json("/interface.config:textboxCursorVertical"));
|
|
}
|
|
|
|
void TextBoxWidget::renderImpl() {
|
|
float blueRate = 0;
|
|
if (m_isHover && !m_isPressed)
|
|
blueRate = 0.2f;
|
|
else
|
|
blueRate = 0.0f;
|
|
|
|
Vec2F pos(screenPosition());
|
|
if (m_hAnchor == HorizontalAnchor::HMidAnchor)
|
|
pos += Vec2F(size()[0] / 2.0f, 0);
|
|
else if (m_hAnchor == HorizontalAnchor::RightAnchor)
|
|
pos += Vec2F(size()[0], 0);
|
|
|
|
context()->setFont(m_font);
|
|
if ((m_maxWidth != -1) && m_overfillMode) {
|
|
context()->setFontSize(m_fontSize);
|
|
int shift = std::max(0, getCursorOffset() - m_maxWidth);
|
|
pos += Vec2F(-shift, 0);
|
|
}
|
|
|
|
context()->setFontSize(m_fontSize);
|
|
context()->setFontProcessingDirectives(m_processingDirectives);
|
|
if (m_text.empty()) {
|
|
context()->setFontColor(m_color.mix(Color::rgbf(0.3f, 0.3f, 0.3f), 0.8f).mix(Color::rgbf(0.0f, 0.0f, 1.0f), blueRate).toRgba());
|
|
context()->renderInterfaceText(m_hint, {pos, m_hAnchor, m_vAnchor});
|
|
} else {
|
|
context()->setFontColor(m_color.mix(Color::rgbf(0, 0, 1), blueRate).toRgba());
|
|
if (m_textHidden) {
|
|
String hiddenText('*', m_text.length());
|
|
context()->renderInterfaceText(hiddenText, { pos, m_hAnchor, m_vAnchor });
|
|
}
|
|
else
|
|
context()->renderInterfaceText(m_text, { pos, m_hAnchor, m_vAnchor });
|
|
}
|
|
context()->setDefaultFont();
|
|
context()->setFontProcessingDirectives("");
|
|
context()->setFontColor(Vec4B::filled(255));
|
|
|
|
if (hasFocus()) {
|
|
// render cursor
|
|
float cc = 0.6f + 0.4f * std::sin((double)Time::monotonicMilliseconds() / 300.0);
|
|
Color cursorColor = Color::rgbf(cc, cc, cc);
|
|
|
|
context()->drawInterfaceLine(
|
|
pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[0]),
|
|
pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[1]),
|
|
cursorColor.toRgba());
|
|
context()->drawInterfaceLine(
|
|
pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[0], m_fontSize * m_cursorVert[0]),
|
|
pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[1], m_fontSize * m_cursorVert[0]),
|
|
cursorColor.toRgba());
|
|
}
|
|
|
|
if (m_drawBorder)
|
|
context()->drawInterfacePolyLines(PolyF(screenBoundRect()), Color(Color::White).toRgba());
|
|
}
|
|
|
|
int TextBoxWidget::getCursorOffset() { // horizontal only
|
|
float scale;
|
|
context()->setFont(m_font);
|
|
context()->setFontSize(m_fontSize);
|
|
if (m_hAnchor == HorizontalAnchor::LeftAnchor) {
|
|
scale = 1.0;
|
|
} else if (m_hAnchor == HorizontalAnchor::HMidAnchor) {
|
|
scale = 0.5;
|
|
} else if (m_hAnchor == HorizontalAnchor::RightAnchor) {
|
|
scale = -1.0;
|
|
if (m_textHidden) {
|
|
int width = context()->stringInterfaceWidth("*");
|
|
size_t chars = m_text.size();
|
|
return (width * chars) * scale + (width * (chars - m_cursorOffset));
|
|
} else {
|
|
return context()->stringInterfaceWidth(m_text) * scale
|
|
+ context()->stringInterfaceWidth(m_text.substr(m_cursorOffset, m_text.size()));
|
|
}
|
|
} else {
|
|
throw GuiException("Somehow, the value of m_hAnchor became bad");
|
|
}
|
|
|
|
if (m_textHidden) {
|
|
int width = context()->stringInterfaceWidth("*");
|
|
size_t chars = m_text.size();
|
|
return (int)std::ceil((width * chars) * scale - (width * (chars - m_cursorOffset)));
|
|
} else {
|
|
return (int)std::ceil(context()->stringInterfaceWidth(m_text) * scale
|
|
- context()->stringInterfaceWidth(m_text.substr(m_cursorOffset, m_text.size())));
|
|
}
|
|
}
|
|
|
|
void TextBoxWidget::update(float dt) {
|
|
Widget::update(dt);
|
|
if (m_repeatCode != SpecialRepeatKeyCodes::None) {
|
|
if (Time::monotonicMilliseconds() >= m_repeatKeyThreshold) {
|
|
m_repeatKeyThreshold += 50;
|
|
if (m_repeatCode == SpecialRepeatKeyCodes::Delete) {
|
|
if (m_cursorOffset < (int)m_text.size())
|
|
modText(m_text.substr(0, m_cursorOffset) + m_text.substr(m_cursorOffset + 1));
|
|
} else if (m_repeatCode == SpecialRepeatKeyCodes::Backspace) {
|
|
if (m_cursorOffset > 0) {
|
|
if (modText(m_text.substr(0, m_cursorOffset - 1) + m_text.substr(m_cursorOffset)))
|
|
m_cursorOffset -= 1;
|
|
}
|
|
} else if (m_repeatCode == SpecialRepeatKeyCodes::Left) {
|
|
if (m_cursorOffset > 0) {
|
|
m_cursorOffset--;
|
|
if (m_cursorOffset < 0)
|
|
m_cursorOffset = 0;
|
|
}
|
|
} else if (m_repeatCode == SpecialRepeatKeyCodes::Right) {
|
|
if (m_cursorOffset < (int)m_text.size()) {
|
|
m_cursorOffset++;
|
|
if (m_cursorOffset > (int)m_text.size())
|
|
m_cursorOffset = m_text.size();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
String const& TextBoxWidget::getText() const {
|
|
return m_text;
|
|
}
|
|
|
|
bool TextBoxWidget::setText(String const& text, bool callback) {
|
|
if (m_text == text)
|
|
return true;
|
|
|
|
if (!newTextValid(text))
|
|
return false;
|
|
|
|
m_text = text;
|
|
m_cursorOffset = m_text.size();
|
|
m_repeatCode = SpecialRepeatKeyCodes::None;
|
|
if (callback)
|
|
m_callback(this);
|
|
return true;
|
|
}
|
|
|
|
bool TextBoxWidget::getHidden() const {
|
|
return m_textHidden;
|
|
}
|
|
|
|
void TextBoxWidget::setHidden(bool hidden) {
|
|
m_textHidden = hidden;
|
|
}
|
|
|
|
String TextBoxWidget::getRegex() {
|
|
return m_regex;
|
|
}
|
|
|
|
void TextBoxWidget::setRegex(String const& regex) {
|
|
m_regex = regex;
|
|
}
|
|
|
|
void TextBoxWidget::setColor(Color const& color) {
|
|
m_color = color;
|
|
}
|
|
|
|
void TextBoxWidget::setDirectives(String const& directives) {
|
|
m_processingDirectives = directives;
|
|
}
|
|
|
|
void TextBoxWidget::setFontSize(int fontSize) {
|
|
m_fontSize = fontSize;
|
|
}
|
|
|
|
void TextBoxWidget::setMaxWidth(int maxWidth) {
|
|
m_maxWidth = maxWidth;
|
|
setSize({m_maxWidth + 6, m_fontSize + 2});
|
|
}
|
|
|
|
void TextBoxWidget::setOverfillMode(bool overtype) {
|
|
m_overfillMode = overtype;
|
|
}
|
|
|
|
void TextBoxWidget::setOnBlurCallback(WidgetCallbackFunc onBlur) {
|
|
m_onBlur = onBlur;
|
|
}
|
|
|
|
void TextBoxWidget::setOnEnterKeyCallback(WidgetCallbackFunc onEnterKey) {
|
|
m_onEnterKey = onEnterKey;
|
|
}
|
|
|
|
void TextBoxWidget::setOnEscapeKeyCallback(WidgetCallbackFunc onEscapeKey) {
|
|
m_onEscapeKey = onEscapeKey;
|
|
}
|
|
|
|
void TextBoxWidget::setNextFocus(Maybe<String> nextFocus) {
|
|
m_nextFocus = nextFocus;
|
|
}
|
|
|
|
void TextBoxWidget::setPrevFocus(Maybe<String> prevFocus) {
|
|
m_prevFocus = prevFocus;
|
|
}
|
|
|
|
void TextBoxWidget::mouseOver() {
|
|
Widget::mouseOver();
|
|
m_isHover = true;
|
|
}
|
|
|
|
void TextBoxWidget::mouseOut() {
|
|
Widget::mouseOut();
|
|
m_isHover = false;
|
|
m_isPressed = false;
|
|
}
|
|
|
|
void TextBoxWidget::mouseReturnStillDown() {
|
|
Widget::mouseReturnStillDown();
|
|
m_isHover = true;
|
|
m_isPressed = true;
|
|
}
|
|
|
|
void TextBoxWidget::blur() {
|
|
Widget::blur();
|
|
if (m_onBlur)
|
|
m_onBlur(this);
|
|
m_repeatCode = SpecialRepeatKeyCodes::None;
|
|
}
|
|
|
|
KeyboardCaptureMode TextBoxWidget::keyboardCaptured() const {
|
|
if (active() && hasFocus())
|
|
return KeyboardCaptureMode::TextInput;
|
|
return KeyboardCaptureMode::None;
|
|
}
|
|
|
|
bool TextBoxWidget::sendEvent(InputEvent const& event) {
|
|
if (!hasFocus())
|
|
return false;
|
|
|
|
if (event.is<KeyUpEvent>()) {
|
|
m_repeatCode = SpecialRepeatKeyCodes::None;
|
|
m_callback(this);
|
|
return false;
|
|
}
|
|
|
|
if (innerSendEvent(event)) {
|
|
m_callback(this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TextBoxWidget::innerSendEvent(InputEvent const& event) {
|
|
if (auto keyDown = event.ptr<KeyDownEvent>()) {
|
|
m_repeatKeyThreshold = Time::monotonicMilliseconds() + 300LL;
|
|
|
|
if (keyDown->key == Key::Escape) {
|
|
if (m_onEscapeKey) {
|
|
m_onEscapeKey(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (keyDown->key == Key::Return || keyDown->key == Key::Kp_enter) {
|
|
if (m_onEnterKey) {
|
|
m_onEnterKey(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (keyDown->key == Key::Tab) {
|
|
if ((KeyMod::LShift & keyDown->mods) == KeyMod::LShift || (KeyMod::RShift & keyDown->mods) == KeyMod::RShift) {
|
|
if (m_prevFocus) {
|
|
if (auto prevWidget = parent()->fetchChild(*m_prevFocus)) {
|
|
prevWidget->focus();
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
if (m_nextFocus) {
|
|
if (auto nextWidget = parent()->fetchChild(*m_nextFocus)) {
|
|
nextWidget->focus();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
if ((keyDown->mods & (KeyMod::LCtrl | KeyMod::RCtrl)) != KeyMod::NoMod) {
|
|
if (keyDown->key == Key::C) {
|
|
context()->setClipboard(m_text);
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::X) {
|
|
context()->setClipboard(m_text);
|
|
if (modText(""))
|
|
m_cursorOffset = 0;
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::V) {
|
|
String clipboard = context()->getClipboard();
|
|
if (modText(m_text.substr(0, m_cursorOffset) + clipboard + m_text.substr(m_cursorOffset)))
|
|
m_cursorOffset += clipboard.size();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
auto calculateSteps = [&](bool dir) {
|
|
int steps = 1;
|
|
if ((keyDown->mods & (KeyMod::LCtrl | KeyMod::RCtrl)) != KeyMod::NoMod || (keyDown->mods & (KeyMod::LAlt | KeyMod::RAlt)) != KeyMod::NoMod) {
|
|
if (dir) // right
|
|
steps = m_text.findNextBoundary(m_cursorOffset) - m_cursorOffset;
|
|
else // left
|
|
steps = m_cursorOffset - m_text.findNextBoundary(m_cursorOffset, true);
|
|
|
|
return steps < 1 ? 1 : steps;
|
|
}
|
|
return steps;
|
|
};
|
|
|
|
if (keyDown->key == Key::Backspace) {
|
|
int steps = calculateSteps(false);
|
|
m_repeatCode = SpecialRepeatKeyCodes::Backspace;
|
|
for (int i = 0; i < steps; i++) {
|
|
if (m_cursorOffset > 0) {
|
|
if (modText(m_text.substr(0, m_cursorOffset - 1) + m_text.substr(m_cursorOffset)))
|
|
m_cursorOffset -= 1;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::Delete) {
|
|
int steps = calculateSteps(true);
|
|
m_repeatCode = SpecialRepeatKeyCodes::Delete;
|
|
for (int i = 0; i < steps; i++) {
|
|
if (m_cursorOffset < (int)m_text.size())
|
|
modText(m_text.substr(0, m_cursorOffset) + m_text.substr(m_cursorOffset + 1));
|
|
}
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::Left) {
|
|
int steps = calculateSteps(false);
|
|
m_repeatCode = SpecialRepeatKeyCodes::Left;
|
|
for (int i = 0; i < steps; i++) {
|
|
m_cursorOffset--;
|
|
if (m_cursorOffset < 0)
|
|
m_cursorOffset = 0;
|
|
}
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::Right) {
|
|
int steps = calculateSteps(true);
|
|
m_repeatCode = SpecialRepeatKeyCodes::Right;
|
|
for (int i = 0; i < steps; i++) {
|
|
m_cursorOffset++;
|
|
if (m_cursorOffset > (int)m_text.size())
|
|
m_cursorOffset = m_text.size();
|
|
}
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::Home) {
|
|
m_cursorOffset = 0;
|
|
return true;
|
|
}
|
|
if (keyDown->key == Key::End) {
|
|
m_cursorOffset = m_text.size();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (auto textInput = event.ptr<TextInputEvent>()) {
|
|
if (modText(m_text.substr(0, m_cursorOffset) + textInput->text + m_text.substr(m_cursorOffset)))
|
|
m_cursorOffset += textInput->text.size();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TextBoxWidget::setDrawBorder(bool drawBorder) {
|
|
m_drawBorder = drawBorder;
|
|
}
|
|
|
|
void TextBoxWidget::setTextAlign(HorizontalAnchor hAnchor) {
|
|
m_hAnchor = hAnchor;
|
|
}
|
|
|
|
bool TextBoxWidget::modText(String const& text) {
|
|
if (m_text != text && newTextValid(text)) {
|
|
m_text = text;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool TextBoxWidget::newTextValid(String const& text) const {
|
|
if (!text.regexMatch(m_regex))
|
|
return false;
|
|
if ((m_maxWidth != -1) && !m_overfillMode) {
|
|
context()->setFont(m_font);
|
|
context()->setFontSize(m_fontSize);
|
|
return context()->stringInterfaceWidth(text) <= m_maxWidth;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
}
|