diff --git a/src/activities/settings/CategorySettingsActivity.cpp b/src/activities/settings/CategorySettingsActivity.cpp new file mode 100644 index 0000000..77ff0b0 --- /dev/null +++ b/src/activities/settings/CategorySettingsActivity.cpp @@ -0,0 +1,184 @@ +#include "CategorySettingsActivity.h" + +#include +#include + +#include + +#include "CalibreSettingsActivity.h" +#include "CrossPointSettings.h" +#include "KOReaderSettingsActivity.h" +#include "MappedInputManager.h" +#include "OtaUpdateActivity.h" +#include "fontIds.h" + +void CategorySettingsActivity::taskTrampoline(void* param) { + auto* self = static_cast(param); + self->displayTaskLoop(); +} + +void CategorySettingsActivity::onEnter() { + Activity::onEnter(); + renderingMutex = xSemaphoreCreateMutex(); + + selectedSettingIndex = 0; + updateRequired = true; + + xTaskCreate(&CategorySettingsActivity::taskTrampoline, "CategorySettingsActivityTask", 4096, this, 1, + &displayTaskHandle); +} + +void CategorySettingsActivity::onExit() { + ActivityWithSubactivity::onExit(); + + // Wait until not rendering to delete task to avoid killing mid-instruction to EPD + xSemaphoreTake(renderingMutex, portMAX_DELAY); + if (displayTaskHandle) { + vTaskDelete(displayTaskHandle); + displayTaskHandle = nullptr; + } + vSemaphoreDelete(renderingMutex); + renderingMutex = nullptr; +} + +void CategorySettingsActivity::loop() { + if (subActivity) { + subActivity->loop(); + return; + } + + // Handle actions with early return + if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { + toggleCurrentSetting(); + updateRequired = true; + return; + } + + if (mappedInput.wasPressed(MappedInputManager::Button::Back)) { + SETTINGS.saveToFile(); + onGoBack(); + return; + } + + // Handle navigation + if (mappedInput.wasPressed(MappedInputManager::Button::Up) || + mappedInput.wasPressed(MappedInputManager::Button::Left)) { + selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1); + updateRequired = true; + } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || + mappedInput.wasPressed(MappedInputManager::Button::Right)) { + selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0; + updateRequired = true; + } +} + +void CategorySettingsActivity::toggleCurrentSetting() { + if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) { + return; + } + + const auto& setting = settingsList[selectedSettingIndex]; + + if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) { + // Toggle the boolean value using the member pointer + const bool currentValue = SETTINGS.*(setting.valuePtr); + SETTINGS.*(setting.valuePtr) = !currentValue; + } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { + const uint8_t currentValue = SETTINGS.*(setting.valuePtr); + SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size()); + } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { + const int8_t currentValue = SETTINGS.*(setting.valuePtr); + if (currentValue + setting.valueRange.step > setting.valueRange.max) { + SETTINGS.*(setting.valuePtr) = setting.valueRange.min; + } else { + SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; + } + } else if (setting.type == SettingType::ACTION) { + if (strcmp(setting.name, "KOReader Sync") == 0) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + } else if (strcmp(setting.name, "Calibre Settings") == 0) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + } else if (strcmp(setting.name, "Check for updates") == 0) { + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); + enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); + } + } else { + return; + } + + SETTINGS.saveToFile(); +} + +void CategorySettingsActivity::displayTaskLoop() { + while (true) { + if (updateRequired && !subActivity) { + updateRequired = false; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + render(); + xSemaphoreGive(renderingMutex); + } + vTaskDelay(10 / portTICK_PERIOD_MS); + } +} + +void CategorySettingsActivity::render() const { + renderer.clearScreen(); + + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + + renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD); + + // Draw selection highlight + renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30); + + // Draw all settings + for (int i = 0; i < settingsCount; i++) { + const int settingY = 60 + i * 30; // 30 pixels between settings + const bool isSelected = (i == selectedSettingIndex); + + // Draw setting name + renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected); + + // Draw value based on setting type + std::string valueText; + if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) { + const bool value = SETTINGS.*(settingsList[i].valuePtr); + valueText = value ? "ON" : "OFF"; + } else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) { + const uint8_t value = SETTINGS.*(settingsList[i].valuePtr); + valueText = settingsList[i].enumValues[value]; + } else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) { + valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr)); + } + if (!valueText.empty()) { + const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); + renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected); + } + } + + renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), + pageHeight - 60, CROSSPOINT_VERSION); + + const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", ""); + renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + + renderer.displayBuffer(); +} diff --git a/src/activities/settings/CategorySettingsActivity.h b/src/activities/settings/CategorySettingsActivity.h new file mode 100644 index 0000000..a7d1f0c --- /dev/null +++ b/src/activities/settings/CategorySettingsActivity.h @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include + +#include +#include +#include + +#include "activities/ActivityWithSubactivity.h" + +class CrossPointSettings; + +enum class SettingType { TOGGLE, ENUM, ACTION, VALUE }; + +struct SettingInfo { + const char* name; + SettingType type; + uint8_t CrossPointSettings::* valuePtr; + std::vector enumValues; + + struct ValueRange { + uint8_t min; + uint8_t max; + uint8_t step; + }; + ValueRange valueRange; + + static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr) { + return {name, SettingType::TOGGLE, ptr}; + } + + static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector values) { + return {name, SettingType::ENUM, ptr, std::move(values)}; + } + + static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; } + + static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) { + return {name, SettingType::VALUE, ptr, {}, valueRange}; + } +}; + +class CategorySettingsActivity final : public ActivityWithSubactivity { + TaskHandle_t displayTaskHandle = nullptr; + SemaphoreHandle_t renderingMutex = nullptr; + bool updateRequired = false; + int selectedSettingIndex = 0; + const char* categoryName; + const SettingInfo* settingsList; + int settingsCount; + const std::function onGoBack; + + static void taskTrampoline(void* param); + [[noreturn]] void displayTaskLoop(); + void render() const; + void toggleCurrentSetting(); + + public: + CategorySettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const char* categoryName, + const SettingInfo* settingsList, int settingsCount, const std::function& onGoBack) + : ActivityWithSubactivity("CategorySettings", renderer, mappedInput), + categoryName(categoryName), + settingsList(settingsList), + settingsCount(settingsCount), + onGoBack(onGoBack) {} + void onEnter() override; + void onExit() override; + void loop() override; +}; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 7907e50..2b58932 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -3,48 +3,52 @@ #include #include -#include - -#include "CalibreSettingsActivity.h" +#include "CategorySettingsActivity.h" #include "CrossPointSettings.h" -#include "KOReaderSettingsActivity.h" #include "MappedInputManager.h" -#include "OtaUpdateActivity.h" #include "fontIds.h" -// Define the static settings list +const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"}; + namespace { -constexpr int settingsCount = 22; -const SettingInfo settingsList[settingsCount] = { +constexpr int displaySettingsCount = 5; +const SettingInfo displaySettings[displaySettingsCount] = { // Should match with SLEEP_SCREEN_MODE SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}), SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), - SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), - SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing), - SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"}), + SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, + {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})}; + +constexpr int readerSettingsCount = 9; +const SettingInfo readerSettings[readerSettingsCount] = { + SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}), + SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), + SettingInfo::Enum("Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}), + SettingInfo::Value("Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), + SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment, + {"Justify", "Left", "Center", "Right"}), + SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled), SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation, {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}), + SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing), + SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)}; + +constexpr int controlsSettingsCount = 4; +const SettingInfo controlsSettings[controlsSettingsCount] = { SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout, {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}), SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, {"Prev, Next", "Next, Prev"}), SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip), - SettingInfo::Enum("Reader Font Family", &CrossPointSettings::fontFamily, - {"Bookerly", "Noto Sans", "Open Dyslexic"}), - SettingInfo::Enum("Reader Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), - SettingInfo::Enum("Reader Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}), - SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), - SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment, - {"Justify", "Left", "Center", "Right"}), - SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled), + SettingInfo::Enum("Short Power Button Click", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})}; + +constexpr int systemSettingsCount = 4; +const SettingInfo systemSettings[systemSettingsCount] = { SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), - SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency, - {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), - SettingInfo::Action("KOReader Sync"), - SettingInfo::Action("Calibre Settings"), + SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Check for updates")}; } // namespace @@ -57,8 +61,8 @@ void SettingsActivity::onEnter() { Activity::onEnter(); renderingMutex = xSemaphoreCreateMutex(); - // Reset selection to first item - selectedSettingIndex = 0; + // Reset selection to first category + selectedCategoryIndex = 0; // Trigger first update updateRequired = true; @@ -90,10 +94,9 @@ void SettingsActivity::loop() { return; } - // Handle actions with early return + // Handle category selection if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) { - toggleCurrentSetting(); - updateRequired = true; + enterCategory(selectedCategoryIndex); return; } @@ -107,72 +110,52 @@ void SettingsActivity::loop() { if (mappedInput.wasPressed(MappedInputManager::Button::Up) || mappedInput.wasPressed(MappedInputManager::Button::Left)) { // Move selection up (with wrap-around) - selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1); + selectedCategoryIndex = (selectedCategoryIndex > 0) ? (selectedCategoryIndex - 1) : (categoryCount - 1); updateRequired = true; } else if (mappedInput.wasPressed(MappedInputManager::Button::Down) || mappedInput.wasPressed(MappedInputManager::Button::Right)) { // Move selection down (with wrap around) - selectedSettingIndex = (selectedSettingIndex < settingsCount - 1) ? (selectedSettingIndex + 1) : 0; + selectedCategoryIndex = (selectedCategoryIndex < categoryCount - 1) ? (selectedCategoryIndex + 1) : 0; updateRequired = true; } } -void SettingsActivity::toggleCurrentSetting() { - if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) { +void SettingsActivity::enterCategory(int categoryIndex) { + if (categoryIndex < 0 || categoryIndex >= categoryCount) { return; } - const auto& setting = settingsList[selectedSettingIndex]; + xSemaphoreTake(renderingMutex, portMAX_DELAY); + exitActivity(); - if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) { - // Toggle the boolean value using the member pointer - const bool currentValue = SETTINGS.*(setting.valuePtr); - SETTINGS.*(setting.valuePtr) = !currentValue; - } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { - const uint8_t currentValue = SETTINGS.*(setting.valuePtr); - SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size()); - } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { - // Decreasing would also be nice for large ranges I think but oh well can't have everything - const int8_t currentValue = SETTINGS.*(setting.valuePtr); - // Wrap to minValue if exceeding setting value boundary - if (currentValue + setting.valueRange.step > setting.valueRange.max) { - SETTINGS.*(setting.valuePtr) = setting.valueRange.min; - } else { - SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; - } - } else if (setting.type == SettingType::ACTION) { - if (strcmp(setting.name, "KOReader Sync") == 0) { - xSemaphoreTake(renderingMutex, portMAX_DELAY); - exitActivity(); - enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] { - exitActivity(); - updateRequired = true; - })); - xSemaphoreGive(renderingMutex); - } else if (strcmp(setting.name, "Calibre Settings") == 0) { - xSemaphoreTake(renderingMutex, portMAX_DELAY); - exitActivity(); - enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] { - exitActivity(); - updateRequired = true; - })); - xSemaphoreGive(renderingMutex); - } else if (strcmp(setting.name, "Check for updates") == 0) { - xSemaphoreTake(renderingMutex, portMAX_DELAY); - exitActivity(); - enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] { - exitActivity(); - updateRequired = true; - })); - xSemaphoreGive(renderingMutex); - } - } else { - // Only toggle if it's a toggle type and has a value pointer - return; + const SettingInfo* settingsList = nullptr; + int settingsCount = 0; + + switch (categoryIndex) { + case 0: // Display + settingsList = displaySettings; + settingsCount = displaySettingsCount; + break; + case 1: // Reader + settingsList = readerSettings; + settingsCount = readerSettingsCount; + break; + case 2: // Controls + settingsList = controlsSettings; + settingsCount = controlsSettingsCount; + break; + case 3: // System + settingsList = systemSettings; + settingsCount = systemSettingsCount; + break; } - // Save settings when they change - SETTINGS.saveToFile(); + enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList, + settingsCount, [this] { + exitActivity(); + updateRequired = true; + })); + xSemaphoreGive(renderingMutex); } void SettingsActivity::displayTaskLoop() { @@ -196,32 +179,15 @@ void SettingsActivity::render() const { // Draw header renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD); - // Draw selection highlight - renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30); + // Draw selection + renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30); - // Draw all settings - for (int i = 0; i < settingsCount; i++) { - const int settingY = 60 + i * 30; // 30 pixels between settings - const bool isSelected = (i == selectedSettingIndex); + // Draw all categories + for (int i = 0; i < categoryCount; i++) { + const int categoryY = 60 + i * 30; // 30 pixels between categories - // Draw setting name - renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected); - - // Draw value based on setting type - std::string valueText; - if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) { - const bool value = SETTINGS.*(settingsList[i].valuePtr); - valueText = value ? "ON" : "OFF"; - } else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) { - const uint8_t value = SETTINGS.*(settingsList[i].valuePtr); - valueText = settingsList[i].enumValues[value]; - } else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) { - valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr)); - } - if (!valueText.empty()) { - const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); - renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected); - } + // Draw category name + renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex); } // Draw version text above button hints @@ -229,7 +195,7 @@ void SettingsActivity::render() const { pageHeight - 60, CROSSPOINT_VERSION); // Draw help text - const auto labels = mappedInput.mapLabels("« Save", "Toggle", "", ""); + const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); // Always use standard refresh for settings screen diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h index 157689e..821dda4 100644 --- a/src/activities/settings/SettingsActivity.h +++ b/src/activities/settings/SettingsActivity.h @@ -10,51 +10,22 @@ #include "activities/ActivityWithSubactivity.h" class CrossPointSettings; - -enum class SettingType { TOGGLE, ENUM, ACTION, VALUE }; - -// Structure to hold setting information -struct SettingInfo { - const char* name; // Display name of the setting - SettingType type; // Type of setting - uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM/VALUE) - std::vector enumValues; - - struct ValueRange { - uint8_t min; - uint8_t max; - uint8_t step; - }; - // Bounds/step for VALUE type settings - ValueRange valueRange; - - // Static constructors - static SettingInfo Toggle(const char* name, uint8_t CrossPointSettings::* ptr) { - return {name, SettingType::TOGGLE, ptr}; - } - - static SettingInfo Enum(const char* name, uint8_t CrossPointSettings::* ptr, std::vector values) { - return {name, SettingType::ENUM, ptr, std::move(values)}; - } - - static SettingInfo Action(const char* name) { return {name, SettingType::ACTION, nullptr}; } - - static SettingInfo Value(const char* name, uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) { - return {name, SettingType::VALUE, ptr, {}, valueRange}; - } -}; +struct SettingInfo; class SettingsActivity final : public ActivityWithSubactivity { TaskHandle_t displayTaskHandle = nullptr; SemaphoreHandle_t renderingMutex = nullptr; bool updateRequired = false; - int selectedSettingIndex = 0; // Currently selected setting + int selectedCategoryIndex = 0; // Currently selected category const std::function onGoHome; + static constexpr int categoryCount = 4; + static const char* categoryNames[categoryCount]; + static void taskTrampoline(void* param); [[noreturn]] void displayTaskLoop(); void render() const; - void toggleCurrentSetting(); + void enterCategory(int categoryIndex); public: explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,