Fixes and new apps (#30)

- M5 Unit Modules library + M5 Unit Test app
- Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout
- Fixed SerialConsole to use the uart controller as it was broken in one of the many updates
- Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake
- Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button
- Epub Reader app
This commit is contained in:
Shadowtrance
2026-06-07 23:56:32 +10:00
committed by GitHub
parent 1f959c8bbf
commit dbf850c434
127 changed files with 11156 additions and 174 deletions
+42 -15
View File
@@ -13,6 +13,7 @@
#include <tt_lvgl_keyboard.h>
#include <tactility/lvgl_module.h>
#include <tactility/lvgl_fonts.h>
constexpr auto* TAG = "Breakout";
@@ -97,14 +98,19 @@ static uint32_t levelRng(uint32_t& seed) {
// ── UI Creation ──────────────────────────────────────────────
static int getToolbarHeight(UiDensity density) {
if (density == LVGL_UI_DENSITY_COMPACT) {
return 22;
static uint32_t getToolbarHeight(UiDensity uiDensity) {
if (uiDensity == LVGL_UI_DENSITY_COMPACT) {
return lvgl_get_text_font_height(FONT_SIZE_DEFAULT) * 1.4f;
} else {
return 40;
return lvgl_get_text_font_height(FONT_SIZE_LARGE) * 2.2f;
}
}
static uint32_t getActionIconPadding(UiDensity uiDensity) {
auto toolbar_height = getToolbarHeight(uiDensity);
return (uiDensity != LVGL_UI_DENSITY_COMPACT) ? (uint32_t)(toolbar_height * 0.2f) : 8;
}
void Breakout::onShow(AppHandle appHandle, lv_obj_t* parent) {
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
@@ -157,21 +163,21 @@ void Breakout::onShow(AppHandle appHandle, lv_obj_t* parent) {
lv_obj_set_style_text_color(livesLabel, lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_align(livesLabel, LV_ALIGN_CENTER, 0, 0);
auto ui_density = lvgl_get_ui_density();
auto toolbar_height = getToolbarHeight(ui_density);
auto icon_padding = getActionIconPadding(ui_density);
// Toolbar buttons wrapper
lv_obj_t* btnsWrapper = lv_obj_create(toolbar);
lv_obj_set_width(btnsWrapper, LV_SIZE_CONTENT);
lv_obj_set_flex_flow(btnsWrapper, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(btnsWrapper, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(btnsWrapper, icon_padding / 2, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(btnsWrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(btnsWrapper, 0, LV_STATE_DEFAULT);
auto ui_density = lvgl_get_ui_density();
auto toolbar_height = getToolbarHeight(ui_density);
int btnSize = (ui_density == LVGL_UI_DENSITY_COMPACT) ? toolbar_height - 8 : toolbar_height - 6;
// Pause button
lv_obj_t* pauseBtn = lv_btn_create(btnsWrapper);
lv_obj_set_size(pauseBtn, btnSize, btnSize);
lv_obj_set_size(pauseBtn, toolbar_height - icon_padding, toolbar_height - icon_padding);
lv_obj_set_style_pad_all(pauseBtn, 0, LV_STATE_DEFAULT);
lv_obj_align(pauseBtn, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_event_cb(pauseBtn, onPauseClicked, LV_EVENT_CLICKED, this);
@@ -182,7 +188,7 @@ void Breakout::onShow(AppHandle appHandle, lv_obj_t* parent) {
// Sound toggle button
lv_obj_t* soundBtn = lv_btn_create(btnsWrapper);
lv_obj_set_size(soundBtn, btnSize, btnSize);
lv_obj_set_size(soundBtn, toolbar_height - icon_padding, toolbar_height - icon_padding);
lv_obj_set_style_pad_all(soundBtn, 0, LV_STATE_DEFAULT);
lv_obj_align(soundBtn, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_event_cb(soundBtn, onSoundToggled, LV_EVENT_CLICKED, this);
@@ -333,9 +339,9 @@ void Breakout::onShow(AppHandle appHandle, lv_obj_t* parent) {
lv_obj_add_event_cb(gameArea, onPressed, LV_EVENT_PRESSING, this);
lv_obj_add_event_cb(gameArea, onClicked, LV_EVENT_SHORT_CLICKED, this);
lv_obj_add_event_cb(gameArea, onKey, LV_EVENT_KEY, this);
lv_obj_add_event_cb(gameArea, onFocused, LV_EVENT_FOCUSED, this);
lv_obj_add_event_cb(gameArea, onReenterKeyMode, LV_EVENT_CLICKED, this);
// Keyboard focus
// Keyboard focus - explicit enter/exit, no focus/defocus handlers
lv_group_t* group = lv_group_get_default();
if (group) {
lv_group_add_obj(group, gameArea);
@@ -352,6 +358,11 @@ void Breakout::onHide(AppHandle appHandle) {
lv_timer_delete(gameTimer);
gameTimer = nullptr;
}
if (gameArea) {
lv_group_t* group = lv_group_get_default();
if (group) lv_group_set_editing(group, false);
lv_group_remove_obj(gameArea);
}
gameArea = nullptr;
paddle = nullptr;
for (int i = 0; i < MAX_BRICKS; i++) bricks[i] = nullptr;
@@ -1454,6 +1465,16 @@ void Breakout::onKey(lv_event_t* e) {
}
}
break;
case LV_KEY_ESC:
case 'q':
case 'Q': {
// Exit key/edit mode - remove from group so navigation is restored
// Re-entry: tap/click the game area to return to key mode
lv_group_t* group = lv_group_get_default();
if (group) lv_group_set_editing(group, false);
lv_group_remove_obj(lv_event_get_current_target_obj(e));
break;
}
}
}
@@ -1472,7 +1493,13 @@ void Breakout::onSoundToggled(lv_event_t* e) {
self->updateSoundIcon();
}
void Breakout::onFocused(lv_event_t* e) {
void Breakout::onReenterKeyMode(lv_event_t* e) {
lv_obj_t* area = lv_event_get_current_target_obj(e);
lv_group_t* group = lv_group_get_default();
if (group) lv_group_set_editing(group, true);
if (!group) return;
if (lv_obj_get_group(area) == NULL) {
lv_group_add_obj(group, area);
}
lv_group_focus_obj(area);
lv_group_set_editing(group, true);
}
+1 -1
View File
@@ -133,7 +133,7 @@ private:
static void onPressed(lv_event_t* e);
static void onClicked(lv_event_t* e);
static void onKey(lv_event_t* e);
static void onFocused(lv_event_t* e);
static void onReenterKeyMode(lv_event_t* e);
static void onPauseClicked(lv_event_t* e);
static void onSoundToggled(lv_event_t* e);
+2 -2
View File
@@ -5,7 +5,7 @@ sdk=0.7.0-dev
platforms=esp32,esp32s3,esp32c6,esp32p4
[app]
id=one.tactility.breakout
versionName=0.1.0
versionCode=1
versionName=0.2.0
versionCode=2
name=Breakout
description=Classic brick-breaking arcade game