Launcher: only redraw if app list changed

This commit is contained in:
Thomas Farstrike
2025-10-30 21:03:42 +01:00
parent 394908fdae
commit bf09cbf12b
@@ -8,91 +8,156 @@
# All icons took: 1457ms
# All icons took: 1250ms
# Most of this time is actually spent reading and parsing manifests.
import uos
import lvgl as lv
import mpos.apps
import mpos.ui
from mpos.content.pm import PackageManager
from mpos import Activity
import time
import uhashlib
import ubinascii
class Launcher(Activity):
def __init__(self):
super().__init__()
# Cache of the last app list + a quick hash of the icons
self._last_app_list = None # list of tuples (name, path, icon_hash)
self._last_ui_built = False # was UI built at least once?
def onCreate(self):
print("launcher.py onCreate()")
main_screen = lv.obj()
main_screen.set_style_border_width(0, lv.PART.MAIN)
main_screen.set_style_radius(0, 0)
main_screen.set_pos(0, mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT) # leave some margin for the notification bar
#main_screen.set_size(lv.pct(100), lv.pct(100))
main_screen.set_pos(0, mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT)
main_screen.set_style_pad_hor(mpos.ui.pct_of_display_width(2), 0)
main_screen.set_style_pad_ver(mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT, 0)
main_screen.set_flex_flow(lv.FLEX_FLOW.ROW_WRAP)
self.setContentView(main_screen)
# ------------------------------------------------------------------
# Helper: compute a cheap hash of a file (or return None if missing)
@staticmethod
def _hash_file(path):
try:
with open(path, "rb") as f:
h = uhashlib.sha1()
while True:
data = f.read(1024)
if not data:
break
h.update(data)
return ubinascii.hexlify(h.digest()).decode()
except Exception:
return None
# ------------------------------------------------------------------
def onResume(self, screen):
# Grid parameters
icon_size = 64 # Adjust based on your display
label_height = 24
iconcont_width = icon_size + label_height
iconcont_height = icon_size + label_height
# ------------------------------------------------------------------
# 1. Build a *compact* representation of the current app list
current_apps = []
for app in PackageManager.get_app_list():
if app.category == "launcher":
continue
icon_path = f"{app.installed_path}/res/mipmap-mdpi/icon_64x64.png"
icon_hash = Launcher._hash_file(icon_path) # cheap SHA-1 of the icon file
current_apps.append((app.name, app.installed_path, icon_hash))
import time
# ------------------------------------------------------------------
# 2. Compare with the cached list if identical we skip UI rebuild
start = time.ticks_ms()
rebuild_needed = True
if (self._last_app_list is not None and
len(self._last_app_list) == len(current_apps)):
# element-wise compare (name, path, icon_hash)
if all(a == b for a, b in zip(self._last_app_list, current_apps)):
rebuild_needed = False
if not rebuild_needed:
end = time.ticks_ms()
print(f"Redraw icons took: {end-start}ms (cached no change)")
return
# ------------------------------------------------------------------
# 3. UI needs (re)building clear screen and create widgets
screen.clean()
# Get the group for focusable objects
focusgroup = lv.group_get_default()
if not focusgroup:
print("WARNING: could not get default focusgroup")
# Create UI for each app
# Grid parameters
icon_size = 64
label_height = 24
iconcont_width = icon_size + label_height
iconcont_height = icon_size + label_height
for app in PackageManager.get_app_list():
if app.category == "launcher":
print("Skipping launcher app from launcher apps...")
continue
app_name = app.name
app_dir_fullpath = app.installed_path
print(f"Adding app {app_name} from {app_dir_fullpath}")
# Create container for each app (icon + label)
# ----- container ------------------------------------------------
app_cont = lv.obj(screen)
app_cont.set_size(iconcont_width, iconcont_height)
app_cont.set_style_border_width(0, lv.PART.MAIN)
app_cont.set_style_pad_all(0, 0)
app_cont.set_style_bg_opa(lv.OPA.TRANSP,0) # prevent default style from adding slight gray to this container
app_cont.set_style_bg_opa(lv.OPA.TRANSP, 0)
app_cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
# Load and display icon
# ----- icon ----------------------------------------------------
icon_path = f"{app_dir_fullpath}/res/mipmap-mdpi/icon_64x64.png"
image = lv.image(app_cont)
try:
image.set_src(Launcher.load_icon(icon_path))
except Exception as e:
print(f"Error loading icon {icon_path}: {e} - loading default icon")
print(f"Error loading icon {icon_path}: {e} - loading default")
icon_path = "builtin/res/mipmap-mdpi/default_icon_64x64.png"
try:
image.set_src(Launcher.load_icon(icon_path))
except Exception as e:
print(f"Error loading default icon {icon_path}: {e} - using symbol")
print(f"Error loading default {icon_path}: {e} - using symbol")
image.set_src(lv.SYMBOL.STOP)
image.align(lv.ALIGN.TOP_MID, 0, 0)
image.set_size(icon_size, icon_size)
# ----- label ---------------------------------------------------
label = lv.label(app_cont)
label.set_text(app_name) # Use app_name directly
label.set_text(app_name)
label.set_long_mode(lv.label.LONG_MODE.WRAP)
label.set_width(iconcont_width)
label.align(lv.ALIGN.BOTTOM_MID, 0, 0)
label.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0)
app_cont.add_event_cb(lambda e, fullname=app.fullname: mpos.apps.start_app(fullname), lv.EVENT.CLICKED, None)
app_cont.add_event_cb(lambda e, app_cont=app_cont: self.focus_app_cont(app_cont),lv.EVENT.FOCUSED,None)
app_cont.add_event_cb(lambda e, app_cont=app_cont: self.defocus_app_cont(app_cont),lv.EVENT.DEFOCUSED,None)
# ----- events --------------------------------------------------
app_cont.add_event_cb(
lambda e, fullname=app.fullname: mpos.apps.start_app(fullname),
lv.EVENT.CLICKED, None)
app_cont.add_event_cb(
lambda e, cont=app_cont: self.focus_app_cont(cont),
lv.EVENT.FOCUSED, None)
app_cont.add_event_cb(
lambda e, cont=app_cont: self.defocus_app_cont(cont),
lv.EVENT.DEFOCUSED, None)
if focusgroup:
focusgroup.add_obj(app_cont)
end = time.ticks_ms()
print(f"Redraw icons took: {end-start}ms")
# ------------------------------------------------------------------
# 4. Store the new representation for the next resume
self._last_app_list = current_apps
self._last_ui_built = True
end = time.ticks_ms()
print(f"Redraw icons took: {end-start}ms (full rebuild)")
# ------------------------------------------------------------------
@staticmethod
def load_icon(icon_path):
with open(icon_path, 'rb') as f:
@@ -103,12 +168,11 @@ class Launcher(Activity):
})
return image_dsc
# ------------------------------------------------------------------
def focus_app_cont(self, app_cont):
#print(f"app_cont {app_cont} focused, setting border...")
app_cont.set_style_border_color(lv.theme_get_color_primary(None),lv.PART.MAIN)
app_cont.set_style_border_color(lv.theme_get_color_primary(None), lv.PART.MAIN)
app_cont.set_style_border_width(1, lv.PART.MAIN)
app_cont.scroll_to_view(True) # scroll to bring it into view
app_cont.scroll_to_view(True)
def defocus_app_cont(self, app_cont):
#print(f"app_cont {app_cont} defocused, unsetting border...")
app_cont.set_style_border_width(0, lv.PART.MAIN)