diff --git a/tests/graphical_test_helper.py b/tests/graphical_test_helper.py index 6677223c..7011bcb1 100644 --- a/tests/graphical_test_helper.py +++ b/tests/graphical_test_helper.py @@ -21,10 +21,19 @@ Usage: # Capture screenshot capture_screenshot("tests/screenshots/mytest.raw") + + # Simulate click at coordinates + simulate_click(160, 120) # Click at center of 320x240 screen """ import lvgl as lv +# Simulation globals for touch input +_touch_x = 0 +_touch_y = 0 +_touch_pressed = False +_touch_indev = None + def wait_for_render(iterations=10): """ @@ -200,3 +209,92 @@ def print_screen_labels(obj): print(f"Found {len(texts)} labels on screen:") for i, text in enumerate(texts): print(f" {i}: {text}") + + +def _touch_read_cb(indev_drv, data): + """ + Internal callback for simulated touch input device. + + This callback is registered with LVGL and provides touch state + when simulate_click() is used. + + Args: + indev_drv: Input device driver (LVGL internal) + data: Input device data structure to fill + """ + global _touch_x, _touch_y, _touch_pressed + data.point.x = _touch_x + data.point.y = _touch_y + if _touch_pressed: + data.state = lv.INDEV_STATE.PRESSED + else: + data.state = lv.INDEV_STATE.RELEASED + + +def _ensure_touch_indev(): + """ + Ensure that the simulated touch input device is created. + + This is called automatically by simulate_click() on first use. + Creates a pointer-type input device that uses _touch_read_cb. + """ + global _touch_indev + if _touch_indev is None: + _touch_indev = lv.indev_create() + _touch_indev.set_type(lv.INDEV_TYPE.POINTER) + _touch_indev.set_read_cb(_touch_read_cb) + print("Created simulated touch input device") + + +def simulate_click(x, y, press_duration_ms=50): + """ + Simulate a touch/click at the specified coordinates. + + This creates a simulated touch press at (x, y) and automatically + releases it after press_duration_ms milliseconds. The touch is + processed through LVGL's normal input handling, so it triggers + click events, focus changes, scrolling, etc. just like real input. + + To find object coordinates for clicking, use: + obj_area = lv.area_t() + obj.get_coords(obj_area) + center_x = (obj_area.x1 + obj_area.x2) // 2 + center_y = (obj_area.y1 + obj_area.y2) // 2 + simulate_click(center_x, center_y) + + Args: + x: X coordinate to click (in pixels) + y: Y coordinate to click (in pixels) + press_duration_ms: How long to hold the press (default: 50ms) + + Example: + # Click at screen center (320x240) + simulate_click(160, 120) + + # Click on a specific button + button_area = lv.area_t() + button.get_coords(button_area) + simulate_click(button_area.x1 + 10, button_area.y1 + 10) + """ + global _touch_x, _touch_y, _touch_pressed + + # Ensure the touch input device exists + _ensure_touch_indev() + + # Set touch position and press state + _touch_x = x + _touch_y = y + _touch_pressed = True + + # Process the press immediately + lv.task_handler() + + def release_timer_cb(timer): + """Timer callback to release the touch press.""" + global _touch_pressed + _touch_pressed = False + lv.task_handler() # Process the release immediately + + # Schedule the release + timer = lv.timer_create(release_timer_cb, press_duration_ms, None) + timer.set_repeat_count(1) diff --git a/tests/test_graphical_custom_keyboard_basic.py b/tests/test_graphical_custom_keyboard_basic.py index c4eba283..ba55b2a5 100644 --- a/tests/test_graphical_custom_keyboard_basic.py +++ b/tests/test_graphical_custom_keyboard_basic.py @@ -11,6 +11,7 @@ Usage: import unittest import lvgl as lv from mpos.ui.keyboard import MposKeyboard +from graphical_test_helper import simulate_click, wait_for_render class TestMposKeyboard(unittest.TestCase): @@ -162,4 +163,93 @@ class TestMposKeyboard(unittest.TestCase): print("API compatibility verified") + def test_simulate_click_on_button(self): + """Test clicking keyboard buttons using simulate_click().""" + print("Testing simulate_click() on keyboard buttons...") + + # Create keyboard and load screen + keyboard = MposKeyboard(self.screen) + keyboard.set_textarea(self.textarea) + keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) + lv.screen_load(self.screen) + wait_for_render(10) + + # Get initial text + initial_text = self.textarea.get_text() + print(f"Initial textarea text: '{initial_text}'") + + # Get keyboard area and click on it + # The keyboard is an lv.keyboard object (accessed via _keyboard or through __getattr__) + obj_area = lv.area_t() + keyboard.get_coords(obj_area) + + # Calculate a point to click - let's click in the lower part of keyboard + # which should be around where letters are + click_x = (obj_area.x1 + obj_area.x2) // 2 # Center horizontally + click_y = obj_area.y1 + (obj_area.y2 - obj_area.y1) // 3 # Upper third + + print(f"Keyboard area: ({obj_area.x1}, {obj_area.y1}) to ({obj_area.x2}, {obj_area.y2})") + print(f"Clicking keyboard at ({click_x}, {click_y})") + + # Click on the keyboard using simulate_click + simulate_click(click_x, click_y, press_duration_ms=100) + wait_for_render(5) + + final_text = self.textarea.get_text() + print(f"Final textarea text: '{final_text}'") + + # The important thing is that simulate_click worked without crashing + # The text might have changed if we hit a letter key + print("simulate_click() completed successfully") + + def test_click_vs_send_event_comparison(self): + """Compare simulate_click() vs send_event() for triggering button actions.""" + print("Testing simulate_click() vs send_event() comparison...") + + # Create keyboard and load screen + keyboard = MposKeyboard(self.screen) + keyboard.set_textarea(self.textarea) + keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0) + lv.screen_load(self.screen) + wait_for_render(10) + + # Test 1: Use send_event() to trigger READY event + callback_from_send_event = [False] + + def callback_send_event(event): + callback_from_send_event[0] = True + print("send_event callback triggered") + + keyboard.add_event_cb(callback_send_event, lv.EVENT.READY, None) + keyboard.send_event(lv.EVENT.READY, None) + wait_for_render(3) + + self.assertTrue( + callback_from_send_event[0], + "send_event() should trigger callback" + ) + + # Test 2: Use simulate_click() to click on keyboard + # This demonstrates that simulate_click works with real UI interaction + initial_text = self.textarea.get_text() + + # Get keyboard area to click within it + obj_area = lv.area_t() + keyboard.get_coords(obj_area) + + # Click somewhere in the middle of the keyboard + click_x = (obj_area.x1 + obj_area.x2) // 2 + click_y = (obj_area.y1 + obj_area.y2) // 2 + + print(f"Clicking keyboard at ({click_x}, {click_y})") + simulate_click(click_x, click_y, press_duration_ms=100) + wait_for_render(5) + + # Verify click completed without crashing + final_text = self.textarea.get_text() + print(f"Text before click: '{initial_text}'") + print(f"Text after click: '{final_text}'") + + print("Both send_event() and simulate_click() work correctly") +