mod: improve home screen with adaptive layouts, clock, and set time

- 1-book view: horizontal layout with cover left, title/author right
- 2-3 book view: fix cover stretching by preserving aspect ratio
- 0-book view: show "Choose something to read" placeholder
- Selection highlight now fully contains title and author text
- Add optional clock display in home screen header (AM/PM or 24H)
- Add "Home Screen Clock" setting under Display
- Add "Set Time" activity for manual clock setting via Settings
- Increase homeCoverTileHeight to 310 for title/author breathing room

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-17 00:46:05 -05:00
parent 1d7971ae60
commit 7b3de29c59
19 changed files with 459 additions and 45 deletions

View File

@@ -9,7 +9,9 @@
#include <PlaceholderCoverGenerator.h>
#include <Xtc.h>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <vector>
#include "Battery.h"
@@ -238,6 +240,23 @@ void HomeActivity::render(Activity::RenderLock&&) {
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.homeTopPadding}, nullptr);
// Draw clock in the header area (left side) if enabled
if (SETTINGS.homeScreenClock != CrossPointSettings::CLOCK_OFF) {
time_t now = time(nullptr);
struct tm* t = localtime(&now);
if (t != nullptr && t->tm_year > 100) {
char timeBuf[16];
if (SETTINGS.homeScreenClock == CrossPointSettings::CLOCK_24H) {
snprintf(timeBuf, sizeof(timeBuf), "%02d:%02d", t->tm_hour, t->tm_min);
} else {
int hour12 = t->tm_hour % 12;
if (hour12 == 0) hour12 = 12;
snprintf(timeBuf, sizeof(timeBuf), "%d:%02d %s", hour12, t->tm_min, t->tm_hour >= 12 ? "PM" : "AM");
}
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, metrics.topPadding, timeBuf, true);
}
}
GUI.drawRecentBookCover(renderer, Rect{0, metrics.homeTopPadding, pageWidth, metrics.homeCoverTileHeight},
recentBooks, selectorIndex, coverRendered, coverBufferStored, bufferRestored,
std::bind(&HomeActivity::storeCoverBuffer, this));

View File

@@ -0,0 +1,157 @@
#include "SetTimeActivity.h"
#include <GfxRenderer.h>
#include <I18n.h>
#include <cstdio>
#include <ctime>
#include <sys/time.h>
#include "CrossPointSettings.h"
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
void SetTimeActivity::onEnter() {
Activity::onEnter();
// Initialize from current system time if it's been set (year > 2000)
time_t now = time(nullptr);
struct tm* t = localtime(&now);
if (t != nullptr && t->tm_year > 100) {
hour = t->tm_hour;
minute = t->tm_min;
} else {
hour = 12;
minute = 0;
}
selectedField = 0;
requestUpdate();
}
void SetTimeActivity::onExit() { Activity::onExit(); }
void SetTimeActivity::loop() {
// Back button: discard and exit
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
onBack();
return;
}
// Confirm button: apply time and exit
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
applyTime();
onBack();
return;
}
// Left/Right: switch between hour and minute fields
if (mappedInput.wasReleased(MappedInputManager::Button::Left)) {
selectedField = 0;
requestUpdate();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Right)) {
selectedField = 1;
requestUpdate();
return;
}
// Up/Down: increment/decrement the selected field
if (mappedInput.wasPressed(MappedInputManager::Button::Up)) {
if (selectedField == 0) {
hour = (hour + 1) % 24;
} else {
minute = (minute + 1) % 60;
}
requestUpdate();
return;
}
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
if (selectedField == 0) {
hour = (hour + 23) % 24;
} else {
minute = (minute + 59) % 60;
}
requestUpdate();
return;
}
}
void SetTimeActivity::render(Activity::RenderLock&&) {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const int lineHeight12 = renderer.getLineHeight(UI_12_FONT_ID);
// Title
renderer.drawCenteredText(UI_12_FONT_ID, 20, tr(STR_SET_TIME), true, EpdFontFamily::BOLD);
// Format hour and minute strings
char hourStr[4];
char minuteStr[4];
snprintf(hourStr, sizeof(hourStr), "%02d", hour);
snprintf(minuteStr, sizeof(minuteStr), "%02d", minute);
const int colonWidth = renderer.getTextWidth(UI_12_FONT_ID, " : ");
const int digitWidth = renderer.getTextWidth(UI_12_FONT_ID, "00");
const int totalWidth = digitWidth * 2 + colonWidth;
const int startX = (pageWidth - totalWidth) / 2;
const int timeY = 80;
// Draw selection highlight behind the selected field
constexpr int highlightPad = 6;
if (selectedField == 0) {
renderer.fillRoundedRect(startX - highlightPad, timeY - 4, digitWidth + highlightPad * 2, lineHeight12 + 8, 6,
Color::LightGray);
} else {
renderer.fillRoundedRect(startX + digitWidth + colonWidth - highlightPad, timeY - 4,
digitWidth + highlightPad * 2, lineHeight12 + 8, 6, Color::LightGray);
}
// Draw the time digits and colon
renderer.drawText(UI_12_FONT_ID, startX, timeY, hourStr, true);
renderer.drawText(UI_12_FONT_ID, startX + digitWidth, timeY, " : ", true);
renderer.drawText(UI_12_FONT_ID, startX + digitWidth + colonWidth, timeY, minuteStr, true);
// Draw up/down arrows above and below the selected field
const int arrowX = (selectedField == 0) ? startX + digitWidth / 2 : startX + digitWidth + colonWidth + digitWidth / 2;
const int arrowUpY = timeY - 20;
const int arrowDownY = timeY + lineHeight12 + 12;
// Up arrow (simple triangle using lines)
constexpr int arrowSize = 6;
for (int row = 0; row < arrowSize; row++) {
renderer.drawLine(arrowX - row, arrowUpY + row, arrowX + row, arrowUpY + row);
}
// Down arrow
for (int row = 0; row < arrowSize; row++) {
renderer.drawLine(arrowX - row, arrowDownY + arrowSize - 1 - row, arrowX + row, arrowDownY + arrowSize - 1 - row);
}
// Button hints
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SAVE), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
void SetTimeActivity::applyTime() {
time_t now = time(nullptr);
struct tm newTime = {};
struct tm* current = localtime(&now);
if (current != nullptr && current->tm_year > 100) {
newTime = *current;
} else {
// If time was never set, use a reasonable date (2025-01-01)
newTime.tm_year = 125; // years since 1900
newTime.tm_mon = 0;
newTime.tm_mday = 1;
}
newTime.tm_hour = hour;
newTime.tm_min = minute;
newTime.tm_sec = 0;
time_t newEpoch = mktime(&newTime);
struct timeval tv = {.tv_sec = newEpoch, .tv_usec = 0};
settimeofday(&tv, nullptr);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include <functional>
#include "activities/Activity.h"
class SetTimeActivity final : public Activity {
public:
explicit SetTimeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()>& onBack)
: Activity("SetTime", renderer, mappedInput), onBack(onBack) {}
void onEnter() override;
void onExit() override;
void loop() override;
void render(Activity::RenderLock&&) override;
private:
const std::function<void()> onBack;
// 0 = editing hours, 1 = editing minutes
uint8_t selectedField = 0;
int hour = 12;
int minute = 0;
void applyTime();
};

View File

@@ -11,6 +11,7 @@
#include "LanguageSelectActivity.h"
#include "MappedInputManager.h"
#include "OtaUpdateActivity.h"
#include "SetTimeActivity.h"
#include "SettingsList.h"
#include "activities/network/WifiSelectionActivity.h"
#include "components/UITheme.h"
@@ -43,6 +44,7 @@ void SettingsActivity::onEnter() {
}
// Append device-only ACTION items
displaySettings.push_back(SettingInfo::Action(StrId::STR_SET_TIME, SettingAction::SetTime));
controlsSettings.insert(controlsSettings.begin(),
SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons));
systemSettings.push_back(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network));
@@ -202,6 +204,9 @@ void SettingsActivity::toggleCurrentSetting() {
case SettingAction::Language:
enterSubActivity(new LanguageSelectActivity(renderer, mappedInput, onComplete));
break;
case SettingAction::SetTime:
enterSubActivity(new SetTimeActivity(renderer, mappedInput, onComplete));
break;
case SettingAction::None:
// Do nothing
break;

View File

@@ -21,6 +21,7 @@ enum class SettingAction {
ClearCache,
CheckForUpdates,
Language,
SetTime,
};
struct SettingInfo {