create new DateTimeComponent

This commit is contained in:
John Rassa 2018-10-12 18:08:15 -07:00
parent 8c0b00c490
commit a57b0ecfea
14 changed files with 558 additions and 351 deletions

View file

@ -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. - z-index value for component. Components will be rendered in order of z-index value from low to high.
#### datetime #### datetime
* `pos` - type: NORMALIZED_PAIR. * `pos` - type: NORMALIZED_PAIR.
* `size` - 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. * `color` - type: COLOR.
* `backgroundColor` - type: COLOR;
* `fontPath` - type: PATH. * `fontPath` - type: PATH.
- Path to a truetype font (.ttf).
* `fontSize` - type: FLOAT. * `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. * `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 #### sound

View file

@ -1,7 +1,7 @@
#include "components/ScraperSearchComponent.h" #include "components/ScraperSearchComponent.h"
#include "components/ComponentList.h" #include "components/ComponentList.h"
#include "components/DateTimeComponent.h" #include "components/DateTimeEditComponent.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include "components/RatingComponent.h" #include "components/RatingComponent.h"
#include "components/ScrollableContainer.h" #include "components/ScrollableContainer.h"
@ -43,7 +43,7 @@ ScraperSearchComponent::ScraperSearchComponent(Window* window, SearchType type)
const unsigned int mdColor = 0x777777FF; const unsigned int mdColor = 0x777777FF;
const unsigned int mdLblColor = 0x666666FF; const unsigned int mdLblColor = 0x666666FF;
mMD_Rating = std::make_shared<RatingComponent>(mWindow); mMD_Rating = std::make_shared<RatingComponent>(mWindow);
mMD_ReleaseDate = std::make_shared<DateTimeComponent>(mWindow); mMD_ReleaseDate = std::make_shared<DateTimeEditComponent>(mWindow);
mMD_ReleaseDate->setColor(mdColor); mMD_ReleaseDate->setColor(mdColor);
mMD_Developer = std::make_shared<TextComponent>(mWindow, "", font, mdColor); mMD_Developer = std::make_shared<TextComponent>(mWindow, "", font, mdColor);
mMD_Publisher = std::make_shared<TextComponent>(mWindow, "", font, mdColor); mMD_Publisher = std::make_shared<TextComponent>(mWindow, "", font, mdColor);

View file

@ -8,7 +8,7 @@
#include "GuiComponent.h" #include "GuiComponent.h"
class ComponentList; class ComponentList;
class DateTimeComponent; class DateTimeEditComponent;
class ImageComponent; class ImageComponent;
class RatingComponent; class RatingComponent;
class ScrollableContainer; class ScrollableContainer;
@ -69,7 +69,7 @@ private:
std::shared_ptr<ComponentGrid> mMD_Grid; std::shared_ptr<ComponentGrid> mMD_Grid;
std::shared_ptr<RatingComponent> mMD_Rating; std::shared_ptr<RatingComponent> mMD_Rating;
std::shared_ptr<DateTimeComponent> mMD_ReleaseDate; std::shared_ptr<DateTimeEditComponent> mMD_ReleaseDate;
std::shared_ptr<TextComponent> mMD_Developer; std::shared_ptr<TextComponent> mMD_Developer;
std::shared_ptr<TextComponent> mMD_Publisher; std::shared_ptr<TextComponent> mMD_Publisher;
std::shared_ptr<TextComponent> mMD_Genre; std::shared_ptr<TextComponent> mMD_Genre;

View file

@ -2,7 +2,7 @@
#include "components/ButtonComponent.h" #include "components/ButtonComponent.h"
#include "components/ComponentList.h" #include "components/ComponentList.h"
#include "components/DateTimeComponent.h" #include "components/DateTimeEditComponent.h"
#include "components/MenuComponent.h" #include "components/MenuComponent.h"
#include "components/RatingComponent.h" #include "components/RatingComponent.h"
#include "components/SwitchComponent.h" #include "components/SwitchComponent.h"
@ -87,21 +87,21 @@ GuiMetaDataEd::GuiMetaDataEd(Window* window, MetaDataList* md, const std::vector
} }
case MD_DATE: case MD_DATE:
{ {
ed = std::make_shared<DateTimeComponent>(window); ed = std::make_shared<DateTimeEditComponent>(window);
row.addElement(ed, false); row.addElement(ed, false);
auto spacer = std::make_shared<GuiComponent>(mWindow); auto spacer = std::make_shared<GuiComponent>(mWindow);
spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0); spacer->setSize(Renderer::getScreenWidth() * 0.0025f, 0);
row.addElement(spacer, false); 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); row.input_handler = std::bind(&GuiComponent::input, ed.get(), std::placeholders::_1, std::placeholders::_2);
break; break;
} }
case MD_TIME: case MD_TIME:
{ {
ed = std::make_shared<DateTimeComponent>(window, DateTimeComponent::DISP_RELATIVE_TO_NOW); ed = std::make_shared<DateTimeEditComponent>(window, DateTimeEditComponent::DISP_RELATIVE_TO_NOW);
row.addElement(ed, false); row.addElement(ed, false);
break; break;
} }

View file

@ -52,7 +52,7 @@ DetailedGameListView::DetailedGameListView(Window* window, FileData* root) :
addChild(&mPlayers); addChild(&mPlayers);
mLblLastPlayed.setText("Last played: "); mLblLastPlayed.setText("Last played: ");
addChild(&mLblLastPlayed); addChild(&mLblLastPlayed);
mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); mLastPlayed.setDisplayRelative(true);
addChild(&mLastPlayed); addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: "); mLblPlayCount.setText("Times played: ");
addChild(&mLblPlayCount); addChild(&mLblPlayCount);

View file

@ -49,7 +49,7 @@ GridGameListView::GridGameListView(Window* window, FileData* root) :
addChild(&mPlayers); addChild(&mPlayers);
mLblLastPlayed.setText("Last played: "); mLblLastPlayed.setText("Last played: ");
addChild(&mLblLastPlayed); addChild(&mLblLastPlayed);
mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); mLastPlayed.setDisplayRelative(true);
addChild(&mLastPlayed); addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: "); mLblPlayCount.setText("Times played: ");
addChild(&mLblPlayCount); addChild(&mLblPlayCount);

View file

@ -86,7 +86,7 @@ VideoGameListView::VideoGameListView(Window* window, FileData* root) :
addChild(&mPlayers); addChild(&mPlayers);
mLblLastPlayed.setText("Last played: "); mLblLastPlayed.setText("Last played: ");
addChild(&mLblLastPlayed); addChild(&mLblLastPlayed);
mLastPlayed.setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW); mLastPlayed.setDisplayRelative(true);
addChild(&mLastPlayed); addChild(&mLastPlayed);
mLblPlayCount.setText("Times played: "); mLblPlayCount.setText("Times played: ");
addChild(&mLblPlayCount); addChild(&mLblPlayCount);

View file

@ -32,6 +32,7 @@ set(CORE_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentGrid.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.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/HelpComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.h
${CMAKE_CURRENT_SOURCE_DIR}/src/components/IList.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/ComponentGrid.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentList.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.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/HelpComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/GridTileComponent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp

View file

@ -90,10 +90,19 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>> The
{ "datetime", { { "datetime", {
{ "pos", NORMALIZED_PAIR }, { "pos", NORMALIZED_PAIR },
{ "size", NORMALIZED_PAIR }, { "size", NORMALIZED_PAIR },
{ "color", COLOR }, { "origin", NORMALIZED_PAIR },
{ "rotation", FLOAT },
{ "rotationOrigin", NORMALIZED_PAIR },
{ "backgroundColor", COLOR },
{ "fontPath", PATH }, { "fontPath", PATH },
{ "fontSize", FLOAT }, { "fontSize", FLOAT },
{ "color", COLOR },
{ "alignment", STRING },
{ "forceUppercase", BOOLEAN }, { "forceUppercase", BOOLEAN },
{ "lineSpacing", FLOAT },
{ "value", STRING },
{ "format", STRING },
{ "displayRelative", BOOLEAN },
{ "zIndex", FLOAT } } }, { "zIndex", FLOAT } } },
{ "rating", { { "rating", {
{ "pos", NORMALIZED_PAIR }, { "pos", NORMALIZED_PAIR },

View file

@ -1,176 +1,25 @@
#include "components/DateTimeComponent.h" #include "components/DateTimeComponent.h"
#include "resources/Font.h"
#include "utils/StringUtil.h" #include "utils/StringUtil.h"
#include "Log.h"
#include "Renderer.h" #include "Renderer.h"
#include "Settings.h"
DateTimeComponent::DateTimeComponent(Window* window, DisplayMode dispMode) : GuiComponent(window), DateTimeComponent::DateTimeComponent(Window* window) : TextComponent(window), mDisplayRelative(false)
mEditing(false), mEditIndex(0), mDisplayMode(dispMode), mRelativeUpdateAccumulator(0),
mColor(0x777777FF), mFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT)), mUppercase(false), mAutoSize(true)
{ {
updateTextCache(); setFormat("%m/%d/%Y");
} }
void DateTimeComponent::setDisplayMode(DisplayMode mode) DateTimeComponent::DateTimeComponent(Window* window, const std::string& text, const std::shared_ptr<Font>& 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; setFormat("%m/%d/%Y");
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> 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 DateTimeComponent::setValue(const std::string& val) void DateTimeComponent::setValue(const std::string& val)
{ {
mTime = val; mTime = val;
updateTextCache(); onTextChanged();
} }
std::string DateTimeComponent::getValue() const std::string DateTimeComponent::getValue() const
@ -178,159 +27,104 @@ std::string DateTimeComponent::getValue() const
return mTime; return mTime;
} }
DateTimeComponent::DisplayMode DateTimeComponent::getCurrentDisplayMode() const void DateTimeComponent::setFormat(const std::string& format)
{ {
/*if(mEditing) mFormat = format;
{ onTextChanged();
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 DateTimeComponent::getDisplayString(DisplayMode mode) const void DateTimeComponent::setDisplayRelative(bool displayRelative)
{ {
std::string fmt; mDisplayRelative = displayRelative;
switch(mode) onTextChanged();
{ }
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()); void DateTimeComponent::onTextChanged()
Utils::Time::Duration dur(now.getTime() - mTime.getTime()); {
mText = getDisplayString();
char buf[64]; TextComponent::onTextChanged();
}
if(dur.getDays() > 0) std::string DateTimeComponent::getDisplayString() const
sprintf(buf, "%d day%s ago", dur.getDays(), (dur.getDays() > 1) ? "s" : ""); {
else if(dur.getHours() > 0) if (mDisplayRelative) {
sprintf(buf, "%d hour%s ago", dur.getHours(), (dur.getHours() > 1) ? "s" : ""); //relative time
else if(dur.getMinutes() > 0) if(mTime.getTime() == 0)
sprintf(buf, "%d minute%s ago", dur.getMinutes(), (dur.getMinutes() > 1) ? "s" : ""); return "never";
else
sprintf(buf, "%d second%s ago", dur.getSeconds(), (dur.getSeconds() > 1) ? "s" : ""); Utils::Time::DateTime now(Utils::Time::now());
Utils::Time::Duration dur(now.getTime() - mTime.getTime());
return std::string(buf);
} char buf[64];
break;
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) if(mTime.getTime() == 0)
return "unknown"; return "unknown";
return Utils::Time::timeToString(mTime, fmt); return Utils::Time::timeToString(mTime.getTime(), mFormat);
} }
std::shared_ptr<Font> DateTimeComponent::getFont() const void DateTimeComponent::render(const Transform4x4f& parentTrans)
{ {
if(mFont) TextComponent::render(parentTrans);
return mFont;
return Font::get(FONT_SIZE_MEDIUM);
} }
void DateTimeComponent::updateTextCache()
{
DisplayMode mode = getCurrentDisplayMode();
const std::string dispString = mUppercase ? Utils::String::toUpper(getDisplayString(mode)) : getDisplayString(mode);
std::shared_ptr<Font> font = getFont();
mTextCache = std::unique_ptr<TextCache>(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> 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<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) void DateTimeComponent::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); GuiComponent::applyTheme(theme, view, element, properties);
using namespace ThemeFlags; 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<bool>("displayRelative"));
if(elem->has("format"))
setFormat(elem->get<std::string>("format"));
if (properties & COLOR && elem->has("color"))
setColor(elem->get<unsigned int>("color")); setColor(elem->get<unsigned int>("color"));
setRenderBackground(false);
if (properties & COLOR && elem->has("backgroundColor")) {
setBackgroundColor(elem->get<unsigned int>("backgroundColor"));
setRenderBackground(true);
}
if(properties & ALIGNMENT && elem->has("alignment"))
{
std::string str = elem->get<std::string>("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")) if(properties & FORCE_UPPERCASE && elem->has("forceUppercase"))
setUppercase(elem->get<bool>("forceUppercase")); setUppercase(elem->get<bool>("forceUppercase"));
if(properties & LINE_SPACING && elem->has("lineSpacing"))
setLineSpacing(elem->get<float>("lineSpacing"));
setFont(Font::getFromTheme(elem, properties, mFont)); setFont(Font::getFromTheme(elem, properties, mFont));
} }

View file

@ -3,69 +3,37 @@
#define ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H #define ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H
#include "utils/TimeUtil.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. // Used to display date times.
class DateTimeComponent : public GuiComponent class DateTimeComponent : public TextComponent
{ {
public: public:
enum DisplayMode DateTimeComponent(Window* window);
{ DateTimeComponent(Window* window, const std::string& text, const std::shared_ptr<Font>& font, unsigned int color = 0x000000FF, Alignment align = ALIGN_LEFT,
DISP_DATE, Vector3f pos = Vector3f::Zero(), Vector2f size = Vector2f::Zero(), unsigned int bgcolor = 0x00000000);
DISP_DATE_TIME,
DISP_RELATIVE_TO_NOW
};
DateTimeComponent(Window* window, DisplayMode dispMode = DISP_DATE); void render(const Transform4x4f& parentTrans) override;
void setValue(const std::string& val) override; void setValue(const std::string& val) override;
std::string getValue() const override; std::string getValue() const override;
bool input(InputConfig* config, Input input) override; void setFormat(const std::string& format);
void update(int deltaTime) override; void setDisplayRelative(bool displayRelative);
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); // 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<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
private: protected:
std::shared_ptr<Font> getFont() const; void onTextChanged() override;
std::string getDisplayString(DisplayMode mode) const; private:
DisplayMode getCurrentDisplayMode() const; std::string getDisplayString() const;
void updateTextCache();
Utils::Time::DateTime mTime; Utils::Time::DateTime mTime;
Utils::Time::DateTime mTimeBeforeEdit; std::string mFormat;
bool mDisplayRelative;
bool mEditing;
int mEditIndex;
DisplayMode mDisplayMode;
int mRelativeUpdateAccumulator;
std::unique_ptr<TextCache> mTextCache;
std::vector<Vector4f> mCursorBoxes;
unsigned int mColor;
std::shared_ptr<Font> mFont;
bool mUppercase;
bool mAutoSize;
}; };
#endif // ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H #endif // ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H

View file

@ -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> 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<Font> 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> font = getFont();
mTextCache = std::unique_ptr<TextCache>(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> 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));
}

View file

@ -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); // 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<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
private:
std::shared_ptr<Font> 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<TextCache> mTextCache;
std::vector<Vector4f> mCursorBoxes;
unsigned int mColor;
std::shared_ptr<Font> mFont;
bool mUppercase;
bool mAutoSize;
};
#endif // ES_CORE_COMPONENTS_DATE_TIME_COMPONENT_H

View file

@ -37,15 +37,20 @@ public:
unsigned char getOpacity() const override; unsigned char getOpacity() const override;
void setOpacity(unsigned char opacity) override; void setOpacity(unsigned char opacity) override;
inline std::shared_ptr<Font> getFont() const { return mFont; } inline std::shared_ptr<Font> getFont() const { return mFont; }
virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override; virtual void applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties) override;
protected:
virtual void onTextChanged();
std::string mText;
std::shared_ptr<Font> mFont;
private: private:
void calculateExtent(); void calculateExtent();
void onTextChanged();
void onColorChanged(); void onColorChanged();
unsigned int mColor; unsigned int mColor;
@ -54,10 +59,8 @@ private:
unsigned char mBgColorOpacity; unsigned char mBgColorOpacity;
bool mRenderBackground; bool mRenderBackground;
std::shared_ptr<Font> mFont;
bool mUppercase; bool mUppercase;
Vector2i mAutoCalcExtent; Vector2i mAutoCalcExtent;
std::string mText;
std::shared_ptr<TextCache> mTextCache; std::shared_ptr<TextCache> mTextCache;
Alignment mHorizontalAlignment; Alignment mHorizontalAlignment;
Alignment mVerticalAlignment; Alignment mVerticalAlignment;