Added key repeat support to DateTimeEditComponent.

Also made some other improvements and cleaned up the code.
This commit is contained in:
Leon Styhre 2021-10-08 19:38:14 +02:00
parent 72cf219b05
commit ce593686a5
2 changed files with 163 additions and 147 deletions

View file

@ -6,6 +6,9 @@
// Date and time edit component. // Date and time edit component.
// //
#define KEY_REPEAT_START_DELAY 600
#define KEY_REPEAT_SPEED 150 // Lower is faster.
#include "components/DateTimeEditComponent.h" #include "components/DateTimeEditComponent.h"
#include "Settings.h" #include "Settings.h"
@ -13,35 +16,43 @@
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
DateTimeEditComponent::DateTimeEditComponent(Window* window, bool alignRight, DisplayMode dispMode) DateTimeEditComponent::DateTimeEditComponent(Window* window, bool alignRight, DisplayMode dispMode)
: GuiComponent(window) : GuiComponent{window}
, mEditing(false) , mEditing{false}
, mEditIndex(0) , mEditIndex{0}
, mDisplayMode(dispMode) , mDisplayMode{dispMode}
, mRelativeUpdateAccumulator(0) , mKeyRepeatDir{0}
, mColor(0x777777FF) , mKeyRepeatTimer{0}
, mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)) , mRelativeUpdateAccumulator{0}
, mAlignRight(alignRight) , mColor{0x777777FF}
, mUppercase(false) , mFont{Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)}
, mAutoSize(true) , mAlignRight{alignRight}
, mUppercase{false}
, mAutoSize{true}
{ {
updateTextCache(); 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(); updateTextCache();
} }
bool DateTimeEditComponent::input(InputConfig* config, Input input) bool DateTimeEditComponent::input(InputConfig* config, Input input)
{ {
if (input.value == 0) if (config->isMappedTo("a", input) && input.value) {
return false;
if (config->isMappedTo("a", input)) {
if (mDisplayMode != DISP_RELATIVE_TO_NOW) // Don't allow editing for relative times. if (mDisplayMode != DISP_RELATIVE_TO_NOW) // Don't allow editing for relative times.
mEditing = !mEditing; mEditing = !mEditing;
mKeyRepeatDir = 0;
// Change the color of the text to reflect the changes. // Change the color of the text to reflect the changes.
if (mTime == mOriginalValue) if (mTime == mOriginalValue)
setColor(mColorOriginalValue); setColor(mColorOriginalValue);
@ -54,86 +65,77 @@ bool DateTimeEditComponent::input(InputConfig* config, Input input)
// Initialize to now if unset. // Initialize to now if unset.
if (mTime.getTime() == Utils::Time::DEFAULT_TIMEVALUE) { if (mTime.getTime() == Utils::Time::DEFAULT_TIMEVALUE) {
mTime = Utils::Time::now(); mTime = Utils::Time::stringToTime("19990101T0101");
updateTextCache(); updateTextCache();
} }
} }
updateHelpPrompts();
return true; return true;
} }
if (mEditing) { if (mEditing) {
if (config->isMappedLike("lefttrigger", input) || if ((config->isMappedLike("lefttrigger", input) ||
config->isMappedLike("righttrigger", input)) { config->isMappedLike("righttrigger", input))) {
mKeyRepeatDir = 0;
return true; return true;
} }
if (config->isMappedTo("b", input)) { if (config->isMappedTo("y", input) && input.value) {
mEditing = false; mEditing = false;
mTime = mTimeBeforeEdit; mTime = mTimeBeforeEdit;
mKeyRepeatDir = 0;
updateTextCache(); updateTextCache();
return false;
}
if (config->isMappedTo("b", input) && input.value) {
mEditing = false;
mTime = mTimeBeforeEdit;
mKeyRepeatDir = 0;
updateTextCache();
updateHelpPrompts();
return true; return true;
} }
int incDir = 0; if (config->isMappedLike("up", input) || config->isMappedLike("rightshoulder", input)) {
if (config->isMappedLike("up", input) || config->isMappedLike("rightshoulder", input)) if (input.value) {
incDir = 1; mKeyRepeatDir = 1;
else if (config->isMappedLike("down", input) || config->isMappedLike("leftshoulder", input)) mKeyRepeatTimer = -(KEY_REPEAT_START_DELAY - KEY_REPEAT_SPEED);
incDir = -1; changeDate();
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;
}
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 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;
}
// 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; 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 (config->isMappedLike("right", input)) { if (mTime != 0 && config->isMappedLike("right", input) && input.value) {
mEditIndex++; mEditIndex++;
if (mEditIndex >= static_cast<int>(mCursorBoxes.size())) if (mEditIndex >= static_cast<int>(mCursorBoxes.size()))
mEditIndex--; mEditIndex--;
mKeyRepeatDir = 0;
return true; return true;
} }
if (config->isMappedLike("left", input)) { if (mTime != 0 && config->isMappedLike("left", input) && input.value) {
mEditIndex--; mEditIndex--;
if (mEditIndex < 0) if (mEditIndex < 0)
mEditIndex++; mEditIndex++;
mKeyRepeatDir = 0;
return true; return true;
} }
} }
@ -143,6 +145,14 @@ bool DateTimeEditComponent::input(InputConfig* config, Input input)
void DateTimeEditComponent::update(int deltaTime) 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) { if (mDisplayMode == DISP_RELATIVE_TO_NOW) {
mRelativeUpdateAccumulator += deltaTime; mRelativeUpdateAccumulator += deltaTime;
if (mRelativeUpdateAccumulator > 1000) { if (mRelativeUpdateAccumulator > 1000) {
@ -162,12 +172,8 @@ void DateTimeEditComponent::render(const glm::mat4& parentTrans)
std::shared_ptr<Font> font = getFont(); std::shared_ptr<Font> font = getFont();
float referenceSize; float referenceSize;
if (mAlignRight) { if (mAlignRight)
if (mTime != 0) referenceSize = std::round(mParent->getSize().x * 0.1045f);
referenceSize = font->sizeText("ABCDEFG").x;
else
referenceSize = font->sizeText("ABCDEIJ").x;
}
// Vertically center. // Vertically center.
glm::vec3 off{0.0f, (mSize.y - mTextCache->metrics.size.y) / 2.0f, 0.0f}; 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()); mTextCache->setColor((mColor & 0xFFFFFF00) | getOpacity());
font->renderTextCache(mTextCache.get()); font->renderTextCache(mTextCache.get());
if (mEditing) { if (mEditing && mTime != 0) {
if (mEditIndex >= 0 && static_cast<unsigned int>(mEditIndex) < mCursorBoxes.size()) if (mEditIndex >= 0 && static_cast<unsigned int>(mEditIndex) < mCursorBoxes.size())
Renderer::drawRect(mCursorBoxes[mEditIndex][0], mCursorBoxes[mEditIndex][1], Renderer::drawRect(mCursorBoxes[mEditIndex][0], mCursorBoxes[mEditIndex][1],
mCursorBoxes[mEditIndex][2], mCursorBoxes[mEditIndex][3], 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; mDisplayMode = mode;
mOriginalValue = val;
updateTextCache(); updateTextCache();
} }
void DateTimeEditComponent::setColor(unsigned int color)
{
mColor = color;
if (mTextCache)
mTextCache->setColor(color);
}
void DateTimeEditComponent::setFont(std::shared_ptr<Font> font)
{
mFont = font;
updateTextCache();
}
void DateTimeEditComponent::setUppercase(bool uppercase)
{
mUppercase = uppercase;
updateTextCache();
}
std::vector<HelpPrompt> DateTimeEditComponent::getHelpPrompts()
{
std::vector<HelpPrompt> 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<Font> DateTimeEditComponent::getFont() const
{
if (mFont)
return mFont;
return Font::get(FONT_SIZE_MEDIUM);
}
std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const
{ {
// ISO 8601 date format. // ISO 8601 date format.
@ -256,12 +303,44 @@ std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const
return Utils::Time::timeToString(mTime, fmt); return Utils::Time::timeToString(mTime, fmt);
} }
std::shared_ptr<Font> DateTimeEditComponent::getFont() const void DateTimeEditComponent::changeDate()
{ {
if (mFont) tm new_tm = mTime;
return mFont;
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() void DateTimeEditComponent::updateTextCache()
@ -320,64 +399,3 @@ void DateTimeEditComponent::updateTextCache()
// The logic for handling time for 'mode = DISP_DATE_TIME' is missing, but // The logic for handling time for 'mode = DISP_DATE_TIME' is missing, but
// nobody will use it anyway so it's not worthwhile implementing. // 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> 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<ThemeData>& 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<unsigned int>("color"));
if (properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase"));
setFont(Font::getFromTheme(elem, properties, mFont));
}
std::vector<HelpPrompt> DateTimeEditComponent::getHelpPrompts()
{
std::vector<HelpPrompt> prompts;
prompts.push_back(HelpPrompt("a", "edit date"));
return prompts;
}

View file

@ -28,14 +28,15 @@ public:
bool alignRight = false, bool alignRight = false,
DisplayMode dispMode = DISP_DATE); DisplayMode dispMode = DISP_DATE);
void onSizeChanged() override;
void setValue(const std::string& val) override; void setValue(const std::string& val) override;
std::string getValue() const override { return mTime; } std::string getValue() const override { return mTime; }
unsigned int getColor() const override { return mColor; }
bool input(InputConfig* config, Input input) override; bool input(InputConfig* config, Input input) override;
void update(int deltaTime) override; void update(int deltaTime) override;
unsigned int getColor() const override { return mColor; }
void render(const glm::mat4& parentTrans) override; void render(const glm::mat4& parentTrans) override;
void onSizeChanged() override;
// Set how the point in time will be displayed: // Set how the point in time will be displayed:
// * DISP_DATE - only display the date. // * DISP_DATE - only display the date.
@ -57,19 +58,14 @@ public:
// Force text to be uppercase when in DISP_RELATIVE_TO_NOW mode. // Force text to be uppercase when in DISP_RELATIVE_TO_NOW mode.
void setUppercase(bool uppercase); void setUppercase(bool uppercase);
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme,
const std::string& view,
const std::string& element,
unsigned int properties) override;
virtual std::vector<HelpPrompt> getHelpPrompts() override; virtual std::vector<HelpPrompt> getHelpPrompts() override;
private: private:
std::shared_ptr<Font> getFont() const override; std::shared_ptr<Font> getFont() const override;
std::string getDisplayString(DisplayMode mode) const; std::string getDisplayString(DisplayMode mode) const;
DisplayMode getCurrentDisplayMode() const { return mDisplayMode; } DisplayMode getCurrentDisplayMode() const { return mDisplayMode; }
void changeDate();
void updateTextCache(); void updateTextCache();
Utils::Time::DateTime mTime; Utils::Time::DateTime mTime;
@ -79,6 +75,8 @@ private:
int mEditIndex; int mEditIndex;
DisplayMode mDisplayMode; DisplayMode mDisplayMode;
int mKeyRepeatDir;
int mKeyRepeatTimer;
int mRelativeUpdateAccumulator; int mRelativeUpdateAccumulator;
std::unique_ptr<TextCache> mTextCache; std::unique_ptr<TextCache> mTextCache;