Refacrored the separator component to be more flexible and reusable across different activities. Updated the EpubReaderMenuActivity and ClockSettingsActivity to utilize the new separator component for better UI consistency and maintainability. This change also involved updating the UITheme component to support the new separator styles.
This commit is contained in:
@@ -29,7 +29,7 @@ std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuI
|
||||
std::vector<MenuItem> items;
|
||||
items.reserve(18);
|
||||
// Navigation
|
||||
items.push_back({MenuAction::NONE, StrId::STR_READER_NAVIGATION, true});
|
||||
items.push_back(MenuItem::separator(StrId::STR_READER_NAVIGATION));
|
||||
items.push_back({MenuAction::SELECT_CHAPTER, StrId::STR_SELECT_CHAPTER});
|
||||
items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT});
|
||||
if (hasFootnotes) {
|
||||
@@ -38,7 +38,7 @@ std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuI
|
||||
items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN});
|
||||
|
||||
// Appearance
|
||||
items.push_back({MenuAction::NONE, StrId::STR_READER_APPEARANCE, true});
|
||||
items.push_back(MenuItem::separator(StrId::STR_READER_APPEARANCE));
|
||||
items.push_back({MenuAction::EMBEDDED_STYLE, StrId::STR_EMBEDDED_STYLE});
|
||||
items.push_back({MenuAction::IMAGE_RENDERING, StrId::STR_IMAGES});
|
||||
items.push_back({MenuAction::TEXT_DARKNESS, StrId::STR_TEXT_DARKNESS});
|
||||
@@ -46,13 +46,13 @@ std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuI
|
||||
|
||||
// Synchronisation (only if credentials are set, to avoid confusion)
|
||||
if (KOREADER_STORE.hasCredentials()) {
|
||||
items.push_back({MenuAction::NONE, StrId::STR_KOREADER_SYNC, true});
|
||||
items.push_back(MenuItem::separator(StrId::STR_KOREADER_SYNC));
|
||||
items.push_back({MenuAction::PULL_REMOTE, StrId::STR_PULL_PROGRESS_FROM_OTHER_DEVICES});
|
||||
items.push_back({MenuAction::PUSH_LOCAL, StrId::STR_PUSH_PROGRESS_FROM_THIS_DEVICE});
|
||||
}
|
||||
|
||||
// Tools
|
||||
items.push_back({MenuAction::NONE, StrId::STR_READER_TOOLS, true});
|
||||
items.push_back(MenuItem::separator(StrId::STR_READER_TOOLS));
|
||||
items.push_back({MenuAction::SCREENSHOT, StrId::STR_SCREENSHOT_BUTTON});
|
||||
items.push_back({MenuAction::DISPLAY_QR, StrId::STR_DISPLAY_QR});
|
||||
items.push_back({MenuAction::DELETE_CACHE, StrId::STR_DELETE_CACHE});
|
||||
@@ -60,17 +60,16 @@ std::vector<EpubReaderMenuActivity::MenuItem> EpubReaderMenuActivity::buildMenuI
|
||||
return items;
|
||||
}
|
||||
|
||||
std::function<bool(int)> EpubReaderMenuActivity::buildSelectablePredicate() const {
|
||||
return [this](int index) {
|
||||
return index >= 0 && index < static_cast<int>(menuItems.size()) && !menuItems[index].isSeparator;
|
||||
};
|
||||
}
|
||||
|
||||
void EpubReaderMenuActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
const auto selectablePredicate = buildSelectablePredicate();
|
||||
buttonNavigator.setSelectablePredicate(selectablePredicate, static_cast<int>(menuItems.size()));
|
||||
if (!selectablePredicate(selectedIndex)) {
|
||||
const auto pred = UITheme::makeSelectablePredicate(
|
||||
static_cast<int>(menuItems.size()), [this](int i) {
|
||||
const auto& item = menuItems[i];
|
||||
const auto t = I18N.get(item.labelId);
|
||||
return item.isSeparator ? UITheme::makeSeparatorTitle(t) : t;
|
||||
});
|
||||
buttonNavigator.setSelectablePredicate(pred, static_cast<int>(menuItems.size()));
|
||||
if (!pred(selectedIndex)) {
|
||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
@@ -45,12 +45,11 @@ class EpubReaderMenuActivity final : public Activity {
|
||||
MenuAction action;
|
||||
StrId labelId;
|
||||
bool isSeparator = false;
|
||||
static MenuItem separator(StrId label) { return {MenuAction::NONE, label, true}; }
|
||||
};
|
||||
|
||||
static std::vector<MenuItem> buildMenuItems(bool hasFootnotes);
|
||||
|
||||
std::function<bool(int)> buildSelectablePredicate() const;
|
||||
|
||||
// Fixed menu layout
|
||||
const std::vector<MenuItem> menuItems;
|
||||
|
||||
|
||||
@@ -23,28 +23,27 @@ std::vector<ClockSettingsActivity::MenuItem> ClockSettingsActivity::buildMenuIte
|
||||
std::vector<MenuItem> items;
|
||||
items.reserve(7);
|
||||
// Settings
|
||||
items.push_back({Action::NONE, StrId::STR_SETTINGS_TITLE, true});
|
||||
items.push_back(MenuItem::separator(StrId::STR_SETTINGS_TITLE));
|
||||
items.push_back({Action::USE_CLOCK, StrId::STR_USE_CLOCK});
|
||||
items.push_back({Action::CLOCK_FORMAT, StrId::STR_CLOCK_FORMAT});
|
||||
items.push_back({Action::TIMEZONE, StrId::STR_TIMEZONE});
|
||||
|
||||
// Tools
|
||||
items.push_back({Action::NONE, StrId::STR_READER_TOOLS, true});
|
||||
items.push_back(MenuItem::separator(StrId::STR_READER_TOOLS));
|
||||
items.push_back({Action::DETECT_TIMEZONE, StrId::STR_DETECT_TIMEZONE});
|
||||
items.push_back({Action::SYNC_TIME, StrId::STR_SYNC_TIME});
|
||||
return items;
|
||||
}
|
||||
|
||||
std::function<bool(int)> ClockSettingsActivity::buildSelectablePredicate() const {
|
||||
return [this](int index) {
|
||||
return index >= 0 && index < static_cast<int>(menuItems.size()) && !menuItems[index].isSeparator;
|
||||
};
|
||||
}
|
||||
|
||||
void ClockSettingsActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
buttonNavigator.setSelectablePredicate(buildSelectablePredicate(), static_cast<int>(menuItems.size()));
|
||||
if (!buildSelectablePredicate()(selectedIndex)) {
|
||||
const auto pred = UITheme::makeSelectablePredicate(
|
||||
static_cast<int>(menuItems.size()), [this](int i) {
|
||||
const auto t = I18N.get(menuItems[i].labelId);
|
||||
return menuItems[i].isSeparator ? UITheme::makeSeparatorTitle(t) : t;
|
||||
});
|
||||
buttonNavigator.setSelectablePredicate(pred, static_cast<int>(menuItems.size()));
|
||||
if (!pred(selectedIndex)) {
|
||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
||||
}
|
||||
requestUpdate();
|
||||
|
||||
@@ -14,6 +14,7 @@ class ClockSettingsActivity final : public Activity {
|
||||
Action action;
|
||||
StrId labelId;
|
||||
bool isSeparator = false;
|
||||
static MenuItem separator(StrId label) { return {Action::NONE, label, true}; }
|
||||
};
|
||||
|
||||
ButtonNavigator buttonNavigator;
|
||||
@@ -22,7 +23,6 @@ class ClockSettingsActivity final : public Activity {
|
||||
|
||||
static std::vector<MenuItem> buildMenuItems();
|
||||
void handleSelection();
|
||||
std::function<bool(int)> buildSelectablePredicate() const;
|
||||
|
||||
public:
|
||||
explicit ClockSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||
|
||||
@@ -108,6 +108,13 @@ std::string UITheme::stripSeparatorTitle(const std::string& title) {
|
||||
return isSeparatorTitle(title) ? title.substr(2) : title;
|
||||
}
|
||||
|
||||
std::function<bool(int)> UITheme::makeSelectablePredicate(int total,
|
||||
std::function<std::string(int)> titleGetter) {
|
||||
return [total, titleGetter](int index) {
|
||||
return index >= 0 && index < total && !isSeparatorTitle(titleGetter(index));
|
||||
};
|
||||
}
|
||||
|
||||
std::string UITheme::getCoverThumbPath(std::string coverBmpPath, int coverHeight) {
|
||||
size_t pos = coverBmpPath.find("[HEIGHT]", 0);
|
||||
if (pos != std::string::npos) {
|
||||
|
||||
@@ -26,6 +26,33 @@ class UITheme {
|
||||
static std::string makeSeparatorTitle(StrId labelId);
|
||||
static bool isSeparatorTitle(const std::string& title);
|
||||
static std::string stripSeparatorTitle(const std::string& title);
|
||||
// Returns a selectable predicate for use with ButtonNavigator::setSelectablePredicate().
|
||||
// Items whose title is marked as a separator (via makeSeparatorTitle) are skipped during
|
||||
// navigation. Pass the same title getter you pass to drawList so rendering and navigation
|
||||
// always agree on which items are separators.
|
||||
//
|
||||
// Typical usage pattern for a menu with section headers:
|
||||
//
|
||||
// struct MenuItem {
|
||||
// Action action; StrId labelId; bool isSeparator = false;
|
||||
// static MenuItem separator(StrId label) { return {Action::NONE, label, true}; }
|
||||
// };
|
||||
// const std::vector<MenuItem> items = buildMenuItems();
|
||||
// // In buildMenuItems, use MenuItem::separator(StrId) for section headers.
|
||||
//
|
||||
// // Title getter — used by both drawList and makeSelectablePredicate:
|
||||
// auto titleGetter = [&](int i) {
|
||||
// const auto t = I18N.get(items[i].labelId);
|
||||
// return items[i].isSeparator ? UITheme::makeSeparatorTitle(t) : t;
|
||||
// };
|
||||
//
|
||||
// // onEnter: wire navigation so separators are skipped:
|
||||
// const auto pred = UITheme::makeSelectablePredicate(items.size(), titleGetter);
|
||||
// buttonNavigator.setSelectablePredicate(pred, items.size());
|
||||
//
|
||||
// // render: pass the same getter to drawList; separator rows are drawn automatically:
|
||||
// GUI.drawList(renderer, rect, items.size(), selectedIndex, titleGetter, ...);
|
||||
static std::function<bool(int)> makeSelectablePredicate(int total, std::function<std::string(int)> titleGetter);
|
||||
|
||||
// Returns the drawable content Rect accounting for screen orientation and visible button hints.
|
||||
// Bottom hints occupy the physical bottom edge; side hints occupy the physical right edge.
|
||||
|
||||
Reference in New Issue
Block a user