Added a clock element and a corresponding menu entry

This commit is contained in:
Leon Styhre 2025-02-08 16:12:22 +01:00
parent c86044a6a4
commit 21110810e4
12 changed files with 154 additions and 14 deletions

View file

@ -947,6 +947,17 @@ void GuiMenu::openUIOptions()
}
});
// Display clock.
auto displayClock = std::make_shared<SwitchComponent>();
displayClock->setState(Settings::getInstance()->getBool("DisplayClock"));
s->addWithLabel(_("DISPLAY CLOCK"), displayClock);
s->addSaveFunc([displayClock, s] {
if (displayClock->getState() != Settings::getInstance()->getBool("DisplayClock")) {
Settings::getInstance()->setBool("DisplayClock", displayClock->getState());
s->setNeedsSaving();
}
});
// Blur background when the menu is open.
auto menuBlurBackground = std::make_shared<SwitchComponent>();
if (mRenderer->getScreenRotation() == 90 || mRenderer->getScreenRotation() == 270) {

View file

@ -88,6 +88,11 @@ void GamelistView::onShow()
for (auto& video : mStaticVideoComponents)
video->stopVideoPlayer();
mWindow->passClockComponents(&mClockComponents);
for (auto& clock : mClockComponents)
clock->update(500);
mLastUpdated = nullptr;
GuiComponent::onShow();
@ -353,9 +358,20 @@ void GamelistView::onThemeChanged(const std::shared_ptr<ThemeData>& theme)
mHelpComponents.emplace_back(std::make_unique<HelpComponent>());
mHelpComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
}
else if (element.second.type == "clock") {
mClockComponents.emplace_back(std::make_unique<DateTimeComponent>());
mClockComponents.back()->applyTheme(theme, "gamelist", element.first, ALL);
}
}
}
if (mClockComponents.empty()) {
// Apply a default clock if the theme does not contain any configuration for it.
mClockComponents.emplace_back(std::make_unique<DateTimeComponent>());
mClockComponents.back()->applyTheme(theme, "gamelist", "clock_default", ThemeFlags::ALL);
mClockComponents.back()->update(1000);
}
if (mPrimary == nullptr) {
mTextList = std::make_unique<TextListComponent<FileData*>>();
mPrimary = mTextList.get();

View file

@ -135,6 +135,7 @@ private:
std::vector<std::unique_ptr<TextComponent>> mContainerTextComponents;
std::vector<std::unique_ptr<TextComponent>> mGamelistInfoComponents;
std::vector<std::unique_ptr<HelpComponent>> mHelpComponents;
std::vector<std::unique_ptr<DateTimeComponent>> mClockComponents;
};
#endif // ES_APP_VIEWS_GAMELIST_VIEW_H

View file

@ -246,6 +246,10 @@ std::vector<HelpPrompt> SystemView::getHelpPrompts()
void SystemView::onCursorChanged(const CursorState& state)
{
mWindow->passHelpComponents(nullptr);
mWindow->passClockComponents(&mSystemElements[mPrimary->getCursor()].clockComponents);
for (auto& clock : mSystemElements[mPrimary->getCursor()].clockComponents)
clock->update(1000);
// Reset horizontally scrolling text.
for (auto& text : mSystemElements[mPrimary->getCursor()].gameCountComponents)
@ -727,9 +731,22 @@ void SystemView::populate()
elements.helpComponents.back()->applyTheme(theme, "system", element.first,
ThemeFlags::ALL);
}
else if (element.second.type == "clock") {
elements.clockComponents.emplace_back(std::make_unique<DateTimeComponent>());
elements.clockComponents.back()->applyTheme(theme, "system", element.first,
ThemeFlags::ALL);
}
}
}
if (elements.clockComponents.empty()) {
// Apply a default clock if the theme does not contain any configuration for it.
elements.clockComponents.emplace_back(std::make_unique<DateTimeComponent>());
elements.clockComponents.back()->applyTheme(theme, "system", "clock_default",
ThemeFlags::ALL);
elements.clockComponents.back()->update(1000);
}
std::stable_sort(
elements.children.begin(), elements.children.end(),
[](GuiComponent* a, GuiComponent* b) { return b->getZIndex() > a->getZIndex(); });
@ -894,6 +911,8 @@ void SystemView::populate()
else
mWindow->passHelpComponents(&mSystemElements[mPrimary->getCursor()].helpComponents);
mWindow->passClockComponents(&mSystemElements[mPrimary->getCursor()].clockComponents);
mFadeTransitions = (static_cast<ViewTransitionAnimation>(Settings::getInstance()->getInt(
"TransitionsSystemToSystem")) == ViewTransitionAnimation::FADE);
}

View file

@ -138,6 +138,7 @@ private:
std::vector<std::unique_ptr<DateTimeComponent>> dateTimeComponents;
std::vector<std::unique_ptr<RatingComponent>> ratingComponents;
std::vector<std::unique_ptr<HelpComponent>> helpComponents;
std::vector<std::unique_ptr<DateTimeComponent>> clockComponents;
};
Renderer* mRenderer;

View file

@ -222,6 +222,7 @@ void Settings::setDefaults()
mBoolMap["ScreensaverVideoBlur"] = {false, false};
mBoolMap["ThemeVariantTriggers"] = {true, true};
mBoolMap["DisplayClock"] = {false, false};
mBoolMap["MenuBlurBackground"] = {true, true};
mBoolMap["FoldersOnTop"] = {true, true};
mBoolMap["FavoritesFirst"] = {true, true};

View file

@ -572,6 +572,23 @@ std::map<std::string, std::map<std::string, ThemeData::ElementPropertyType>>
{"opacity", FLOAT},
{"opacityDimmed", FLOAT},
{"customButtonIcon", PATH}}},
{"clock",
{{"pos", NORMALIZED_PAIR},
{"size", NORMALIZED_PAIR},
{"origin", NORMALIZED_PAIR},
{"rotation", FLOAT},
{"rotationOrigin", NORMALIZED_PAIR},
{"fontPath", PATH},
{"fontSize", FLOAT},
{"horizontalAlignment", STRING},
{"verticalAlignment", STRING},
{"color", COLOR},
{"backgroundColor", COLOR},
{"backgroundMargins", NORMALIZED_PAIR},
{"backgroundCornerRadius", FLOAT},
{"lineSpacing", FLOAT},
{"format", STRING},
{"opacity", FLOAT}}},
{"sound",
{{"path", PATH}}}};
// clang-format on

View file

@ -31,6 +31,7 @@
Window::Window() noexcept
: mRenderer {Renderer::getInstance()}
, mHelpComponents {nullptr}
, mClockComponents {nullptr}
, mSplashTextPositions {0.0f, 0.0f, 0.0f, 0.0f}
, mBackgroundOverlayOpacity {1.0f}
, mScreensaver {nullptr}
@ -442,6 +443,11 @@ void Window::update(int deltaTime)
if (mScreensaver && mRenderScreensaver)
mScreensaver->update(deltaTime);
if (mClockComponents != nullptr) {
for (auto& clockComponent : *mClockComponents)
clockComponent->update(deltaTime);
}
#if defined(__ANDROID__) || defined(__IOS__)
if (Settings::getInstance()->getBool("InputTouchOverlay"))
InputOverlay::getInstance().update(deltaTime);
@ -630,6 +636,11 @@ void Window::render()
}
}
if (mClockComponents != nullptr) {
for (auto& clockComponent : *mClockComponents)
clockComponent->render(trans);
}
// Render the quick list scrolling overlay, which is triggered in IList.
if (mListScrollOpacity != 0.0f) {
mRenderer->setMatrix(mRenderer->getIdentity());

View file

@ -14,6 +14,7 @@
#include "HelpPrompt.h"
#include "InputConfig.h"
#include "Settings.h"
#include "components/DateTimeComponent.h"
#include "components/HelpComponent.h"
#include "components/ImageComponent.h"
#include "components/TextComponent.h"
@ -173,6 +174,11 @@ public:
mHelpComponents = helpComponents;
}
void passClockComponents(std::vector<std::unique_ptr<DateTimeComponent>>* clockComponents)
{
mClockComponents = clockComponents;
}
private:
Window() noexcept;
~Window();
@ -191,6 +197,7 @@ private:
Renderer* mRenderer;
std::vector<std::unique_ptr<HelpComponent>>* mHelpComponents;
std::unique_ptr<HelpComponent> mHelp;
std::vector<std::unique_ptr<DateTimeComponent>>* mClockComponents;
std::unique_ptr<ImageComponent> mBackgroundOverlay;
std::unique_ptr<ImageComponent> mSplash;
std::unique_ptr<TextComponent> mSplashTextScanning;

View file

@ -16,7 +16,9 @@
#include "utils/StringUtil.h"
DateTimeComponent::DateTimeComponent()
: mDisplayRelative {false}
: mClockAccumulator {0}
, mClockMode {false}
, mDisplayRelative {false}
{
// ISO 8601 date format.
setFormat("%Y-%m-%d");
@ -32,6 +34,8 @@ DateTimeComponent::DateTimeComponent(const std::string& text,
: TextComponent {text, font, color, horizontalAlignment, ALIGN_CENTER, glm::vec2 {1, 0},
pos, size, bgcolor}
, mRenderer {Renderer::getInstance()}
, mClockAccumulator {0}
, mClockMode {false}
, mDisplayRelative {false}
{
// ISO 8601 date format.
@ -70,6 +74,10 @@ void DateTimeComponent::onTextChanged()
std::string DateTimeComponent::getDisplayString() const
{
if (mClockMode)
return (Utils::Time::timeToString(Utils::Time::DateTime {Utils::Time::now()}.getTime(),
mFormat));
if (mDisplayRelative) {
// Workaround to handle Unix epoch for different time zones.
if (mTime.getTime() < 82800) {
@ -116,8 +124,29 @@ std::string DateTimeComponent::getDisplayString() const
return Utils::Time::timeToString(mTime.getTime(), mFormat);
}
void DateTimeComponent::update(int deltaTime)
{
if (!mClockMode || (mClockMode && !Settings::getInstance()->getBool("DisplayClock")))
return;
mClockAccumulator += deltaTime;
if (mClockAccumulator >= 500) {
mClockAccumulator = 0;
mTime = Utils::Time::now();
const std::string newTime {Utils::Time::timeToString(mTime, mFormat)};
// The setValue() function with its text cache rebuild is an expensive operation so we only
// call this when the actual date/time string needs updating.
if (newTime != mText)
setValue(newTime);
}
}
void DateTimeComponent::render(const glm::mat4& parentTrans)
{
if (mClockMode && !Settings::getInstance()->getBool("DisplayClock"))
return;
// Render the component.
TextComponent::render(parentTrans);
}
@ -130,7 +159,25 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
using namespace ThemeFlags;
GuiComponent::applyTheme(theme, view, element, properties);
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "datetime")};
std::string elementType {"datetime"};
std::string componentName {"DateTimeComponent"};
if (element.substr(0, 6) == "clock_") {
mClockMode = true;
elementType = "clock";
componentName = "ClockComponent";
// Apply default clock settings as the theme may not define any configuration for it.
setFont(Font::get(FONT_SIZE_SMALL, FONT_PATH_LIGHT));
const glm::vec2 scale {
getParent() ? getParent()->getSize() :
glm::vec2 {Renderer::getScreenWidth(), Renderer::getScreenHeight()}};
setPosition(0.008f * scale.x, 0.012f * scale.y);
mSize.y = mFont->getLetterHeight();
setColor(0xFFFFFFFF);
setFormat("%H:%M");
}
const ThemeData::ThemeElement* elem {theme->getElement(view, element, elementType)};
if (!elem)
return;
@ -150,9 +197,6 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
<< element.substr(9) << "\" defined as \"" << stationary << "\"";
}
if (elem->has("format"))
setFormat(elem->get<std::string>("format"));
if (properties & COLOR && elem->has("color"))
setColor(elem->get<unsigned int>("color"));
@ -182,10 +226,11 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else if (horizontalAlignment == "right")
setHorizontalAlignment(ALIGN_RIGHT);
else
LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property "
LOG(LogWarning) << componentName
<< ": Invalid theme configuration, property "
"\"horizontalAlignment\" for element \""
<< element.substr(9) << "\" defined as \"" << horizontalAlignment
<< "\"";
<< element.substr(elementType == "clock" ? 6 : 9) << "\" defined as \""
<< horizontalAlignment << "\"";
}
if (properties & ALIGNMENT && elem->has("verticalAlignment")) {
@ -197,9 +242,11 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
else if (verticalAlignment == "bottom")
setVerticalAlignment(ALIGN_BOTTOM);
else
LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property "
LOG(LogWarning) << componentName
<< ": Invalid theme configuration, property "
"\"verticalAlignment\" for element \""
<< element.substr(9) << "\" defined as \"" << verticalAlignment << "\"";
<< element.substr(elementType == "clock" ? 6 : 9) << "\" defined as \""
<< verticalAlignment << "\"";
}
if (properties & METADATA && elem->has("metadata")) {
@ -240,7 +287,8 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setCapitalize(true);
}
else if (letterCase != "none") {
LOG(LogWarning) << "DateTimeComponent: Invalid theme configuration, property "
LOG(LogWarning) << componentName
<< ": Invalid theme configuration, property "
"\"letterCase\" for element \""
<< element.substr(9) << "\" defined as \"" << letterCase << "\"";
}
@ -265,4 +313,9 @@ void DateTimeComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
setFont(Font::getFromTheme(elem, properties, mFont, maxHeight));
mSize = glm::round(mSize);
if (elem->has("format"))
setFormat(elem->get<std::string>("format"));
else if (mClockMode)
setFormat("%H:%M");
}

View file

@ -29,6 +29,7 @@ public:
glm::vec2 size = {0.0f, 0.0f},
unsigned int bgcolor = 0x00000000);
void update(int deltaTime) override;
void render(const glm::mat4& parentTrans) override;
void setValue(const std::string& val) override;
@ -49,9 +50,11 @@ private:
std::string getDisplayString() const;
Renderer* mRenderer;
int mClockAccumulator;
std::string mDefaultValue;
Utils::Time::DateTime mTime;
std::string mFormat;
bool mClockMode;
bool mDisplayRelative;
};

View file

@ -187,7 +187,7 @@ namespace Utils
#else
localtime_r(&time, &timeStruct);
#endif
char buf[256] = {'\0'};
char buf[256] {'\0'};
char* s = buf;
while (*f) {
@ -197,7 +197,7 @@ namespace Utils
switch (*f++) {
// Year, including century [1970,xxxx]
case 'Y': {
const int year = timeStruct.tm_year + 1900;
const int year {timeStruct.tm_year + 1900};
*s++ = static_cast<char>((year - (year % 1000)) / 1000) + '0';
*s++ = static_cast<char>(((year % 1000) - (year % 100)) / 100) + '0';
*s++ = static_cast<char>(((year % 100) - (year % 10)) / 10) + '0';
@ -206,7 +206,7 @@ namespace Utils
// Month number [00,11]
case 'm': {
const int mon = timeStruct.tm_mon + 1;
const int mon {timeStruct.tm_mon + 1};
*s++ = static_cast<char>(mon / 10) + '0';
*s++ = static_cast<char>(mon % 10) + '0';
} break;