diff --git a/THEMES.md b/THEMES.md index 2af630b7e..fc4ff063b 100644 --- a/THEMES.md +++ b/THEMES.md @@ -731,14 +731,38 @@ EmulationStation borrows the concept of "nine patches" from Android (or "9-Slice - z-index value for component. Components will be rendered in order of z-index value from low to high. #### datetime - * `pos` - type: NORMALIZED_PAIR. * `size` - type: NORMALIZED_PAIR. - - You should probably not set this. Leave it to `fontSize`. + - Possible combinations: + - `0 0` - automatically size so text fits on one line (expanding horizontally). + - `w 0` - automatically wrap text so it doesn't go beyond `w` (expanding vertically). + - `w h` - works like a "text box." If `h` is non-zero and `h` <= `fontSize` (implying it should be a single line of text), text that goes beyond `w` will be truncated with an elipses (...). +* `origin` - type: NORMALIZED_PAIR. + - Where on the component `pos` refers to. For example, an origin of `0.5 0.5` and a `pos` of `0.5 0.5` would place the component exactly in the middle of the screen. If the "POSITION" and "SIZE" attributes are themable, "ORIGIN" is implied. +* `rotation` - type: FLOAT. + - angle in degrees that the text should be rotated. Positive values will rotate clockwise, negative values will rotate counterclockwise. +* `rotationOrigin` - type: NORMALIZED_PAIR. + - Point around which the text will be rotated. Defaults to `0.5 0.5`. * `color` - type: COLOR. +* `backgroundColor` - type: COLOR; * `fontPath` - type: PATH. + - Path to a truetype font (.ttf). * `fontSize` - type: FLOAT. + - Size of the font as a percentage of screen height (e.g. for a value of `0.1`, the text's height would be 10% of the screen height). +* `alignment` - type: STRING. + - Valid values are "left", "center", or "right". Controls alignment on the X axis. "center" will also align vertically. * `forceUppercase` - type: BOOLEAN. Draw text in uppercase. +* `lineSpacing` - type: FLOAT. Controls the space between lines (as a multiple of font height). Default is 1.5. +* `zIndex` - type: FLOAT. + - z-index value for component. Components will be rendered in order of z-index value from low to high. +* `displayRelative` - type: BOOLEAN. Renders the datetime as a a relative string (ex: 'x days ago') +* `format` - type: STRING. Specifies format for rendering datetime. + - %Y: The year, including the century (1900) + - %m: The month number [01,12] + - %d: The day of the month [01,31] + - %H: The hour (24-hour clock) [00,23] + - %M: The minute [00,59] + - %S: The second [00,59] #### sound diff --git a/es-app/src/components/ScraperSearchComponent.cpp b/es-app/src/components/ScraperSearchComponent.cpp index 3cffa40ec..80428f9ea 100644 --- a/es-app/src/components/ScraperSearchComponent.cpp +++ b/es-app/src/components/ScraperSearchComponent.cpp @@ -1,7 +1,7 @@ #include "components/ScraperSearchComponent.h" #include "components/ComponentList.h" -#include "components/DateTimeComponent.h" +#include "components/DateTimeEditComponent.h" #include "components/ImageComponent.h" #include "components/RatingComponent.h" #include "components/ScrollableContainer.h" @@ -43,7 +43,7 @@ ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type) const unsigned int mdColor = 0x777777FF; const unsigned int mdLblColor = 0x666666FF; mMD_Rating = std::make_shared(mWindow); - mMD_ReleaseDate = std::make_shared(mWindow); + mMD_ReleaseDate = std::make_shared(mWindow); mMD_ReleaseDate->setColor(mdColor); mMD_Developer = std::make_shared(mWindow, "", font, mdColor); mMD_Publisher = std::make_shared(mWindow, "", font, mdColor); diff --git a/es-app/src/components/ScraperSearchComponent.h b/es-app/src/components/ScraperSearchComponent.h index cb3ac6cae..b2924a7a1 100644 --- a/es-app/src/components/ScraperSearchComponent.h +++ b/es-app/src/components/ScraperSearchComponent.h @@ -8,7 +8,7 @@ #include "GuiComponent.h" class ComponentList; -class DateTimeComponent; +class DateTimeEditComponent; class ImageComponent; class RatingComponent; class ScrollableContainer; @@ -69,7 +69,7 @@ private: std::shared_ptr mMD_Grid; std::shared_ptr mMD_Rating; - std::shared_ptr mMD_ReleaseDate; + std::shared_ptr mMD_ReleaseDate; std::shared_ptr mMD_Developer; std::shared_ptr mMD_Publisher; std::shared_ptr mMD_Genre; diff --git a/es-app/src/guis/GuiMetaDataEd.cpp b/es-app/src/guis/GuiMetaDataEd.cpp index fcf780774..0650b0214 100644 --- a/es-app/src/guis/GuiMetaDataEd.cpp +++ b/es-app/src/guis/GuiMetaDataEd.cpp @@ -2,7 +2,7 @@ #include "components/ButtonComponent.h" #include "components/ComponentList.h" -#include "components/DateTimeComponent.h" +#include "components/DateTimeEditComponent.h" #include "components/MenuComponent.h" #include "components/RatingComponent.h" #include "components/SwitchComponent.h" @@ -87,21 +87,21 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector } case MD_DATE: { - ed = std::make_shared(window); + ed = std::make_shared(window); row.addElement(ed, false); auto spacer = std::make_shared(mWindow); spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0); row.addElement(spacer, false); - // pass input to the actual DateTimeComponent instead of the spacer + // pass input to the actual DateTimeEditComponent instead of the spacer row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1, std::placeholders::_2); break; } case MD_TIME: { - ed = std::make_shared(window, DateTimeComponent::DISP_RELATIVE_TO_NOW); + ed = std::make_shared(window, DateTimeEditComponent::DISP_RELATIVE_TO_NOW); row.addElement(ed, false); break; } diff --git a/es-app/src/views/gamelist/DetailedGameListView.cpp b/es-app/src/views/gamelist/DetailedGameListView.cpp index 247c92d10..87d2893f5 100644 --- a/es-app/src/views/gamelist/DetailedGameListView.cpp +++ b/es-app/src/views/gamelist/DetailedGameListView.cpp @@ -52,7 +52,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) : addChild(&mPlayers); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); - mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); + mLastPlayed.setDisplayRelative(true); addChild(&mLastPlayed); mLblPlayCount.setText("Times played: "); addChild(&mLblPlayCount); diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index a568a0943..084c3fc49 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -49,7 +49,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) : addChild(&mPlayers); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); - mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); + mLastPlayed.setDisplayRelative(true); addChild(&mLastPlayed); mLblPlayCount.setText("Times played: "); addChild(&mLblPlayCount); diff --git a/es-app/src/views/gamelist/VideoGameListView.cpp b/es-app/src/views/gamelist/VideoGameListView.cpp index ee1d06b7b..b57d66944 100644 --- a/es-app/src/views/gamelist/VideoGameListView.cpp +++ b/es-app/src/views/gamelist/VideoGameListView.cpp @@ -86,7 +86,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) : addChild(&mPlayers); mLblLastPlayed.setText("Last played: "); addChild(&mLblLastPlayed); - mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); + mLastPlayed.setDisplayRelative(true); addChild(&mLastPlayed); mLblPlayCount.setText("Times played: "); addChild(&mLblPlayCount); diff --git a/es-core/CMakeLists.txt b/es-core/CMakeLists.txt index fb4dcbee8..468884929 100644 --- a/es-core/CMakeLists.txt +++ b/es-core/CMakeLists.txt @@ -32,6 +32,7 @@ set(CORE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.h @@ -106,6 +107,7 @@ set(CORE_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeEditComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/HelpComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index fe78e4cee..628f085a7 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -90,10 +90,19 @@ std::map> The { "datetime", { { "pos", NORMALIZED_PAIR }, { "size", NORMALIZED_PAIR }, - { "color", COLOR }, + { "origin", NORMALIZED_PAIR }, + { "rotation", FLOAT }, + { "rotationOrigin", NORMALIZED_PAIR }, + { "backgroundColor", COLOR }, { "fontPath", PATH }, { "fontSize", FLOAT }, + { "color", COLOR }, + { "alignment", STRING }, { "forceUppercase", BOOLEAN }, + { "lineSpacing", FLOAT }, + { "value", STRING }, + { "format", STRING }, + { "displayRelative", BOOLEAN }, { "zIndex", FLOAT } } }, { "rating", { { "pos", NORMALIZED_PAIR }, diff --git a/es-core/src/components/DateTimeComponent.cpp b/es-core/src/components/DateTimeComponent.cpp index 644fd4f7e..0052f11ad 100644 --- a/es-core/src/components/DateTimeComponent.cpp +++ b/es-core/src/components/DateTimeComponent.cpp @@ -1,176 +1,25 @@ #include "components/DateTimeComponent.h" -#include "resources/Font.h" #include "utils/StringUtil.h" +#include "Log.h" #include "Renderer.h" +#include "Settings.h" -DateTimeComponent::DateTimeComponent(Window* window, DisplayMode dispMode) : GuiComponent(window), - mEditing(false), mEditIndex(0), mDisplayMode(dispMode), mRelativeUpdateAccumulator(0), - mColor(0x777777FF), mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)), mUppercase(false), mAutoSize(true) +DateTimeComponent::DateTimeComponent(Window* window) : TextComponent(window), mDisplayRelative(false) { - updateTextCache(); + setFormat("%m/%d/%Y"); } -void DateTimeComponent::setDisplayMode(DisplayMode mode) +DateTimeComponent::DateTimeComponent(Window* window, const std::string& text, const std::shared_ptr& font, unsigned int color, Alignment align, + Vector3f pos, Vector2f size, unsigned int bgcolor) : TextComponent(window, text, font, color, align, pos, size, bgcolor), mDisplayRelative(false) { - mDisplayMode = mode; - updateTextCache(); -} - -bool DateTimeComponent::input(InputConfig* config, Input input) -{ - if(input.value == 0) - return false; - - if(config->isMappedTo("a", input)) - { - if(mDisplayMode != DISP_RELATIVE_TO_NOW) //don't allow editing for relative times - mEditing = !mEditing; - - if(mEditing) - { - //started editing - mTimeBeforeEdit = mTime; - - //initialize to now if unset - if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME) - { - mTime = Utils::Time::now(); - updateTextCache(); - } - } - - return true; - } - - if(mEditing) - { - if(config->isMappedTo("b", input)) - { - mEditing = false; - mTime = mTimeBeforeEdit; - updateTextCache(); - return true; - } - - int incDir = 0; - if(config->isMappedTo("up", input) || config->isMappedTo("pageup", input)) - incDir = 1; - else if(config->isMappedTo("down", input) || config->isMappedTo("pagedown", input)) - incDir = -1; - - if(incDir != 0) - { - tm new_tm = mTime; - - if(mEditIndex == 0) - { - 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 == 1) - { - 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(mEditIndex == 2) - { - new_tm.tm_year += incDir; - - if(new_tm.tm_year < 0) - new_tm.tm_year = 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->isMappedTo("right", input)) - { - mEditIndex++; - if(mEditIndex >= (int)mCursorBoxes.size()) - mEditIndex--; - return true; - } - - if(config->isMappedTo("left", input)) - { - mEditIndex--; - if(mEditIndex < 0) - mEditIndex++; - return true; - } - } - - return GuiComponent::input(config, input); -} - -void DateTimeComponent::update(int deltaTime) -{ - if(mDisplayMode == DISP_RELATIVE_TO_NOW) - { - mRelativeUpdateAccumulator += deltaTime; - if(mRelativeUpdateAccumulator > 1000) - { - mRelativeUpdateAccumulator = 0; - updateTextCache(); - } - } - - GuiComponent::update(deltaTime); -} - -void DateTimeComponent::render(const Transform4x4f& parentTrans) -{ - Transform4x4f trans = parentTrans * getTransform(); - - if(mTextCache) - { - // vertically center - Vector3f off(0, (mSize.y() - mTextCache->metrics.size.y()) / 2, 0); - trans.translate(off); - trans.round(); - - Renderer::setMatrix(trans); - - std::shared_ptr font = getFont(); - - mTextCache->setColor((mColor & 0xFFFFFF00) | getOpacity()); - font->renderTextCache(mTextCache.get()); - - if(mEditing) - { - if(mEditIndex >= 0 && (unsigned int)mEditIndex < mCursorBoxes.size()) - { - Renderer::drawRect((int)mCursorBoxes[mEditIndex][0], (int)mCursorBoxes[mEditIndex][1], - (int)mCursorBoxes[mEditIndex][2], (int)mCursorBoxes[mEditIndex][3], 0x00000022); - } - } - } + setFormat("%m/%d/%Y"); } void DateTimeComponent::setValue(const std::string& val) { mTime = val; - updateTextCache(); + onTextChanged(); } std::string DateTimeComponent::getValue() const @@ -178,159 +27,104 @@ std::string DateTimeComponent::getValue() const return mTime; } -DateTimeComponent::DisplayMode DateTimeComponent::getCurrentDisplayMode() const +void DateTimeComponent::setFormat(const std::string& format) { - /*if(mEditing) - { - if(mDisplayMode == DISP_RELATIVE_TO_NOW) - { - //TODO: if time component == 00:00:00, return DISP_DATE, else return DISP_DATE_TIME - return DISP_DATE; - } - }*/ - - return mDisplayMode; + mFormat = format; + onTextChanged(); } -std::string DateTimeComponent::getDisplayString(DisplayMode mode) const +void DateTimeComponent::setDisplayRelative(bool displayRelative) { - std::string fmt; - switch(mode) - { - case DISP_DATE: - fmt = "%m/%d/%Y"; - break; - case DISP_DATE_TIME: - fmt = "%m/%d/%Y %H:%M:%S"; - break; - case DISP_RELATIVE_TO_NOW: - { - //relative time - if(mTime.getTime() == 0) - return "never"; + mDisplayRelative = displayRelative; + onTextChanged(); +} - Utils::Time::DateTime now(Utils::Time::now()); - Utils::Time::Duration dur(now.getTime() - mTime.getTime()); +void DateTimeComponent::onTextChanged() +{ + mText = getDisplayString(); - char buf[64]; + TextComponent::onTextChanged(); +} - if(dur.getDays() > 0) - sprintf(buf, "%d day%s ago", dur.getDays(), (dur.getDays() > 1) ? "s" : ""); - else if(dur.getHours() > 0) - sprintf(buf, "%d hour%s ago", dur.getHours(), (dur.getHours() > 1) ? "s" : ""); - else if(dur.getMinutes() > 0) - sprintf(buf, "%d minute%s ago", dur.getMinutes(), (dur.getMinutes() > 1) ? "s" : ""); - else - sprintf(buf, "%d second%s ago", dur.getSeconds(), (dur.getSeconds() > 1) ? "s" : ""); - - return std::string(buf); - } - break; +std::string DateTimeComponent::getDisplayString() const +{ + if (mDisplayRelative) { + //relative time + if(mTime.getTime() == 0) + return "never"; + + Utils::Time::DateTime now(Utils::Time::now()); + Utils::Time::Duration dur(now.getTime() - mTime.getTime()); + + char buf[64]; + + if(dur.getDays() > 0) + sprintf(buf, "%d day%s ago", dur.getDays(), (dur.getDays() > 1) ? "s" : ""); + else if(dur.getHours() > 0) + sprintf(buf, "%d hour%s ago", dur.getHours(), (dur.getHours() > 1) ? "s" : ""); + else if(dur.getMinutes() > 0) + sprintf(buf, "%d minute%s ago", dur.getMinutes(), (dur.getMinutes() > 1) ? "s" : ""); + else + sprintf(buf, "%d second%s ago", dur.getSeconds(), (dur.getSeconds() > 1) ? "s" : ""); + + return std::string(buf); } - + if(mTime.getTime() == 0) return "unknown"; - return Utils::Time::timeToString(mTime, fmt); + return Utils::Time::timeToString(mTime.getTime(), mFormat); } -std::shared_ptr DateTimeComponent::getFont() const +void DateTimeComponent::render(const Transform4x4f& parentTrans) { - if(mFont) - return mFont; - - return Font::get(FONT_SIZE_MEDIUM); + TextComponent::render(parentTrans); } -void DateTimeComponent::updateTextCache() -{ - DisplayMode mode = getCurrentDisplayMode(); - const std::string dispString = mUppercase ? Utils::String::toUpper(getDisplayString(mode)) : getDisplayString(mode); - std::shared_ptr font = getFont(); - mTextCache = std::unique_ptr(font->buildTextCache(dispString, 0, 0, mColor)); - - if(mAutoSize) - { - mSize = mTextCache->metrics.size; - - mAutoSize = false; - if(getParent()) - getParent()->onSizeChanged(); - } - - //set up cursor positions - mCursorBoxes.clear(); - - if(dispString.empty() || mode == DISP_RELATIVE_TO_NOW) - return; - - //month - Vector2f start(0, 0); - Vector2f end = font->sizeText(dispString.substr(0, 2)); - Vector2f diff = end - start; - mCursorBoxes.push_back(Vector4f(start[0], start[1], diff[0], diff[1])); - - //day - start[0] = font->sizeText(dispString.substr(0, 3)).x(); - end = font->sizeText(dispString.substr(0, 5)); - diff = end - start; - mCursorBoxes.push_back(Vector4f(start[0], start[1], diff[0], diff[1])); - - //year - start[0] = font->sizeText(dispString.substr(0, 6)).x(); - end = font->sizeText(dispString.substr(0, 10)); - diff = end - start; - mCursorBoxes.push_back(Vector4f(start[0], start[1], diff[0], diff[1])); - - //if mode == DISP_DATE_TIME do times too but I don't wanna do the logic for editing times because no one will ever use it so screw it -} - -void DateTimeComponent::setColor(unsigned int color) -{ - mColor = color; - if(mTextCache) - mTextCache->setColor(color); -} - -void DateTimeComponent::setFont(std::shared_ptr font) -{ - mFont = font; - updateTextCache(); -} - -void DateTimeComponent::onSizeChanged() -{ - mAutoSize = false; - updateTextCache(); -} - -void DateTimeComponent::setUppercase(bool uppercase) -{ - mUppercase = uppercase; - updateTextCache(); -} void DateTimeComponent::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")) + const ThemeData::ThemeElement* elem = theme->getElement(view, element, "datetime"); + if(!elem) + return; + + if(elem->has("displayRelative")) + setDisplayRelative(elem->get("displayRelative")); + + if(elem->has("format")) + setFormat(elem->get("format")); + + if (properties & COLOR && elem->has("color")) setColor(elem->get("color")); + setRenderBackground(false); + if (properties & COLOR && elem->has("backgroundColor")) { + setBackgroundColor(elem->get("backgroundColor")); + setRenderBackground(true); + } + + if(properties & ALIGNMENT && elem->has("alignment")) + { + std::string str = elem->get("alignment"); + if(str == "left") + setHorizontalAlignment(ALIGN_LEFT); + else if(str == "center") + setHorizontalAlignment(ALIGN_CENTER); + else if(str == "right") + setHorizontalAlignment(ALIGN_RIGHT); + else + LOG(LogError) << "Unknown text alignment string: " << str; + } + if(properties & FORCE_UPPERCASE && elem->has("forceUppercase")) setUppercase(elem->get("forceUppercase")); + if(properties & LINE_SPACING && elem->has("lineSpacing")) + setLineSpacing(elem->get("lineSpacing")); + setFont(Font::getFromTheme(elem, properties, mFont)); } diff --git a/es-core/src/components/DateTimeComponent.h b/es-core/src/components/DateTimeComponent.h index 8e629c491..55d70ca5f 100644 --- a/es-core/src/components/DateTimeComponent.h +++ b/es-core/src/components/DateTimeComponent.h @@ -3,69 +3,37 @@ #define ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H #include "utils/TimeUtil.h" -#include "GuiComponent.h" +#include "TextComponent.h" -class TextCache; +class ThemeData; -// Used to enter or display a specific point in time. -class DateTimeComponent : public GuiComponent +// Used to display date times. +class DateTimeComponent : public TextComponent { public: - enum DisplayMode - { - DISP_DATE, - DISP_DATE_TIME, - DISP_RELATIVE_TO_NOW - }; + DateTimeComponent(Window* window); + DateTimeComponent(Window* window, const std::string& text, const std::shared_ptr& font, unsigned int color = 0x000000FF, Alignment align = ALIGN_LEFT, + Vector3f pos = Vector3f::Zero(), Vector2f size = Vector2f::Zero(), unsigned int bgcolor = 0x00000000); - DateTimeComponent(Window* window, DisplayMode dispMode = DISP_DATE); + void render(const Transform4x4f& parentTrans) override; void setValue(const std::string& val) override; std::string getValue() const override; - bool input(InputConfig* config, Input input) override; - void update(int deltaTime) override; - void render(const Transform4x4f& parentTrans) override; - void onSizeChanged() override; - - // Set how the point in time will be displayed: - // * DISP_DATE - only display the date. - // * DISP_DATE_TIME - display both the date and the time on that date. - // * DISP_RELATIVE_TO_NOW - intelligently display the point in time relative to right now (e.g. "5 secs ago", "3 minutes ago", "1 day ago". Automatically updates as time marches on. - // The initial value is DISP_DATE. - void setDisplayMode(DisplayMode mode); - - void setColor(unsigned int color); // Text color. - void setFont(std::shared_ptr font); // Font to display with. Default is Font::get(FONT_SIZE_MEDIUM). - void setUppercase(bool uppercase); // Force text to be uppercase when in DISP_RELATIVE_TO_NOW mode. + void setFormat(const std::string& format); + void setDisplayRelative(bool displayRelative); virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; -private: - std::shared_ptr getFont() const; +protected: + void onTextChanged() override; - std::string getDisplayString(DisplayMode mode) const; - DisplayMode getCurrentDisplayMode() const; - - void updateTextCache(); +private: + std::string getDisplayString() const; Utils::Time::DateTime mTime; - Utils::Time::DateTime mTimeBeforeEdit; - - bool mEditing; - int mEditIndex; - DisplayMode mDisplayMode; - - int mRelativeUpdateAccumulator; - - std::unique_ptr mTextCache; - std::vector mCursorBoxes; - - unsigned int mColor; - std::shared_ptr mFont; - bool mUppercase; - - bool mAutoSize; + std::string mFormat; + bool mDisplayRelative; }; #endif // ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H diff --git a/es-core/src/components/DateTimeEditComponent.cpp b/es-core/src/components/DateTimeEditComponent.cpp new file mode 100644 index 000000000..1617934c0 --- /dev/null +++ b/es-core/src/components/DateTimeEditComponent.cpp @@ -0,0 +1,336 @@ +#include "components/DateTimeEditComponent.h" + +#include "resources/Font.h" +#include "utils/StringUtil.h" +#include "Renderer.h" + +DateTimeEditComponent::DateTimeEditComponent(Window* window, DisplayMode dispMode) : GuiComponent(window), + mEditing(false), mEditIndex(0), mDisplayMode(dispMode), mRelativeUpdateAccumulator(0), + mColor(0x777777FF), mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)), mUppercase(false), mAutoSize(true) +{ + updateTextCache(); +} + +void DateTimeEditComponent::setDisplayMode(DisplayMode mode) +{ + mDisplayMode = mode; + updateTextCache(); +} + +bool DateTimeEditComponent::input(InputConfig* config, Input input) +{ + if(input.value == 0) + return false; + + if(config->isMappedTo("a", input)) + { + if(mDisplayMode != DISP_RELATIVE_TO_NOW) //don't allow editing for relative times + mEditing = !mEditing; + + if(mEditing) + { + //started editing + mTimeBeforeEdit = mTime; + + //initialize to now if unset + if(mTime.getTime() == Utils::Time::NOT_A_DATE_TIME) + { + mTime = Utils::Time::now(); + updateTextCache(); + } + } + + return true; + } + + if(mEditing) + { + if(config->isMappedTo("b", input)) + { + mEditing = false; + mTime = mTimeBeforeEdit; + updateTextCache(); + return true; + } + + int incDir = 0; + if(config->isMappedTo("up", input) || config->isMappedTo("pageup", input)) + incDir = 1; + else if(config->isMappedTo("down", input) || config->isMappedTo("pagedown", input)) + incDir = -1; + + if(incDir != 0) + { + tm new_tm = mTime; + + if(mEditIndex == 0) + { + 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 == 1) + { + 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(mEditIndex == 2) + { + new_tm.tm_year += incDir; + + if(new_tm.tm_year < 0) + new_tm.tm_year = 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->isMappedTo("right", input)) + { + mEditIndex++; + if(mEditIndex >= (int)mCursorBoxes.size()) + mEditIndex--; + return true; + } + + if(config->isMappedTo("left", input)) + { + mEditIndex--; + if(mEditIndex < 0) + mEditIndex++; + return true; + } + } + + return GuiComponent::input(config, input); +} + +void DateTimeEditComponent::update(int deltaTime) +{ + if(mDisplayMode == DISP_RELATIVE_TO_NOW) + { + mRelativeUpdateAccumulator += deltaTime; + if(mRelativeUpdateAccumulator > 1000) + { + mRelativeUpdateAccumulator = 0; + updateTextCache(); + } + } + + GuiComponent::update(deltaTime); +} + +void DateTimeEditComponent::render(const Transform4x4f& parentTrans) +{ + Transform4x4f trans = parentTrans * getTransform(); + + if(mTextCache) + { + // vertically center + Vector3f off(0, (mSize.y() - mTextCache->metrics.size.y()) / 2, 0); + trans.translate(off); + trans.round(); + + Renderer::setMatrix(trans); + + std::shared_ptr font = getFont(); + + mTextCache->setColor((mColor & 0xFFFFFF00) | getOpacity()); + font->renderTextCache(mTextCache.get()); + + if(mEditing) + { + if(mEditIndex >= 0 && (unsigned int)mEditIndex < mCursorBoxes.size()) + { + Renderer::drawRect((int)mCursorBoxes[mEditIndex][0], (int)mCursorBoxes[mEditIndex][1], + (int)mCursorBoxes[mEditIndex][2], (int)mCursorBoxes[mEditIndex][3], 0x00000022); + } + } + } +} + +void DateTimeEditComponent::setValue(const std::string& val) +{ + mTime = val; + updateTextCache(); +} + +std::string DateTimeEditComponent::getValue() const +{ + return mTime; +} + +DateTimeEditComponent::DisplayMode DateTimeEditComponent::getCurrentDisplayMode() const +{ + /*if(mEditing) + { + if(mDisplayMode == DISP_RELATIVE_TO_NOW) + { + //TODO: if time component == 00:00:00, return DISP_DATE, else return DISP_DATE_TIME + return DISP_DATE; + } + }*/ + + return mDisplayMode; +} + +std::string DateTimeEditComponent::getDisplayString(DisplayMode mode) const +{ + std::string fmt; + switch(mode) + { + case DISP_DATE: + fmt = "%m/%d/%Y"; + break; + case DISP_DATE_TIME: + fmt = "%m/%d/%Y %H:%M:%S"; + break; + case DISP_RELATIVE_TO_NOW: + { + //relative time + if(mTime.getTime() == 0) + return "never"; + + Utils::Time::DateTime now(Utils::Time::now()); + Utils::Time::Duration dur(now.getTime() - mTime.getTime()); + + char buf[64]; + + if(dur.getDays() > 0) + sprintf(buf, "%d day%s ago", dur.getDays(), (dur.getDays() > 1) ? "s" : ""); + else if(dur.getHours() > 0) + sprintf(buf, "%d hour%s ago", dur.getHours(), (dur.getHours() > 1) ? "s" : ""); + else if(dur.getMinutes() > 0) + sprintf(buf, "%d minute%s ago", dur.getMinutes(), (dur.getMinutes() > 1) ? "s" : ""); + else + sprintf(buf, "%d second%s ago", dur.getSeconds(), (dur.getSeconds() > 1) ? "s" : ""); + + return std::string(buf); + } + break; + } + + if(mTime.getTime() == 0) + return "unknown"; + + return Utils::Time::timeToString(mTime, fmt); +} + +std::shared_ptr DateTimeEditComponent::getFont() const +{ + if(mFont) + return mFont; + + return Font::get(FONT_SIZE_MEDIUM); +} + +void DateTimeEditComponent::updateTextCache() +{ + DisplayMode mode = getCurrentDisplayMode(); + const std::string dispString = mUppercase ? Utils::String::toUpper(getDisplayString(mode)) : getDisplayString(mode); + std::shared_ptr font = getFont(); + mTextCache = std::unique_ptr(font->buildTextCache(dispString, 0, 0, mColor)); + + if(mAutoSize) + { + mSize = mTextCache->metrics.size; + + mAutoSize = false; + if(getParent()) + getParent()->onSizeChanged(); + } + + //set up cursor positions + mCursorBoxes.clear(); + + if(dispString.empty() || mode == DISP_RELATIVE_TO_NOW) + return; + + //month + Vector2f start(0, 0); + Vector2f end = font->sizeText(dispString.substr(0, 2)); + Vector2f diff = end - start; + mCursorBoxes.push_back(Vector4f(start[0], start[1], diff[0], diff[1])); + + //day + start[0] = font->sizeText(dispString.substr(0, 3)).x(); + end = font->sizeText(dispString.substr(0, 5)); + diff = end - start; + mCursorBoxes.push_back(Vector4f(start[0], start[1], diff[0], diff[1])); + + //year + start[0] = font->sizeText(dispString.substr(0, 6)).x(); + end = font->sizeText(dispString.substr(0, 10)); + diff = end - start; + mCursorBoxes.push_back(Vector4f(start[0], start[1], diff[0], diff[1])); + + //if mode == DISP_DATE_TIME do times too but I don't wanna do the logic for editing times because no one will ever use it so screw it +} + +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)); +} diff --git a/es-core/src/components/DateTimeEditComponent.h b/es-core/src/components/DateTimeEditComponent.h new file mode 100644 index 000000000..535ba2f86 --- /dev/null +++ b/es-core/src/components/DateTimeEditComponent.h @@ -0,0 +1,71 @@ +#pragma once +#ifndef ES_CORE_COMPONENTS_DATE_TIME_EDIT_COMPONENT_H +#define ES_CORE_COMPONENTS_DATE_TIME_EDIT_COMPONENT_H + +#include "utils/TimeUtil.h" +#include "GuiComponent.h" + +class TextCache; + +// Used to enter or display a specific point in time. +class DateTimeEditComponent : public GuiComponent +{ +public: + enum DisplayMode + { + DISP_DATE, + DISP_DATE_TIME, + DISP_RELATIVE_TO_NOW + }; + + DateTimeEditComponent(Window* window, DisplayMode dispMode = DISP_DATE); + + void setValue(const std::string& val) override; + std::string getValue() const override; + + bool input(InputConfig* config, Input input) override; + void update(int deltaTime) override; + void render(const Transform4x4f& parentTrans) override; + void onSizeChanged() override; + + // Set how the point in time will be displayed: + // * DISP_DATE - only display the date. + // * DISP_DATE_TIME - display both the date and the time on that date. + // * DISP_RELATIVE_TO_NOW - intelligently display the point in time relative to right now (e.g. "5 secs ago", "3 minutes ago", "1 day ago". Automatically updates as time marches on. + // The initial value is DISP_DATE. + void setDisplayMode(DisplayMode mode); + + void setColor(unsigned int color); // Text color. + void setFont(std::shared_ptr font); // Font to display with. Default is Font::get(FONT_SIZE_MEDIUM). + void setUppercase(bool uppercase); // Force text to be uppercase when in DISP_RELATIVE_TO_NOW mode. + + virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; + +private: + std::shared_ptr getFont() const; + + std::string getDisplayString(DisplayMode mode) const; + DisplayMode getCurrentDisplayMode() const; + + void updateTextCache(); + + Utils::Time::DateTime mTime; + Utils::Time::DateTime mTimeBeforeEdit; + + bool mEditing; + int mEditIndex; + DisplayMode mDisplayMode; + + int mRelativeUpdateAccumulator; + + std::unique_ptr mTextCache; + std::vector mCursorBoxes; + + unsigned int mColor; + std::shared_ptr mFont; + bool mUppercase; + + bool mAutoSize; +}; + +#endif // ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H diff --git a/es-core/src/components/TextComponent.h b/es-core/src/components/TextComponent.h index 2603327f9..172110577 100644 --- a/es-core/src/components/TextComponent.h +++ b/es-core/src/components/TextComponent.h @@ -37,15 +37,20 @@ public: unsigned char getOpacity() const override; void setOpacity(unsigned char opacity) override; - + inline std::shared_ptr getFont() const { return mFont; } virtual void applyTheme(const std::shared_ptr& theme, const std::string& view, const std::string& element, unsigned int properties) override; +protected: + virtual void onTextChanged(); + + std::string mText; + std::shared_ptr mFont; + private: void calculateExtent(); - void onTextChanged(); void onColorChanged(); unsigned int mColor; @@ -54,10 +59,8 @@ private: unsigned char mBgColorOpacity; bool mRenderBackground; - std::shared_ptr mFont; bool mUppercase; Vector2i mAutoCalcExtent; - std::string mText; std::shared_ptr mTextCache; Alignment mHorizontalAlignment; Alignment mVerticalAlignment;