diff --git a/es-core/src/components/DateTimeEditComponent.cpp b/es-core/src/components/DateTimeEditComponent.cpp index 23b900aae..352932383 100644 --- a/es-core/src/components/DateTimeEditComponent.cpp +++ b/es-core/src/components/DateTimeEditComponent.cpp @@ -6,6 +6,9 @@ // 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" @@ -13,35 +16,43 @@ #include "utils/StringUtil.h" DateTimeEditComponent::DateTimeEditComponent(Window* window, bool alignRight, DisplayMode dispMode) - : GuiComponent(window) - , mEditing(false) - , mEditIndex(0) - , mDisplayMode(dispMode) - , mRelativeUpdateAccumulator(0) - , mColor(0x777777FF) - , mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)) - , mAlignRight(alignRight) - , mUppercase(false) - , mAutoSize(true) + : GuiComponent{window} + , mEditing{false} + , mEditIndex{0} + , mDisplayMode{dispMode} + , mKeyRepeatDir{0} + , mKeyRepeatTimer{0} + , mRelativeUpdateAccumulator{0} + , mColor{0x777777FF} + , mFont{Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)} + , mAlignRight{alignRight} + , mUppercase{false} + , mAutoSize{true} { updateTextCache(); } -void DateTimeEditComponent::setDisplayMode(DisplayMode mode) +void DateTimeEditComponent::onSizeChanged() { - mDisplayMode = mode; + mAutoSize = false; + updateTextCache(); +} + +void DateTimeEditComponent::setValue(const std::string& val) +{ + mTime = val; + mOriginalValue = val; updateTextCache(); } bool DateTimeEditComponent::input(InputConfig* config, Input input) { - if (input.value == 0) - return false; - - if (config->isMappedTo("a", input)) { + if (config->isMappedTo("a", input) && input.value) { if (mDisplayMode != DISP_RELATIVE_TO_NOW) // Don't allow editing for relative times. mEditing = !mEditing; + mKeyRepeatDir = 0; + // Change the color of the text to reflect the changes. if (mTime == mOriginalValue) setColor(mColorOriginalValue); @@ -54,86 +65,77 @@ bool DateTimeEditComponent::input(InputConfig* config, Input input) // Initialize to now if unset. if (mTime.getTime() == Utils::Time::DEFAULT_TIMEVALUE) { - mTime = Utils::Time::now(); + mTime = Utils::Time::stringToTime("19990101T0101"); updateTextCache(); } } + updateHelpPrompts(); return true; } if (mEditing) { - if (config->isMappedLike("lefttrigger", input) || - config->isMappedLike("righttrigger", input)) { + if ((config->isMappedLike("lefttrigger", input) || + config->isMappedLike("righttrigger", input))) { + mKeyRepeatDir = 0; return true; } - if (config->isMappedTo("b", input)) { + if (config->isMappedTo("y", input) && input.value) { mEditing = false; mTime = mTimeBeforeEdit; + mKeyRepeatDir = 0; updateTextCache(); + return false; + } + + if (config->isMappedTo("b", input) && input.value) { + mEditing = false; + mTime = mTimeBeforeEdit; + mKeyRepeatDir = 0; + updateTextCache(); + updateHelpPrompts(); return true; } - int incDir = 0; - if (config->isMappedLike("up", input) || config->isMappedLike("rightshoulder", input)) - incDir = 1; - else if (config->isMappedLike("down", input) || config->isMappedLike("leftshoulder", input)) - incDir = -1; - - if (incDir != 0) { - tm new_tm = mTime; - - // ISO 8601 date format. - if (mEditIndex == 0) { - new_tm.tm_year += incDir; - - if (new_tm.tm_year < 0) - new_tm.tm_year = 0; + 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 if (mEditIndex == 1) { - new_tm.tm_mon += incDir; - - if (new_tm.tm_mon > 11) - new_tm.tm_mon = 0; - else if (new_tm.tm_mon < 0) - new_tm.tm_mon = 11; + else { + mKeyRepeatDir = 0; } - 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 += incDir; - - 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; + } + 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; } - - // 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; - - mTime = new_tm; - - updateTextCache(); - return true; } - if (config->isMappedLike("right", input)) { + if (mTime != 0 && config->isMappedLike("right", input) && input.value) { mEditIndex++; if (mEditIndex >= static_cast(mCursorBoxes.size())) mEditIndex--; + mKeyRepeatDir = 0; return true; } - if (config->isMappedLike("left", input)) { + if (mTime != 0 && config->isMappedLike("left", input) && input.value) { mEditIndex--; if (mEditIndex < 0) mEditIndex++; + mKeyRepeatDir = 0; return true; } } @@ -143,6 +145,14 @@ bool DateTimeEditComponent::input(InputConfig* config, Input input) void DateTimeEditComponent::update(int deltaTime) { + if (mKeyRepeatDir != 0) { + mKeyRepeatTimer += deltaTime; + while (mKeyRepeatTimer >= KEY_REPEAT_SPEED) { + mKeyRepeatTimer -= KEY_REPEAT_SPEED; + changeDate(); + } + } + if (mDisplayMode == DISP_RELATIVE_TO_NOW) { mRelativeUpdateAccumulator += deltaTime; if (mRelativeUpdateAccumulator > 1000) { @@ -162,12 +172,8 @@ void DateTimeEditComponent::render(const glm::mat4& parentTrans) std::shared_ptr font = getFont(); float referenceSize; - if (mAlignRight) { - if (mTime != 0) - referenceSize = font->sizeText("ABCDEFG").x; - else - referenceSize = font->sizeText("ABCDEIJ").x; - } + if (mAlignRight) + referenceSize = std::round(mParent->getSize().x * 0.1045f); // Vertically center. glm::vec3 off{0.0f, (mSize.y - mTextCache->metrics.size.y) / 2.0f, 0.0f}; @@ -191,7 +197,7 @@ void DateTimeEditComponent::render(const glm::mat4& parentTrans) mTextCache->setColor((mColor & 0xFFFFFF00) | getOpacity()); font->renderTextCache(mTextCache.get()); - if (mEditing) { + if (mEditing && mTime != 0) { if (mEditIndex >= 0 && static_cast(mEditIndex) < mCursorBoxes.size()) Renderer::drawRect(mCursorBoxes[mEditIndex][0], mCursorBoxes[mEditIndex][1], mCursorBoxes[mEditIndex][2], mCursorBoxes[mEditIndex][3], @@ -200,13 +206,54 @@ void DateTimeEditComponent::render(const glm::mat4& parentTrans) } } -void DateTimeEditComponent::setValue(const std::string& val) +void DateTimeEditComponent::setDisplayMode(DisplayMode mode) { - mTime = val; - mOriginalValue = val; + mDisplayMode = mode; updateTextCache(); } +void DateTimeEditComponent::setColor(unsigned int color) +{ + mColor = color; + if (mTextCache) + mTextCache->setColor(color); +} + +void DateTimeEditComponent::setFont(std::shared_ptr font) +{ + mFont = font; + updateTextCache(); +} + +void DateTimeEditComponent::setUppercase(bool uppercase) +{ + mUppercase = uppercase; + updateTextCache(); +} + +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::shared_ptr DateTimeEditComponent::getFont() const +{ + if (mFont) + return mFont; + + return Font::get(FONT_SIZE_MEDIUM); +} + std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const { // ISO 8601 date format. @@ -256,12 +303,44 @@ std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const return Utils::Time::timeToString(mTime, fmt); } -std::shared_ptr DateTimeEditComponent::getFont() const +void DateTimeEditComponent::changeDate() { - if (mFont) - return mFont; + tm new_tm = mTime; - return Font::get(FONT_SIZE_MEDIUM); + // 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; + + mTime = new_tm; + + updateTextCache(); } void DateTimeEditComponent::updateTextCache() @@ -320,64 +399,3 @@ void DateTimeEditComponent::updateTextCache() // The logic for handling time for 'mode = DISP_DATE_TIME' is missing, but // nobody will use it anyway so it's not worthwhile implementing. } - -void DateTimeEditComponent::setColor(unsigned int color) -{ - mColor = color; - if (mTextCache) - mTextCache->setColor(color); -} - -void DateTimeEditComponent::setFont(std::shared_ptr font) -{ - mFont = font; - updateTextCache(); -} - -void DateTimeEditComponent::onSizeChanged() -{ - mAutoSize = false; - updateTextCache(); -} - -void DateTimeEditComponent::setUppercase(bool uppercase) -{ - mUppercase = uppercase; - updateTextCache(); -} - -void DateTimeEditComponent::applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) -{ - const ThemeData::ThemeElement* elem = theme->getElement(view, element, "datetime"); - - if (!elem) - return; - - // We set mAutoSize BEFORE calling GuiComponent::applyTheme because it calls - // setSize(), which will call updateTextCache(), which will reset mSize if - // mAutoSize == true, ignoring the theme's value. - if (properties & ThemeFlags::SIZE) - mAutoSize = !elem->has("size"); - - GuiComponent::applyTheme(theme, view, element, properties); - - using namespace ThemeFlags; - - if (properties & COLOR && elem->has("color")) - setColor(elem->get("color")); - - if (properties & FORCE_UPPERCASE && elem->has("forceUppercase")) - setUppercase(elem->get("forceUppercase")); - - setFont(Font::getFromTheme(elem, properties, mFont)); -} - -std::vector DateTimeEditComponent::getHelpPrompts() -{ - std::vector prompts; - prompts.push_back(HelpPrompt("a", "edit date")); - return prompts; -} diff --git a/es-core/src/components/DateTimeEditComponent.h b/es-core/src/components/DateTimeEditComponent.h index a2c7ab58f..aa1d72457 100644 --- a/es-core/src/components/DateTimeEditComponent.h +++ b/es-core/src/components/DateTimeEditComponent.h @@ -28,14 +28,15 @@ public: bool alignRight = false, DisplayMode dispMode = DISP_DATE); + void onSizeChanged() override; + void setValue(const std::string& val) override; std::string getValue() const override { return mTime; } + unsigned int getColor() const override { return mColor; } bool input(InputConfig* config, Input input) override; void update(int deltaTime) override; - unsigned int getColor() const override { return mColor; } void render(const glm::mat4& parentTrans) override; - void onSizeChanged() override; // Set how the point in time will be displayed: // * DISP_DATE - only display the date. @@ -57,19 +58,14 @@ public: // Force text to be uppercase when in DISP_RELATIVE_TO_NOW mode. void setUppercase(bool uppercase); - virtual void applyTheme(const std::shared_ptr& theme, - const std::string& view, - const std::string& element, - unsigned int properties) override; - virtual std::vector getHelpPrompts() override; private: std::shared_ptr getFont() const override; - std::string getDisplayString(DisplayMode mode) const; DisplayMode getCurrentDisplayMode() const { return mDisplayMode; } + void changeDate(); void updateTextCache(); Utils::Time::DateTime mTime; @@ -79,6 +75,8 @@ private: int mEditIndex; DisplayMode mDisplayMode; + int mKeyRepeatDir; + int mKeyRepeatTimer; int mRelativeUpdateAccumulator; std::unique_ptr mTextCache;