osb/source/windowing/StarTextBoxWidget.cpp
Kae ac7577b4df Rename Keypad enums
noticed keypad binds were named like this in SE (probably due to using the names given by SDL there) and it's nicer anyway. better do this sooner than later.
2024-09-02 22:17:26 +10:00

431 lines
12 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:textBoxTextStyle");
m_textStyle = fontConfig;
// Meh, padding is hard-coded here
setSize({m_maxWidth + 6, m_textStyle.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()->setTextStyle(m_textStyle);
if ((m_maxWidth != -1) && m_overfillMode) {
int shift = std::max(0, getCursorOffset() - m_maxWidth);
pos += Vec2F(-shift, 0);
}
if (m_text.empty()) {
context()->setFontColor(Color::rgba(m_textStyle.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(Color::rgba(m_textStyle.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()->clearTextStyle();
if (hasFocus()) {
// render cursor
float cc = 0.6f + 0.4f * std::sin((double)Time::monotonicMilliseconds() / 300.0);
Color cursorColor = Color::rgbf(cc, cc, cc);
float fontSize = m_textStyle.fontSize;
context()->drawInterfaceLine(
pos + Vec2F(getCursorOffset(), fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset(), fontSize * m_cursorVert[1]),
cursorColor.toRgba());
context()->drawInterfaceLine(
pos + Vec2F(getCursorOffset() + fontSize * m_cursorHoriz[0], fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset() + fontSize * m_cursorHoriz[1], 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()->setTextStyle(m_textStyle);
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, bool moveCursor) {
if (m_text == text)
return true;
if (!newTextValid(text))
return false;
m_text = text;
size_t size = m_text.size();
if (moveCursor || m_cursorOffset > size)
m_cursorOffset = 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_textStyle.color = color.toRgba();
}
void TextBoxWidget::setDirectives(String const& directives) {
m_textStyle.directives = directives;
}
void TextBoxWidget::setFontSize(int fontSize) {
m_textStyle.fontSize = fontSize;
}
void TextBoxWidget::setMaxWidth(int maxWidth) {
m_maxWidth = maxWidth;
setSize({m_maxWidth + 6, m_textStyle.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::KeypadEnter) {
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()->setTextStyle(m_textStyle);
return context()->stringInterfaceWidth(text) <= m_maxWidth;
}
return true;
}
}