Refactor mpos.ui

This commit is contained in:
Thomas Farstrike
2025-10-30 20:31:02 +01:00
parent 7e3c01e960
commit a2af392cb1
10 changed files with 409 additions and 431 deletions
+5 -1
View File
@@ -5,8 +5,12 @@ import traceback
import mpos.info
import mpos.ui
from mpos import Activity, Intent
from mpos.app.activity import Activity
from mpos.content.intent import Intent
from mpos.content.pm import PackageManager
# the code uses things like:
# mpos.ui.set_foreground_app(fullname)
# Activity.startActivity(None, Intent(activity_class=main_activity))
def good_stack_size():
stacksize = 24*1024
+28 -426
View File
@@ -1,427 +1,29 @@
import lvgl as lv
import mpos.time
import mpos.wifi
from mpos.ui.anim import WidgetAnimator
import mpos.ui.focus_direction
import mpos.ui.topmenu
import mpos.util
# lib/mpos/ui/__init__.py
from .view import (
setContentView, back_screen, empty_screen_stack,
screen_stack
)
from .widget import handle_back_swipe, handle_top_swipe
from .topmenu import open_bar, close_bar, open_drawer, drawer_open
from .focus import save_and_clear_current_focusgroup
from .display import (
get_display_width, get_display_height,
pct_of_display_width, pct_of_display_height,
min_resolution, max_resolution,
get_pointer_xy # ← now correct
)
from .event import get_event_name, print_event
from .util import shutdown, set_foreground_app, get_foreground_app, show_launcher
th = None
# These get set by init_rootscreen():
horizontal_resolution = None
vertical_resolution = None
down_start_x = 0
back_start_y = 0
# Widgets:
downbutton = None
backbutton = None
foreground_app_name=None
def get_pointer_xy():
indev = lv.indev_active()
if indev:
point = lv.point_t()
indev.get_point(point)
return point.x, point.y
else:
return -1,-1 # make it visible that this occurred
# Shutdown function to run in main thread
def shutdown():
print("Shutting down...")
lv.deinit() # Deinitialize LVGL (if supported)
# Add driver cleanup here
import sys
sys.exit(0)
def set_foreground_app(appname):
global foreground_app_name
foreground_app_name = appname
print(f"foreground app is: {foreground_app_name}")
def show_launcher():
import mpos.apps
mpos.apps.restart_launcher()
def init_rootscreen():
global horizontal_resolution, vertical_resolution
rootscreen = lv.screen_active()
horizontal_resolution = rootscreen.get_display().get_horizontal_resolution()
vertical_resolution = rootscreen.get_display().get_vertical_resolution()
# Create a style for the undecorated screen
style = lv.style_t()
style.init()
# Remove background (make it transparent or set no color)
style.set_bg_opa(lv.OPA.TRANSP) # Transparent background
style.set_border_width(0) # No border
style.set_outline_width(0) # No outline
style.set_shadow_width(0) # No shadow
style.set_pad_all(0) # No padding
style.set_radius(0) # No corner radius (sharp edges)
# Apply the style to the screen
rootscreen.add_style(style, 0)
rootscreen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
rootscreen.set_scroll_dir(lv.DIR.NONE)
rootlabel = lv.label(rootscreen)
rootlabel.set_text("Welcome to MicroPythonOS")
rootlabel.center()
EVENT_MAP = {
lv.EVENT.ALL: "ALL",
lv.EVENT.CANCEL: "CANCEL",
lv.EVENT.CHILD_CHANGED: "CHILD_CHANGED",
lv.EVENT.CHILD_CREATED: "CHILD_CREATED",
lv.EVENT.CHILD_DELETED: "CHILD_DELETED",
lv.EVENT.CLICKED: "CLICKED",
lv.EVENT.COLOR_FORMAT_CHANGED: "COLOR_FORMAT_CHANGED",
lv.EVENT.COVER_CHECK: "COVER_CHECK",
lv.EVENT.CREATE: "CREATE",
lv.EVENT.DEFOCUSED: "DEFOCUSED",
lv.EVENT.DELETE: "DELETE",
lv.EVENT.DRAW_MAIN: "DRAW_MAIN",
lv.EVENT.DRAW_MAIN_BEGIN: "DRAW_MAIN_BEGIN",
lv.EVENT.DRAW_MAIN_END: "DRAW_MAIN_END",
lv.EVENT.DRAW_POST: "DRAW_POST",
lv.EVENT.DRAW_POST_BEGIN: "DRAW_POST_BEGIN",
lv.EVENT.DRAW_POST_END: "DRAW_POST_END",
lv.EVENT.DRAW_TASK_ADDED: "DRAW_TASK_ADDED",
lv.EVENT.FLUSH_FINISH: "FLUSH_FINISH",
lv.EVENT.FLUSH_START: "FLUSH_START",
lv.EVENT.FLUSH_WAIT_FINISH: "FLUSH_WAIT_FINISH",
lv.EVENT.FLUSH_WAIT_START: "FLUSH_WAIT_START",
lv.EVENT.FOCUSED: "FOCUSED",
lv.EVENT.GESTURE: "GESTURE",
lv.EVENT.GET_SELF_SIZE: "GET_SELF_SIZE",
lv.EVENT.HIT_TEST: "HIT_TEST",
lv.EVENT.HOVER_LEAVE: "HOVER_LEAVE",
lv.EVENT.HOVER_OVER: "HOVER_OVER",
lv.EVENT.INDEV_RESET: "INDEV_RESET",
lv.EVENT.INSERT: "INSERT",
lv.EVENT.INVALIDATE_AREA: "INVALIDATE_AREA",
lv.EVENT.KEY: "KEY",
lv.EVENT.LAST: "LAST",
lv.EVENT.LAYOUT_CHANGED: "LAYOUT_CHANGED",
lv.EVENT.LEAVE: "LEAVE",
lv.EVENT.LONG_PRESSED: "LONG_PRESSED",
lv.EVENT.LONG_PRESSED_REPEAT: "LONG_PRESSED_REPEAT",
lv.EVENT.PREPROCESS: "PREPROCESS",
lv.EVENT.PRESSED: "PRESSED",
lv.EVENT.PRESSING: "PRESSING",
lv.EVENT.PRESS_LOST: "PRESS_LOST",
lv.EVENT.READY: "READY",
lv.EVENT.REFRESH: "REFRESH",
lv.EVENT.REFR_EXT_DRAW_SIZE: "REFR_EXT_DRAW_SIZE",
lv.EVENT.REFR_READY: "REFR_READY",
lv.EVENT.REFR_REQUEST: "REFR_REQUEST",
lv.EVENT.REFR_START: "REFR_START",
lv.EVENT.RELEASED: "RELEASED",
lv.EVENT.RENDER_READY: "RENDER_READY",
lv.EVENT.RENDER_START: "RENDER_START",
lv.EVENT.RESOLUTION_CHANGED: "RESOLUTION_CHANGED",
lv.EVENT.ROTARY: "ROTARY",
lv.EVENT.SCREEN_LOADED: "SCREEN_LOADED",
lv.EVENT.SCREEN_LOAD_START: "SCREEN_LOAD_START",
lv.EVENT.SCREEN_UNLOADED: "SCREEN_UNLOADED",
lv.EVENT.SCREEN_UNLOAD_START: "SCREEN_UNLOAD_START",
lv.EVENT.SCROLL: "SCROLL",
lv.EVENT.SCROLL_BEGIN: "SCROLL_BEGIN",
lv.EVENT.SCROLL_END: "SCROLL_END",
lv.EVENT.SCROLL_THROW_BEGIN: "SCROLL_THROW_BEGIN",
lv.EVENT.SHORT_CLICKED: "SHORT_CLICKED",
lv.EVENT.SIZE_CHANGED: "SIZE_CHANGED",
lv.EVENT.STYLE_CHANGED: "STYLE_CHANGED",
lv.EVENT.VALUE_CHANGED: "VALUE_CHANGED",
lv.EVENT.VSYNC: "VSYNC"
}
# Function to translate event code to name
def get_event_name(event_code):
return EVENT_MAP.get(event_code, f"Unknown event {event_code}")
def print_event(event):
global canvas
event_code=event.get_code()
#print(f"got event {event_code}")
# Ignore:
# =======
# 19: HIT_TEST
# COVER_CHECK
# DRAW_MAIN
# DRAW_MAIN_BEGIN
# DRAW_MAIN_END
# 31: DRAW_POST_BEGIN
# 32: DRAW_POST
# 33: DRAW_POST_END
# 39: CHILD_CHANGED
# 52: GET_SELF_SIZE
if event_code not in [19,23,25,26,27,28,29,30,31, 32, 33, 39,49, 52]:
name = get_event_name(event_code)
target_obj=event.get_target_obj()
key = ""
if event_code == lv.EVENT.KEY:
key = f", key: {event.get_key()}"
print(f"{target_obj} got event code={event_code}, name={name}{key}")
def close_top_layer_msgboxes():
"""
Iterate through all widgets in lv.layer_top() and close any lv.msgbox instances.
"""
top_layer = lv.layer_top()
if not top_layer:
print("No top layer found")
return
# Get number of children
child_count = top_layer.get_child_count_by_type(lv.msgbox_backdrop_class)
print(f"Top layer has {child_count} msgbox_backdrops")
# Iterate through children (use index to avoid modifying list during deletion)
i = 0
while i < top_layer.get_child_count_by_type(lv.msgbox_backdrop_class):
child = top_layer.get_child_by_type(i,lv.msgbox_backdrop_class)
print("Found msgbox, closing it")
msgbox = child.get_child_by_type(0,lv.msgbox_class)
msgbox.close() # Close the message box
# Note: lv.msgbox_close() may delete the object, so child count may change
# Optional: Verify no msgboxes remain
child_count = top_layer.get_child_count_by_type(lv.msgbox_backdrop_class)
if child_count == 0:
print("All msgboxes closed, top layer empty")
else:
print(f"Top layer still has {child_count} children")
screen_stack = [] # Stack of (activity, screen, focusgroup, focused_object) tuples
def empty_screen_stack():
global screen_stack
screen_stack.clear()
def move_focusgroup_objects(fromgroup, togroup):
#print(f"Moving {fromgroup.get_obj_count()} focused objects")
for objnr in range(fromgroup.get_obj_count()):
#print(f"saving object {objnr} from default focusgroup to current_focusgroup")
next = fromgroup.get_obj_by_index(0)
#mpos.util.print_lvgl_widget(next)
if next:
togroup.add_obj(next)
#print("Done moving focused objects")
# Saves all objects from the default focus group in the activity's focus group
def save_and_clear_current_focusgroup():
global screen_stack
default_focusgroup = lv.group_get_default()
if default_focusgroup and len(screen_stack) > 0:
current_activity, current_screen, current_focusgroup, _ = screen_stack.pop()
current_focused_object = default_focusgroup.get_focused()
#if current_focused_object:
# print("current_focused_object: ")
# mpos.util.print_lvgl_widget(current_focused_object)
move_focusgroup_objects(lv.group_get_default(), current_focusgroup)
screen_stack.append((current_activity, current_screen, current_focusgroup, current_focused_object))
# new_activity might be None for compatibility, can be removed if compatibility is no longer needed
def setContentView(new_activity, new_screen):
global screen_stack
# Get current activity and screen
if len(screen_stack) > 0:
current_activity, current_screen, current_focusgroup, current_focused_object = screen_stack[-1]
# Notify current activity that it's being backgrounded:
current_activity.onPause(current_screen)
current_activity.onStop(current_screen)
# don't destroy because the user might go back to it
print("Appending new activity, new screen and new focusgroup to screen_stack")
screen_stack.append((new_activity, new_screen, lv.group_create(), None))
close_top_layer_msgboxes() # otherwise they remain
if new_activity:
#start_time = utime.ticks_ms()
new_activity.onStart(new_screen) # Initialize UI elements
#end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
#print(f"ui.py setContentView: new_activity.onStart took {end_time}ms")
#start_time = utime.ticks_ms()
lv.screen_load_anim(new_screen, lv.SCR_LOAD_ANIM.OVER_LEFT, 500, 0, False)
#end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
#print(f"ui.py setContentView: screen_load took {end_time}ms")
if new_activity:
#start_time = utime.ticks_ms()
new_activity.onResume(new_screen) # Screen is now active
#end_time = utime.ticks_diff(utime.ticks_ms(), start_time)
#print(f"ui.py setContentView: new_activity.onResume took {end_time}ms")
def remove_and_stop_current_activity():
current_activity, current_screen, current_focusgroup, current_focused_object = screen_stack.pop() # Get current activity and its screen
if current_activity:
current_activity.onPause(current_screen)
current_activity.onStop(current_screen)
current_activity.onDestroy(current_screen)
if current_screen:
current_screen.clean() # should free up memory
def back_screen():
print("back_screen() running")
global screen_stack
if len(screen_stack) <= 1:
print("Warning: can't go back because screen_stack is empty.")
return False # No previous screen
#close_top_layer_msgboxes() # would be nicer to "cancel" all input events
remove_and_stop_current_activity()
prev_activity, prev_screen, prev_focusgroup, prev_focused_object = screen_stack[-1] # load previous screen
print("loading prev_screen with animation")
lv.screen_load_anim(prev_screen, lv.SCR_LOAD_ANIM.OVER_RIGHT, 500, 0, True) # True means delete the old screen, which is fine as we're going back and current_activity.onDestroy() was called
# Restore the focused objects
default_focusgroup = lv.group_get_default()
if default_focusgroup:
move_focusgroup_objects(prev_focusgroup, default_focusgroup)
#print("restoring prev_focused_object: ")
#mpos.util.print_lvgl_widget(prev_focused_object)
mpos.ui.focus_direction.emulate_focus_obj(default_focusgroup, prev_focused_object) # LVGL 9.3 should have: default_focusgroup.focus_obj(prev_focused_object)
if prev_activity:
prev_activity.onResume(prev_screen)
if len(screen_stack) == 1:
mpos.ui.topmenu.open_bar()
# Would be better to somehow save other events, like clicks, and pass them down to the layers below if released with x < 60
def back_swipe_cb(event):
if mpos.ui.topmenu.drawer_open:
print("ignoring back gesture because drawer is open")
return
global backbutton, back_start_y
event_code = event.get_code()
#name = mpos.ui.get_event_name(event_code)
indev = lv.indev_active()
if indev:
point = lv.point_t()
indev.get_point(point)
x = point.x
y = point.y
#print(f"visual_back_swipe_cb event_code={event_code} and event_name={name} and pos: {x}, {y}")
if event_code == lv.EVENT.PRESSED:
mpos.ui.anim.smooth_show(backbutton)
back_start_y = y
elif event_code == lv.EVENT.PRESSING:
magnetic_x = round(x / 10)
backbutton.set_pos(magnetic_x,back_start_y)
elif event_code == lv.EVENT.RELEASED:
mpos.ui.anim.smooth_hide(backbutton)
if x > min(100,horizontal_resolution / 3):
mpos.ui.back_screen()
def handle_back_swipe():
global backbutton
rect = lv.obj(lv.layer_top())
rect.set_size(round(mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT/2), lv.layer_top().get_height()-mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT) # narrow because it overlaps buttons
rect.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
rect.set_scroll_dir(lv.DIR.NONE)
rect.set_pos(0, mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT)
style = lv.style_t()
style.init()
style.set_bg_opa(lv.OPA.TRANSP)
style.set_border_width(0)
style.set_radius(0)
if False: # debug the back swipe zone with a red border
style.set_bg_opa(15)
style.set_border_width(4)
style.set_border_color(lv.color_hex(0xFF0000)) # Red border for visibility
style.set_border_opa(lv.OPA._50) # 50% opacity for the border
rect.add_style(style, 0)
#rect.add_flag(lv.obj.FLAG.CLICKABLE) # Make the object clickable
#rect.add_flag(lv.obj.FLAG.GESTURE_BUBBLE) # Allow dragging
rect.add_event_cb(back_swipe_cb, lv.EVENT.PRESSED, None)
rect.add_event_cb(back_swipe_cb, lv.EVENT.PRESSING, None)
rect.add_event_cb(back_swipe_cb, lv.EVENT.RELEASED, None)
#rect.add_event_cb(back_swipe_cb, lv.EVENT.ALL, None)
# button with label that shows up during the dragging:
backbutton = lv.button(lv.layer_top())
backbutton.set_pos(0, round(lv.layer_top().get_height() / 2))
backbutton.add_flag(lv.obj.FLAG.HIDDEN)
backbutton.add_state(lv.STATE.DISABLED)
backlabel = lv.label(backbutton)
backlabel.set_text(lv.SYMBOL.LEFT)
backlabel.set_style_text_font(lv.font_montserrat_18, 0)
backlabel.center()
# Would be better to somehow save other events, like clicks, and pass them down to the layers below if released with x < 60
def top_swipe_cb(event):
if mpos.ui.topmenu.drawer_open:
print("ignoring top swipe gesture because drawer is open")
return
global downbutton, down_start_x
event_code = event.get_code()
name = mpos.ui.get_event_name(event_code)
indev = lv.indev_active()
if indev:
point = lv.point_t()
indev.get_point(point)
x = point.x
y = point.y
#print(f"visual_back_swipe_cb event_code={event_code} and event_name={name} and pos: {x}, {y}")
if event_code == lv.EVENT.PRESSED:
mpos.ui.anim.smooth_show(downbutton)
down_start_x = x
elif event_code == lv.EVENT.PRESSING:
magnetic_y = round(y/ 10)
downbutton.set_pos(down_start_x,magnetic_y)
elif event_code == lv.EVENT.RELEASED:
mpos.ui.anim.smooth_hide(downbutton)
if y > min(80,vertical_resolution / 3):
mpos.ui.topmenu.open_drawer()
def handle_top_swipe():
global downbutton
rect = lv.obj(lv.layer_top())
rect.set_size(lv.pct(100), round(mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT*2/3))
rect.set_pos(0, 0)
rect.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
style = lv.style_t()
style.init()
style.set_bg_opa(lv.OPA.TRANSP)
#style.set_bg_opa(15)
style.set_border_width(0)
style.set_radius(0)
#style.set_border_color(lv.color_hex(0xFF0000)) # White border for visibility
#style.set_border_opa(lv.OPA._50) # 50% opacity for the border
rect.add_style(style, 0)
#rect.add_flag(lv.obj.FLAG.CLICKABLE) # Make the object clickable
#rect.add_flag(lv.obj.FLAG.GESTURE_BUBBLE) # Allow dragging
rect.add_event_cb(top_swipe_cb, lv.EVENT.PRESSED, None)
rect.add_event_cb(top_swipe_cb, lv.EVENT.PRESSING, None)
rect.add_event_cb(top_swipe_cb, lv.EVENT.RELEASED, None)
# button with label that shows up during the dragging:
downbutton = lv.button(lv.layer_top())
downbutton.set_pos(0, round(lv.layer_top().get_height() / 2))
downbutton.add_flag(lv.obj.FLAG.HIDDEN)
downbutton.add_state(lv.STATE.DISABLED)
downlabel = lv.label(downbutton)
downlabel.set_text(lv.SYMBOL.DOWN)
downlabel.set_style_text_font(lv.font_montserrat_18, 0)
downlabel.center()
def pct_of_display_width(percent):
return round(horizontal_resolution * percent / 100)
def pct_of_display_height(percent):
return round(vertical_resolution * percent / 100)
def min_resolution():
return min(mpos.ui.horizontal_resolution,mpos.ui.vertical_resolution)
def max_resolution():
return max(mpos.ui.horizontal_resolution,mpos.ui.vertical_resolution)
__all__ = [
"setContentView", "back_screen", "empty_screen_stack",
"handle_back_swipe", "handle_top_swipe",
"open_bar", "close_bar", "open_drawer", "drawer_open",
"save_and_clear_current_focusgroup",
"get_display_width", "get_display_height",
"pct_of_display_width", "pct_of_display_height",
"min_resolution", "max_resolution",
"get_pointer_xy",
"get_event_name", "print_event",
"shutdown", "set_foreground_app", "get_foreground_app", "show_launcher"
]
@@ -0,0 +1,59 @@
# lib/mpos/ui/display.py
import lvgl as lv
_horizontal_resolution = None
_vertical_resolution = None
def init_rootscreen():
global _horizontal_resolution, _vertical_resolution
screen = lv.screen_active()
disp = screen.get_display()
_horizontal_resolution = disp.get_horizontal_resolution()
_vertical_resolution = disp.get_vertical_resolution()
print(f"init_rootscreen set _vertical_resolution to {_vertical_resolution}")
style = lv.style_t()
style.init()
style.set_bg_opa(lv.OPA.TRANSP)
style.set_border_width(0)
style.set_outline_width(0)
style.set_shadow_width(0)
style.set_pad_all(0)
style.set_radius(0)
screen.add_style(style, 0)
screen.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
screen.set_scroll_dir(lv.DIR.NONE)
label = lv.label(screen)
label.set_text("Welcome to MicroPythonOS")
label.center()
def get_pointer_xy():
indev = lv.indev_active()
if indev:
p = lv.point_t()
indev.get_point(p)
return p.x, p.y
return -1, -1
def pct_of_display_width(pct):
return round(_horizontal_resolution * pct / 100)
def pct_of_display_height(pct):
return round(_vertical_resolution * pct / 100)
def min_resolution():
return min(_horizontal_resolution, _vertical_resolution)
def max_resolution():
return max(_horizontal_resolution, _vertical_resolution)
def get_display_width():
if _horizontal_resolution is None:
_init_resolution()
return _horizontal_resolution
def get_display_height():
if _vertical_resolution is None:
_init_resolution()
return _vertical_resolution
+85
View File
@@ -0,0 +1,85 @@
# lib/mpos/ui/event.py
import lvgl as lv
EVENT_MAP = {
lv.EVENT.ALL: "ALL",
lv.EVENT.CANCEL: "CANCEL",
lv.EVENT.CHILD_CHANGED: "CHILD_CHANGED",
lv.EVENT.CHILD_CREATED: "CHILD_CREATED",
lv.EVENT.CHILD_DELETED: "CHILD_DELETED",
lv.EVENT.CLICKED: "CLICKED",
lv.EVENT.COLOR_FORMAT_CHANGED: "COLOR_FORMAT_CHANGED",
lv.EVENT.COVER_CHECK: "COVER_CHECK",
lv.EVENT.CREATE: "CREATE",
lv.EVENT.DEFOCUSED: "DEFOCUSED",
lv.EVENT.DELETE: "DELETE",
lv.EVENT.DRAW_MAIN: "DRAW_MAIN",
lv.EVENT.DRAW_MAIN_BEGIN: "DRAW_MAIN_BEGIN",
lv.EVENT.DRAW_MAIN_END: "DRAW_MAIN_END",
lv.EVENT.DRAW_POST: "DRAW_POST",
lv.EVENT.DRAW_POST_BEGIN: "DRAW_POST_BEGIN",
lv.EVENT.DRAW_POST_END: "DRAW_POST_END",
lv.EVENT.DRAW_TASK_ADDED: "DRAW_TASK_ADDED",
lv.EVENT.FLUSH_FINISH: "FLUSH_FINISH",
lv.EVENT.FLUSH_START: "FLUSH_START",
lv.EVENT.FLUSH_WAIT_FINISH: "FLUSH_WAIT_FINISH",
lv.EVENT.FLUSH_WAIT_START: "FLUSH_WAIT_START",
lv.EVENT.FOCUSED: "FOCUSED",
lv.EVENT.GESTURE: "GESTURE",
lv.EVENT.GET_SELF_SIZE: "GET_SELF_SIZE",
lv.EVENT.HIT_TEST: "HIT_TEST",
lv.EVENT.HOVER_LEAVE: "HOVER_LEAVE",
lv.EVENT.HOVER_OVER: "HOVER_OVER",
lv.EVENT.INDEV_RESET: "INDEV_RESET",
lv.EVENT.INSERT: "INSERT",
lv.EVENT.INVALIDATE_AREA: "INVALIDATE_AREA",
lv.EVENT.KEY: "KEY",
lv.EVENT.LAST: "LAST",
lv.EVENT.LAYOUT_CHANGED: "LAYOUT_CHANGED",
lv.EVENT.LEAVE: "LEAVE",
lv.EVENT.LONG_PRESSED: "LONG_PRESSED",
lv.EVENT.LONG_PRESSED_REPEAT: "LONG_PRESSED_REPEAT",
lv.EVENT.PREPROCESS: "PREPROCESS",
lv.EVENT.PRESSED: "PRESSED",
lv.EVENT.PRESSING: "PRESSING",
lv.EVENT.PRESS_LOST: "PRESS_LOST",
lv.EVENT.READY: "READY",
lv.EVENT.REFRESH: "REFRESH",
lv.EVENT.REFR_EXT_DRAW_SIZE: "REFR_EXT_DRAW_SIZE",
lv.EVENT.REFR_READY: "REFR_READY",
lv.EVENT.REFR_REQUEST: "REFR_REQUEST",
lv.EVENT.REFR_START: "REFR_START",
lv.EVENT.RELEASED: "RELEASED",
lv.EVENT.RENDER_READY: "RENDER_READY",
lv.EVENT.RENDER_START: "RENDER_START",
lv.EVENT.RESOLUTION_CHANGED: "RESOLUTION_CHANGED",
lv.EVENT.ROTARY: "ROTARY",
lv.EVENT.SCREEN_LOADED: "SCREEN_LOADED",
lv.EVENT.SCREEN_LOAD_START: "SCREEN_LOAD_START",
lv.EVENT.SCREEN_UNLOADED: "SCREEN_UNLOADED",
lv.EVENT.SCREEN_UNLOAD_START: "SCREEN_UNLOAD_START",
lv.EVENT.SCROLL: "SCROLL",
lv.EVENT.SCROLL_BEGIN: "SCROLL_BEGIN",
lv.EVENT.SCROLL_END: "SCROLL_END",
lv.EVENT.SCROLL_THROW_BEGIN: "SCROLL_THROW_BEGIN",
lv.EVENT.SHORT_CLICKED: "SHORT_CLICKED",
lv.EVENT.SIZE_CHANGED: "SIZE_CHANGED",
lv.EVENT.STYLE_CHANGED: "STYLE_CHANGED",
lv.EVENT.VALUE_CHANGED: "VALUE_CHANGED",
lv.EVENT.VSYNC: "VSYNC"
}
def get_event_name(code):
return EVENT_MAP.get(code, f"Unknown {code}")
def print_event(event):
code = event.get_code()
ignored = {19,23,25,26,27,28,29,30,31,32,33,39,49,52}
if code in ignored:
return
name = get_event_name(code)
target = event.get_target_obj()
key = f", key: {event.get_key()}" if code == lv.EVENT.KEY else ""
print(f"{target}{name}{key}")
+16
View File
@@ -0,0 +1,16 @@
# lib/mpos/ui/focus.py
import lvgl as lv
def move_focusgroup_objects(fromgroup, togroup):
for i in range(fromgroup.get_obj_count()):
obj = fromgroup.get_obj_by_index(0)
if obj:
togroup.add_obj(obj)
def save_and_clear_current_focusgroup():
from .view import screen_stack
default = lv.group_get_default()
if default and screen_stack:
activity, screen, focusgroup, focused = screen_stack.pop()
move_focusgroup_objects(default, focusgroup)
screen_stack.append((activity, screen, focusgroup, default.get_focused()))
+5 -3
View File
@@ -2,6 +2,8 @@ import lvgl as lv
import mpos.ui
import mpos.battery_voltage
from .display import (get_display_width, get_display_height)
from .util import (get_foreground_app)
from mpos.ui.anim import WidgetAnimator
@@ -44,8 +46,8 @@ def close_drawer(to_launcher=False):
global drawer_open, drawer
if drawer_open:
drawer_open=False
if not to_launcher and not mpos.apps.is_launcher(mpos.ui.foreground_app_name):
print(f"close_drawer: also closing bar because to_launcher is {to_launcher} and foreground_app_name is {mpos.ui.foreground_app_name}")
if not to_launcher and not "launcher" in get_foreground_app():
print(f"close_drawer: also closing bar because to_launcher is {to_launcher} and foreground_app_name is {get_foreground_app()}")
close_bar(False)
WidgetAnimator.hide_widget(drawer, anim_type="slide_up", duration=1000, delay=0)
@@ -339,7 +341,7 @@ def create_drawer(display=None):
# Add invisible padding at the bottom to make the drawer scrollable
l2 = lv.label(drawer)
l2.set_text("\n")
l2.set_pos(0,mpos.ui.vertical_resolution)
l2.set_pos(0,get_display_height())
def drawer_scroll_callback(event):
+35
View File
@@ -0,0 +1,35 @@
# lib/mpos/ui/util.py
import lvgl as lv
import sys
from ..apps import restart_launcher
_foreground_app_name = None
def set_foreground_app(name):
global _foreground_app_name
_foreground_app_name = name
print(f"Foreground app: {name}")
def get_foreground_app():
global _foreground_app_name
return _foreground_app_name
def show_launcher():
restart_launcher()
def shutdown():
print("Shutting down...")
lv.deinit()
sys.exit(0)
def close_top_layer_msgboxes():
top = lv.layer_top()
if not top:
return
i = 0
while i < top.get_child_count_by_type(lv.msgbox_backdrop_class):
child = top.get_child_by_type(i, lv.msgbox_backdrop_class)
msgbox = child.get_child_by_type(0, lv.msgbox_class)
if msgbox:
msgbox.close()
i += 1
+66
View File
@@ -0,0 +1,66 @@
# lib/mpos/ui/view.py
import lvgl as lv
from ..apps import restart_launcher
from .focus import save_and_clear_current_focusgroup
from .topmenu import open_bar
screen_stack = []
def empty_screen_stack():
global screen_stack
screen_stack.clear()
def setContentView(new_activity, new_screen):
global screen_stack
if screen_stack:
current_activity, current_screen, current_focusgroup, _ = screen_stack[-1]
current_activity.onPause(current_screen)
current_activity.onStop(current_screen)
from .util import close_top_layer_msgboxes
close_top_layer_msgboxes()
screen_stack.append((new_activity, new_screen, lv.group_create(), None))
if new_activity:
new_activity.onStart(new_screen)
lv.screen_load_anim(new_screen, lv.SCR_LOAD_ANIM.OVER_LEFT, 500, 0, False)
if new_activity:
new_activity.onResume(new_screen)
def back_screen():
global screen_stack
if len(screen_stack) <= 1:
print("Warning: can't go back — stack empty")
return False
from .util import close_top_layer_msgboxes
close_top_layer_msgboxes()
# Pop current
current_activity, current_screen, current_focusgroup, _ = screen_stack.pop()
if current_activity:
current_activity.onPause(current_screen)
current_activity.onStop(current_screen)
current_activity.onDestroy(current_screen)
if current_screen:
current_screen.clean()
# Load previous
prev_activity, prev_screen, prev_focusgroup, prev_focused = screen_stack[-1]
lv.screen_load_anim(prev_screen, lv.SCR_LOAD_ANIM.OVER_RIGHT, 500, 0, True)
default_group = lv.group_get_default()
if default_group:
from .focus import move_focusgroup_objects
move_focusgroup_objects(prev_focusgroup, default_group)
from .focus_direction import emulate_focus_obj
emulate_focus_obj(default_group, prev_focused)
if prev_activity:
prev_activity.onResume(prev_screen)
if len(screen_stack) == 1:
open_bar()
return True
+108
View File
@@ -0,0 +1,108 @@
# lib/mpos/ui/widget.py
import lvgl as lv
from .anim import smooth_show, smooth_hide
from .view import back_screen
from .topmenu import open_drawer, drawer_open
from .display import get_display_width, get_display_height
downbutton = None
backbutton = None
down_start_x = 0
back_start_y = 0
def _back_swipe_cb(event):
if drawer_open:
return
global back_start_y, backbutton
code = event.get_code()
indev = lv.indev_active()
if not indev:
return
point = lv.point_t()
indev.get_point(point)
x, y = point.x, point.y
if code == lv.EVENT.PRESSED:
smooth_show(backbutton)
back_start_y = y
elif code == lv.EVENT.PRESSING:
magnetic_x = round(x / 10)
backbutton.set_pos(magnetic_x, back_start_y)
elif code == lv.EVENT.RELEASED:
smooth_hide(backbutton)
if x > min(100, get_display_width() / 3):
back_screen()
def _top_swipe_cb(event):
if drawer_open:
return
global down_start_x, downbutton
code = event.get_code()
indev = lv.indev_active()
if not indev:
return
point = lv.point_t()
indev.get_point(point)
x, y = point.x, point.y
if code == lv.EVENT.PRESSED:
smooth_show(downbutton)
down_start_x = x
elif code == lv.EVENT.PRESSING:
magnetic_y = round(y / 10)
downbutton.set_pos(down_start_x, magnetic_y)
elif code == lv.EVENT.RELEASED:
smooth_hide(downbutton)
if y > min(80, get_display_height() / 3):
open_drawer()
def handle_back_swipe():
global backbutton
rect = lv.obj(lv.layer_top())
rect.set_size(round(60), lv.layer_top().get_height() - 80)
rect.set_pos(0, 80)
rect.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
style = lv.style_t()
style.init()
style.set_bg_opa(lv.OPA.TRANSP)
style.set_border_width(0)
rect.add_style(style, 0)
rect.add_event_cb(_back_swipe_cb, lv.EVENT.PRESSED, None)
rect.add_event_cb(_back_swipe_cb, lv.EVENT.PRESSING, None)
rect.add_event_cb(_back_swipe_cb, lv.EVENT.RELEASED, None)
backbutton = lv.button(lv.layer_top())
backbutton.set_pos(0, 200)
backbutton.add_flag(lv.obj.FLAG.HIDDEN)
backbutton.add_state(lv.STATE.DISABLED)
lbl = lv.label(backbutton)
lbl.set_text(lv.SYMBOL.LEFT)
lbl.set_style_text_font(lv.font_montserrat_18, 0)
lbl.center()
def handle_top_swipe():
global downbutton
rect = lv.obj(lv.layer_top())
rect.set_size(lv.pct(100), 60)
rect.set_pos(0, 0)
rect.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
style = lv.style_t()
style.init()
style.set_bg_opa(lv.OPA.TRANSP)
rect.add_style(style, 0)
rect.add_event_cb(_top_swipe_cb, lv.EVENT.PRESSED, None)
rect.add_event_cb(_top_swipe_cb, lv.EVENT.PRESSING, None)
rect.add_event_cb(_top_swipe_cb, lv.EVENT.RELEASED, None)
downbutton = lv.button(lv.layer_top())
downbutton.set_pos(100, 0)
downbutton.add_flag(lv.obj.FLAG.HIDDEN)
downbutton.add_state(lv.STATE.DISABLED)
lbl = lv.label(downbutton)
lbl.set_text(lv.SYMBOL.DOWN)
lbl.set_style_text_font(lv.font_montserrat_18, 0)
lbl.center()
+2 -1
View File
@@ -11,6 +11,7 @@ import mpos.apps
import mpos.config
import mpos.ui
import mpos.ui.topmenu
from mpos.ui.display import init_rootscreen
prefs = mpos.config.SharedPreferences("com.micropythonos.settings")
@@ -31,7 +32,7 @@ theme = lv.theme_default_init(display._disp_drv, primary_color, lv.color_hex(0xF
#display.set_theme(theme)
mpos.ui.init_rootscreen()
init_rootscreen()
mpos.ui.topmenu.create_notification_bar()
mpos.ui.topmenu.create_drawer(display)
mpos.ui.handle_back_swipe()