Submenu basis
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
// previous item's, SettingsActivity::onEnter() automatically inserts a separator
|
||||
// row before it. Add with .withSubcategory(StrId::STR_MY_SECTION).
|
||||
// Items without a subcategory (STR_NONE_OPT) never trigger a separator.
|
||||
// submenu — optional submenu grouping. Items with the same submenu StrId are hidden from
|
||||
// the main list and collected behind a single placeholder entry. Selecting that
|
||||
// entry launches SettingsSubmenuActivity with those items. withSubcategory()
|
||||
// works inside a submenu exactly as it does in the parent tab.
|
||||
// Add with .withSubmenu(StrId::STR_MY_SUBMENU).
|
||||
// key — JSON property name used by the web settings API (nullptr = device-only).
|
||||
//
|
||||
// ACTION-type entries and entries without a key are device-only and are added directly
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "SettingsList.h"
|
||||
#include "SettingsSubmenuActivity.h"
|
||||
#include "StatusBarSettingsActivity.h"
|
||||
#include "SyncTimeActivity.h"
|
||||
#include "SystemInformationActivity.h"
|
||||
@@ -45,13 +46,27 @@ void SettingsActivity::onEnter() {
|
||||
readerSettings.clear();
|
||||
controlsSettings.clear();
|
||||
systemSettings.clear();
|
||||
submenuData.clear();
|
||||
|
||||
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 = [](std::vector<SettingInfo>& vec, StrId& lastSub, SettingInfo s) {
|
||||
auto addTo = [this](std::vector<SettingInfo>& vec, StrId& lastSub, SettingInfo s) {
|
||||
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()) {
|
||||
vec.push_back(SettingInfo::SubmenuEntry(s.submenu));
|
||||
submenuData.push_back({s.submenu, {}});
|
||||
it = submenuData.end() - 1;
|
||||
}
|
||||
it->items.push_back(std::move(s));
|
||||
return;
|
||||
}
|
||||
if (s.subcategory != StrId::STR_NONE_OPT && s.subcategory != lastSub) {
|
||||
vec.push_back(SettingInfo::Separator(s.subcategory));
|
||||
lastSub = s.subcategory;
|
||||
@@ -267,6 +282,16 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
case SettingAction::DetectTimezone:
|
||||
startActivityForResult(std::make_unique<DetectTimezoneActivity>(renderer, mappedInput), resultHandler);
|
||||
break;
|
||||
case 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()) {
|
||||
startActivityForResult(
|
||||
std::make_unique<SettingsSubmenuActivity>(renderer, mappedInput, setting.nameId, it->items),
|
||||
resultHandler);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SettingAction::None:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
@@ -26,6 +26,7 @@ enum class SettingAction {
|
||||
DetectTimezone,
|
||||
SyncTime,
|
||||
Weather,
|
||||
Submenu,
|
||||
};
|
||||
|
||||
struct SettingInfo {
|
||||
@@ -152,13 +153,31 @@ struct SettingInfo {
|
||||
}
|
||||
|
||||
bool isSeparator = false;
|
||||
StrId subcategory = StrId::STR_NONE_OPT;
|
||||
StrId subcategory = StrId::STR_NONE_OPT; // Triggers a separator row on first use and on change
|
||||
StrId submenu = StrId::STR_NONE_OPT; // Routes item into a submenu; hidden from main list
|
||||
[[nodiscard]] std::string getTitle() const;
|
||||
|
||||
// Inserts a separator row in the parent tab when this item's subcategory first appears or changes.
|
||||
SettingInfo& withSubcategory(StrId sub) {
|
||||
subcategory = sub;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Hides this item from the parent tab and places it inside a SettingsSubmenuActivity instead.
|
||||
// All items sharing the same submenu StrId are grouped under one placeholder entry.
|
||||
SettingInfo& withSubmenu(StrId sub) {
|
||||
submenu = sub;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// For internal use by SettingsActivity: placeholder entry that launches the submenu.
|
||||
static SettingInfo SubmenuEntry(StrId titleId) {
|
||||
SettingInfo s;
|
||||
s.nameId = titleId;
|
||||
s.type = SettingType::ACTION;
|
||||
s.action = SettingAction::Submenu;
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
class SettingsActivity final : public Activity {
|
||||
@@ -178,6 +197,12 @@ class SettingsActivity final : public Activity {
|
||||
static constexpr int categoryCount = 4;
|
||||
static const StrId categoryNames[categoryCount];
|
||||
|
||||
struct SubmenuData {
|
||||
StrId id;
|
||||
std::vector<SettingInfo> items;
|
||||
};
|
||||
std::vector<SubmenuData> submenuData;
|
||||
|
||||
void enterCategory(int categoryIndex);
|
||||
void toggleCurrentSetting();
|
||||
[[nodiscard]] bool isListItemSelectable(int settingIdx) const;
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "SettingsSubmenuActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "ButtonRemapActivity.h"
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "ClearCacheActivity.h"
|
||||
#include "ClockSettingsActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
#include "LanguageSelectActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "StatusBarSettingsActivity.h"
|
||||
#include "SystemInformationActivity.h"
|
||||
#include "activities/network/WifiSelectionActivity.h"
|
||||
#include "activities/weather/WeatherSettingsActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
void SettingsSubmenuActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
itemCount = static_cast<int>(items.size());
|
||||
const auto pred = UITheme::makeSelectablePredicate(itemCount,
|
||||
[this](int i) { return items[i].getTitle(); });
|
||||
buttonNavigator.setSelectablePredicate(pred, itemCount);
|
||||
if (!pred(selectedIndex)) {
|
||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
||||
}
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
void SettingsSubmenuActivity::onExit() { Activity::onExit(); }
|
||||
|
||||
void SettingsSubmenuActivity::loop() {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
toggleItem();
|
||||
requestUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
buttonNavigator.onNextRelease([this] {
|
||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
||||
requestUpdate();
|
||||
});
|
||||
buttonNavigator.onPreviousRelease([this] {
|
||||
selectedIndex = buttonNavigator.previousIndex(selectedIndex);
|
||||
requestUpdate();
|
||||
});
|
||||
buttonNavigator.onNextContinuous([this] {
|
||||
selectedIndex = buttonNavigator.nextIndex(selectedIndex);
|
||||
requestUpdate();
|
||||
});
|
||||
buttonNavigator.onPreviousContinuous([this] {
|
||||
selectedIndex = buttonNavigator.previousIndex(selectedIndex);
|
||||
requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
void SettingsSubmenuActivity::toggleItem() {
|
||||
if (selectedIndex < 0 || selectedIndex >= itemCount) return;
|
||||
const auto& setting = items[selectedIndex];
|
||||
if (setting.isSeparator) return;
|
||||
|
||||
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
||||
SETTINGS.*(setting.valuePtr) = !SETTINGS.*(setting.valuePtr);
|
||||
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||
const uint8_t cur = SETTINGS.*(setting.valuePtr);
|
||||
SETTINGS.*(setting.valuePtr) = (cur + 1) % static_cast<uint8_t>(setting.enumValues.size());
|
||||
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||
const int8_t cur = SETTINGS.*(setting.valuePtr);
|
||||
SETTINGS.*(setting.valuePtr) = (cur + setting.valueRange.step > setting.valueRange.max)
|
||||
? setting.valueRange.min
|
||||
: cur + setting.valueRange.step;
|
||||
} else if (setting.type == SettingType::ACTION) {
|
||||
auto resultHandler = [this](const ActivityResult&) { SETTINGS.saveToFile(); };
|
||||
switch (setting.action) {
|
||||
case SettingAction::RemapFrontButtons:
|
||||
startActivityForResult(std::make_unique<ButtonRemapActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::CustomiseStatusBar:
|
||||
startActivityForResult(std::make_unique<StatusBarSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::ClockSettings:
|
||||
startActivityForResult(std::make_unique<ClockSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::KOReaderSync:
|
||||
startActivityForResult(std::make_unique<KOReaderSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::OPDSBrowser:
|
||||
startActivityForResult(std::make_unique<CalibreSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::Network:
|
||||
startActivityForResult(std::make_unique<WifiSelectionActivity>(renderer, mappedInput, false), resultHandler);
|
||||
return;
|
||||
case SettingAction::ClearCache:
|
||||
startActivityForResult(std::make_unique<ClearCacheActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::CheckForUpdates:
|
||||
startActivityForResult(std::make_unique<OtaUpdateActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::Language:
|
||||
startActivityForResult(std::make_unique<LanguageSelectActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::Weather:
|
||||
startActivityForResult(std::make_unique<WeatherSettingsActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
case SettingAction::SystemInfo:
|
||||
startActivityForResult(std::make_unique<SystemInformationActivity>(renderer, mappedInput), resultHandler);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
|
||||
void SettingsSubmenuActivity::render(RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto& metrics = UITheme::getInstance().getMetrics();
|
||||
const Rect contentRect = UITheme::getContentRect(renderer, true, false);
|
||||
|
||||
GUI.drawHeader(renderer, Rect{contentRect.x, metrics.topPadding, contentRect.width, metrics.headerHeight},
|
||||
I18N.get(titleId));
|
||||
|
||||
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
|
||||
const int contentHeight = contentRect.height - contentTop - metrics.verticalSpacing;
|
||||
|
||||
GUI.drawList(
|
||||
renderer, Rect{contentRect.x, contentTop, contentRect.width, contentHeight}, itemCount, selectedIndex,
|
||||
[this](int index) { return items[index].getTitle(); }, nullptr, nullptr,
|
||||
[this](int i) {
|
||||
const auto& setting = items[i];
|
||||
if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
|
||||
return std::string(SETTINGS.*(setting.valuePtr) ? tr(STR_STATE_ON) : tr(STR_STATE_OFF));
|
||||
}
|
||||
if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(setting.valuePtr);
|
||||
if (value < setting.enumValues.size()) {
|
||||
return std::string(I18N.get(setting.enumValues[value]));
|
||||
}
|
||||
}
|
||||
if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
|
||||
return std::to_string(SETTINGS.*(setting.valuePtr));
|
||||
}
|
||||
return std::string();
|
||||
},
|
||||
true);
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include <I18n.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "activities/Activity.h"
|
||||
#include "activities/settings/SettingsActivity.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
// Displays a flat list of SettingInfo items launched from a SettingsActivity submenu entry.
|
||||
// Supports subcategory separators (withSubcategory) exactly as the parent settings tabs do.
|
||||
class SettingsSubmenuActivity final : public Activity {
|
||||
StrId titleId;
|
||||
std::vector<SettingInfo> items;
|
||||
int selectedIndex = 0;
|
||||
int itemCount = 0;
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
void toggleItem();
|
||||
|
||||
public:
|
||||
explicit SettingsSubmenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, StrId titleId,
|
||||
std::vector<SettingInfo> items)
|
||||
: Activity("SettingsSubmenu", renderer, mappedInput), titleId(titleId), items(std::move(items)) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
void render(RenderLock&&) override;
|
||||
};
|
||||
Reference in New Issue
Block a user