Skip to content

Commit

Permalink
Overlay combo keys configuration UI
Browse files Browse the repository at this point in the history
  • Loading branch information
diwo committed Feb 18, 2020
1 parent c6ed6e0 commit 49e071f
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 0 deletions.
67 changes: 67 additions & 0 deletions include/ComboConfig.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <tesla.hpp>

#include <chrono>

enum ComboConfigState { STATE_CAPTURE, STATE_CAPTURE_RELEASE, STATE_CONFIRM, STATE_DONE, STATE_EXIT };

class ComboConfigFrame : public tsl::element::Frame {
public:
ComboConfigFrame(ComboConfigState *state): m_state(state) {}
~ComboConfigFrame() {}

bool onClick(s64 key) override;

private:
ComboConfigState *m_state;
std::chrono::steady_clock::time_point m_lastPlusTime;
};

class ComboConfig : public tsl::Gui {
public:
ComboConfig(): m_state(STATE_CAPTURE), m_combo(0), m_comboConfirm(0) {}
~ComboConfig() {}

tsl::element::Element* createUI() override;
void handleInputs(s64 keysDown, s64 keysHeld, JoystickPosition joyStickPosLeft, JoystickPosition joyStickPosRight, u32 touchX, u32 touchY) override;

void preDraw(tsl::Screen *screen) override;

protected:
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:
void filterAllowedKeys(u64 &keysDown, u64 &keysHeld);
std::string comboGlyphs(u64 combo);

private:
static const s32 CAPTURE_HOLD_MILLI = 2500;
ComboConfigState m_state;
u64 m_combo;
u64 m_comboConfirm;
std::chrono::steady_clock::time_point m_lastComboTime;
s32 m_remainHoldMilli;
};
168 changes: 168 additions & 0 deletions source/ComboConfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* 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 <http://www.gnu.org/licenses/>.
*/

#include <sstream>
#include <iomanip>

#include <ComboConfig.hpp>

tsl::element::Element* ComboConfig::createUI() {
auto frame = new ComboConfigFrame(&m_state);

frame->addElement(new tsl::element::CustomDrawer(0, 0, FB_WIDTH, FB_HEIGHT, [this](u16 x, u16 y, tsl::Screen *screen) {
u32 line_y = 50;
screen->drawString("Customize Combo Keys", false, 20, line_y, 30, tsl::a(0xFFFF));

line_y = 110;
screen->drawString("Input new combo:", true, 20, line_y, 24, tsl::a(0xFFFF));
screen->drawString(comboGlyphs(m_combo).c_str(), true, 20, line_y + 50, 32, tsl::a(0xFFFF));
if (m_state == STATE_CAPTURE && m_combo) {
double freq = (double)(CAPTURE_HOLD_MILLI - m_remainHoldMilli) / 1000;
double off = sin(2 * 3.14 * freq) * 10 * m_remainHoldMilli / CAPTURE_HOLD_MILLI;
screen->drawString("Hold still...", true, 20, line_y + 100 - off, 24, tsl::a(0xFFFF));
if (m_remainHoldMilli < CAPTURE_HOLD_MILLI - 500) {
std::stringstream ss;
ss << std::fixed << std::setprecision(2) << ((float)m_remainHoldMilli / 1000) << "s";
screen->drawString(ss.str().c_str(), true, 150, line_y + 100, 24, tsl::a(0xFFFF));
}
} else if (m_state > STATE_CAPTURE) {
screen->drawString("Got it!", true, 20, line_y + 100, 24, tsl::a(0xFFFF));
}

line_y = 270;
if (m_state >= STATE_CAPTURE_RELEASE) {
screen->drawString("Input combo again to confirm:", true, 20, line_y, 24, tsl::a(0xFFFF));
if (m_state == STATE_CAPTURE_RELEASE) {
auto keysHeldTime = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - m_lastComboTime).count();
if (keysHeldTime > 2000)
screen->drawString("(Release the buttons first)", true, 20, line_y + 40, 20, tsl::a(0xFFFF));
}
else if (m_state >= STATE_CONFIRM)
screen->drawString(comboGlyphs(m_comboConfirm).c_str(), true, 20, line_y + 50, 32, tsl::a(0xFFFF));
}

line_y = 400;
if (m_state >= STATE_DONE) {
screen->drawString("We're done!", true, 20, line_y, 24, tsl::a(0xFFFF));
screen->drawString("Press \uE0A0 to continue", true, 20, line_y + 40, 24, tsl::a(0xFFFF));
}

line_y = FB_HEIGHT - 20;
if (m_state < STATE_DONE)
screen->drawString("Press \uE0B5 twice to exit", true, 170, line_y, 24, tsl::a(0xFFFF));
}));

return frame;
}

void ComboConfig::handleInputs(
s64 keysDown, s64 keysHeld,
JoystickPosition joyStickPosLeft, JoystickPosition joyStickPosRight,
u32 touchX, u32 touchY)
{
switch (m_state) {
case STATE_CAPTURE:
return handleCaptureInput(keysDown, keysHeld);
case STATE_CAPTURE_RELEASE:
return handleCaptureReleaseInput(keysDown, keysHeld);
case STATE_CONFIRM:
return handleConfirmInput(keysDown, keysHeld);
case STATE_DONE:
return handleDoneInput(keysDown, keysHeld);
default:
return;
}
}

void ComboConfig::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<std::chrono::milliseconds>(now - m_lastComboTime).count();
if (m_remainHoldMilli <= 0) {
m_state = STATE_CAPTURE_RELEASE;
m_lastComboTime = now;
}
}
}

void ComboConfig::handleCaptureReleaseInput(u64 keysDown, u64 keysHeld) {
filterAllowedKeys(keysDown, keysHeld);
if (!keysHeld)
m_state = STATE_CONFIRM;
}

void ComboConfig::handleConfirmInput(u64 keysDown, u64 keysHeld) {
filterAllowedKeys(keysDown, keysHeld);
m_comboConfirm = keysHeld;
// Confirmation must be exact match
if ((keysHeld == m_combo) && (keysDown & m_combo)) {
tsl::KeyCombo::update(m_combo);
m_state = STATE_DONE;
}
}

void ComboConfig::handleDoneInput(u64 keysDown, u64 keysHeld) {
if (keysDown & KEY_A)
m_state = STATE_EXIT;
}

void ComboConfig::preDraw(tsl::Screen *screen) {
screen->fillScreen(tsl::a({ 0x0, 0x0, 0x0, 0xD }));
if (m_state == STATE_EXIT)
Gui::goBack();
}

bool ComboConfigFrame::onClick(s64 key) {
if (key & KEY_PLUS) {
auto now = std::chrono::steady_clock::now();
auto elapsedMilli = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastPlusTime).count();
if (*m_state < STATE_DONE && elapsedMilli < 500)
tsl::Gui::goBack();
else
m_lastPlusTime = now;
}
return true;
}

void ComboConfig::filterAllowedKeys(u64 &keysDown, u64 &keysHeld) {
u64 allowedKeys = 0;
for (auto &i : tsl::KeyCombo::KEYS_INFO) {
allowedKeys |= i.key;
}
keysDown &= allowedKeys;
keysHeld &= allowedKeys;
}

std::string ComboConfig::comboGlyphs(u64 combo) {
std::string str;
for (auto &i : tsl::KeyCombo::KEYS_INFO) {
if (combo & i.key) {
str.append(i.glyph).append(" ");
}
}
return str;
}

0 comments on commit 49e071f

Please sign in to comment.