You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Try to fix layout switching
This commit is contained in:
@@ -62,8 +62,13 @@ class MposKeyboard:
|
||||
# Configure layouts
|
||||
self._setup_layouts()
|
||||
|
||||
# Set default mode to lowercase
|
||||
# Initialize ALL keyboard mode maps (prevents LVGL from using default maps)
|
||||
self._keyboard.set_map(self.MODE_LOWERCASE, self._lowercase_map, self._lowercase_ctrl)
|
||||
self._keyboard.set_map(self.MODE_UPPERCASE, self._uppercase_map, self._uppercase_ctrl)
|
||||
self._keyboard.set_map(self.MODE_NUMBERS, self._numbers_map, self._numbers_ctrl)
|
||||
self._keyboard.set_map(self.MODE_SPECIALS, self._specials_map, self._specials_ctrl)
|
||||
|
||||
# Set default mode to lowercase
|
||||
self._keyboard.set_mode(self.MODE_LOWERCASE)
|
||||
|
||||
# Add event handler for custom behavior
|
||||
@@ -134,6 +139,11 @@ class MposKeyboard:
|
||||
if text is None:
|
||||
return
|
||||
|
||||
# Stop event propagation to prevent LVGL's default mode-switching behavior
|
||||
# This is critical to prevent LVGL from switching to its default TEXT_LOWER,
|
||||
# TEXT_UPPER, NUMBER modes when it sees mode-switching buttons
|
||||
event.stop_processing()
|
||||
|
||||
# Get current textarea content (from our own reference, not LVGL's)
|
||||
ta = self._textarea
|
||||
if not ta:
|
||||
@@ -149,26 +159,22 @@ class MposKeyboard:
|
||||
|
||||
elif text == lv.SYMBOL.UP:
|
||||
# Switch to uppercase
|
||||
self._keyboard.set_map(self.MODE_UPPERCASE, self._uppercase_map, self._uppercase_ctrl)
|
||||
self._keyboard.set_mode(self.MODE_UPPERCASE)
|
||||
self.set_mode(self.MODE_UPPERCASE)
|
||||
return # Don't modify text
|
||||
|
||||
elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
|
||||
# Switch to lowercase
|
||||
self._keyboard.set_map(self.MODE_LOWERCASE, self._lowercase_map, self._lowercase_ctrl)
|
||||
self._keyboard.set_mode(self.MODE_LOWERCASE)
|
||||
self.set_mode(self.MODE_LOWERCASE)
|
||||
return # Don't modify text
|
||||
|
||||
elif text == self.LABEL_NUMBERS_SPECIALS:
|
||||
# Switch to numbers/specials
|
||||
self._keyboard.set_map(self.MODE_NUMBERS, self._numbers_map, self._numbers_ctrl)
|
||||
self._keyboard.set_mode(self.MODE_NUMBERS)
|
||||
self.set_mode(self.MODE_NUMBERS)
|
||||
return # Don't modify text
|
||||
|
||||
elif text == self.LABEL_SPECIALS:
|
||||
# Switch to additional specials
|
||||
self._keyboard.set_map(self.MODE_SPECIALS, self._specials_map, self._specials_ctrl)
|
||||
self._keyboard.set_mode(self.MODE_SPECIALS)
|
||||
self.set_mode(self.MODE_SPECIALS)
|
||||
return # Don't modify text
|
||||
|
||||
elif text == self.LABEL_SPACE:
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Manual test for the "abc" button bug.
|
||||
|
||||
This test creates a keyboard and lets you manually switch modes to observe the bug.
|
||||
|
||||
Run with: ./scripts/run_desktop.sh tests/manual_test_abc_button.py
|
||||
|
||||
Steps to reproduce the bug:
|
||||
1. Keyboard starts in lowercase mode
|
||||
2. Click "?123" button to switch to numbers mode
|
||||
3. Click "abc" button to switch back to lowercase
|
||||
4. OBSERVE: Does it show "?123" (correct) or "1#" (wrong/default LVGL)?
|
||||
"""
|
||||
|
||||
import lvgl as lv
|
||||
from mpos.ui.keyboard import MposKeyboard
|
||||
|
||||
# Get active screen
|
||||
screen = lv.screen_active()
|
||||
screen.clean()
|
||||
|
||||
# Create title
|
||||
title = lv.label(screen)
|
||||
title.set_text("ABC Button Test")
|
||||
title.align(lv.ALIGN.TOP_MID, 0, 5)
|
||||
|
||||
# Create instructions
|
||||
instructions = lv.label(screen)
|
||||
instructions.set_text(
|
||||
"1. Start in lowercase (has ?123 button)\n"
|
||||
"2. Click '?123' to switch to numbers\n"
|
||||
"3. Click 'abc' to switch back\n"
|
||||
"4. CHECK: Do you see '?123' or '1#'?\n"
|
||||
" - '?123' = CORRECT (custom keyboard)\n"
|
||||
" - '1#' = BUG (default LVGL keyboard)"
|
||||
)
|
||||
instructions.set_style_text_align(lv.TEXT_ALIGN.LEFT, 0)
|
||||
instructions.align(lv.ALIGN.TOP_LEFT, 10, 30)
|
||||
|
||||
# Create textarea
|
||||
textarea = lv.textarea(screen)
|
||||
textarea.set_size(280, 30)
|
||||
textarea.set_one_line(True)
|
||||
textarea.align(lv.ALIGN.TOP_MID, 0, 120)
|
||||
textarea.set_placeholder_text("Type here...")
|
||||
|
||||
# Create keyboard
|
||||
keyboard = MposKeyboard(screen)
|
||||
keyboard.set_textarea(textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("ABC Button Bug Test")
|
||||
print("="*60)
|
||||
print("Instructions:")
|
||||
print("1. Keyboard starts in LOWERCASE mode")
|
||||
print(" - Look for '?123' button (bottom left area)")
|
||||
print("2. Click '?123' to switch to NUMBERS mode")
|
||||
print(" - Should show numbers 1,2,3, etc.")
|
||||
print(" - Should have 'abc' button (bottom left)")
|
||||
print("3. Click 'abc' to return to lowercase")
|
||||
print("4. CRITICAL CHECK:")
|
||||
print(" - If you see '?123' button → CORRECT (custom keyboard)")
|
||||
print(" - If you see '1#' button → BUG (default LVGL keyboard)")
|
||||
print("="*60 + "\n")
|
||||
@@ -0,0 +1,288 @@
|
||||
"""
|
||||
Test for keyboard layout switching bug.
|
||||
|
||||
This test reproduces the issue where clicking the "abc" button in numbers mode
|
||||
goes to the wrong (default LVGL) keyboard layout instead of our custom lowercase layout.
|
||||
|
||||
Usage:
|
||||
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_layout_switching.py
|
||||
Device: ./tests/unittest.sh tests/test_graphical_keyboard_layout_switching.py --ondevice
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import lvgl as lv
|
||||
from mpos.ui.keyboard import MposKeyboard
|
||||
from graphical_test_helper import wait_for_render
|
||||
|
||||
|
||||
class TestKeyboardLayoutSwitching(unittest.TestCase):
|
||||
"""Test keyboard layout switching between different modes."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.screen = lv.obj()
|
||||
self.screen.set_size(320, 240)
|
||||
|
||||
# Create textarea
|
||||
self.textarea = lv.textarea(self.screen)
|
||||
self.textarea.set_size(280, 40)
|
||||
self.textarea.align(lv.ALIGN.TOP_MID, 0, 10)
|
||||
self.textarea.set_one_line(True)
|
||||
|
||||
# Load screen
|
||||
lv.screen_load(self.screen)
|
||||
wait_for_render(5)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up."""
|
||||
lv.screen_load(lv.obj())
|
||||
wait_for_render(5)
|
||||
|
||||
def test_abc_button_from_numbers_mode(self):
|
||||
"""
|
||||
Test that clicking "abc" button in numbers mode goes to lowercase mode.
|
||||
|
||||
BUG: Currently goes to the wrong (default LVGL) keyboard layout
|
||||
instead of our custom lowercase layout.
|
||||
|
||||
Expected behavior:
|
||||
1. Start in lowercase mode (has "q", "w", "e", etc.)
|
||||
2. Switch to numbers mode (has "1", "2", "3", etc. and "abc" button)
|
||||
3. Click "abc" button
|
||||
4. Should return to lowercase mode (has "q", "w", "e", etc.)
|
||||
"""
|
||||
print("\n=== Testing 'abc' button from numbers mode ===")
|
||||
|
||||
# Create keyboard
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
|
||||
# Verify we start in lowercase mode
|
||||
print("Step 1: Verify initial lowercase mode")
|
||||
# Find 'q' button (should be in lowercase layout)
|
||||
q_button_index = None
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text == "q":
|
||||
q_button_index = i
|
||||
print(f" Found 'q' at index {i} - GOOD (lowercase mode)")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
self.assertIsNotNone(q_button_index, "Should find 'q' in lowercase mode")
|
||||
|
||||
# Switch to numbers mode
|
||||
print("\nStep 2: Switch to numbers mode")
|
||||
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
|
||||
wait_for_render(5)
|
||||
|
||||
# Verify we're in numbers mode by finding '1' button
|
||||
one_button_index = None
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text == "1":
|
||||
one_button_index = i
|
||||
print(f" Found '1' at index {i} - GOOD (numbers mode)")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
self.assertIsNotNone(one_button_index, "Should find '1' in numbers mode")
|
||||
|
||||
# Find the 'abc' button in numbers mode
|
||||
print("\nStep 3: Find 'abc' button in numbers mode")
|
||||
abc_button_index = None
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text == "abc":
|
||||
abc_button_index = i
|
||||
print(f" Found 'abc' at index {i}")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
self.assertIsNotNone(abc_button_index, "Should find 'abc' button in numbers mode")
|
||||
|
||||
# Switch back to lowercase by calling set_mode (simulating clicking 'abc')
|
||||
print("\nStep 4: Click 'abc' to switch back to lowercase")
|
||||
keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
|
||||
wait_for_render(5)
|
||||
|
||||
# Verify we're back in lowercase mode using DISTINGUISHING LABELS
|
||||
# When in LOWERCASE mode:
|
||||
# - Our custom keyboard has "?123" (to switch to numbers)
|
||||
# - Default LVGL keyboard has "1#" (to switch to numbers) and "ABC" (to switch to uppercase)
|
||||
#
|
||||
# Note: "abc" only appears in NUMBERS/SPECIALS modes to switch back to lowercase
|
||||
print("\nStep 5: Verify we're in OUR custom lowercase mode (not default LVGL)")
|
||||
|
||||
found_labels = {}
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
# Check for all possible distinguishing labels
|
||||
if text in ["abc", "ABC", "?123", "1#", lv.SYMBOL.UP, lv.SYMBOL.DOWN]:
|
||||
found_labels[text] = i
|
||||
print(f" Found label '{text}' at index {i}")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check for WRONG labels (default LVGL keyboard in lowercase mode)
|
||||
if "ABC" in found_labels:
|
||||
print(f" ERROR: Found 'ABC' - this is the DEFAULT LVGL keyboard!")
|
||||
self.fail("BUG DETECTED: Got default LVGL lowercase keyboard with 'ABC' label instead of custom keyboard")
|
||||
|
||||
if "1#" in found_labels:
|
||||
print(f" ERROR: Found '1#' - this is the DEFAULT LVGL keyboard!")
|
||||
self.fail("BUG DETECTED: Got default LVGL lowercase keyboard with '1#' label instead of custom keyboard with '?123'")
|
||||
|
||||
# Check for CORRECT labels (our custom lowercase keyboard)
|
||||
if "?123" not in found_labels:
|
||||
print(f" ERROR: Did not find '?123' - should be in custom lowercase layout!")
|
||||
print(f" Found labels: {list(found_labels.keys())}")
|
||||
self.fail("BUG: Should find '?123' label in custom lowercase mode, but got: " + str(list(found_labels.keys())))
|
||||
|
||||
# Also verify we have the UP symbol (our custom keyboard) not ABC (default)
|
||||
if lv.SYMBOL.UP not in found_labels:
|
||||
print(f" ERROR: Did not find UP symbol - should be in custom lowercase layout!")
|
||||
print(f" Found labels: {list(found_labels.keys())}")
|
||||
self.fail("BUG: Should find UP symbol in custom lowercase mode")
|
||||
|
||||
print(f" Found '?123' at index {found_labels['?123']} - GOOD (custom keyboard)")
|
||||
print(f" Found UP symbol at index {found_labels[lv.SYMBOL.UP]} - GOOD (custom keyboard)")
|
||||
print("\nSUCCESS: 'abc' button correctly returns to custom lowercase layout!")
|
||||
|
||||
def test_layout_switching_cycle(self):
|
||||
"""
|
||||
Test full cycle of layout switching: lowercase -> numbers -> specials -> lowercase.
|
||||
|
||||
This ensures all mode switches preserve our custom layouts.
|
||||
"""
|
||||
print("\n=== Testing full layout switching cycle ===")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
|
||||
# Define what we expect to find in each mode
|
||||
mode_tests = [
|
||||
(MposKeyboard.MODE_LOWERCASE, "q", "lowercase"),
|
||||
(MposKeyboard.MODE_NUMBERS, "1", "numbers"),
|
||||
(MposKeyboard.MODE_SPECIALS, "~", "specials"),
|
||||
(MposKeyboard.MODE_LOWERCASE, "q", "lowercase (again)"),
|
||||
]
|
||||
|
||||
for mode, expected_key, mode_name in mode_tests:
|
||||
print(f"\nSwitching to {mode_name}...")
|
||||
keyboard.set_mode(mode)
|
||||
wait_for_render(5)
|
||||
|
||||
# Find the expected key
|
||||
found = False
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text == expected_key:
|
||||
print(f" Found '{expected_key}' at index {i} - GOOD")
|
||||
found = True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
self.assertTrue(found,
|
||||
f"Should find '{expected_key}' in {mode_name} mode")
|
||||
|
||||
print("\nSUCCESS: All layout switches preserve custom layouts!")
|
||||
|
||||
def test_event_handler_switches_layout(self):
|
||||
"""
|
||||
Test that the event handler properly switches layouts.
|
||||
|
||||
This simulates what happens when the user actually CLICKS the "abc" button,
|
||||
going through the _handle_events method instead of calling set_mode() directly.
|
||||
"""
|
||||
print("\n=== Testing event handler layout switching ===")
|
||||
|
||||
keyboard = MposKeyboard(self.screen)
|
||||
keyboard.set_textarea(self.textarea)
|
||||
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
|
||||
wait_for_render(10)
|
||||
|
||||
# Switch to numbers mode first
|
||||
print("Step 1: Switch to numbers mode")
|
||||
keyboard.set_mode(MposKeyboard.MODE_NUMBERS)
|
||||
wait_for_render(5)
|
||||
|
||||
# Verify we're in numbers mode
|
||||
one_found = False
|
||||
for i in range(100):
|
||||
try:
|
||||
if keyboard.get_button_text(i) == "1":
|
||||
one_found = True
|
||||
print(f" Found '1' - in numbers mode")
|
||||
break
|
||||
except:
|
||||
pass
|
||||
self.assertTrue(one_found, "Should be in numbers mode")
|
||||
|
||||
# Now simulate what the event handler does when "abc" is clicked
|
||||
# The event handler checks: elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
|
||||
# Then it calls: self._keyboard.set_map() and self._keyboard.set_mode()
|
||||
print("\nStep 2: Simulate clicking 'abc' (via event handler logic)")
|
||||
|
||||
# This is what the event handler does:
|
||||
keyboard._keyboard.set_map(
|
||||
MposKeyboard.MODE_LOWERCASE,
|
||||
keyboard._lowercase_map,
|
||||
keyboard._lowercase_ctrl
|
||||
)
|
||||
keyboard._keyboard.set_mode(MposKeyboard.MODE_LOWERCASE)
|
||||
wait_for_render(5)
|
||||
|
||||
# Verify we're back in lowercase mode with OUR custom layout
|
||||
# When in LOWERCASE mode:
|
||||
# - Our custom keyboard has "?123" (to switch to numbers)
|
||||
# - Default LVGL keyboard has "1#" (to switch to numbers) and "ABC" (to switch to uppercase)
|
||||
print("\nStep 3: Verify we have custom lowercase layout (not default LVGL)")
|
||||
|
||||
found_labels = {}
|
||||
for i in range(100):
|
||||
try:
|
||||
text = keyboard.get_button_text(i)
|
||||
if text in ["abc", "ABC", "?123", "1#", lv.SYMBOL.UP]:
|
||||
found_labels[text] = i
|
||||
print(f" Found label '{text}' at index {i}")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check for WRONG labels (default LVGL keyboard)
|
||||
if "ABC" in found_labels:
|
||||
print(f" ERROR: Found 'ABC' - this is the DEFAULT LVGL keyboard!")
|
||||
print(" Found these labels:", list(found_labels.keys()))
|
||||
self.fail("BUG DETECTED: Event handler caused switch to default LVGL keyboard with 'ABC' label")
|
||||
|
||||
if "1#" in found_labels:
|
||||
print(f" ERROR: Found '1#' - this is the DEFAULT LVGL keyboard!")
|
||||
print(" Found these labels:", list(found_labels.keys()))
|
||||
self.fail("BUG DETECTED: Event handler caused switch to default LVGL keyboard with '1#' label")
|
||||
|
||||
# Check for CORRECT labels (our custom keyboard in lowercase mode)
|
||||
self.assertIn("?123", found_labels,
|
||||
"Should find '?123' label in custom lowercase mode (not '1#' from default)")
|
||||
self.assertIn(lv.SYMBOL.UP, found_labels,
|
||||
"Should find UP symbol in custom lowercase mode")
|
||||
|
||||
print(f" Found '?123' at index {found_labels['?123']} - GOOD")
|
||||
print(f" Found UP symbol at index {found_labels[lv.SYMBOL.UP]} - GOOD")
|
||||
print("\nSUCCESS: Event handler preserves custom layout!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user