diff --git a/internal_filesystem/lib/mpos/apps.py b/internal_filesystem/lib/mpos/apps.py index cdee2b39..d60e3f98 100644 --- a/internal_filesystem/lib/mpos/apps.py +++ b/internal_filesystem/lib/mpos/apps.py @@ -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 diff --git a/internal_filesystem/lib/mpos/ui/__init__.py b/internal_filesystem/lib/mpos/ui/__init__.py index 3e25ba77..77ba3040 100644 --- a/internal_filesystem/lib/mpos/ui/__init__.py +++ b/internal_filesystem/lib/mpos/ui/__init__.py @@ -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" +] diff --git a/internal_filesystem/lib/mpos/ui/display.py b/internal_filesystem/lib/mpos/ui/display.py new file mode 100644 index 00000000..e148dd9b --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/display.py @@ -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 diff --git a/internal_filesystem/lib/mpos/ui/event.py b/internal_filesystem/lib/mpos/ui/event.py new file mode 100644 index 00000000..eb21fb4b --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/event.py @@ -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}") diff --git a/internal_filesystem/lib/mpos/ui/focus.py b/internal_filesystem/lib/mpos/ui/focus.py new file mode 100644 index 00000000..6a170dc7 --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/focus.py @@ -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())) diff --git a/internal_filesystem/lib/mpos/ui/topmenu.py b/internal_filesystem/lib/mpos/ui/topmenu.py index b961b3aa..071ae870 100644 --- a/internal_filesystem/lib/mpos/ui/topmenu.py +++ b/internal_filesystem/lib/mpos/ui/topmenu.py @@ -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): diff --git a/internal_filesystem/lib/mpos/ui/util.py b/internal_filesystem/lib/mpos/ui/util.py new file mode 100644 index 00000000..060db233 --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/util.py @@ -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 diff --git a/internal_filesystem/lib/mpos/ui/view.py b/internal_filesystem/lib/mpos/ui/view.py new file mode 100644 index 00000000..4cf2e6c4 --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/view.py @@ -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 diff --git a/internal_filesystem/lib/mpos/ui/widget.py b/internal_filesystem/lib/mpos/ui/widget.py new file mode 100644 index 00000000..69c7db9e --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/widget.py @@ -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() diff --git a/internal_filesystem/main.py b/internal_filesystem/main.py index b15fb607..469a25ff 100644 --- a/internal_filesystem/main.py +++ b/internal_filesystem/main.py @@ -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()