diff --git a/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py b/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py index 50929d43..e522b96e 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py @@ -22,10 +22,10 @@ class Launcher(mpos.apps.Activity): main_screen = lv.obj() main_screen.set_style_border_width(0, 0) main_screen.set_style_radius(0, 0) - main_screen.set_pos(0, mpos.ui.NOTIFICATION_BAR_HEIGHT) # leave some margin for the notification bar + 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_style_pad_hor(mpos.ui.pct_of_display_width(2), 0) - main_screen.set_style_pad_ver(mpos.ui.NOTIFICATION_BAR_HEIGHT, 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) diff --git a/internal_filesystem/lib/mpos/apps.py b/internal_filesystem/lib/mpos/apps.py index ff6d461a..94f94a29 100644 --- a/internal_filesystem/lib/mpos/apps.py +++ b/internal_filesystem/lib/mpos/apps.py @@ -130,9 +130,9 @@ def start_app(app_dir, is_launcher=False): execute_script(start_script_fullpath, True, app_dir + "/assets/", main_launcher_activity.get("classname")) # Launchers have the bar, other apps don't have it if is_launcher: - mpos.ui.open_bar() + mpos.ui.topmenu.open_bar() else: - mpos.ui.close_bar() + mpos.ui.topmenu.close_bar() end_time = utime.ticks_diff(utime.ticks_ms(), start_time) print(f"start_app() took {end_time}ms") diff --git a/internal_filesystem/lib/mpos/ui/__init__.py b/internal_filesystem/lib/mpos/ui/__init__.py index 56edbb86..f91aa10b 100644 --- a/internal_filesystem/lib/mpos/ui/__init__.py +++ b/internal_filesystem/lib/mpos/ui/__init__.py @@ -6,6 +6,7 @@ import mpos.battery_voltage import mpos.time import mpos.wifi from mpos.ui.anim import WidgetAnimator +import mpos.ui.topmenu th = None @@ -13,32 +14,10 @@ th = None horizontal_resolution = None vertical_resolution = None -NOTIFICATION_BAR_HEIGHT=24 - -CLOCK_UPDATE_INTERVAL = 1000 # 10 or even 1 ms doesn't seem to change the framerate but 100ms is enough -WIFI_ICON_UPDATE_INTERVAL = 1500 -BATTERY_ICON_UPDATE_INTERVAL = 5000 -TEMPERATURE_UPDATE_INTERVAL = 2000 -MEMFREE_UPDATE_INTERVAL = 5000 # not too frequent because there's a forced gc.collect() to give it a reliable value - -DRAWER_ANIM_DURATION=300 - -drawer=None -drawer_open=False -bar_open=False - -hide_bar_animation = None -show_bar_animation = None -show_bar_animation_start_value = -NOTIFICATION_BAR_HEIGHT -show_bar_animation_end_value = 0 -hide_bar_animation_start_value = show_bar_animation_end_value -hide_bar_animation_end_value = show_bar_animation_start_value - down_start_x = 0 back_start_y = 0 # Widgets: -notification_bar = None downbutton = None backbutton = None @@ -51,23 +30,7 @@ def get_pointer_xy(): indev.get_point(point) return point.x, point.y else: - return indev_error_x,indev_error_y # make it visible that this occurred - - -drawer_swipe_start_y = 0 -def drawer_swipe_cb(event): - global drawer_swipe_start_y - event_code = event.get_code() - #name = mpos.ui.get_event_name(event_code) - #print(f"drawer_swipe_cb {event_code} and {name}") - if event_code == lv.EVENT.PRESSED: - x, drawer_swipe_start_y = get_pointer_xy() - elif event_code == lv.EVENT.RELEASED: - x, end_y = get_pointer_xy() - if end_y < drawer_swipe_start_y - NOTIFICATION_BAR_HEIGHT: - mpos.ui.close_drawer() - drawer_swipe_start_y = 0 - + return -1,-1 # make it visible that this occurred # Shutdown function to run in main thread def shutdown(): @@ -77,46 +40,11 @@ def shutdown(): 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 open_drawer(): - global drawer_open, drawer - if not drawer_open: - open_bar() - drawer_open=True - WidgetAnimator.show_widget(drawer, anim_type="slide_down", duration=1000, delay=0) - -def close_drawer(to_launcher=False): - global drawer_open, drawer, foreground_app_name - if drawer_open: - drawer_open=False - if not to_launcher and not mpos.apps.is_launcher(foreground_app_name): - print(f"close_drawer: also closing bar because to_launcher is {to_launcher} and foreground_app_name is {foreground_app_name}") - close_bar() - WidgetAnimator.hide_widget(drawer, anim_type="slide_up", duration=1000, delay=0) - -def open_bar(): - print("opening bar...") - global bar_open, show_bar_animation, hide_bar_animation, notification_bar - if not bar_open: - #print("not open so opening...") - bar_open=True - hide_bar_animation.current_value = hide_bar_animation_end_value - show_bar_animation.start() - else: - print("bar already open") - -def close_bar(): - global bar_open, show_bar_animation, hide_bar_animation - if bar_open: - bar_open=False - show_bar_animation.current_value = show_bar_animation_end_value - hide_bar_animation.start() - def show_launcher(): mpos.apps.restart_launcher() @@ -143,268 +71,6 @@ def init_rootscreen(): rootlabel.set_text("Welcome to MicroPythonOS") rootlabel.center() -def create_notification_bar(): - global notification_bar - # Create notification bar - notification_bar = lv.obj(lv.layer_top()) - notification_bar.set_size(lv.pct(100), NOTIFICATION_BAR_HEIGHT) - notification_bar.set_pos(0, show_bar_animation_start_value) - notification_bar.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF) - notification_bar.set_scroll_dir(lv.DIR.NONE) - notification_bar.set_style_border_width(0, 0) - notification_bar.set_style_radius(0, 0) - # Time label - time_label = lv.label(notification_bar) - time_label.set_text("00:00:00") - time_label.align(lv.ALIGN.LEFT_MID, 0, 0) - temp_label = lv.label(notification_bar) - temp_label.set_text("00°C") - temp_label.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7) , 0) - memfree_label = lv.label(notification_bar) - memfree_label.set_text("") - memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, mpos.ui.pct_of_display_width(7), 0) - #style = lv.style_t() - #style.init() - #style.set_text_font(lv.font_montserrat_8) # tiny font - #memfree_label.add_style(style, 0) - # Notification icon (bell) - #notif_icon = lv.label(notification_bar) - #notif_icon.set_text(lv.SYMBOL.BELL) - #notif_icon.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, PADDING_TINY, 0) - # Battery percentage - #battery_label = lv.label(notification_bar) - #battery_label.set_text("100%") - #battery_label.align(lv.ALIGN.RIGHT_MID, 0, 0) - #battery_label.add_flag(lv.obj.FLAG.HIDDEN) - # Battery icon - battery_icon = lv.label(notification_bar) - battery_icon.set_text(lv.SYMBOL.BATTERY_FULL) - #battery_icon.align_to(battery_label, lv.ALIGN.OUT_LEFT_MID, 0, 0) - battery_icon.align(lv.ALIGN.RIGHT_MID, 0, 0) - battery_icon.add_flag(lv.obj.FLAG.HIDDEN) # keep it hidden until it has a correct value - # WiFi icon - wifi_icon = lv.label(notification_bar) - wifi_icon.set_text(lv.SYMBOL.WIFI) - wifi_icon.align_to(battery_icon, lv.ALIGN.OUT_LEFT_MID, -mpos.ui.pct_of_display_width(1), 0) - wifi_icon.add_flag(lv.obj.FLAG.HIDDEN) - # Update time - def update_time(timer): - hours = mpos.time.localtime()[3] - minutes = mpos.time.localtime()[4] - seconds = mpos.time.localtime()[5] - time_label.set_text(f"{hours:02d}:{minutes:02d}:{seconds:02d}") - - can_check_network = False - try: - import network - can_check_network = True - except Exception as e: - print("Warning: could not check WLAN status:", str(e)) - - def update_battery_icon(timer=None): - percent = mpos.battery_voltage.get_battery_percentage() - if percent > 80: # 4.1V - battery_icon.set_text(lv.SYMBOL.BATTERY_FULL) - elif percent > 60: # 4.0V - battery_icon.set_text(lv.SYMBOL.BATTERY_3) - elif percent > 40: # 3.9V - battery_icon.set_text(lv.SYMBOL.BATTERY_2) - elif percent > 20: # 3.8V - battery_icon.set_text(lv.SYMBOL.BATTERY_1) - else: # > 3.7V - battery_icon.set_text(lv.SYMBOL.BATTERY_EMPTY) - battery_icon.remove_flag(lv.obj.FLAG.HIDDEN) - # Percentage is not shown for now: - #battery_label.set_text(f"{round(percent)}%") - #battery_label.remove_flag(lv.obj.FLAG.HIDDEN) - update_battery_icon() # run it immediately instead of waiting for the timer - - def update_wifi_icon(timer): - if mpos.wifi.WifiService.is_connected(): - wifi_icon.remove_flag(lv.obj.FLAG.HIDDEN) - else: - wifi_icon.add_flag(lv.obj.FLAG.HIDDEN) - - can_check_temperature = False - try: - import esp32 - can_check_temperature = True - except Exception as e: - print("Warning: can't check temperature sensor:", str(e)) - - def update_temperature(timer): - if can_check_temperature: - temp_label.set_text(f"{esp32.mcu_temperature()}°C") - else: - temp_label.set_text("42°C") - - def update_memfree(timer): - import gc - gc.collect() # otherwise it goes down to 10% before shooting back up to 70% - free = gc.mem_free() - used = gc.mem_alloc() - total_memory = gc.mem_free() + gc.mem_alloc() - percentage = round(free * 100 / (free + used)) - memfree_label.set_text(f"{percentage}%") - - lv.timer_create(update_time, CLOCK_UPDATE_INTERVAL, None) - lv.timer_create(update_temperature, TEMPERATURE_UPDATE_INTERVAL, None) - lv.timer_create(update_memfree, MEMFREE_UPDATE_INTERVAL, None) - lv.timer_create(update_wifi_icon, WIFI_ICON_UPDATE_INTERVAL, None) - lv.timer_create(update_battery_icon, BATTERY_ICON_UPDATE_INTERVAL, None) - - # hide bar animation - global hide_bar_animation - hide_bar_animation = lv.anim_t() - hide_bar_animation.init() - hide_bar_animation.set_var(notification_bar) - hide_bar_animation.set_values(0, -NOTIFICATION_BAR_HEIGHT) - hide_bar_animation.set_time(2000) - hide_bar_animation.set_custom_exec_cb(lambda not_used, value : notification_bar.set_y(value)) - - # show bar animation - global show_bar_animation - show_bar_animation = lv.anim_t() - show_bar_animation.init() - show_bar_animation.set_var(notification_bar) - show_bar_animation.set_values(show_bar_animation_start_value, show_bar_animation_end_value) - show_bar_animation.set_time(1000) - show_bar_animation.set_custom_exec_cb(lambda not_used, value : notification_bar.set_y(value)) - - - -def create_drawer(display=None): - global drawer - drawer=lv.obj(lv.layer_top()) - drawer.set_size(lv.pct(100),lv.pct(90)) - drawer.set_pos(0,NOTIFICATION_BAR_HEIGHT) - drawer.set_scroll_dir(lv.DIR.NONE) - drawer.set_style_pad_all(15, 0) - drawer.set_style_border_width(0, 0) - drawer.set_style_radius(0, 0) - drawer.add_flag(lv.obj.FLAG.HIDDEN) - #drawer.add_flag(lv.obj.FLAG.GESTURE_BUBBLE) # no gestures are received... maybe not supported by SDL Pointer Driver... - drawer.add_event_cb(drawer_swipe_cb, lv.EVENT.PRESSED, None) - drawer.add_event_cb(drawer_swipe_cb, lv.EVENT.RELEASED, None) - slider_label=lv.label(drawer) - prefs = mpos.config.SharedPreferences("com.micropythonos.settings") - brightness_int = prefs.get_int("display_brightness", 100) - if display: - display.set_backlight(brightness_int) - slider_label.set_text(f"Brightness: {brightness_int}%") - slider_label.align(lv.ALIGN.TOP_MID,0,lv.pct(4)) - slider=lv.slider(drawer) - slider.set_range(1,100) - slider.set_value(int(brightness_int),False) - slider.set_width(lv.pct(80)) - slider.align_to(slider_label,lv.ALIGN.OUT_BOTTOM_MID,0,10) - def brightness_slider_changed(e): - brightness_int = slider.get_value() - slider_label.set_text(f"Brightness: {brightness_int}%") - if display: - display.set_backlight(brightness_int) - def brightness_slider_released(e): - brightness_int = slider.get_value() - prefs = mpos.config.SharedPreferences("com.micropythonos.settings") - old_brightness_int = prefs.get_int("display_brightness") - if old_brightness_int != brightness_int: - editor = prefs.edit() - editor.put_int("display_brightness", brightness_int) - editor.commit() - slider.add_event_cb(brightness_slider_changed,lv.EVENT.VALUE_CHANGED,None) - slider.add_event_cb(brightness_slider_released,lv.EVENT.RELEASED,None) - drawer_button_pct = 31 - wifi_btn=lv.button(drawer) - wifi_btn.set_size(lv.pct(drawer_button_pct),lv.pct(20)) - wifi_btn.align(lv.ALIGN.LEFT_MID,0,0) - wifi_label=lv.label(wifi_btn) - wifi_label.set_text(lv.SYMBOL.WIFI+" WiFi") - wifi_label.center() - def wifi_event(e): - close_drawer() - mpos.apps.start_app_by_name("com.micropythonos.wifi") - wifi_btn.add_event_cb(wifi_event,lv.EVENT.CLICKED,None) - settings_btn=lv.button(drawer) - settings_btn.set_size(lv.pct(drawer_button_pct),lv.pct(20)) - settings_btn.align(lv.ALIGN.RIGHT_MID,0,0) - settings_label=lv.label(settings_btn) - settings_label.set_text(lv.SYMBOL.SETTINGS+" Settings") - settings_label.center() - def settings_event(e): - close_drawer() - mpos.apps.start_app_by_name("com.micropythonos.settings") - settings_btn.add_event_cb(settings_event,lv.EVENT.CLICKED,None) - launcher_btn=lv.button(drawer) - launcher_btn.set_size(lv.pct(drawer_button_pct),lv.pct(20)) - launcher_btn.align(lv.ALIGN.CENTER,0,0) - launcher_label=lv.label(launcher_btn) - launcher_label.set_text(lv.SYMBOL.HOME+" Home") - launcher_label.center() - def launcher_event(e): - print("Home button pressed!") - close_drawer(True) - show_launcher() - launcher_btn.add_event_cb(launcher_event,lv.EVENT.CLICKED,None) - ''' - sleep_btn=lv.button(drawer) - sleep_btn.set_size(lv.pct(drawer_button_pct),lv.pct(20)) - sleep_btn.align(lv.ALIGN.BOTTOM_LEFT,0,0) - sleep_label=lv.label(sleep_btn) - sleep_label.set_text("Zz Sleep") - sleep_label.center() - def sleep_event(e): - print("Sleep button pressed!") - import sys - if sys.platform == "esp32": - #On ESP32, there's no power off but there's a hundred-year deepsleep. - import machine - machine.deepsleep(10000) # TODO: make it wakeup when it receives an interrupt from the accelerometer or a button press - else: # assume unix: - # maybe do a system suspend here? or at least show a popup toast "not supported" - close_drawer(True) - show_launcher() - sleep_btn.add_event_cb(sleep_event,lv.EVENT.CLICKED,None) - ''' - restart_btn=lv.button(drawer) - restart_btn.set_size(lv.pct(45),lv.pct(20)) - restart_btn.align(lv.ALIGN.BOTTOM_LEFT,0,0) - restart_label=lv.label(restart_btn) - restart_label.set_text(lv.SYMBOL.REFRESH+" Reset") - restart_label.center() - def reset_cb(e): - import machine - if hasattr(machine, 'reset'): - machine.reset() - elif hasattr(machine, 'soft_reset'): - machine.soft_reset() - else: - print("Warning: machine has no reset or soft_reset method available") - restart_btn.add_event_cb(reset_cb,lv.EVENT.CLICKED,None) - poweroff_btn=lv.button(drawer) - poweroff_btn.set_size(lv.pct(45),lv.pct(20)) - poweroff_btn.align(lv.ALIGN.BOTTOM_RIGHT,0,0) - poweroff_label=lv.label(poweroff_btn) - poweroff_label.set_text(lv.SYMBOL.POWER+" Off") - poweroff_label.center() - def poweroff_cb(e): - print("Power off action...") - remove_and_stop_current_activity() # make sure current app, like camera, does cleanup, saves progress, stops hardware etc. - import sys - if sys.platform == "esp32": - #On ESP32, there's no power off but there is a forever sleep - import machine - # DON'T configure BOOT button (Pin 0) as wake-up source because it wakes up immediately. - # Luckily, the RESET button can be used to wake it up. - #wake_pin = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP) # Pull-up enabled, active low - #import esp32 - #esp32.wake_on_ext0(pin=wake_pin, level=esp32.WAKEUP_ALL_LOW) - print("Entering deep sleep. Press BOOT button to wake up.") - machine.deepsleep() # sleep forever - else: # assume unix: - lv.deinit() # Deinitialize LVGL (if supported) - sys.exit(0) - poweroff_btn.add_event_cb(poweroff_cb,lv.EVENT.CLICKED,None) - EVENT_MAP = { lv.EVENT.ALL: "ALL", @@ -515,10 +181,6 @@ def empty_screen_stack(): global screen_stack screen_stack.clear() - -#def load_screen(screen): -# setContentView(None, screen) # for compatibility with old apps - # 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 @@ -576,12 +238,12 @@ def back_screen(): if prev_activity: prev_activity.onResume(prev_screen) if len(screen_stack) == 1: - open_bar() + 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 drawer_open: + if mpos.ui.topmenu.drawer_open: print("ignoring back gesture because drawer is open") return @@ -609,10 +271,10 @@ def back_swipe_cb(event): def handle_back_swipe(): global backbutton rect = lv.obj(lv.layer_top()) - rect.set_size(round(NOTIFICATION_BAR_HEIGHT/2), lv.layer_top().get_height()-NOTIFICATION_BAR_HEIGHT) # narrow because it overlaps buttons + 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, NOTIFICATION_BAR_HEIGHT) + rect.set_pos(0, mpos.ui.topmenu.NOTIFICATION_BAR_HEIGHT) style = lv.style_t() style.init() style.set_bg_opa(lv.OPA.TRANSP) @@ -642,7 +304,7 @@ def handle_back_swipe(): # 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 drawer_open: + if mpos.ui.topmenu.drawer_open: print("ignoring top swipe gesture because drawer is open") return @@ -665,13 +327,13 @@ def top_swipe_cb(event): elif event_code == lv.EVENT.RELEASED: mpos.ui.anim.smooth_hide(downbutton) if y > min(80,vertical_resolution / 3): - mpos.ui.open_drawer() + mpos.ui.topmenu.open_drawer() def handle_top_swipe(): global downbutton rect = lv.obj(lv.layer_top()) - rect.set_size(lv.pct(100), round(NOTIFICATION_BAR_HEIGHT*2/3)) + 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() diff --git a/internal_filesystem/main.py b/internal_filesystem/main.py index 32458b44..ad5018a8 100644 --- a/internal_filesystem/main.py +++ b/internal_filesystem/main.py @@ -10,6 +10,7 @@ fs_driver.fs_register(fs_drv, 'M') import mpos.apps import mpos.config import mpos.ui +import mpos.ui.topmenu prefs = mpos.config.SharedPreferences("com.micropythonos.settings") @@ -31,8 +32,8 @@ theme = lv.theme_default_init(display._disp_drv, primary_color, lv.color_hex(0xF #display.set_theme(theme) mpos.ui.init_rootscreen() -mpos.ui.create_notification_bar() -mpos.ui.create_drawer(display) +mpos.ui.topmenu.create_notification_bar() +mpos.ui.topmenu.create_drawer(display) mpos.ui.handle_back_swipe() mpos.ui.handle_top_swipe() mpos.ui.th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop