mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-21 21:55:38 +00:00
DateTimeComponent.
Can display dates, date + times, and an english description of a time relative to now ("2 secs ago", "1 day ago", etc.). Supports editing dates (including day-of-month validation). This took a lot longer than I thought.
This commit is contained in:
parent
7db0100edd
commit
838b8ee422
|
@ -160,6 +160,7 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.h
|
||||
|
@ -214,6 +215,7 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/AsyncReqComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ButtonComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ComponentListComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/DateTimeComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/ImageComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/NinePatchComponent.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/components/RatingComponent.cpp
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "components/TextEditComponent.h"
|
||||
#include "components/RatingComponent.h"
|
||||
#include "components/DateTimeComponent.h"
|
||||
|
||||
MetaDataList::MetaDataList()
|
||||
{
|
||||
|
@ -23,7 +24,7 @@ std::vector<MetaDataDecl> MetaDataList::getDefaultGameMDD()
|
|||
{"image", MD_IMAGE_PATH, "", false},
|
||||
{"thumbnail", MD_IMAGE_PATH, "", false},
|
||||
{"rating", MD_RATING, "0", false},
|
||||
{"releasedate", MD_TIME, "0", false},
|
||||
{"releasedate", MD_DATE, "0", false},
|
||||
{"playcount", MD_INT, "0", true},
|
||||
{"lastplayed", MD_TIME, "0", true}
|
||||
};
|
||||
|
@ -131,6 +132,17 @@ GuiComponent* MetaDataList::makeEditor(Window* window, MetaDataType as)
|
|||
comp->setSize(comp->getSize().x(), comp->getSize().y() * 3);
|
||||
return comp;
|
||||
}
|
||||
case MD_DATE:
|
||||
{
|
||||
DateTimeComponent* comp = new DateTimeComponent(window);
|
||||
return comp;
|
||||
}
|
||||
case MD_TIME:
|
||||
{
|
||||
DateTimeComponent* comp = new DateTimeComponent(window);
|
||||
comp->setDisplayMode(DateTimeComponent::DISP_RELATIVE_TO_NOW);
|
||||
return comp;
|
||||
}
|
||||
default:
|
||||
{
|
||||
TextEditComponent* comp = new TextEditComponent(window);
|
||||
|
|
|
@ -17,7 +17,8 @@ enum MetaDataType
|
|||
MD_MULTILINE_STRING,
|
||||
MD_IMAGE_PATH,
|
||||
MD_RATING,
|
||||
MD_TIME
|
||||
MD_DATE,
|
||||
MD_TIME //used for lastplayed
|
||||
};
|
||||
|
||||
struct MetaDataDecl
|
||||
|
@ -28,7 +29,7 @@ struct MetaDataDecl
|
|||
bool isStatistic; //if true, ignore scraper values for this metadata
|
||||
};
|
||||
|
||||
boost::posix_time::ptime string_to_ptime(const std::string& str, const std::string& fmt);
|
||||
boost::posix_time::ptime string_to_ptime(const std::string& str, const std::string& fmt = "%Y%m%dT%H%M%S%F%q");
|
||||
|
||||
class MetaDataList
|
||||
{
|
||||
|
@ -41,7 +42,7 @@ public:
|
|||
MetaDataList(const std::vector<MetaDataDecl>& mdd);
|
||||
|
||||
void set(const std::string& key, const std::string& value);
|
||||
void setTime(const std::string& key, const boost::posix_time::ptime& time);
|
||||
void setTime(const std::string& key, const boost::posix_time::ptime& time); //times are internally stored as ISO strings (e.g. boost::posix_time::to_iso_string(ptime))
|
||||
|
||||
const std::string& get(const std::string& key) const;
|
||||
int getInt(const std::string& key) const;
|
||||
|
|
269
src/components/DateTimeComponent.cpp
Normal file
269
src/components/DateTimeComponent.cpp
Normal file
|
@ -0,0 +1,269 @@
|
|||
#include "DateTimeComponent.h"
|
||||
#include "../MetaData.h"
|
||||
#include "../Renderer.h"
|
||||
#include "../Window.h"
|
||||
#include "../Log.h"
|
||||
|
||||
DateTimeComponent::DateTimeComponent(Window* window) : GuiComponent(window),
|
||||
mEditing(false), mEditIndex(0), mDisplayMode(DISP_DATE), mRelativeUpdateAccumulator(0)
|
||||
{
|
||||
mSize << 64, (float)getFont()->getHeight();
|
||||
updateTextCache();
|
||||
}
|
||||
|
||||
void DateTimeComponent::setDisplayMode(DisplayMode mode)
|
||||
{
|
||||
mDisplayMode = mode;
|
||||
}
|
||||
|
||||
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 == boost::posix_time::not_a_date_time)
|
||||
{
|
||||
mTime = boost::posix_time::ptime(boost::gregorian::day_clock::local_day());
|
||||
updateTextCache();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(mEditing)
|
||||
{
|
||||
if(config->isMappedTo("b", input))
|
||||
{
|
||||
mEditing = false;
|
||||
mTime = mTimeBeforeEdit;
|
||||
updateTextCache();
|
||||
}
|
||||
|
||||
int incDir = 0;
|
||||
if(config->isMappedTo("up", input))
|
||||
incDir = 1;
|
||||
else if(config->isMappedTo("down", input))
|
||||
incDir = -1;
|
||||
|
||||
if(incDir != 0)
|
||||
{
|
||||
tm new_tm = boost::posix_time::to_tm(mTime);
|
||||
|
||||
if(mEditIndex == 0)
|
||||
{
|
||||
new_tm.tm_mon += incDir;
|
||||
|
||||
if(new_tm.tm_mon > 11)
|
||||
new_tm.tm_mon = 11;
|
||||
else if(new_tm.tm_mon < 0)
|
||||
new_tm.tm_mon = 0;
|
||||
|
||||
}else if(mEditIndex == 1)
|
||||
{
|
||||
new_tm.tm_mday += incDir;
|
||||
int days_in_month = mTime.date().end_of_month().day().as_number();
|
||||
if(new_tm.tm_mday > days_in_month)
|
||||
new_tm.tm_mday = days_in_month;
|
||||
else if(new_tm.tm_mday < 1)
|
||||
new_tm.tm_mday = 1;
|
||||
|
||||
}else if(mEditIndex == 2)
|
||||
{
|
||||
new_tm.tm_year += incDir;
|
||||
if(new_tm.tm_year < 0)
|
||||
new_tm.tm_year = 0;
|
||||
}
|
||||
|
||||
//validate day
|
||||
int days_in_month = boost::gregorian::date(new_tm.tm_year + 1900, new_tm.tm_mon + 1, 1).end_of_month().day().as_number();
|
||||
if(new_tm.tm_mday > days_in_month)
|
||||
new_tm.tm_mday = days_in_month;
|
||||
|
||||
mTime = boost::posix_time::ptime_from_tm(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 Eigen::Affine3f& parentTrans)
|
||||
{
|
||||
Eigen::Affine3f trans = parentTrans * getTransform();
|
||||
Renderer::setMatrix(trans);
|
||||
|
||||
if(mTextCache)
|
||||
{
|
||||
std::shared_ptr<Font> font = getFont();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderChildren(trans);
|
||||
}
|
||||
|
||||
void DateTimeComponent::setValue(const std::string& val)
|
||||
{
|
||||
mTime = string_to_ptime(val);
|
||||
updateTextCache();
|
||||
}
|
||||
|
||||
std::string DateTimeComponent::getValue() const
|
||||
{
|
||||
return boost::posix_time::to_iso_string(mTime);
|
||||
}
|
||||
|
||||
DateTimeComponent::DisplayMode DateTimeComponent::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 DateTimeComponent::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
|
||||
using namespace boost::posix_time;
|
||||
|
||||
if(mTime == not_a_date_time)
|
||||
return "never";
|
||||
|
||||
ptime now = second_clock::universal_time();
|
||||
time_duration dur = now - mTime;
|
||||
|
||||
if(dur < seconds(2))
|
||||
return "just now";
|
||||
if(dur < seconds(60))
|
||||
return std::to_string((long long)dur.seconds()) + " secs ago";
|
||||
if(dur < minutes(60))
|
||||
return std::to_string((long long)dur.minutes()) + " min" + (dur < minutes(2) ? "" : "s") + " ago";
|
||||
if(dur < hours(24))
|
||||
return std::to_string((long long)dur.hours()) + " hour" + (dur < hours(2) ? "" : "s") + " ago";
|
||||
|
||||
return std::to_string((long long)(ptime() + dur).date().day_count().as_number());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(mTime == boost::posix_time::not_a_date_time)
|
||||
return "unknown";
|
||||
|
||||
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet();
|
||||
facet->format(fmt.c_str());
|
||||
std::locale loc(std::locale::classic(), facet);
|
||||
|
||||
std::stringstream ss;
|
||||
ss.imbue(loc);
|
||||
ss << mTime;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::shared_ptr<Font> DateTimeComponent::getFont() const
|
||||
{
|
||||
return Font::get(*mWindow->getResourceManager(), Font::getDefaultPath(), FONT_SIZE_MEDIUM);
|
||||
}
|
||||
|
||||
void DateTimeComponent::updateTextCache()
|
||||
{
|
||||
DisplayMode mode = getCurrentDisplayMode();
|
||||
const std::string dispString = getDisplayString(mode);
|
||||
std::shared_ptr<Font> font = getFont();
|
||||
mTextCache = std::unique_ptr<TextCache>(font->buildTextCache(dispString, 0, 0, 0x000000FF));
|
||||
|
||||
//set up cursor positions
|
||||
mCursorBoxes.clear();
|
||||
|
||||
if(dispString.empty() || mode == DISP_RELATIVE_TO_NOW)
|
||||
return;
|
||||
|
||||
//month
|
||||
Eigen::Vector2f start(0, 0);
|
||||
Eigen::Vector2f end = font->sizeText(dispString.substr(0, 2));
|
||||
Eigen::Vector2f diff = end - start;
|
||||
mCursorBoxes.push_back(Eigen::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(Eigen::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(Eigen::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
|
||||
}
|
47
src/components/DateTimeComponent.h
Normal file
47
src/components/DateTimeComponent.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
#pragma once
|
||||
|
||||
#include "../GuiComponent.h"
|
||||
#include <boost/date_time.hpp>
|
||||
#include "../Font.h"
|
||||
|
||||
class DateTimeComponent : public GuiComponent
|
||||
{
|
||||
public:
|
||||
DateTimeComponent(Window* window);
|
||||
|
||||
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 Eigen::Affine3f& parentTrans) override;
|
||||
|
||||
enum DisplayMode
|
||||
{
|
||||
DISP_DATE,
|
||||
DISP_DATE_TIME,
|
||||
DISP_RELATIVE_TO_NOW
|
||||
};
|
||||
|
||||
void setDisplayMode(DisplayMode mode);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Font> getFont() const;
|
||||
|
||||
std::string getDisplayString(DisplayMode mode) const;
|
||||
DisplayMode getCurrentDisplayMode() const;
|
||||
|
||||
void updateTextCache();
|
||||
|
||||
boost::posix_time::ptime mTime;
|
||||
boost::posix_time::ptime mTimeBeforeEdit;
|
||||
|
||||
bool mEditing;
|
||||
int mEditIndex;
|
||||
DisplayMode mDisplayMode;
|
||||
|
||||
int mRelativeUpdateAccumulator;
|
||||
|
||||
std::unique_ptr<TextCache> mTextCache;
|
||||
std::vector<Eigen::Vector4f> mCursorBoxes;
|
||||
};
|
Loading…
Reference in a new issue