diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 95faa9b..0bcbbbf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,6 +9,18 @@ on: permissions: read-all jobs: + CheckVersionBumps: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + - name: "Check version bumps" + env: + BASE_REF: ${{ github.base_ref }} + run: python3 Scripts/check-version-bumps.py "origin/$BASE_REF" GenerateAppMatrix: runs-on: ubuntu-latest outputs: diff --git a/Apps/Brainfuck/manifest.properties b/Apps/Brainfuck/manifest.properties index 5187f61..46cf5ac 100644 --- a/Apps/Brainfuck/manifest.properties +++ b/Apps/Brainfuck/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.brainfuck -version.name=0.15.0 -version.code=15 +version.name=0.16.0 +version.code=16 name=Brainfuck description=Brainfuck esoteric language interpreter app.0.id=tactility.brainfuck diff --git a/Apps/Breakout/main/Source/Breakout.cpp b/Apps/Breakout/main/Source/Breakout.cpp index d4b78ce..a8729ff 100644 --- a/Apps/Breakout/main/Source/Breakout.cpp +++ b/Apps/Breakout/main/Source/Breakout.cpp @@ -17,7 +17,6 @@ #include #include -#include constexpr auto* TAG = "Breakout"; @@ -151,19 +150,6 @@ static int scoreBrick(Context* ctx, int idx); /* ── UI Creation ──────────────────────────────────────────────── */ -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 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; -} - /* ── Event Callbacks (declared here so createWidgets can wire them up) ── */ static void onTick(lv_timer_t* timer); @@ -228,38 +214,9 @@ void breakoutCreateWidgets(lv_obj_t* parent, void* userData) { lv_obj_set_style_text_color(ctx->livesLabel, lv_palette_main(LV_PALETTE_RED), 0); lv_obj_align(ctx->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, 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); - - // Pause button - lv_obj_t* pauseBtn = lv_btn_create(btnsWrapper); - 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, ctx); - - lv_obj_t* pauseIcon = lv_label_create(pauseBtn); - lv_label_set_text(pauseIcon, LV_SYMBOL_PAUSE); - lv_obj_align(pauseIcon, LV_ALIGN_CENTER, 0, 0); - - // Sound toggle button - lv_obj_t* soundBtn = lv_btn_create(btnsWrapper); - 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, ctx); - - ctx->soundBtnIcon = lv_label_create(soundBtn); - lv_obj_align(ctx->soundBtnIcon, LV_ALIGN_CENTER, 0, 0); + lvgl_toolbar_add_text_button_action(toolbar, LV_SYMBOL_PAUSE, onPauseClicked, ctx); + lv_obj_t* soundBtn = lvgl_toolbar_add_text_button_action(toolbar, "", onSoundToggled, ctx); + ctx->soundBtnIcon = lv_obj_get_child(soundBtn, 0); updateSoundIcon(ctx); // Screen size detection (The Book) diff --git a/Apps/Breakout/manifest.properties b/Apps/Breakout/manifest.properties index cba12e5..239ec50 100644 --- a/Apps/Breakout/manifest.properties +++ b/Apps/Breakout/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.breakout -version.name=0.19.0 -version.code=19 +version.name=0.20.0 +version.code=20 name=Breakout description=Classic brick-breaking arcade game app.0.id=tactility.breakout diff --git a/Apps/BusyBoxVi/manifest.properties b/Apps/BusyBoxVi/manifest.properties index fbae5ea..d05436d 100644 --- a/Apps/BusyBoxVi/manifest.properties +++ b/Apps/BusyBoxVi/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 id=tactility.busyboxvi -version.name=0.1.0 -version.code=1 +version.name=0.2.0 +version.code=2 name=Vi description=BusyBox vi text editor app.0.id=tactility.busyboxvi diff --git a/Apps/Calculator/manifest.properties b/Apps/Calculator/manifest.properties index 2894e48..67c5bd9 100644 --- a/Apps/Calculator/manifest.properties +++ b/Apps/Calculator/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.calculator -version.name=0.16.0 -version.code=16 +version.name=0.17.0 +version.code=17 name=Calculator app.0.id=tactility.calculator app.0.name=Calculator diff --git a/Apps/Diceware/manifest.properties b/Apps/Diceware/manifest.properties index bd20ea5..4cbf3ef 100644 --- a/Apps/Diceware/manifest.properties +++ b/Apps/Diceware/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.diceware -version.name=0.18.0 -version.code=18 +version.name=0.19.0 +version.code=19 name=Diceware description=Password generator app.0.id=tactility.diceware diff --git a/Apps/Doom/manifest.properties b/Apps/Doom/manifest.properties index a7ed958..e15169f 100644 --- a/Apps/Doom/manifest.properties +++ b/Apps/Doom/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32p4 id=tactility.doom -version.name=0.9.0 -version.code=9 +version.name=0.10.0 +version.code=10 name=Doom description=Classic DOOM. Tab5 only, keyboard required to play. app.0.id=tactility.doom diff --git a/Apps/EpubReader/manifest.properties b/Apps/EpubReader/manifest.properties index fc8dd48..b9d44f3 100644 --- a/Apps/EpubReader/manifest.properties +++ b/Apps/EpubReader/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32s3,esp32p4 id=tactility.epubreader -version.name=0.15.0 -version.code=15 +version.name=0.16.0 +version.code=16 name=ePub Reader description=ePub and text file reader. Requires PSRAM! app.0.id=tactility.epubreader diff --git a/Apps/EspNowBridge/manifest.properties b/Apps/EspNowBridge/manifest.properties index 4c4e6d2..a4fd13e 100644 --- a/Apps/EspNowBridge/manifest.properties +++ b/Apps/EspNowBridge/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32p4 id=tactility.espnowbridge -version.name=0.13.0 -version.code=13 +version.name=0.14.0 +version.code=14 name=ESP-NOW Bridge description=Companion app for updating P4 device C6 co-processor firmware to enable ESP-NOW bridge support. app.0.id=tactility.espnowbridge diff --git a/Apps/GPIO/manifest.properties b/Apps/GPIO/manifest.properties index 5523529..74e8959 100644 --- a/Apps/GPIO/manifest.properties +++ b/Apps/GPIO/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.gpio -version.name=0.18.0 -version.code=18 +version.name=0.19.0 +version.code=19 name=GPIO description=Shows the live state of all GPIO pins app.0.id=tactility.gpio diff --git a/Apps/GraphicsDemo/manifest.properties b/Apps/GraphicsDemo/manifest.properties index 77618aa..c951512 100644 --- a/Apps/GraphicsDemo/manifest.properties +++ b/Apps/GraphicsDemo/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.graphicsdemo -version.name=0.16.0 -version.code=16 +version.name=0.17.0 +version.code=17 name=Graphics Demo app.0.id=tactility.graphicsdemo app.0.name=Graphics Demo diff --git a/Apps/HelloWorld/manifest.properties b/Apps/HelloWorld/manifest.properties index e8b166b..993c05d 100644 --- a/Apps/HelloWorld/manifest.properties +++ b/Apps/HelloWorld/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 id=tactility.helloworld -version.name=0.16.0 -version.code=16 +version.name=0.17.0 +version.code=17 name=Hello World app.0.id=tactility.helloworld app.0.name=Hello World diff --git a/Apps/M5UnitTest/manifest.properties b/Apps/M5UnitTest/manifest.properties index 6f43ea1..58e9f5f 100644 --- a/Apps/M5UnitTest/manifest.properties +++ b/Apps/M5UnitTest/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32s3,esp32p4 id=tactility.m5unittest -version.name=0.14.0 -version.code=14 +version.name=0.15.0 +version.code=15 name=M5 Unit Test app.0.id=tactility.m5unittest app.0.name=M5 Unit Test diff --git a/Apps/Magic8Ball/manifest.properties b/Apps/Magic8Ball/manifest.properties index b818016..71187d0 100644 --- a/Apps/Magic8Ball/manifest.properties +++ b/Apps/Magic8Ball/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.magic8ball -version.name=0.15.0 -version.code=15 +version.name=0.16.0 +version.code=16 name=Magic 8-Ball app.0.id=tactility.magic8ball app.0.name=Magic 8-Ball diff --git a/Apps/MediaKeys/manifest.properties b/Apps/MediaKeys/manifest.properties index da7b86b..d67f71f 100644 --- a/Apps/MediaKeys/manifest.properties +++ b/Apps/MediaKeys/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32s3,esp32p4 id=tactility.mediakeys -version.name=0.16.0 -version.code=16 +version.name=0.17.0 +version.code=17 name=Media Keys description=Bluetooth media keys. Touch or Physical Keyboard control\nB - previous, P - play/pause, N - next, M - mute, D - volume down, U - volume up.\nQ or ESC to exit focus. app.0.id=tactility.mediakeys diff --git a/Apps/MystifyDemo/manifest.properties b/Apps/MystifyDemo/manifest.properties index 827f35b..8dbf615 100644 --- a/Apps/MystifyDemo/manifest.properties +++ b/Apps/MystifyDemo/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.mystifydemo -version.name=0.17.0 -version.code=17 +version.name=0.18.0 +version.code=18 name=Mystify Demo description=A graphics demo that is inspired by a Windows 95 screensaver. app.0.id=tactility.mystifydemo diff --git a/Apps/Neatvi/manifest.properties b/Apps/Neatvi/manifest.properties index e8feb16..d530d13 100644 --- a/Apps/Neatvi/manifest.properties +++ b/Apps/Neatvi/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 id=tactility.neatvi -version.name=0.2.0 -version.code=2 +version.name=0.3.0 +version.code=3 requires.ram=2M name=Vi description=Neatvi text editor diff --git a/Apps/Rcc700/manifest.properties b/Apps/Rcc700/manifest.properties index 1c8fc0a..7fef6b9 100644 --- a/Apps/Rcc700/manifest.properties +++ b/Apps/Rcc700/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 id=tactility.rcc700 -version.name=0.1.0 -version.code=1 +version.name=0.2.0 +version.code=2 requires.ram=2M name=rcc700 description=Mini C compiler for ESP32 RISC-V diff --git a/Apps/SerialConsole/manifest.properties b/Apps/SerialConsole/manifest.properties index 45029e3..d0086ac 100644 --- a/Apps/SerialConsole/manifest.properties +++ b/Apps/SerialConsole/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.serialconsole -version.name=0.18.0 -version.code=18 +version.name=0.19.0 +version.code=19 name=Serial Console app.0.id=tactility.serialconsole app.0.name=Serial Console diff --git a/Apps/Snake/manifest.properties b/Apps/Snake/manifest.properties index 828cc63..aa9560d 100644 --- a/Apps/Snake/manifest.properties +++ b/Apps/Snake/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.snake -version.name=0.19.0 -version.code=19 +version.name=0.20.0 +version.code=20 name=Snake description=Classic Snake game app.0.id=tactility.snake diff --git a/Apps/TamaTac/manifest.properties b/Apps/TamaTac/manifest.properties index e96b108..d9a5da0 100644 --- a/Apps/TamaTac/manifest.properties +++ b/Apps/TamaTac/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.tamatac -version.name=0.14.0 -version.code=14 +version.name=0.15.0 +version.code=15 name=TamaTac description=Virtual pet inspired by Tamagotchi. Only runs on devices with PSRAM. app.0.id=tactility.tamatac diff --git a/Apps/TodoList/manifest.properties b/Apps/TodoList/manifest.properties index e57cde6..0032ce3 100644 --- a/Apps/TodoList/manifest.properties +++ b/Apps/TodoList/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.todolist -version.name=0.17.0 -version.code=17 +version.name=0.18.0 +version.code=18 name=Todo List description=Simple task list manager app.0.id=tactility.todolist diff --git a/Apps/TwoEleven/manifest.properties b/Apps/TwoEleven/manifest.properties index 7fc405c..a9a558c 100644 --- a/Apps/TwoEleven/manifest.properties +++ b/Apps/TwoEleven/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4 id=tactility.twoeleven -version.name=0.18.0 -version.code=18 +version.name=0.19.0 +version.code=19 name=2048 description=A fun, customizable 2048 sliding tile game for tactility!\nSlide tiles to combine numbers and reach 2048.\nChoose grid sizes: 3x3 (easy), 4x4 (classic), 5x5, or 6x6 (expert). app.0.id=tactility.twoeleven diff --git a/Apps/Xcc700/manifest.properties b/Apps/Xcc700/manifest.properties index fcf9e1e..e2c98a1 100644 --- a/Apps/Xcc700/manifest.properties +++ b/Apps/Xcc700/manifest.properties @@ -2,8 +2,8 @@ manifest.version=0.3 target.sdk=0.8.0-dev target.platforms=esp32,esp32s3,esp32c6,esp32p4,posix-x86_64 id=tactility.xcc700 -version.name=0.1.0 -version.code=1 +version.name=0.2.0 +version.code=2 name=xcc700 description=Mini C compiler for ESP32 Xtensa app.0.id=tactility.xcc700 diff --git a/Scripts/check-version-bumps.py b/Scripts/check-version-bumps.py new file mode 100755 index 0000000..51f1615 --- /dev/null +++ b/Scripts/check-version-bumps.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +"""Check that apps changed on the current branch have bumped version code and version name. + +Diff the current branch against a base ref: for each app directory under Apps/ that has +changes, verify that `version.code` increased and `version.name` changed in its +manifest.properties. + +Usage: + check-version-bumps.py [base_ref] (default base_ref: main) + +Exit code: + 0 – all changed apps have bumped versions (or no apps changed) + 1 – one or more apps are missing version bumps +""" + +import subprocess +import sys +from pathlib import Path + +APPS_DIR = "Apps" +MANIFEST = "manifest.properties" +VERSION_CODE_KEY = "version.code" +VERSION_NAME_KEY = "version.name" + + +def run(cmd: list[str], **kwargs) -> subprocess.CompletedProcess: + return subprocess.run(cmd, capture_output=True, text=True, check=True, **kwargs) + + +def parse_properties(text: str) -> dict[str, str]: + props: dict[str, str] = {} + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if "=" in line: + key, _, value = line.partition("=") + props[key.strip()] = value.strip() + return props + + +def parse_version_code(value: str) -> int | None: + """Return version code as an integer, or None if it isn't a non-negative integer.""" + return int(value) if value.isdecimal() else None + + +def get_changed_apps(base_ref: str) -> set[str]: + """Return set of app names that have file changes vs base_ref.""" + result = run(["git", "diff", base_ref, "--name-only", "--", APPS_DIR]) + apps: set[str] = set() + for line in result.stdout.splitlines(): + parts = line.split("/") + if len(parts) >= 2 and parts[0] == APPS_DIR: + apps.add(parts[1]) + return apps + + +def get_file_at_ref(ref: str, path: str) -> str | None: + """Return file contents at a git ref, or None if it doesn't exist.""" + try: + result = run(["git", "show", f"{ref}:{path}"]) + return result.stdout + except subprocess.CalledProcessError: + return None + + +def path_exists_at_ref(ref: str, path: str) -> bool: + """Return True if a file or directory exists at a git ref.""" + try: + run(["git", "cat-file", "-e", f"{ref}:{path}"]) + return True + except subprocess.CalledProcessError: + return False + + +def main() -> int: + base_ref = sys.argv[1] if len(sys.argv) > 1 else "main" + + # Make sure base ref exists + try: + run(["git", "rev-parse", "--verify", base_ref]) + except subprocess.CalledProcessError: + print(f"Error: cannot resolve ref '{base_ref}'", file=sys.stderr) + return 1 + + changed_apps = get_changed_apps(base_ref) + if not changed_apps: + print("No app changes detected.") + return 0 + + print(f"Changed apps: {', '.join(sorted(changed_apps))}\n") + + failures: list[str] = [] + + for app_name in sorted(changed_apps): + manifest_path = f"{APPS_DIR}/{app_name}/{MANIFEST}" + + base_content = get_file_at_ref(base_ref, manifest_path) + head_content = get_file_at_ref("HEAD", manifest_path) + + if head_content is None: + if not path_exists_at_ref("HEAD", f"{APPS_DIR}/{app_name}"): + print(f" {app_name}: removed app – skipped") + continue + failures.append(f"{app_name}: manifest.properties missing on HEAD") + continue + + if base_content is None: + # New app – no check needed + print(f" {app_name}: new app (no base manifest) – OK") + continue + + base_props = parse_properties(base_content) + head_props = parse_properties(head_content) + + missing = [key for key in (VERSION_CODE_KEY, VERSION_NAME_KEY) if not head_props.get(key)] + if missing: + failures.append(f"{app_name}: {', '.join(missing)} missing on HEAD") + continue + + base_code = base_props.get(VERSION_CODE_KEY, "") + head_code = head_props.get(VERSION_CODE_KEY, "") + base_name = base_props.get(VERSION_NAME_KEY, "") + head_name = head_props.get(VERSION_NAME_KEY, "") + + head_code_num = parse_version_code(head_code) + if head_code_num is None: + failures.append(f"{app_name}: version.code is not an integer ({head_code})") + continue + + # A non-numeric base code can't be compared; accept any valid head code then + base_code_num = parse_version_code(base_code) + code_bumped = base_code_num is None or head_code_num > base_code_num + name_bumped = base_name != head_name + + if code_bumped and name_bumped: + print(f" {app_name}: version bumped ({base_name}→{head_name}, {base_code}→{head_code}) – OK") + else: + problems: list[str] = [] + if not code_bumped: + problems.append(f"version.code not increased ({base_code}→{head_code})") + if not name_bumped: + problems.append(f"version.name unchanged ({head_name})") + failures.append(f"{app_name}: {', '.join(problems)}") + + print() + + if failures: + print("FAIL – missing version bump:") + for f in failures: + print(f" ✗ {f}") + return 1 + + print("PASS – all changed apps have bumped versions.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())