From badfcec528a6eea16b2d58cd14bd20107e2d9289 Mon Sep 17 00:00:00 2001 From: Dickson Wong Date: Fri, 6 Mar 2020 01:58:01 -0800 Subject: [PATCH] Options menu and combo config --- include/keyconfig.hpp | 54 +++++++++++++ include/options.hpp | 45 +++++++++++ source/keyconfig.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++ source/main.cpp | 23 +++++- 4 files changed, 293 insertions(+), 2 deletions(-) create mode 100644 include/keyconfig.hpp create mode 100644 include/options.hpp create mode 100644 source/keyconfig.cpp diff --git a/include/keyconfig.hpp b/include/keyconfig.hpp new file mode 100644 index 0000000..87bda35 --- /dev/null +++ b/include/keyconfig.hpp @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2020 diwo + * + * This file is part of Tesla Menu. + * + * Tesla Menu is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Tesla Menu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tesla Menu. If not, see . + */ +#pragma once + +#include +#include + +class KeyConfig : public tsl::Gui { +public: + KeyConfig() : m_state(STATE_CAPTURE), m_combo(0), m_comboConfirm(0) {} + + tsl::elm::Element* createUI() override; + + bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) override; + +protected: + void handleExitMenuInput(u64 keysDown, u64 keysHeld); + void handleCaptureInput(u64 keysDown, u64 keysHeld); + void handleCaptureReleaseInput(u64 keysDown, u64 keysHeld); + void handleConfirmInput(u64 keysDown, u64 keysHeld); + void handleDoneInput(u64 keysDown, u64 keysHeld); + +private: + enum ComboConfigState { STATE_CAPTURE, STATE_CAPTURE_RELEASE, STATE_CONFIRM, STATE_DONE }; + + void filterAllowedKeys(u64 &keysDown, u64 &keysHeld); + std::string comboGlyphs(u64 combo); + +private: + static const std::chrono::milliseconds CAPTURE_HOLD_MILLI; + + ComboConfigState m_state; + u64 m_combo; + u64 m_comboConfirm; + std::chrono::steady_clock::time_point m_lastPlusTime; + std::chrono::steady_clock::time_point m_lastComboTime; + std::chrono::milliseconds m_remainHoldMilli; +}; diff --git a/include/options.hpp b/include/options.hpp new file mode 100644 index 0000000..57a11df --- /dev/null +++ b/include/options.hpp @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2020 diwo + * + * This file is part of Tesla Menu. + * + * Tesla Menu is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Tesla Menu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tesla Menu. If not, see . + */ +#pragma once + +#include +#include + +class OptionsMenu : public tsl::Gui { +public: + tsl::elm::Element* createUI() override { + auto frame = new tsl::elm::OverlayFrame("Tesla Options", ""); + + auto list = new tsl::elm::List(); + + auto keysConfig = new tsl::elm::ListItem("Change combo keys"); + keysConfig->setClickListener([](u64 keys) { + if (keys & KEY_A) { + tsl::changeTo(); + return true; + } + return false; + }); + + list->addItem(keysConfig); + frame->setContent(list); + + return frame; + } +}; diff --git a/source/keyconfig.cpp b/source/keyconfig.cpp new file mode 100644 index 0000000..450bc57 --- /dev/null +++ b/source/keyconfig.cpp @@ -0,0 +1,173 @@ +/** + * Copyright (C) 2020 diwo + * + * This file is part of Tesla Menu. + * + * Tesla Menu is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Tesla Menu is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tesla Menu. If not, see . + */ + +#include + +#include +#include + +using namespace std::chrono_literals; + +const std::chrono::milliseconds KeyConfig::CAPTURE_HOLD_MILLI = 2500ms; + +tsl::elm::Element* KeyConfig::createUI() { + auto COLOR_DEFAULT = a({ 0xE, 0xE, 0xE, 0xF }); + auto COLOR_HEADING = a({ 0xC, 0xC, 0xC, 0xF }); + auto COLOR_PENDING = a({ 0x8, 0x7, 0x1, 0xF }); + auto COLOR_SUCCESS = a({ 0x0, 0xA, 0x2, 0xF }); + auto COLOR_INFO = a({ 0x8, 0x8, 0x8, 0xF }); + + return new tsl::elm::CustomDrawer([=](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h) { + renderer->fillScreen(a({ 0x0, 0x0, 0x0, 0xD })); + + u32 line_y = 50; + renderer->drawString("Change Combo Keys", false, 20, line_y, 30, a(0xFFFF)); + + line_y = 110; + renderer->drawString("Input new combo:", false, 20, line_y, 24, COLOR_HEADING); + auto comboColor = m_state == STATE_CAPTURE ? COLOR_PENDING : COLOR_SUCCESS; + renderer->drawString(comboGlyphs(m_combo).c_str(), false, 20, line_y + 50, 32, comboColor); + if (m_state == STATE_CAPTURE && m_combo) { + double freq = (CAPTURE_HOLD_MILLI - m_remainHoldMilli).count() / 1000; + double offset = sin(2 * 3.14 * freq) * 10 * m_remainHoldMilli / CAPTURE_HOLD_MILLI; + renderer->drawString("Hold still...", false, 20, line_y + 100 - offset, 24, COLOR_INFO); + if (m_remainHoldMilli < CAPTURE_HOLD_MILLI - 500ms) { + std::stringstream timerStream; + timerStream << std::fixed << std::setprecision(1) << ((float)m_remainHoldMilli.count() / 1000) << "s"; + renderer->drawString(timerStream.str().c_str(), false, 160, line_y + 100, 30, COLOR_INFO); + } + } else if (m_state > STATE_CAPTURE) { + renderer->drawString("Got it!", false, 20, line_y + 100, 24, COLOR_DEFAULT); + } + + line_y = 270; + if (m_state >= STATE_CAPTURE_RELEASE) { + renderer->drawString("Input combo again to confirm:", false, 20, line_y, 24, COLOR_HEADING); + if (m_state == STATE_CAPTURE_RELEASE) { + renderer->drawString("(Release the buttons first)", false, 20, line_y + 40, 20, COLOR_INFO); + } + else if (m_state >= STATE_CONFIRM) { + auto comboConfirmColor = m_state == STATE_CONFIRM ? COLOR_PENDING : COLOR_SUCCESS; + renderer->drawString(comboGlyphs(m_comboConfirm).c_str(), false, 20, line_y + 50, 32, comboConfirmColor); + } + } + + line_y = 400; + if (m_state >= STATE_DONE) { + renderer->drawString("We're done!", false, 20, line_y, 24, COLOR_DEFAULT); + renderer->drawString("Press \uE0A0 to continue", false, 20, line_y + 60, 24, COLOR_DEFAULT); + } + + line_y = tsl::cfg::FramebufferHeight - 20; + if (m_state < STATE_DONE) + renderer->drawString("Press \uE0B5 twice to exit", false, 170, line_y, 24, COLOR_INFO); + }); +} + +bool KeyConfig::handleInput( + u64 keysDown, u64 keysHeld, touchPosition touchInput, + JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) +{ + handleExitMenuInput(keysDown, keysHeld); + + switch (m_state) { + case STATE_CAPTURE: + handleCaptureInput(keysDown, keysHeld); + return true; + case STATE_CAPTURE_RELEASE: + handleCaptureReleaseInput(keysDown, keysHeld); + return true; + case STATE_CONFIRM: + handleConfirmInput(keysDown, keysHeld); + return true; + case STATE_DONE: + handleDoneInput(keysDown, keysHeld); + return true; + default: + return false; + } +} + +void KeyConfig::handleExitMenuInput(u64 keysDown, u64 keysHeld) { + if (keysDown & KEY_PLUS) { + auto now = std::chrono::steady_clock::now(); + auto elapsedMilli = std::chrono::duration_cast(now - m_lastPlusTime); + if (m_state < STATE_DONE && elapsedMilli < 500ms) + tsl::goBack(); + else + m_lastPlusTime = now; + } +} + +void KeyConfig::handleCaptureInput(u64 keysDown, u64 keysHeld) { + filterAllowedKeys(keysDown, keysHeld); + auto now = std::chrono::steady_clock::now(); + if (keysHeld != m_combo) { + m_lastComboTime = now; + m_remainHoldMilli = CAPTURE_HOLD_MILLI; + m_combo = keysHeld; + } + else if (m_combo) { + m_remainHoldMilli = CAPTURE_HOLD_MILLI - + std::chrono::duration_cast(now - m_lastComboTime); + if (m_remainHoldMilli <= 0ms) { + m_state = STATE_CAPTURE_RELEASE; + } + } +} + +void KeyConfig::handleCaptureReleaseInput(u64 keysDown, u64 keysHeld) { + filterAllowedKeys(keysDown, keysHeld); + if (!keysHeld) + m_state = STATE_CONFIRM; +} + +void KeyConfig::handleConfirmInput(u64 keysDown, u64 keysHeld) { + filterAllowedKeys(keysDown, keysHeld); + m_comboConfirm = keysHeld; + // Confirmation must be exact match + if ((keysHeld == m_combo) && (keysDown & m_combo)) { + tsl::impl::updateCombo(m_combo); + m_state = STATE_DONE; + } +} + +void KeyConfig::handleDoneInput(u64 keysDown, u64 keysHeld) { + if (keysDown & KEY_A) + tsl::goBack(); +} + +void KeyConfig::filterAllowedKeys(u64 &keysDown, u64 &keysHeld) { + u64 allowedKeys = 0; + for (auto &keyInfo : tsl::impl::KEYS_INFO) { + allowedKeys |= keyInfo.key; + } + keysDown &= allowedKeys; + keysHeld &= allowedKeys; +} + +std::string KeyConfig::comboGlyphs(u64 combo) { + std::string str; + for (auto &keyInfo : tsl::impl::KEYS_INFO) { + if (combo & keyInfo.key) { + str.append(keyInfo.glyph).append(" "); + } + } + return str; +} diff --git a/source/main.cpp b/source/main.cpp index 00923df..a25f245 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include "logo_bin.h" constexpr int Module_OverlayLoader = 348; @@ -73,14 +75,21 @@ class TeslaMenuFrame : public tsl::elm::OverlayFrame { ~TeslaMenuFrame() {} virtual void draw(tsl::gfx::Renderer *renderer) override { - OverlayFrame::draw(renderer); + renderer->fillScreen(a({ 0x0, 0x0, 0x0, 0xD })); renderer->drawBitmap(20, 20, 84, 31, logo_bin); renderer->drawString(envGetLoaderInfo(), false, 20, 68, 15, renderer->a(0xFFFF)); + + renderer->drawRect(15, 720 - 73, tsl::cfg::FramebufferWidth - 30, 1, a(0xFFFF)); + renderer->drawString("\uE0EF Options \uE0E0 OK", false, 30, 693, 23, a(0xFFFF)); + + if (this->m_contentElement != nullptr) + this->m_contentElement->frame(renderer); } }; static TeslaMenuFrame *rootFrame = nullptr; +static tsl::Gui *guiMain = nullptr; static void rebuildUI() { auto *overlayList = new tsl::elm::List(); @@ -132,12 +141,22 @@ class GuiMain : public tsl::Gui { ~GuiMain() { } tsl::elm::Element* createUI() override { + guiMain = this; + rootFrame = new TeslaMenuFrame(); rebuildUI(); return rootFrame; } + + bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) { + if (keysDown & KEY_PLUS) { + tsl::changeTo(); + return true; + } + return false; + } }; class OverlayTeslaMenu : public tsl::Overlay { @@ -146,7 +165,7 @@ class OverlayTeslaMenu : public tsl::Overlay { ~OverlayTeslaMenu() { } void onShow() override { - if (rootFrame != nullptr) { + if (rootFrame != nullptr && tsl::Overlay::get()->getCurrentGui().get() == guiMain) { tsl::Overlay::get()->getCurrentGui()->removeFocus(); rebuildUI(); rootFrame->invalidate();