Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add controller buttons combo #116

Merged
merged 3 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading