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
+16 -12
View File
@@ -10,6 +10,7 @@
#include <tt_app_selectiondialog.h>
#include <tt_preferences.h>
#include <tactility/lvgl_module.h>
#include <tactility/lvgl_fonts.h>
#include <TactilityCpp/LvglLock.h>
constexpr auto* TAG = "TwoEleven";
@@ -39,14 +40,19 @@ static constexpr int32_t SELECTION_6X6 = 4;
// Grid size options (index matches selection - 1)
static const uint16_t gridSizes[SIZE_COUNT] = { 3, 4, 5, 6 };
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;
}
static void loadHighScores() {
PreferencesHandle prefs = tt_preferences_alloc(PREF_NAMESPACE);
if (prefs) {
@@ -206,23 +212,21 @@ void TwoEleven::createGame(lv_obj_t* parent, uint16_t size, lv_obj_t* tb) {
lv_obj_set_style_text_color(scoreLabel, lv_palette_main(LV_PALETTE_AMBER), LV_PART_MAIN);
lv_obj_add_event_cb(gameObject, twoElevenEventCb, LV_EVENT_VALUE_CHANGED, this);
auto ui_density = lvgl_get_ui_density();
auto toolbar_height = getToolbarHeight(ui_density);
auto icon_padding = getActionIconPadding(ui_density);
// Create new game button wrapper
newGameWrapper = lv_obj_create(tb);
lv_obj_set_width(newGameWrapper, LV_SIZE_CONTENT);
lv_obj_set_flex_flow(newGameWrapper, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_all(newGameWrapper, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(newGameWrapper, icon_padding / 2, LV_STATE_DEFAULT);
lv_obj_set_style_border_width(newGameWrapper, 0, LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(newGameWrapper, 0, LV_STATE_DEFAULT);
// Create new game button
auto ui_density = lvgl_get_ui_density();
auto toolbar_height = getToolbarHeight(ui_density);
lv_obj_t* newGameBtn = lv_btn_create(newGameWrapper);
if (ui_density == LVGL_UI_DENSITY_COMPACT) {
lv_obj_set_size(newGameBtn, toolbar_height - 8, toolbar_height - 8);
} else {
lv_obj_set_size(newGameBtn, toolbar_height - 6, toolbar_height - 6);
}
lv_obj_set_size(newGameBtn, toolbar_height - icon_padding, toolbar_height - icon_padding);
lv_obj_set_style_pad_all(newGameBtn, 0, LV_STATE_DEFAULT);
lv_obj_align(newGameBtn, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_event_cb(newGameBtn, newGameBtnEvent, LV_EVENT_CLICKED, this);
+23 -20
View File
@@ -7,7 +7,7 @@
static void game_play_event(lv_event_t * e);
static void btnm_event_cb(lv_event_t * e);
static void focus_event(lv_event_t* e);
static void reenter_key_mode_event(lv_event_t* e);
/**
* @brief Free all resources for the 2048 game object
@@ -17,12 +17,11 @@ static void delete_event(lv_event_t * e)
lv_obj_t * obj = lv_event_get_target_obj(e);
twoeleven_t * game_2048 = (twoeleven_t *)lv_obj_get_user_data(obj);
if (game_2048) {
// Reset group editing mode if we enabled it
// Restore edit mode and remove from group before cleanup
if (tt_lvgl_hardware_keyboard_is_available()) {
lv_group_t* group = lv_group_get_default();
if (group) {
lv_group_set_editing(group, false);
}
if (group) lv_group_set_editing(group, false);
lv_group_remove_obj(game_2048->btnm);
}
for (uint16_t index = 0; index < game_2048->map_count; index++) {
if (game_2048->btnm_map[index]) {
@@ -42,21 +41,17 @@ static void delete_event(lv_event_t * e)
}
/**
* @brief Handle focus/defocus to manage edit mode for keyboard input
* @brief Re-enter key mode when the game area is clicked after Q/Esc exit
*/
static void focus_event(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
static void reenter_key_mode_event(lv_event_t* e) {
lv_obj_t* btnm = lv_event_get_current_target_obj(e);
lv_group_t* group = lv_group_get_default();
if (!group) return;
if (code == LV_EVENT_FOCUSED) {
// Enable edit mode so arrow keys control the game
lv_group_set_editing(group, true);
} else if (code == LV_EVENT_DEFOCUSED) {
// Restore normal focus navigation
lv_group_set_editing(group, false);
if (lv_obj_get_group(btnm) == NULL) {
lv_group_add_obj(group, btnm);
}
lv_group_focus_obj(btnm);
lv_group_set_editing(group, true);
}
/**
@@ -149,11 +144,9 @@ lv_obj_t * twoeleven_create(lv_obj_t * parent, uint16_t matrix_size)
lv_group_t* group = lv_group_get_default();
if (group) {
lv_group_add_obj(group, game_2048->btnm);
// Register focus handlers to manage edit mode lifecycle
lv_obj_add_event_cb(game_2048->btnm, focus_event, LV_EVENT_FOCUSED, NULL);
lv_obj_add_event_cb(game_2048->btnm, focus_event, LV_EVENT_DEFOCUSED, NULL);
// Focus the container (will trigger FOCUSED event and enable edit mode)
lv_obj_add_event_cb(game_2048->btnm, reenter_key_mode_event, LV_EVENT_CLICKED, NULL);
lv_group_focus_obj(game_2048->btnm);
lv_group_set_editing(group, true);
}
}
@@ -242,6 +235,16 @@ static void game_play_event(lv_event_t * e)
case '/':
success = move_down(&(game_2048->score), game_2048->matrix_size, game_2048->matrix);
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* grp = lv_group_get_default();
if (grp) lv_group_set_editing(grp, false);
lv_group_remove_obj(lv_event_get_current_target_obj(e));
break;
}
default:
break;
}