From ab7183a913a1ff837ea8ad86dda52a5ea5cd3bd2 Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Fri, 14 Nov 2025 16:58:12 +0100 Subject: [PATCH] Connect4: add directional controls --- CLAUDE.md | 48 +++++++++++++++++++ .../assets/connect4.py | 19 ++++++++ 2 files changed, 67 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 192d8333..a50e6c49 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -447,6 +447,54 @@ def handle_result(self, result): - `mpos.ui.focus_direction`: Keyboard/joystick navigation helpers - `mpos.ui.anim`: Animation utilities +### Keyboard and Focus Navigation + +MicroPythonOS supports keyboard/joystick navigation through LVGL's focus group system. This allows users to navigate apps using arrow keys and select items with Enter. + +**Basic focus handling pattern**: +```python +def onCreate(self): + # Get the default focus group + focusgroup = lv.group_get_default() + if not focusgroup: + print("WARNING: could not get default focusgroup") + + # Create a clickable object + button = lv.button(screen) + + # Add focus/defocus event handlers + button.add_event_cb(lambda e, b=button: self.focus_handler(b), lv.EVENT.FOCUSED, None) + button.add_event_cb(lambda e, b=button: self.defocus_handler(b), lv.EVENT.DEFOCUSED, None) + + # Add to focus group (enables keyboard navigation) + if focusgroup: + focusgroup.add_obj(button) + +def focus_handler(self, obj): + """Called when object receives focus""" + obj.set_style_border_color(lv.theme_get_color_primary(None), lv.PART.MAIN) + obj.set_style_border_width(2, lv.PART.MAIN) + obj.scroll_to_view(True) # Scroll into view if needed + +def defocus_handler(self, obj): + """Called when object loses focus""" + obj.set_style_border_width(0, lv.PART.MAIN) +``` + +**Key principles**: +- Get the default focus group with `lv.group_get_default()` +- Add objects to the focus group to make them keyboard-navigable +- Use `lv.EVENT.FOCUSED` to highlight focused elements (usually with a border) +- Use `lv.EVENT.DEFOCUSED` to remove highlighting +- Use theme color for consistency: `lv.theme_get_color_primary(None)` +- Call `scroll_to_view(True)` to auto-scroll focused items into view +- The focus group automatically handles arrow key navigation between objects + +**Example apps with focus handling**: +- **Launcher** (`builtin/apps/com.micropythonos.launcher/assets/launcher.py`): App icons are focusable +- **Settings** (`builtin/apps/com.micropythonos.settings/assets/settings_app.py`): Settings items are focusable +- **Connect 4** (`apps/com.micropythonos.connect4/assets/connect4.py`): Game columns are focusable + **Other utilities**: - `mpos.apps.good_stack_size()`: Returns appropriate thread stack size for platform (16KB ESP32, 24KB desktop) - `mpos.wifi`: WiFi management utilities diff --git a/internal_filesystem/apps/com.micropythonos.connect4/assets/connect4.py b/internal_filesystem/apps/com.micropythonos.connect4/assets/connect4.py index 5d9ef94e..a410db44 100644 --- a/internal_filesystem/apps/com.micropythonos.connect4/assets/connect4.py +++ b/internal_filesystem/apps/com.micropythonos.connect4/assets/connect4.py @@ -129,6 +129,10 @@ class Connect4(Activity): self.pieces.append(piece_row) # Create column buttons (invisible clickable areas) + focusgroup = lv.group_get_default() + if not focusgroup: + print("WARNING: could not get default focusgroup") + for col in range(self.COLS): btn = lv.obj(self.screen) btn.set_size(self.CELL_SIZE, self.ROWS * self.CELL_SIZE) @@ -138,6 +142,12 @@ class Connect4(Activity): btn.set_style_border_width(0, 0) btn.add_flag(lv.obj.FLAG.CLICKABLE) btn.add_event_cb(lambda e, c=col: self.on_column_click(c), lv.EVENT.CLICKED, None) + btn.add_event_cb(lambda e, b=btn: self.focus_column(b), lv.EVENT.FOCUSED, None) + btn.add_event_cb(lambda e, b=btn: self.defocus_column(b), lv.EVENT.DEFOCUSED, None) + + if focusgroup: + focusgroup.add_obj(btn) + self.column_buttons.append(btn) self.setContentView(self.screen) @@ -145,6 +155,15 @@ class Connect4(Activity): def onResume(self, screen): self.last_time = time.ticks_ms() + def focus_column(self, column_btn): + """Highlight column when focused""" + column_btn.set_style_border_color(lv.theme_get_color_primary(None), lv.PART.MAIN) + column_btn.set_style_border_width(3, lv.PART.MAIN) + + def defocus_column(self, column_btn): + """Remove highlight when unfocused""" + column_btn.set_style_border_width(0, lv.PART.MAIN) + def cycle_difficulty(self, event): if self.animating: return