// SPDX-License-Identifier: MIT // // ES-DE Frontend // DateTimeEditComponent.cpp // // Date and time edit component. // #define KEY_REPEAT_START_DELAY 600 #define KEY_REPEAT_SPEED 150 // Lower is faster. #include "components/DateTimeEditComponent.h" #include "Settings.h" #include "utils/LocalizationUtil.h" #include "utils/StringUtil.h" DateTimeEditComponent::DateTimeEditComponent(bool alignRight) : mRenderer {Renderer::getInstance()} , mEditing {false} , mEditIndex {0} , mKeyRepeatDir {0} , mKeyRepeatTimer {0} , mColor {mMenuColorPrimary} , mAlignRight {alignRight} , mUppercase {false} , mAutoSize {true} { mDateText = std::make_unique("", Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)); updateText(); } void DateTimeEditComponent::onSizeChanged() { mAutoSize = false; updateText(); } void DateTimeEditComponent::setValue(const std::string& val) { mTime = val; mOriginalValue = val; if (mAlignRight) mAutoSize = true; updateText(); } bool DateTimeEditComponent::input(InputConfig* config, Input input) { if (config->isMappedTo("a", input) && input.value) { mEditing = !mEditing; mKeyRepeatDir = 0; // Change the color of the text to reflect the changes. if (mTime == mOriginalValue) setColor(mColorOriginalValue); else setColor(mColorChangedValue); if (mEditing) { // Started editing. mTimeBeforeEdit = mTime; // Initialize to the arbitrary value 1999-01-01 if unset. if (mTime == 0) { mTime = Utils::Time::stringToTime("19990101T000000"); mAutoSize = true; updateText(); } } updateHelpPrompts(); return true; } if (mEditing) { if ((config->isMappedLike("lefttrigger", input) || config->isMappedLike("righttrigger", input))) { mKeyRepeatDir = 0; return true; } if (config->isMappedTo("y", input) && input.value) { mEditing = false; mTime = mTimeBeforeEdit; mKeyRepeatDir = 0; updateText(); return false; } if (config->isMappedTo("b", input) && input.value) { mEditing = false; mTime = mTimeBeforeEdit; mKeyRepeatDir = 0; mAutoSize = true; updateText(); updateHelpPrompts(); return true; } if (config->isMappedLike("up", input) || config->isMappedLike("rightshoulder", input)) { if (input.value) { mKeyRepeatDir = 1; mKeyRepeatTimer = -(KEY_REPEAT_START_DELAY - KEY_REPEAT_SPEED); changeDate(); return true; } else { mKeyRepeatDir = 0; } } else if (config->isMappedLike("down", input) || config->isMappedLike("leftshoulder", input)) { if (input.value) { mKeyRepeatDir = -1; mKeyRepeatTimer = -(KEY_REPEAT_START_DELAY - KEY_REPEAT_SPEED); changeDate(); return true; } else { mKeyRepeatDir = 0; } } if (mTime != 0 && config->isMappedLike("right", input) && input.value) { ++mEditIndex; if (mEditIndex >= static_cast(mCursorBoxes.size())) --mEditIndex; mKeyRepeatDir = 0; return true; } if (mTime != 0 && config->isMappedLike("left", input) && input.value) { --mEditIndex; if (mEditIndex < 0) ++mEditIndex; mKeyRepeatDir = 0; return true; } } return GuiComponent::input(config, input); } void DateTimeEditComponent::update(int deltaTime) { if (mKeyRepeatDir != 0) { mKeyRepeatTimer += deltaTime; while (mKeyRepeatTimer >= KEY_REPEAT_SPEED) { mKeyRepeatTimer -= KEY_REPEAT_SPEED; changeDate(); } } GuiComponent::update(deltaTime); } void DateTimeEditComponent::render(const glm::mat4& parentTrans) { glm::mat4 trans {parentTrans * getTransform()}; // Center vertically. glm::vec3 off {0.0f, (mSize.y - mDateText->getSize().y) / 2.0f, 0.0f}; trans = glm::translate(trans, glm::round(off)); mRenderer->setMatrix(trans); if (Settings::getInstance()->getBool("DebugText") && mDateText->getValue() != "") { mRenderer->setMatrix(trans); mDateText->setDebugRendering(false); if (mDateText->getSize().x > 0.0f) { mRenderer->drawRect(0.0f, 0.0f - off.y, mSize.x - off.x, mSize.y, 0x0000FF33, 0x0000FF33); } mRenderer->drawRect(0.0f, 0.0f, mDateText->getSize().x, mDateText->getSize().y, 0x00000033, 0x00000033); } mDateText->setColor((mColor & 0xFFFFFF00) | static_cast(getOpacity() * 255.0f)); mDateText->render(trans); if (mEditing && mTime != 0) { if (mEditIndex >= 0 && static_cast(mEditIndex) < mCursorBoxes.size()) mRenderer->drawRect(mCursorBoxes[mEditIndex][0], mCursorBoxes[mEditIndex][1], mCursorBoxes[mEditIndex][2], mCursorBoxes[mEditIndex][3], mMenuColorDateTimeEditMarker, mMenuColorDateTimeEditMarker); } } void DateTimeEditComponent::setColor(unsigned int color) { mColor = color; mDateText->setColor(color); } void DateTimeEditComponent::setFont(std::shared_ptr font) { mDateText->setFont(font); updateText(); } void DateTimeEditComponent::setUppercase(bool uppercase) { mUppercase = uppercase; updateText(); } std::vector DateTimeEditComponent::getHelpPrompts() { std::vector prompts; if (!mEditing) { prompts.push_back(HelpPrompt("a", _("edit date"))); } else { prompts.push_back(HelpPrompt("b", _("cancel"))); prompts.push_back(HelpPrompt("a", _("apply"))); prompts.push_back(HelpPrompt("left/right", _("Y-M-D"))); prompts.push_back(HelpPrompt("up/down", _("modify"))); } return prompts; } std::string DateTimeEditComponent::getDisplayString() const { // ISO 8601 date format. std::string fmt; if (mTime.getTime() == 0) return _("unknown"); fmt = "%Y-%m-%d"; return Utils::Time::timeToString(mTime, fmt); } void DateTimeEditComponent::changeDate() { tm new_tm = mTime; if (mTime.getIsoString() == "19700101T000000") #if defined(_WIN64) new_tm = {0, 0, 0, 1, 0, 70, 0, 0, -1}; #else new_tm = {0, 0, 0, 1, 0, 70, 0, 0, -1, 0, 0}; #endif // ISO 8601 date format. if (mEditIndex == 0) { new_tm.tm_year += mKeyRepeatDir; if (new_tm.tm_year < 0) new_tm.tm_year = 0; } else if (mEditIndex == 1) { new_tm.tm_mon += mKeyRepeatDir; if (new_tm.tm_mon > 11) new_tm.tm_mon = 0; else if (new_tm.tm_mon < 0) new_tm.tm_mon = 11; } else if (mEditIndex == 2) { const int days_in_month { Utils::Time::daysInMonth(new_tm.tm_year + 1900, new_tm.tm_mon + 1)}; new_tm.tm_mday += mKeyRepeatDir; if (new_tm.tm_mday > days_in_month) new_tm.tm_mday = 1; else if (new_tm.tm_mday < 1) new_tm.tm_mday = days_in_month; } // Validate day. const int days_in_month {Utils::Time::daysInMonth(new_tm.tm_year + 1900, new_tm.tm_mon + 1)}; if (new_tm.tm_mday > days_in_month) new_tm.tm_mday = days_in_month; if (mktime(&new_tm) <= 0) mTime = 0; else mTime = new_tm; mAutoSize = true; updateText(); } void DateTimeEditComponent::updateText() { std::string dispString; // Hack to set date string to blank instead of 'unknown'. // The calling function simply needs to set this string using setValue(). if (mTime.getIsoString() == "19710101T010101") dispString = ""; else dispString = mUppercase ? Utils::String::toUpper(getDisplayString()) : getDisplayString(); mDateText->setText(dispString); mDateText->setColor(mColor); if (mAlignRight) mSize = mDateText->getSize(); if (mAutoSize) { mSize = mDateText->getSize(); mAutoSize = false; if (getParent()) getParent()->onSizeChanged(); } mCursorBoxes.clear(); if (dispString.empty() || dispString == _("unknown")) return; // Set up cursor positions. std::shared_ptr font {mDateText->getFont()}; // Year. glm::vec2 start {0.0f, 0.0f}; glm::vec2 end {font->sizeText(dispString.substr(0, 4))}; glm::vec2 diff {end - start}; mCursorBoxes.push_back(glm::vec4 {start[0], start[1], diff[0], diff[1]}); // Month. start[0] = font->sizeText(dispString.substr(0, 5)).x; end = font->sizeText(dispString.substr(0, 7)); diff = end - start; mCursorBoxes.push_back(glm::vec4 {start[0], start[1], diff[0], diff[1]}); // Day. start[0] = font->sizeText(dispString.substr(0, 8)).x; end = font->sizeText(dispString.substr(0, 10)); diff = end - start; mCursorBoxes.push_back(glm::vec4 {start[0], start[1], diff[0], diff[1]}); }