Cleanup WidgetAnimator framework

This commit is contained in:
Thomas Farstrike
2026-01-23 21:37:53 +01:00
parent 86d9c38902
commit 68b6ff3886
11 changed files with 262 additions and 217 deletions
+2 -2
View File
@@ -39,7 +39,7 @@ from .ui.topmenu import open_bar, close_bar, open_drawer, drawer_open, NOTIFICAT
from .ui.focus import save_and_clear_current_focusgroup
from .ui.gesture_navigation import handle_back_swipe, handle_top_swipe
from .ui.util import shutdown, set_foreground_app, get_foreground_app
from .ui.anim import smooth_show, smooth_hide
from .ui.widget_animator import WidgetAnimator
from .ui import focus_direction
# Utility modules
@@ -78,7 +78,7 @@ __all__ = [
"save_and_clear_current_focusgroup",
"handle_back_swipe", "handle_top_swipe",
"shutdown", "set_foreground_app", "get_foreground_app",
"smooth_show", "smooth_hide",
"WidgetAnimator",
"focus_direction",
# Testing utilities
"wait_for_render", "capture_screenshot", "simulate_click", "get_widget_coords",
+2 -2
View File
@@ -11,7 +11,7 @@ from .event import get_event_name, print_event
from .util import shutdown, set_foreground_app, get_foreground_app
from .setting_activity import SettingActivity
from .settings_activity import SettingsActivity
from .anim import smooth_show, smooth_hide
from .widget_animator import WidgetAnimator
from . import focus_direction
# main_display is assigned by board-specific initialization code
@@ -28,6 +28,6 @@ __all__ = [
"shutdown", "set_foreground_app", "get_foreground_app",
"SettingActivity",
"SettingsActivity",
"smooth_show", "smooth_hide",
"WidgetAnimator",
"focus_direction"
]
-181
View File
@@ -1,181 +0,0 @@
import lvgl as lv
def safe_widget_access(callback):
"""
Wrapper to safely access a widget, catching LvReferenceError.
If the widget has been deleted, the callback is silently skipped.
This prevents crashes when animations try to access deleted widgets.
Args:
callback: Function to call (should access a widget)
Returns:
None (always, even if callback returns a value)
"""
try:
callback()
except Exception as e:
# Check if it's an LvReferenceError (widget was deleted)
if "LvReferenceError" in str(type(e).__name__) or "Referenced object was deleted" in str(e):
# Widget was deleted - silently ignore
pass
else:
# Some other error - re-raise it
raise
class WidgetAnimator:
# def __init__(self):
# self.animations = {} # Store animations for each widget
# def stop_animation(self, widget):
# """Stop any running animation for the widget."""
# if widget in self.animations:
# self.animations[widget].delete()
# del self.animations[widget]
# show_widget and hide_widget could have a (lambda) callback that sets the final state (eg: drawer_open) at the end
@staticmethod
def show_widget(widget, anim_type="fade", duration=500, delay=0):
lv.anim_delete(widget, None) # stop all ongoing animations to prevent visual glitches
anim = lv.anim_t()
anim.init()
anim.set_var(widget)
anim.set_delay(delay)
anim.set_duration(duration)
# Clear HIDDEN flag to make widget visible for animation:
anim.set_start_cb(lambda *args: safe_widget_access(lambda: widget.remove_flag(lv.obj.FLAG.HIDDEN)))
if anim_type == "fade":
# Create fade-in animation (opacity from 0 to 255)
anim.set_values(0, 255)
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_style_opa(value, 0)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Ensure opacity is reset after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_style_opa(255, 0)))
elif anim_type == "slide_down":
print("doing slide_down")
# Create slide-down animation (y from -height to original y)
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y - height, original_y)
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Reset y position after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_y(original_y)))
else: # "slide_up":
# Create slide-up animation (y from +height to original y)
# Seems to cause scroll bars to be added somehow if done to a keyboard at the bottom of the screen...
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y + height, original_y)
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Reset y position after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_y(original_y)))
anim.start()
return anim
@staticmethod
def hide_widget(widget, anim_type="fade", duration=500, delay=0, hide=True):
lv.anim_delete(widget, None) # stop all ongoing animations to prevent visual glitches
anim = lv.anim_t()
anim.init()
anim.set_var(widget)
anim.set_duration(duration)
anim.set_delay(delay)
"""Hide a widget with an animation (fade or slide)."""
if anim_type == "fade":
# Create fade-out animation (opacity from 255 to 0)
anim.set_values(255, 0)
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_style_opa(value, 0)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Set HIDDEN flag after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: WidgetAnimator.hide_complete_cb(widget, hide=hide)))
elif anim_type == "slide_down":
# Create slide-down animation (y from original y to +height)
# Seems to cause scroll bars to be added somehow if done to a keyboard at the bottom of the screen...
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y, original_y + height)
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Set HIDDEN flag after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: WidgetAnimator.hide_complete_cb(widget, original_y, hide)))
else: # "slide_up":
print("hide with slide_up")
# Create slide-up animation (y from original y to -height)
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y, original_y - height)
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Set HIDDEN flag after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: WidgetAnimator.hide_complete_cb(widget, original_y, hide)))
anim.start()
return anim
@staticmethod
def change_widget(widget, anim_type="interpolate", duration=5000, delay=0, begin_value=0, end_value=100, display_change=None):
"""
Animate a widget's text by interpolating between begin_value and end_value.
Args:
widget: The widget to animate (should have set_text method)
anim_type: Type of animation (currently "interpolate" is supported)
duration: Animation duration in milliseconds
delay: Animation delay in milliseconds
begin_value: Starting value for interpolation
end_value: Ending value for interpolation
display_change: callback to display the change in the UI
Returns:
The animation object
"""
lv.anim_delete(widget, None) # stop all ongoing animations to prevent visual glitches
anim = lv.anim_t()
anim.init()
anim.set_var(widget)
anim.set_delay(delay)
anim.set_duration(duration)
if anim_type == "interpolate":
print(f"Create interpolation animation (value from {begin_value} to {end_value})")
anim.set_values(begin_value, end_value)
if display_change is not None:
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: display_change(value)))
# Ensure final value is set after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: display_change(end_value)))
else:
anim.set_custom_exec_cb(lambda anim, value: safe_widget_access(lambda: widget.set_text(str(value))))
# Ensure final value is set after animation
anim.set_completed_cb(lambda *args: safe_widget_access(lambda: widget.set_text(str(end_value))))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
else:
print(f"change_widget: unknown anim_type {anim_type}")
return
anim.start()
return anim
@staticmethod
def hide_complete_cb(widget, original_y=None, hide=True):
#print("hide_complete_cb")
if hide:
widget.add_flag(lv.obj.FLAG.HIDDEN)
if original_y:
widget.set_y(original_y) # in case it shifted slightly due to rounding etc
def smooth_show(widget, duration=500, delay=0):
return WidgetAnimator.show_widget(widget, anim_type="fade", duration=duration, delay=delay)
def smooth_hide(widget, hide=True, duration=500, delay=0):
return WidgetAnimator.hide_widget(widget, anim_type="fade", duration=duration, delay=delay, hide=hide)
@@ -3,7 +3,7 @@ import lvgl as lv
from ..config import SharedPreferences
from ..app.activity import Activity
from .display import DisplayMetrics
from . import anim
from .widget_animator import WidgetAnimator
class CameraSettingsActivity(Activity):
@@ -354,11 +354,11 @@ class CameraSettingsActivity(Activity):
def exposure_ctrl_changed(e=None):
is_auto = aec_checkbox.get_state() & lv.STATE.CHECKED
if is_auto:
anim.smooth_hide(me_cont, duration=1000)
anim.smooth_show(ae_cont, delay=1000)
WidgetAnimator.smooth_hide(me_cont, duration=1000)
WidgetAnimator.smooth_show(ae_cont, delay=1000)
else:
anim.smooth_hide(ae_cont, duration=1000)
anim.smooth_show(me_cont, delay=1000)
WidgetAnimator.smooth_hide(ae_cont, duration=1000)
WidgetAnimator.smooth_show(me_cont, delay=1000)
aec_checkbox.add_event_cb(exposure_ctrl_changed, lv.EVENT.VALUE_CHANGED, None)
exposure_ctrl_changed()
@@ -382,9 +382,9 @@ class CameraSettingsActivity(Activity):
is_auto = agc_checkbox.get_state() & lv.STATE.CHECKED
gain_slider = self.ui_controls["agc_gain"]
if is_auto:
anim.smooth_hide(agc_cont, duration=1000)
WidgetAnimator.smooth_hide(agc_cont, duration=1000)
else:
anim.smooth_show(agc_cont, duration=1000)
WidgetAnimator.smooth_show(agc_cont, duration=1000)
agc_checkbox.add_event_cb(gain_ctrl_changed, lv.EVENT.VALUE_CHANGED, None)
gain_ctrl_changed()
@@ -414,9 +414,9 @@ class CameraSettingsActivity(Activity):
def whitebal_changed(e=None):
is_auto = wbcheckbox.get_state() & lv.STATE.CHECKED
if is_auto:
anim.smooth_hide(wb_cont, duration=1000)
WidgetAnimator.smooth_hide(wb_cont, duration=1000)
else:
anim.smooth_show(wb_cont, duration=1000)
WidgetAnimator.smooth_show(wb_cont, duration=1000)
wbcheckbox.add_event_cb(whitebal_changed, lv.EVENT.VALUE_CHANGED, None)
whitebal_changed()
@@ -1,6 +1,6 @@
import lvgl as lv
from lvgl import LvReferenceError
from .anim import smooth_show, smooth_hide
from .widget_animator import WidgetAnimator
from .view import back_screen
from mpos.ui import topmenu as topmenu
from .display import DisplayMetrics
@@ -50,12 +50,12 @@ def _back_swipe_cb(event):
should_show = not is_short_movement(dx, dy)
if should_show != backbutton_visible:
backbutton_visible = should_show
smooth_show(backbutton) if should_show else smooth_hide(backbutton)
WidgetAnimator.smooth_show(backbutton) if should_show else WidgetAnimator.smooth_hide(backbutton)
backbutton.set_pos(round(x / 10), back_start_y)
elif event_code == lv.EVENT.RELEASED:
if backbutton_visible:
backbutton_visible = False
smooth_hide(backbutton)
WidgetAnimator.smooth_hide(backbutton)
if x > DisplayMetrics.width() / 5:
if topmenu.drawer_open :
topmenu.close_drawer()
@@ -89,12 +89,12 @@ def _top_swipe_cb(event):
should_show = not is_short_movement(dx, dy)
if should_show != downbutton_visible:
downbutton_visible = should_show
smooth_show(downbutton) if should_show else smooth_hide(downbutton)
WidgetAnimator.smooth_show(downbutton) if should_show else WidgetAnimator.smooth_hide(downbutton)
downbutton.set_pos(down_start_x, round(y / 10))
elif event_code == lv.EVENT.RELEASED:
if downbutton_visible:
downbutton_visible = False
smooth_hide(downbutton)
WidgetAnimator.smooth_hide(downbutton)
dx = abs(x - down_start_x)
dy = abs(y - down_start_y)
if y > DisplayMetrics.height() / 5:
+3 -2
View File
@@ -17,6 +17,7 @@ Usage:
import lvgl as lv
import mpos.ui.theme
from .widget_animator import WidgetAnimator
class MposKeyboard:
"""
@@ -247,7 +248,7 @@ class MposKeyboard:
def show_keyboard(self):
self._saved_scroll_y = self._parent.get_scroll_y()
mpos.ui.anim.smooth_show(self._keyboard, duration=500)
WidgetAnimator.smooth_show(self._keyboard, duration=500)
# Scroll to view on a timer because it will be hidden initially
lv.timer_create(self.scroll_after_show, 250, None).set_repeat_count(1)
# When this is done from a timer, focus styling is not applied so the user doesn't see which button is selected.
@@ -259,7 +260,7 @@ class MposKeyboard:
self.focus_on_keyboard()
def hide_keyboard(self):
mpos.ui.anim.smooth_hide(self._keyboard, duration=500)
WidgetAnimator.smooth_hide(self._keyboard, duration=500)
# Do this after the hide so the scrollbars disappear automatically if not needed
scroll_timer = lv.timer_create(self.scroll_back_after_hide,550,None).set_repeat_count(1)
@@ -3,7 +3,7 @@ import lvgl as lv
from ..app.activity import Activity
from .camera_activity import CameraActivity
from .display import DisplayMetrics
from . import anim
from .widget_animator import WidgetAnimator
from ..camera_manager import CameraManager
"""
@@ -134,7 +134,7 @@ class SettingActivity(Activity):
def onStop(self, screen):
if self.keyboard:
anim.smooth_hide(self.keyboard)
WidgetAnimator.smooth_hide(self.keyboard)
def radio_event_handler(self, event):
print("radio_event_handler called")
+1 -1
View File
@@ -5,7 +5,7 @@ import mpos.battery_voltage
from .display_metrics import DisplayMetrics
from .util import (get_foreground_app)
from . import focus_direction
from .anim import WidgetAnimator
from .widget_animator import WidgetAnimator
NOTIFICATION_BAR_HEIGHT=24
@@ -0,0 +1,225 @@
import lvgl as lv
class WidgetAnimator:
"""
Utility for creating smooth, non-blocking animations on LVGL widgets.
Provides fade, slide, and value interpolation animations with automatic
cleanup and safe widget access handling.
"""
@staticmethod
def _safe_widget_access(callback):
"""
Wrapper to safely access a widget, catching LvReferenceError.
If the widget has been deleted, the callback is silently skipped.
This prevents crashes when animations try to access deleted widgets.
Args:
callback: Function to call (should access a widget)
Returns:
None (always, even if callback returns a value)
"""
try:
callback()
except Exception as e:
# Check if it's an LvReferenceError (widget was deleted)
if "LvReferenceError" in str(type(e).__name__) or "Referenced object was deleted" in str(e):
# Widget was deleted - silently ignore
pass
else:
# Some other error - re-raise it
raise
@staticmethod
def show_widget(widget, anim_type="fade", duration=500, delay=0):
"""
Show a widget with an animation.
Args:
widget (lv.obj): The widget to show
anim_type (str): Animation type - "fade", "slide_down", or "slide_up" (default: "fade")
duration (int): Animation duration in milliseconds (default: 500)
delay (int): Animation delay in milliseconds (default: 0)
Returns:
The animation object
"""
lv.anim_delete(widget, None) # stop all ongoing animations to prevent visual glitches
anim = lv.anim_t()
anim.init()
anim.set_var(widget)
anim.set_delay(delay)
anim.set_duration(duration)
# Clear HIDDEN flag to make widget visible for animation:
anim.set_start_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: widget.remove_flag(lv.obj.FLAG.HIDDEN)))
if anim_type == "fade":
# Create fade-in animation (opacity from 0 to 255)
anim.set_values(0, 255)
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_style_opa(value, 0)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Ensure opacity is reset after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: widget.set_style_opa(255, 0)))
elif anim_type == "slide_down":
# Create slide-down animation (y from -height to original y)
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y - height, original_y)
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Reset y position after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: widget.set_y(original_y)))
else: # "slide_up"
# Create slide-up animation (y from +height to original y)
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y + height, original_y)
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Reset y position after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: widget.set_y(original_y)))
anim.start()
return anim
@staticmethod
def hide_widget(widget, anim_type="fade", duration=500, delay=0, hide=True):
"""
Hide a widget with an animation.
Args:
widget (lv.obj): The widget to hide
anim_type (str): Animation type - "fade", "slide_down", or "slide_up" (default: "fade")
duration (int): Animation duration in milliseconds (default: 500)
delay (int): Animation delay in milliseconds (default: 0)
hide (bool): If True, adds HIDDEN flag after animation. If False, only animates opacity/position (default: True)
Returns:
The animation object
"""
lv.anim_delete(widget, None) # stop all ongoing animations to prevent visual glitches
anim = lv.anim_t()
anim.init()
anim.set_var(widget)
anim.set_duration(duration)
anim.set_delay(delay)
if anim_type == "fade":
# Create fade-out animation (opacity from 255 to 0)
anim.set_values(255, 0)
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_style_opa(value, 0)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Set HIDDEN flag after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: WidgetAnimator._hide_complete_cb(widget, hide=hide)))
elif anim_type == "slide_down":
# Create slide-down animation (y from original y to +height)
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y, original_y + height)
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Set HIDDEN flag after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: WidgetAnimator._hide_complete_cb(widget, original_y, hide)))
else: # "slide_up"
# Create slide-up animation (y from original y to -height)
original_y = widget.get_y()
height = widget.get_height()
anim.set_values(original_y, original_y - height)
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_y(value)))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
# Set HIDDEN flag after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: WidgetAnimator._hide_complete_cb(widget, original_y, hide)))
anim.start()
return anim
@staticmethod
def change_widget(widget, anim_type="interpolate", duration=5000, delay=0, begin_value=0, end_value=100, display_change=None):
"""
Animate a widget's text by interpolating between begin_value and end_value.
Args:
widget: The widget to animate (should have set_text method)
anim_type: Type of animation (currently "interpolate" is supported)
duration: Animation duration in milliseconds
delay: Animation delay in milliseconds
begin_value: Starting value for interpolation
end_value: Ending value for interpolation
display_change: callback to display the change in the UI
Returns:
The animation object
"""
lv.anim_delete(widget, None) # stop all ongoing animations to prevent visual glitches
anim = lv.anim_t()
anim.init()
anim.set_var(widget)
anim.set_delay(delay)
anim.set_duration(duration)
if anim_type == "interpolate":
anim.set_values(begin_value, end_value)
if display_change is not None:
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: display_change(value)))
# Ensure final value is set after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: display_change(end_value)))
else:
anim.set_custom_exec_cb(lambda anim, value: WidgetAnimator._safe_widget_access(lambda: widget.set_text(str(value))))
# Ensure final value is set after animation
anim.set_completed_cb(lambda *args: WidgetAnimator._safe_widget_access(lambda: widget.set_text(str(end_value))))
anim.set_path_cb(lv.anim_t.path_ease_in_out)
else:
return
anim.start()
return anim
@staticmethod
def smooth_show(widget, duration=500, delay=0):
"""
Fade in a widget (shorthand for show_widget with fade animation).
Args:
widget: The widget to show
duration: Animation duration in milliseconds (default: 500)
delay: Animation delay in milliseconds (default: 0)
Returns:
The animation object
"""
return WidgetAnimator.show_widget(widget, anim_type="fade", duration=duration, delay=delay)
@staticmethod
def smooth_hide(widget, hide=True, duration=500, delay=0):
"""
Fade out a widget (shorthand for hide_widget with fade animation).
Args:
widget: The widget to hide
hide: If True, adds HIDDEN flag after animation (default: True)
duration: Animation duration in milliseconds (default: 500)
delay: Animation delay in milliseconds (default: 0)
Returns:
The animation object
"""
return WidgetAnimator.hide_widget(widget, anim_type="fade", duration=duration, delay=delay, hide=hide)
@staticmethod
def _hide_complete_cb(widget, original_y=None, hide=True):
"""
Internal callback for hide animation completion.
Args:
widget: The widget being hidden
original_y: Original Y position (for slide animations)
hide: Whether to add HIDDEN flag
"""
if hide:
widget.add_flag(lv.obj.FLAG.HIDDEN)
if original_y:
widget.set_y(original_y) # in case it shifted slightly due to rounding etc
@@ -17,7 +17,7 @@ Usage:
import unittest
import lvgl as lv
import mpos.ui.anim
from mpos.ui.widget_animator import WidgetAnimator
import time
from mpos import wait_for_render
@@ -57,7 +57,7 @@ class TestAnimationDeletedWidget(unittest.TestCase):
# Start fade-in animation (500ms duration)
print("Starting smooth_show animation...")
mpos.ui.anim.smooth_show(widget)
WidgetAnimator.smooth_show(widget)
# Give animation time to start
wait_for_render(2)
@@ -97,7 +97,7 @@ class TestAnimationDeletedWidget(unittest.TestCase):
# Start fade-out animation
print("Starting smooth_hide animation...")
mpos.ui.anim.smooth_hide(widget)
WidgetAnimator.smooth_hide(widget)
# Give animation time to start
wait_for_render(2)
@@ -144,7 +144,7 @@ class TestAnimationDeletedWidget(unittest.TestCase):
# User clicks textarea - keyboard shows with animation
print("Showing keyboard with animation...")
mpos.ui.anim.smooth_show(keyboard)
WidgetAnimator.smooth_show(keyboard)
# Give animation time to start
wait_for_render(2)
@@ -189,7 +189,7 @@ class TestAnimationDeletedWidget(unittest.TestCase):
# Start animations on all widgets
print("Starting animations on 5 widgets...")
for w in widgets:
mpos.ui.anim.smooth_show(w)
WidgetAnimator.smooth_show(w)
wait_for_render(2)
+8 -8
View File
@@ -1,8 +1,8 @@
"""
Test MposKeyboard animation support (show/hide with mpos.ui.anim).
Test MposKeyboard animation support (show/hide with WidgetAnimator).
This test reproduces the bug where MposKeyboard is missing methods
required by mpos.ui.anim.smooth_show() and smooth_hide().
required by WidgetAnimator.smooth_show() and smooth_hide().
Usage:
Desktop: ./tests/unittest.sh tests/test_graphical_keyboard_animation.py
@@ -12,7 +12,7 @@ Usage:
import unittest
import lvgl as lv
import time
import mpos.ui.anim
from mpos.ui.widget_animator import WidgetAnimator
from base import KeyboardTestBase
@@ -23,7 +23,7 @@ class TestKeyboardAnimation(KeyboardTestBase):
"""
Test that MposKeyboard has set_style_opa method.
This method is required by mpos.ui.anim for fade animations.
This method is required by WidgetAnimator for fade animations.
"""
print("Testing that MposKeyboard has set_style_opa...")
@@ -62,7 +62,7 @@ class TestKeyboardAnimation(KeyboardTestBase):
# This should work without raising AttributeError
try:
mpos.ui.anim.smooth_show(self.keyboard)
WidgetAnimator.smooth_show(self.keyboard)
self.wait_for_render(100)
print("smooth_show called successfully")
except AttributeError as e:
@@ -91,7 +91,7 @@ class TestKeyboardAnimation(KeyboardTestBase):
# This should work without raising AttributeError
try:
mpos.ui.anim.smooth_hide(self.keyboard)
WidgetAnimator.smooth_hide(self.keyboard)
print("smooth_hide called successfully")
except AttributeError as e:
self.fail(f"smooth_hide raised AttributeError: {e}\n"
@@ -117,7 +117,7 @@ class TestKeyboardAnimation(KeyboardTestBase):
# Show keyboard (simulates textarea click)
try:
mpos.ui.anim.smooth_show(self.keyboard)
WidgetAnimator.smooth_show(self.keyboard)
self.wait_for_render(100)
except AttributeError as e:
self.fail(f"Failed during smooth_show: {e}")
@@ -127,7 +127,7 @@ class TestKeyboardAnimation(KeyboardTestBase):
# Hide keyboard (simulates pressing Enter)
try:
mpos.ui.anim.smooth_hide(self.keyboard)
WidgetAnimator.smooth_hide(self.keyboard)
self.wait_for_render(100)
except AttributeError as e:
self.fail(f"Failed during smooth_hide: {e}")