Skip to content

Commit

Permalink
Add controller buttons combo (#116)
Browse files Browse the repository at this point in the history
* Add controller buttons combo

* fix clang-format and cppcheck warnings

* fix cppcheck errors
  • Loading branch information
k3kukua committed Jun 18, 2024
1 parent e521a39 commit 9a5ac4d
Show file tree
Hide file tree
Showing 15 changed files with 932 additions and 1 deletion.
207 changes: 207 additions & 0 deletions firmware/keira/src/apps/demos/combo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#include "combo.h"
#include "utils/combo/core/aggregator.h"
#include "utils/combo/one_button.h"
#include "utils/combo/one_hold_down_button.h"
#include "utils/combo/sequence_buttons.h"
#include "utils/combo/concurrent_buttons.h"

#define SECONDS_FOR_EXIT 3

using lilka::Alignment;
using lilka::Button;

uint8_t leftSecondsForExit = SECONDS_FOR_EXIT;
bool exitApp = false;
uint8_t currentScene = 0;

void exit_process_handler(const Button button, const ButtonStage stage, uint16_t leftTimeToComplete) {
leftSecondsForExit = leftTimeToComplete / 1000;
}

void exit_reset_handler() {
leftSecondsForExit = SECONDS_FOR_EXIT;
}

void exit_completed_handler() {
exitApp = true;
}

void completed_handler() {
currentScene++;
}

ComboApp::ComboApp() : App("Combo") {
leftSecondsForExit = SECONDS_FOR_EXIT;
exitApp = false;
currentScene = 0;
}

void ComboApp::run() {
ComboAggregator comboAggregator;
lilka::Canvas buffer(canvas->width(), canvas->height());

ComboOneHoldDownButton comboExit(Button::SELECT, SECONDS_FOR_EXIT * 1000);
comboExit.setAutoReset(false);
comboExit.setCompletedEventHandler(exit_completed_handler);
comboExit.setResetEventHandler(exit_reset_handler);
comboExit.setProgressEventHandler(exit_process_handler);
comboAggregator.add(&comboExit);

ComboOneButton comboContinue(Button::START);
comboContinue.setAutoStart(false);
comboContinue.setAutoReset(false);
comboContinue.setCompletedEventHandler(completed_handler);
comboAggregator.add(&comboContinue);

ComboOneButton comboScene1PressA(Button::A);
comboScene1PressA.setAutoStart(false);
comboScene1PressA.setAutoReset(false);
comboScene1PressA.setCompletedEventHandler(completed_handler);
comboAggregator.add(&comboScene1PressA);

ComboOneHoldDownButton comboScene2HoldAny(Button::ANY, 1000);
comboScene2HoldAny.setAutoStart(false);
comboScene2HoldAny.setAutoReset(false);
comboScene2HoldAny.setCompletedEventHandler(completed_handler);
comboAggregator.add(&comboScene2HoldAny);

ComboConcurrentButtons comboScene3UpB({Button::UP, Button::B});
comboScene3UpB.setAutoStart(false);
comboScene3UpB.setAutoReset(false);
comboScene3UpB.setCompletedEventHandler(completed_handler);
comboAggregator.add(&comboScene3UpB);

ComboButtonSequence comboSequenceLast(
{Button::DOWN, Button::UP, Button::UP, Button::DOWN, Button::A, Button::B, Button::B, Button::A}
);
comboSequenceLast.setAutoStart(false);
comboSequenceLast.setAutoReset(false);
comboSequenceLast.setCompletedEventHandler(completed_handler);
comboAggregator.add(&comboSequenceLast);

while (!exitApp) {
lilka::State st = lilka::controller.getState();
comboAggregator.loop(st);

buffer.fillScreen(lilka::colors::Black);
mainUI(buffer, st);

switch (currentScene) {
case 0:
comboContinue.start();
scene_0(buffer, st);
break;
case 1:
comboScene1PressA.start();
scene_1(buffer, st);
break;
case 2:
comboScene2HoldAny.start();
scene_2(buffer, st);
break;
case 3:
comboScene3UpB.start();
scene_3(buffer, st);
break;
case 4:
comboSequenceLast.setTimeout(750);
comboSequenceLast.start();
scene_4(buffer, st, 750);
break;
case 5:
comboSequenceLast.setTimeout(500);
if (comboSequenceLast.isCompleted()) {
comboSequenceLast.reset();
}
comboSequenceLast.start();
scene_4(buffer, st, 500);
break;
case 6:
comboSequenceLast.setTimeout(250);
if (comboSequenceLast.isCompleted()) {
comboSequenceLast.reset();
}
comboSequenceLast.start();
scene_4(buffer, st, 250);
break;
case 7:
scene_final(buffer, st);
break;
default:
break;
}
canvas->drawCanvas(&buffer);
queueDraw();
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}

void ComboApp::mainUI(lilka::Canvas& buffer, lilka::State& st) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_10x20_MONO);
buffer.drawTextAligned("Demo Combo", hor_center, 20, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned(
"Для виходу затисніть",
hor_center,
canvas->height() - 40,
lilka::Alignment::ALIGN_CENTER,
lilka::Alignment::ALIGN_START
);

const char* pressByExitMessage = "[SELECT] на %d сек";
char pressByExit[strlen(pressByExitMessage)];
sprintf(pressByExit, pressByExitMessage, leftSecondsForExit);
buffer.drawTextAligned(
pressByExit, hor_center, canvas->height() - 20, Alignment::ALIGN_CENTER, Alignment::ALIGN_START
);
}

void ComboApp::scene_0(lilka::Canvas& buffer, lilka::State& st) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned("Натисніть [START]", hor_center, 90, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);

buffer.drawTextAligned("для продовження", hor_center, 110, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
}

void ComboApp::scene_1(lilka::Canvas& buffer, lilka::State& st) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned("Натисніть", hor_center, 90, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);

buffer.drawTextAligned("[A]", hor_center, 110, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
}

void ComboApp::scene_2(lilka::Canvas& buffer, lilka::State& st) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned("Затисніть будь-яку", hor_center, 90, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);

buffer.drawTextAligned("кнопку на 1 сек", hor_center, 110, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
}

void ComboApp::scene_3(lilka::Canvas& buffer, lilka::State& st) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned("Натисніть разом", hor_center, 90, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);

buffer.drawTextAligned("[UP] + [B]", hor_center, 110, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
}

void ComboApp::scene_4(lilka::Canvas& buffer, lilka::State& st, int timeout) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned("DOWN,UP,UP,DOWN,A,B,B,A", hor_center, 90, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);

const char* txt = "Таймаут %d мс";
char bf[100];
sprintf(bf, txt, timeout);
buffer.drawTextAligned(bf, hor_center, 110, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
}

void ComboApp::scene_final(lilka::Canvas& buffer, lilka::State& st) {
uint16_t hor_center = canvas->width() / 2;
lilka::display.setFont(FONT_8x13_MONO);
buffer.drawTextAligned("Кінець!", hor_center, 90, Alignment::ALIGN_CENTER, Alignment::ALIGN_START);
}
19 changes: 19 additions & 0 deletions firmware/keira/src/apps/demos/combo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "app.h"

class ComboApp : public App {
public:
ComboApp();

private:
void run() override;

void mainUI(lilka::Canvas& buffer, lilka::State& st);
void scene_0(lilka::Canvas& buffer, lilka::State& st);
void scene_1(lilka::Canvas& buffer, lilka::State& st);
void scene_2(lilka::Canvas& buffer, lilka::State& st);
void scene_3(lilka::Canvas& buffer, lilka::State& st);
void scene_4(lilka::Canvas& buffer, lilka::State& st, int timeout);
void scene_final(lilka::Canvas& buffer, lilka::State& st);
};
4 changes: 3 additions & 1 deletion firmware/keira/src/apps/launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "demos/user_spi.h"
#include "demos/scan_i2c.h"
#include "demos/petpet.h"
#include "demos/combo.h"
#include "gpiomanager.h"
#include "tamagotchi/tamagotchi.h"
#include "lua/luarunner.h"
Expand Down Expand Up @@ -68,7 +69,8 @@ ITEM_LIST app_items = {
{ITEM_APP("Клавіатура", KeyboardApp),
ITEM_APP("Тест SPI", UserSPIApp),
ITEM_APP("I2C-сканер", ScanI2CApp),
ITEM_APP("GPIO-менеджер", GPIOManagerApp)},
ITEM_APP("GPIO-менеджер", GPIOManagerApp),
ITEM_APP("Combo", ComboApp)},
),
ITEM_APP("ЛілТрекер", LilTrackerApp),
ITEM_APP("Летріс", LetrisApp),
Expand Down
120 changes: 120 additions & 0 deletions firmware/keira/src/utils/combo/concurrent_buttons.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "concurrent_buttons.h"

ComboConcurrentButtons::ComboConcurrentButtons(std::initializer_list<Button> buttonList) :
ComboBase(),
NUM_BUTTONS(buttonList.size() ? buttonList.size() : 1),
buttons(new Button[NUM_BUTTONS]),
progressEventHandler(nullptr) {
initButtons(buttonList);
}

ComboConcurrentButtons::~ComboConcurrentButtons() {
buttons.reset();
progressEventHandler = nullptr;
}

void ComboConcurrentButtons::initButtons(std::initializer_list<Button> buttonList) {
if (buttonList.size()) {
auto it = buttonList.begin();
for (size_t i = 0; i < NUM_BUTTONS && it != buttonList.end(); ++i, ++it) {
buttons[i] = *it;
}
} else {
buttons[0] = Button::ANY;
}
buttonStage.changeButtonAndReset(Button::ANY);
}

void ComboConcurrentButtons::onProgress() {
if (!progressEventHandler) {
return;
}
progressEventHandler(NUM_BUTTONS, buttons.get(), buttonStage.getStage());
}

void ComboConcurrentButtons::loopAction(State& state) {
if (buttonStage.isReleased()) {
return;
}
const _StateButtons& buttonStates = *reinterpret_cast<const _StateButtons*>(&state);
switch (buttonStage.getStage()) {
case WAITING:
checkAllButtonsPressed(buttonStates);
break;
case PRESSED:
checkAllButtonsReleased(buttonStates);
break;
case RELEASED:
default:
break;
}
}

void ComboConcurrentButtons::resetAction() {
buttonStage.reset();
}

bool ComboConcurrentButtons::isTargetButton(size_t buttonIndex) {
for (size_t i = 0; i < NUM_BUTTONS; i++) {
if (buttons[i] == Button::ANY || buttonIndex == buttons[i]) {
return true;
}
}
return false;
}

bool ComboConcurrentButtons::isNonTargetButtonsPressed(const _StateButtons& buttonStates) {
if (!buttonStates[Button::ANY].pressed) {
return false;
}
size_t size = Button::COUNT - 1; // exclude ANY button
for (size_t i = 0; i < size; i++) {
if (buttonStates[i].pressed && !isTargetButton(i)) {
return true;
}
}
return false;
}

void ComboConcurrentButtons::checkAllButtonsPressed(const _StateButtons& buttonStates) {
if (isNonTargetButtonsPressed(buttonStates)) {
return;
}
bool allButtonsPressed = true;
bool anyButtonJustPressed = false;
for (size_t i = 0; i < NUM_BUTTONS; i++) {
const ButtonState& bs = buttonStates[buttons[i]];
if (!bs.pressed) {
allButtonsPressed = false;
break;
}
if (bs.justPressed) {
anyButtonJustPressed = true;
}
}
if (allButtonsPressed && anyButtonJustPressed) {
buttonStage.setStage(PRESSED);
onProgress();
complete();
}
}

void ComboConcurrentButtons::checkAllButtonsReleased(const _StateButtons& buttonStates) {
bool allButtonsReleased = true;
bool anyButtonJustReleased = false;
for (size_t i = 0; i < NUM_BUTTONS; i++) {
const ButtonState& bs = buttonStates[buttons[i]];
if (bs.pressed) {
allButtonsReleased = false;
break;
}
if (bs.justReleased) {
anyButtonJustReleased = true;
}
}
if (allButtonsReleased && anyButtonJustReleased) {
buttonStage.setStage(RELEASED);
onProgress();
checkAutoReset();
}
}
32 changes: 32 additions & 0 deletions firmware/keira/src/utils/combo/concurrent_buttons.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "core/core.h"
#include "core/stage.h"

using ComboConcurrentButtonsProgressEventHandler =
void (*)(const size_t numOfButtons, const Button buttons[], const ButtonStage stage);

class ComboConcurrentButtons : public ComboBase {
public:
ComboConcurrentButtons(std::initializer_list<Button> buttonList);
~ComboConcurrentButtons();

void setProgressEventHandler(ComboConcurrentButtonsProgressEventHandler handler) {
progressEventHandler = handler;
}

private:
const size_t NUM_BUTTONS;
std::unique_ptr<Button[]> buttons;
ComboButtonStage buttonStage;
ComboConcurrentButtonsProgressEventHandler progressEventHandler;

void initButtons(std::initializer_list<Button> buttonList);
bool isTargetButton(size_t buttonIndex);
bool isNonTargetButtonsPressed(const _StateButtons& buttonStates);
void checkAllButtonsPressed(const _StateButtons& buttonStates);
void checkAllButtonsReleased(const _StateButtons& buttonStates);
void onProgress();
void loopAction(State& state) override;
void resetAction() override;
};
Loading

0 comments on commit 9a5ac4d

Please sign in to comment.