Skip to content

Commit dce55f7

Browse files
Add new and improved keyboard
1 parent 0dc1512 commit dce55f7

File tree

4 files changed

+1005
-0
lines changed

4 files changed

+1005
-0
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
"""
2+
Custom keyboard for MicroPythonOS.
3+
4+
This module provides an enhanced on-screen keyboard with better layout,
5+
more characters (including emoticons), and improved usability compared
6+
to the default LVGL keyboard.
7+
8+
Usage:
9+
from mpos.ui.keyboard import CustomKeyboard
10+
11+
# Create keyboard
12+
keyboard = CustomKeyboard(parent_obj)
13+
keyboard.set_textarea(my_textarea)
14+
keyboard.align(lv.ALIGN.BOTTOM_MID, 0, 0)
15+
16+
# Or use factory function for drop-in replacement
17+
from mpos.ui.keyboard import create_keyboard
18+
keyboard = create_keyboard(parent_obj, custom=True)
19+
"""
20+
21+
import lvgl as lv
22+
import mpos.ui.theme
23+
24+
25+
class CustomKeyboard:
26+
"""
27+
Enhanced keyboard widget with multiple layouts and emoticons.
28+
29+
Features:
30+
- Lowercase and uppercase letter modes
31+
- Numbers and special characters
32+
- Additional special characters with emoticons
33+
- Automatic mode switching
34+
- Compatible with LVGL keyboard API
35+
"""
36+
37+
# Keyboard layout labels
38+
LABEL_NUMBERS_SPECIALS = "?123"
39+
LABEL_SPECIALS = "=\<"
40+
LABEL_LETTERS = "abc"
41+
LABEL_SPACE = " "
42+
43+
# Keyboard modes (using LVGL's USER modes)
44+
MODE_LOWERCASE = lv.keyboard.MODE.USER_1
45+
MODE_UPPERCASE = lv.keyboard.MODE.USER_2
46+
MODE_NUMBERS = lv.keyboard.MODE.USER_3
47+
MODE_SPECIALS = lv.keyboard.MODE.USER_4
48+
49+
def __init__(self, parent):
50+
"""
51+
Create a custom keyboard.
52+
53+
Args:
54+
parent: Parent LVGL object to attach keyboard to
55+
"""
56+
# Create underlying LVGL keyboard widget
57+
self._keyboard = lv.keyboard(parent)
58+
59+
# Configure layouts
60+
self._setup_layouts()
61+
62+
# Set default mode to lowercase
63+
self._keyboard.set_mode(self.MODE_LOWERCASE)
64+
65+
# Add event handler for custom behavior
66+
self._keyboard.add_event_cb(self._handle_events, lv.EVENT.VALUE_CHANGED, None)
67+
68+
# Apply theme fix for light mode visibility
69+
mpos.ui.theme.fix_keyboard_button_style(self._keyboard)
70+
71+
# Set reasonable default height
72+
self._keyboard.set_style_min_height(145, 0)
73+
74+
def _setup_layouts(self):
75+
"""Configure all keyboard layout modes."""
76+
77+
# Lowercase letters
78+
lowercase_map = [
79+
"q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "\n",
80+
"a", "s", "d", "f", "g", "h", "j", "k", "l", "\n",
81+
lv.SYMBOL.UP, "z", "x", "c", "v", "b", "n", "m", lv.SYMBOL.BACKSPACE, "\n",
82+
self.LABEL_NUMBERS_SPECIALS, ",", self.LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
83+
]
84+
lowercase_ctrl = [10] * len(lowercase_map)
85+
self._keyboard.set_map(self.MODE_LOWERCASE, lowercase_map, lowercase_ctrl)
86+
87+
# Uppercase letters
88+
uppercase_map = [
89+
"Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "\n",
90+
"A", "S", "D", "F", "G", "H", "J", "K", "L", "\n",
91+
lv.SYMBOL.DOWN, "Z", "X", "C", "V", "B", "N", "M", lv.SYMBOL.BACKSPACE, "\n",
92+
self.LABEL_NUMBERS_SPECIALS, ",", self.LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
93+
]
94+
uppercase_ctrl = [10] * len(uppercase_map)
95+
self._keyboard.set_map(self.MODE_UPPERCASE, uppercase_map, uppercase_ctrl)
96+
97+
# Numbers and common special characters
98+
numbers_map = [
99+
"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "\n",
100+
"@", "#", "$", "_", "&", "-", "+", "(", ")", "/", "\n",
101+
self.LABEL_SPECIALS, "*", "\"", "'", ":", ";", "!", "?", lv.SYMBOL.BACKSPACE, "\n",
102+
self.LABEL_LETTERS, ",", self.LABEL_SPACE, ".", lv.SYMBOL.NEW_LINE, None
103+
]
104+
numbers_ctrl = [10] * len(numbers_map)
105+
self._keyboard.set_map(self.MODE_NUMBERS, numbers_map, numbers_ctrl)
106+
107+
# Additional special characters with emoticons
108+
specials_map = [
109+
"~", "`", "|", "•", ":-)", ";-)", ":-D", "\n",
110+
":-(" , ":'-(", "^", "°", "=", "{", "}", "\\", "\n",
111+
self.LABEL_NUMBERS_SPECIALS, ":-o", ":-P", "[", "]", lv.SYMBOL.BACKSPACE, "\n",
112+
self.LABEL_LETTERS, "<", self.LABEL_SPACE, ">", lv.SYMBOL.NEW_LINE, None
113+
]
114+
specials_ctrl = [10] * len(specials_map)
115+
self._keyboard.set_map(self.MODE_SPECIALS, specials_map, specials_ctrl)
116+
117+
def _handle_events(self, event):
118+
"""
119+
Handle keyboard button presses.
120+
121+
Args:
122+
event: LVGL event object
123+
"""
124+
# Get the pressed button and its text
125+
button = self._keyboard.get_selected_button()
126+
text = self._keyboard.get_button_text(button)
127+
128+
# Get current textarea content
129+
ta = self._keyboard.get_textarea()
130+
if not ta:
131+
return
132+
133+
current_text = ta.get_text()
134+
new_text = current_text
135+
136+
# Handle special keys
137+
if text == lv.SYMBOL.BACKSPACE:
138+
# Delete last character
139+
new_text = current_text[:-1]
140+
141+
elif text == lv.SYMBOL.UP:
142+
# Switch to uppercase
143+
self._keyboard.set_mode(self.MODE_UPPERCASE)
144+
return # Don't modify text
145+
146+
elif text == lv.SYMBOL.DOWN or text == self.LABEL_LETTERS:
147+
# Switch to lowercase
148+
self._keyboard.set_mode(self.MODE_LOWERCASE)
149+
return # Don't modify text
150+
151+
elif text == self.LABEL_NUMBERS_SPECIALS:
152+
# Switch to numbers/specials
153+
self._keyboard.set_mode(self.MODE_NUMBERS)
154+
return # Don't modify text
155+
156+
elif text == self.LABEL_SPECIALS:
157+
# Switch to additional specials
158+
self._keyboard.set_mode(self.MODE_SPECIALS)
159+
return # Don't modify text
160+
161+
elif text == self.LABEL_SPACE:
162+
# Space bar
163+
new_text = current_text + " "
164+
165+
elif text == lv.SYMBOL.NEW_LINE:
166+
# Handle newline (only for multi-line textareas)
167+
if ta.get_one_line():
168+
# For single-line, trigger READY event
169+
self._keyboard.send_event(lv.EVENT.READY, None)
170+
return
171+
else:
172+
new_text = current_text + "\n"
173+
174+
else:
175+
# Regular character
176+
new_text = current_text + text
177+
178+
# Update textarea
179+
ta.set_text(new_text)
180+
181+
# ========================================================================
182+
# LVGL keyboard-compatible API
183+
# ========================================================================
184+
185+
def set_textarea(self, textarea):
186+
"""Set the textarea that this keyboard should edit."""
187+
self._keyboard.set_textarea(textarea)
188+
189+
def get_textarea(self):
190+
"""Get the currently associated textarea."""
191+
return self._keyboard.get_textarea()
192+
193+
def set_mode(self, mode):
194+
"""Set keyboard mode (use MODE_* constants)."""
195+
self._keyboard.set_mode(mode)
196+
197+
def align(self, align_type, x_offset=0, y_offset=0):
198+
"""Align the keyboard."""
199+
self._keyboard.align(align_type, x_offset, y_offset)
200+
201+
def set_style_min_height(self, height, selector):
202+
"""Set minimum height."""
203+
self._keyboard.set_style_min_height(height, selector)
204+
205+
def set_style_height(self, height, selector):
206+
"""Set height."""
207+
self._keyboard.set_style_height(height, selector)
208+
209+
def set_style_max_height(self, height, selector):
210+
"""Set maximum height."""
211+
self._keyboard.set_style_max_height(height, selector)
212+
213+
def set_style_opa(self, opacity, selector):
214+
"""Set opacity (required for fade animations)."""
215+
self._keyboard.set_style_opa(opacity, selector)
216+
217+
def get_x(self):
218+
"""Get X position."""
219+
return self._keyboard.get_x()
220+
221+
def set_x(self, x):
222+
"""Set X position."""
223+
self._keyboard.set_x(x)
224+
225+
def get_y(self):
226+
"""Get Y position."""
227+
return self._keyboard.get_y()
228+
229+
def set_y(self, y):
230+
"""Set Y position."""
231+
self._keyboard.set_y(y)
232+
233+
def set_pos(self, x, y):
234+
"""Set position."""
235+
self._keyboard.set_pos(x, y)
236+
237+
def get_height(self):
238+
"""Get height."""
239+
return self._keyboard.get_height()
240+
241+
def get_width(self):
242+
"""Get width."""
243+
return self._keyboard.get_width()
244+
245+
def add_flag(self, flag):
246+
"""Add object flag (e.g., HIDDEN)."""
247+
self._keyboard.add_flag(flag)
248+
249+
def remove_flag(self, flag):
250+
"""Remove object flag."""
251+
self._keyboard.remove_flag(flag)
252+
253+
def has_flag(self, flag):
254+
"""Check if object has flag."""
255+
return self._keyboard.has_flag(flag)
256+
257+
def add_event_cb(self, callback, event_code, user_data):
258+
"""Add event callback."""
259+
self._keyboard.add_event_cb(callback, event_code, user_data)
260+
261+
def remove_event_cb(self, callback):
262+
"""Remove event callback."""
263+
self._keyboard.remove_event_cb(callback)
264+
265+
def send_event(self, event_code, param):
266+
"""Send event to keyboard."""
267+
self._keyboard.send_event(event_code, param)
268+
269+
def get_lvgl_obj(self):
270+
"""
271+
Get the underlying LVGL keyboard object.
272+
273+
Use this if you need direct access to LVGL methods not wrapped here.
274+
"""
275+
return self._keyboard
276+
277+
278+
def create_keyboard(parent, custom=False):
279+
"""
280+
Factory function to create a keyboard.
281+
282+
This provides a simple way to switch between standard LVGL keyboard
283+
and custom keyboard.
284+
285+
Args:
286+
parent: Parent LVGL object
287+
custom: If True, create CustomKeyboard; if False, create standard lv.keyboard
288+
289+
Returns:
290+
CustomKeyboard instance or lv.keyboard instance
291+
292+
Example:
293+
# Use custom keyboard
294+
keyboard = create_keyboard(screen, custom=True)
295+
296+
# Use standard LVGL keyboard
297+
keyboard = create_keyboard(screen, custom=False)
298+
"""
299+
if custom:
300+
return CustomKeyboard(parent)
301+
else:
302+
keyboard = lv.keyboard(parent)
303+
mpos.ui.theme.fix_keyboard_button_style(keyboard)
304+
return keyboard

0 commit comments

Comments
 (0)