Refactor Settings usage

This commit is contained in:
jpirnay
2026-04-17 15:38:37 +02:00
parent f0fb6c20aa
commit 3b8b389408
10 changed files with 151 additions and 92 deletions
@@ -19,6 +19,7 @@ const StrId timeZoneNames[CrossPointSettings::TIMEZONE_COUNT] = {
} // namespace
void ClockSettingsActivity::buildMenuItems() {
menuItems.reserve(6);
menuItems.push_back(SettingInfo::Separator(StrId::STR_SETTINGS_TITLE));
menuItems.push_back(
SettingInfo::Toggle(StrId::STR_USE_CLOCK, &CrossPointSettings::useClock, "useClock", StrId::STR_CAT_SYSTEM));
@@ -16,6 +16,7 @@ KOReaderSettingsActivity::KOReaderSettingsActivity(GfxRenderer& renderer, Mapped
}
void KOReaderSettingsActivity::buildMenuItems() {
menuItems.reserve(6);
// Username, Password, Server URL: ACTION items with custom value display
menuItems.push_back(SettingInfo::Action(StrId::STR_SYNC_SERVER_URL, SettingAction::None)
.withSubcategory(StrId::STR_MENU_KOSYNC_SERVER));
@@ -25,8 +26,8 @@ void KOReaderSettingsActivity::buildMenuItems() {
// Document matching: DynamicEnum toggling between Filename and Binary
menuItems.push_back(SettingInfo::DynamicEnum(
StrId::STR_DOCUMENT_MATCHING, {StrId::STR_FILENAME, StrId::STR_BINARY},
[] { return static_cast<uint8_t>(KOREADER_STORE.getMatchMethod()); },
[](uint8_t v) {
[](void*) { return static_cast<uint8_t>(KOREADER_STORE.getMatchMethod()); },
[](void*, uint8_t v) {
KOREADER_STORE.setMatchMethod(static_cast<DocumentMatchMethod>(v));
KOREADER_STORE.saveToFile();
}));
+5 -5
View File
@@ -19,7 +19,7 @@ std::string SettingInfo::getDisplayValue() const {
if (valuePtr)
value = SETTINGS.*(valuePtr);
else if (valueGetter)
value = valueGetter();
value = callValueGetter();
else
return {};
return std::string(value ? tr(STR_STATE_ON) : tr(STR_STATE_OFF));
@@ -29,7 +29,7 @@ std::string SettingInfo::getDisplayValue() const {
if (valuePtr)
value = SETTINGS.*(valuePtr);
else if (valueGetter)
value = valueGetter();
value = callValueGetter();
else
return {};
if (value < enumValues.size()) return std::string(I18N.get(enumValues[value]));
@@ -37,7 +37,7 @@ std::string SettingInfo::getDisplayValue() const {
}
case SettingType::VALUE: {
if (valuePtr) return std::to_string(SETTINGS.*(valuePtr));
if (valueGetter) return std::to_string(valueGetter());
if (valueGetter) return std::to_string(callValueGetter());
return {};
}
case SettingType::ACTION:
@@ -56,7 +56,7 @@ void SettingInfo::toggleValue() const {
if (valuePtr) {
SETTINGS.*(valuePtr) = !(SETTINGS.*(valuePtr));
} else if (valueGetter && valueSetter) {
valueSetter(!valueGetter());
callValueSetter(!callValueGetter());
}
break;
@@ -66,7 +66,7 @@ void SettingInfo::toggleValue() const {
if (valuePtr) {
SETTINGS.*(valuePtr) = (SETTINGS.*(valuePtr) + 1) % count;
} else if (valueGetter && valueSetter) {
valueSetter((valueGetter() + 1) % count);
callValueSetter((callValueGetter() + 1) % count);
}
break;
}
+45 -16
View File
@@ -1,7 +1,6 @@
#pragma once
#include <I18n.h>
#include <functional>
#include <string>
#include <vector>
@@ -49,11 +48,25 @@ struct SettingInfo {
size_t stringOffset = 0;
size_t stringMaxLen = 0;
// Dynamic accessors (for settings stored outside CrossPointSettings, e.g. KOReaderCredentialStore)
std::function<uint8_t()> valueGetter;
std::function<void(uint8_t)> valueSetter;
std::function<std::string()> stringGetter;
std::function<void(const std::string&)> stringSetter;
// Dynamic accessors (for settings stored outside CrossPointSettings, e.g. KOReaderCredentialStore).
// Function pointers + opaque context avoid the heap allocation of std::function. Stateless
// lambdas pass ctx=nullptr; captures must be hand-written as trampoline functions. See
// DynamicEnumCtx / DynamicStringCtx factories below.
using ValueGetterFn = uint8_t (*)(void*);
using ValueSetterFn = void (*)(void*, uint8_t);
using StringGetterFn = std::string (*)(void*);
using StringSetterFn = void (*)(void*, const std::string&);
void* accessorCtx = nullptr;
ValueGetterFn valueGetter = nullptr;
ValueSetterFn valueSetter = nullptr;
StringGetterFn stringGetter = nullptr;
StringSetterFn stringSetter = nullptr;
uint8_t callValueGetter() const { return valueGetter(accessorCtx); }
void callValueSetter(uint8_t v) const { valueSetter(accessorCtx, v); }
std::string callStringGetter() const { return stringGetter(accessorCtx); }
void callStringSetter(const std::string& v) const { stringSetter(accessorCtx, v); }
SettingInfo& withObfuscated() {
obfuscated = true;
@@ -115,33 +128,49 @@ struct SettingInfo {
return s;
}
static SettingInfo DynamicEnum(StrId nameId, std::vector<StrId> values, std::function<uint8_t()> getter,
std::function<void(uint8_t)> setter, const char* key = nullptr,
StrId category = StrId::STR_NONE_OPT) {
// Stateless variant — getter/setter are free/static functions with no captured state.
static SettingInfo DynamicEnum(StrId nameId, std::vector<StrId> values, ValueGetterFn getter, ValueSetterFn setter,
const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) {
SettingInfo s;
s.nameId = nameId;
s.type = SettingType::ENUM;
s.enumValues = std::move(values);
s.valueGetter = std::move(getter);
s.valueSetter = std::move(setter);
s.valueGetter = getter;
s.valueSetter = setter;
s.key = key;
s.category = category;
return s;
}
static SettingInfo DynamicString(StrId nameId, std::function<std::string()> getter,
std::function<void(const std::string&)> setter, const char* key = nullptr,
StrId category = StrId::STR_NONE_OPT) {
// Context-carrying variant — trampolines receive `ctx` as first argument and cast it back to
// their concrete owner type.
static SettingInfo DynamicEnumCtx(StrId nameId, std::vector<StrId> values, void* ctx, ValueGetterFn getter,
ValueSetterFn setter, const char* key = nullptr,
StrId category = StrId::STR_NONE_OPT) {
SettingInfo s = DynamicEnum(nameId, std::move(values), getter, setter, key, category);
s.accessorCtx = ctx;
return s;
}
static SettingInfo DynamicString(StrId nameId, StringGetterFn getter, StringSetterFn setter,
const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) {
SettingInfo s;
s.nameId = nameId;
s.type = SettingType::STRING;
s.stringGetter = std::move(getter);
s.stringSetter = std::move(setter);
s.stringGetter = getter;
s.stringSetter = setter;
s.key = key;
s.category = category;
return s;
}
static SettingInfo DynamicStringCtx(StrId nameId, void* ctx, StringGetterFn getter, StringSetterFn setter,
const char* key = nullptr, StrId category = StrId::STR_NONE_OPT) {
SettingInfo s = DynamicString(nameId, getter, setter, key, category);
s.accessorCtx = ctx;
return s;
}
static SettingInfo Separator(StrId nameId) {
SettingInfo s;
s.nameId = nameId;
+51 -37
View File
@@ -30,16 +30,22 @@ void SettingsActivity::onEnter() {
controlsSettings.clear();
systemSettings.clear();
submenuData.clear();
displaySettings.reserve(20);
readerSettings.reserve(30);
controlsSettings.reserve(8);
systemSettings.reserve(20);
submenuData.reserve(4);
StrId lastDisplaySub = StrId::STR_NONE_OPT;
StrId lastReaderSub = StrId::STR_NONE_OPT;
StrId lastControlsSub = StrId::STR_NONE_OPT;
StrId lastSystemSub = StrId::STR_NONE_OPT;
auto addTo = [this](std::vector<SettingInfo>& vec, StrId& lastSub, SettingInfo s) {
// Shared placement logic — locates submenu target or inserts separator.
// Returns the vector the caller should push into (either `vec` or a submenu's items).
auto locateTarget = [this](std::vector<SettingInfo>& vec, StrId& lastSub,
const SettingInfo& s) -> std::vector<SettingInfo>* {
if (s.submenu != StrId::STR_NONE_OPT) {
// Item belongs to a submenu — collect it and insert a placeholder in the main
// list the first time this submenu ID is encountered.
auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&s](const SubmenuData& d) { return d.id == s.submenu; });
if (it == submenuData.end()) {
@@ -47,14 +53,21 @@ void SettingsActivity::onEnter() {
submenuData.push_back({s.submenu, {}});
it = submenuData.end() - 1;
}
it->items.push_back(std::move(s));
return;
return &it->items;
}
if (s.subcategory != StrId::STR_NONE_OPT && s.subcategory != lastSub) {
vec.push_back(SettingInfo::Separator(s.subcategory));
lastSub = s.subcategory;
}
vec.push_back(std::move(s));
return &vec;
};
auto addTo = [&locateTarget](std::vector<SettingInfo>& vec, StrId& lastSub, const SettingInfo& s) {
locateTarget(vec, lastSub, s)->push_back(s);
};
auto addToMoved = [&locateTarget](std::vector<SettingInfo>& vec, StrId& lastSub, SettingInfo&& s) {
auto* target = locateTarget(vec, lastSub, s);
target->push_back(std::move(s));
};
for (const auto& setting : getSettingsList()) {
@@ -80,34 +93,34 @@ void SettingsActivity::onEnter() {
controlsSettings.insert(controlsSettings.begin(),
SettingInfo::Action(StrId::STR_REMAP_FRONT_BUTTONS, SettingAction::RemapFrontButtons));
addTo(readerSettings, lastReaderSub,
SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));
addToMoved(readerSettings, lastReaderSub,
SettingInfo::Action(StrId::STR_CUSTOMISE_STATUS_BAR, SettingAction::CustomiseStatusBar));
addTo(systemSettings, lastSystemSub, SettingInfo::Action(StrId::STR_LANGUAGE, SettingAction::Language));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network)
.withSubcategory(StrId::STR_MENU_SYS_NETWORK));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_KOREADER_SYNC, SettingAction::KOReaderSync)
.withSubcategory(StrId::STR_MENU_SYS_NETWORK));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_OPDS_BROWSER, SettingAction::OPDSBrowser)
.withSubcategory(StrId::STR_MENU_SYS_NETWORK));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_CLOCK_SETTINGS, SettingAction::ClockSettings)
.withSubcategory(StrId::STR_MENU_SYS_TOOLS));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_WEATHER_SETTINGS, SettingAction::Weather)
.withSubcategory(StrId::STR_MENU_SYS_TOOLS));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_CLEAR_READING_CACHE, SettingAction::ClearCache)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_CHECK_UPDATES, SettingAction::CheckForUpdates)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM));
addTo(systemSettings, lastSystemSub,
SettingInfo::Action(StrId::STR_SYSTEM_INFO, SettingAction::SystemInfo)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM));
addToMoved(systemSettings, lastSystemSub, SettingInfo::Action(StrId::STR_LANGUAGE, SettingAction::Language));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_WIFI_NETWORKS, SettingAction::Network)
.withSubcategory(StrId::STR_MENU_SYS_NETWORK)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_KOREADER_SYNC, SettingAction::KOReaderSync)
.withSubcategory(StrId::STR_MENU_SYS_NETWORK)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_OPDS_BROWSER, SettingAction::OPDSBrowser)
.withSubcategory(StrId::STR_MENU_SYS_NETWORK)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_CLOCK_SETTINGS, SettingAction::ClockSettings)
.withSubcategory(StrId::STR_MENU_SYS_TOOLS)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_WEATHER_SETTINGS, SettingAction::Weather)
.withSubcategory(StrId::STR_MENU_SYS_TOOLS)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_CLEAR_READING_CACHE, SettingAction::ClearCache)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_CHECK_UPDATES, SettingAction::CheckForUpdates)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM)));
addToMoved(systemSettings, lastSystemSub,
std::move(SettingInfo::Action(StrId::STR_SYSTEM_INFO, SettingAction::SystemInfo)
.withSubcategory(StrId::STR_MENU_SYS_SYSTEM)));
// Reset selection to first category
selectedCategoryIndex = 0;
@@ -212,11 +225,12 @@ void SettingsActivity::toggleCurrentSetting() {
auto resultHandler = [this](const ActivityResult&) { SETTINGS.saveToFile(); };
if (setting.action == SettingAction::Submenu) {
const auto it = std::find_if(submenuData.cbegin(), submenuData.cend(),
[&setting](const SubmenuData& d) { return d.id == setting.nameId; });
if (it != submenuData.cend()) {
auto it = std::find_if(submenuData.begin(), submenuData.end(),
[&setting](const SubmenuData& d) { return d.id == setting.nameId; });
if (it != submenuData.end()) {
startActivityForResult(
std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, setting.nameId, it->items), resultHandler);
std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, setting.nameId, std::move(it->items)),
resultHandler);
}
} else {
auto activity = createActivityForAction(setting.action, renderer, mappedInput);