Skip to content

Commit b062aa0

Browse files
Simplify keyboard, robustify animations
1 parent dce55f7 commit b062aa0

File tree

4 files changed

+418
-95
lines changed

4 files changed

+418
-95
lines changed

internal_filesystem/lib/mpos/ui/anim.py

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
import lvgl as lv
22

3+
4+
def safe_widget_access(callback):
5+
"""
6+
Wrapper to safely access a widget, catching LvReferenceError.
7+
8+
If the widget has been deleted, the callback is silently skipped.
9+
This prevents crashes when animations try to access deleted widgets.
10+
11+
Args:
12+
callback: Function to call (should access a widget)
13+
14+
Returns:
15+
None (always, even if callback returns a value)
16+
"""
17+
try:
18+
callback()
19+
except Exception as e:
20+
# Check if it's an LvReferenceError (widget was deleted)
21+
if "LvReferenceError" in str(type(e).__name__) or "Referenced object was deleted" in str(e):
22+
# Widget was deleted - silently ignore
23+
pass
24+
else:
25+
# Some other error - re-raise it
26+
raise
27+
28+
329
class WidgetAnimator:
430

531
# def __init__(self):
@@ -27,10 +53,10 @@ def show_widget(widget, anim_type="fade", duration=500, delay=0):
2753
anim.set_values(0, 255)
2854
anim.set_duration(duration)
2955
anim.set_delay(delay)
30-
anim.set_custom_exec_cb(lambda anim, value: widget.set_style_opa(value, 0))
56+
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_style_opa(value, 0)))
3157
anim.set_path_cb(lv.anim_t.path_ease_in_out)
3258
# Ensure opacity is reset after animation
33-
anim.set_completed_cb(lambda *args: widget.set_style_opa(255, 0))
59+
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_style_opa(255, 0)))
3460
elif anim_type == "slide_down":
3561
print("doing slide_down")
3662
# Create slide-down animation (y from -height to original y)
@@ -42,10 +68,10 @@ def show_widget(widget, anim_type="fade", duration=500, delay=0):
4268
anim.set_values(original_y - height, original_y)
4369
anim.set_duration(duration)
4470
anim.set_delay(delay)
45-
anim.set_custom_exec_cb(lambda anim, value: widget.set_y(value))
71+
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
4672
anim.set_path_cb(lv.anim_t.path_ease_in_out)
4773
# Reset y position after animation
48-
anim.set_completed_cb(lambda *args: widget.set_y(original_y))
74+
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_y(original_y)))
4975
elif anim_type == "slide_up":
5076
# Create slide-up animation (y from +height to original y)
5177
# Seems to cause scroll bars to be added somehow if done to a keyboard at the bottom of the screen...
@@ -57,10 +83,10 @@ def show_widget(widget, anim_type="fade", duration=500, delay=0):
5783
anim.set_values(original_y + height, original_y)
5884
anim.set_duration(duration)
5985
anim.set_delay(delay)
60-
anim.set_custom_exec_cb(lambda anim, value: widget.set_y(value))
86+
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
6187
anim.set_path_cb(lv.anim_t.path_ease_in_out)
6288
# Reset y position after animation
63-
anim.set_completed_cb(lambda *args: widget.set_y(original_y))
89+
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_y(original_y)))
6490

6591
# Store and start animation
6692
#self.animations[widget] = anim
@@ -77,10 +103,10 @@ def hide_widget(widget, anim_type="fade", duration=500, delay=0, hide=True):
77103
anim.set_values(255, 0)
78104
anim.set_duration(duration)
79105
anim.set_delay(delay)
80-
anim.set_custom_exec_cb(lambda anim, value: widget.set_style_opa(value, 0))
106+
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_style_opa(value, 0)))
81107
anim.set_path_cb(lv.anim_t.path_ease_in_out)
82108
# Set HIDDEN flag after animation
83-
anim.set_completed_cb(lambda *args: WidgetAnimator.hide_complete_cb(widget, hide=hide))
109+
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: WidgetAnimator.hide_complete_cb(widget, hide=hide)))
84110
elif anim_type == "slide_down":
85111
# Create slide-down animation (y from original y to +height)
86112
# Seems to cause scroll bars to be added somehow if done to a keyboard at the bottom of the screen...
@@ -92,10 +118,10 @@ def hide_widget(widget, anim_type="fade", duration=500, delay=0, hide=True):
92118
anim.set_values(original_y, original_y + height)
93119
anim.set_duration(duration)
94120
anim.set_delay(delay)
95-
anim.set_custom_exec_cb(lambda anim, value: widget.set_y(value))
121+
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
96122
anim.set_path_cb(lv.anim_t.path_ease_in_out)
97123
# Set HIDDEN flag after animation
98-
anim.set_completed_cb(lambda *args: WidgetAnimator.hide_complete_cb(widget, original_y, hide))
124+
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: WidgetAnimator.hide_complete_cb(widget, original_y, hide)))
99125
elif anim_type == "slide_up":
100126
print("hide with slide_up")
101127
# Create slide-up animation (y from original y to -height)
@@ -107,10 +133,10 @@ def hide_widget(widget, anim_type="fade", duration=500, delay=0, hide=True):
107133
anim.set_values(original_y, original_y - height)
108134
anim.set_duration(duration)
109135
anim.set_delay(delay)
110-
anim.set_custom_exec_cb(lambda anim, value: widget.set_y(value))
136+
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
111137
anim.set_path_cb(lv.anim_t.path_ease_in_out)
112138
# Set HIDDEN flag after animation
113-
anim.set_completed_cb(lambda *args: WidgetAnimator.hide_complete_cb(widget, original_y, hide))
139+
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: WidgetAnimator.hide_complete_cb(widget, original_y, hide)))
114140

115141
# Store and start animation
116142
#self.animations[widget] = anim

internal_filesystem/lib/mpos/ui/keyboard.py

Lines changed: 17 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -179,98 +179,32 @@ def _handle_events(self, event):
179179
ta.set_text(new_text)
180180

181181
# ========================================================================
182-
# LVGL keyboard-compatible API
182+
# Python magic method for automatic method forwarding
183183
# ========================================================================
184184

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)
185+
def __getattr__(self, name):
186+
"""
187+
Forward any undefined method/attribute to the underlying LVGL keyboard.
260188
261-
def remove_event_cb(self, callback):
262-
"""Remove event callback."""
263-
self._keyboard.remove_event_cb(callback)
189+
This allows CustomKeyboard to support ALL lv.keyboard methods automatically
190+
without needing to manually wrap each one. Any method not defined on
191+
CustomKeyboard will be forwarded to self._keyboard.
264192
265-
def send_event(self, event_code, param):
266-
"""Send event to keyboard."""
267-
self._keyboard.send_event(event_code, param)
193+
Examples:
194+
keyboard.set_textarea(ta) # Works
195+
keyboard.align(lv.ALIGN.CENTER) # Works
196+
keyboard.set_style_opa(128, 0) # Works
197+
keyboard.any_lvgl_method() # Works!
198+
"""
199+
# Forward to the underlying keyboard object
200+
return getattr(self._keyboard, name)
268201

269202
def get_lvgl_obj(self):
270203
"""
271204
Get the underlying LVGL keyboard object.
272205
273-
Use this if you need direct access to LVGL methods not wrapped here.
206+
This is now rarely needed since __getattr__ forwards everything automatically.
207+
Kept for backwards compatibility.
274208
"""
275209
return self._keyboard
276210

0 commit comments

Comments
 (0)