From 86d9c38902d4185512e129b2e3ad20a72e69b8fb Mon Sep 17 00:00:00 2001 From: Thomas Farstrike Date: Fri, 23 Jan 2026 20:46:36 +0100 Subject: [PATCH] Introduce DisplayMetrics framework --- .../assets/imageview.py | 6 +- .../assets/fullscreen_qr.py | 4 +- .../assets/nostr_app.py | 6 +- .../com.micropythonos.about/assets/about.py | 10 +-- .../assets/launcher.py | 4 +- .../assets/osupdate.py | 8 +- .../assets/calibrate_imu.py | 4 +- .../assets/check_imu_calibration.py | 6 +- .../com.micropythonos.wifi/assets/wifi.py | 12 +-- internal_filesystem/lib/mpos/__init__.py | 12 +-- internal_filesystem/lib/mpos/ui/__init__.py | 12 +-- .../lib/mpos/ui/camera_activity.py | 8 +- .../lib/mpos/ui/camera_settings.py | 12 +-- internal_filesystem/lib/mpos/ui/display.py | 65 +++++---------- .../lib/mpos/ui/display_metrics.py | 81 +++++++++++++++++++ .../lib/mpos/ui/gesture_navigation.py | 6 +- .../lib/mpos/ui/setting_activity.py | 8 +- .../lib/mpos/ui/settings_activity.py | 4 +- internal_filesystem/lib/mpos/ui/topmenu.py | 16 ++-- 19 files changed, 163 insertions(+), 121 deletions(-) create mode 100644 internal_filesystem/lib/mpos/ui/display_metrics.py diff --git a/internal_filesystem/apps/com.micropythonos.imageview/assets/imageview.py b/internal_filesystem/apps/com.micropythonos.imageview/assets/imageview.py index 7fcda7f8..bc368145 100644 --- a/internal_filesystem/apps/com.micropythonos.imageview/assets/imageview.py +++ b/internal_filesystem/apps/com.micropythonos.imageview/assets/imageview.py @@ -1,7 +1,7 @@ import gc import os -from mpos import Activity, smooth_show, smooth_hide, pct_of_display_width, pct_of_display_height +from mpos import Activity, smooth_show, smooth_hide, DisplayMetrics class ImageView(Activity): @@ -271,8 +271,8 @@ class ImageView(Activity): pct = 100 else: pct = 70 - lvgl_w = pct_of_display_width(pct) - lvgl_h = pct_of_display_height(pct) + lvgl_w = DisplayMetrics.pct_of_width(pct) + lvgl_h = DisplayMetrics.pct_of_height(pct) print(f"scaling to size: {lvgl_w}x{lvgl_h}") header = lv.image_header_t() self.image.decoder_get_info(self.image.get_src(), header) diff --git a/internal_filesystem/apps/com.micropythonos.nostr/assets/fullscreen_qr.py b/internal_filesystem/apps/com.micropythonos.nostr/assets/fullscreen_qr.py index f13022b2..67964714 100644 --- a/internal_filesystem/apps/com.micropythonos.nostr/assets/fullscreen_qr.py +++ b/internal_filesystem/apps/com.micropythonos.nostr/assets/fullscreen_qr.py @@ -1,6 +1,6 @@ import lvgl as lv -from mpos import Activity, min_resolution +from mpos import Activity, DisplayMetrics class FullscreenQR(Activity): # No __init__() so super.__init__() will be called automatically @@ -28,7 +28,7 @@ class FullscreenQR(Activity): qr_screen.set_scroll_dir(lv.DIR.NONE) qr_screen.add_event_cb(lambda e: self.finish(),lv.EVENT.CLICKED,None) big_receive_qr = lv.qrcode(qr_screen) - big_receive_qr.set_size(min_resolution()) + big_receive_qr.set_size(DisplayMetrics.min_dimension()) big_receive_qr.set_dark_color(lv.color_black()) big_receive_qr.set_light_color(lv.color_white()) big_receive_qr.center() diff --git a/internal_filesystem/apps/com.micropythonos.nostr/assets/nostr_app.py b/internal_filesystem/apps/com.micropythonos.nostr/assets/nostr_app.py index d4198dc3..f84391d1 100644 --- a/internal_filesystem/apps/com.micropythonos.nostr/assets/nostr_app.py +++ b/internal_filesystem/apps/com.micropythonos.nostr/assets/nostr_app.py @@ -1,6 +1,6 @@ import lvgl as lv -from mpos import Activity, Intent, ConnectivityManager, pct_of_display_width, pct_of_display_height, SharedPreferences, SettingsActivity +from mpos import Activity, Intent, ConnectivityManager, DisplayMetrics, SharedPreferences, SettingsActivity from fullscreen_qr import FullscreenQR class ShowNpubQRActivity(Activity): @@ -82,13 +82,13 @@ class NostrApp(Activity): self.balance_label.align(lv.ALIGN.TOP_LEFT, 0, 0) self.balance_label.set_style_text_font(lv.font_montserrat_24, 0) self.balance_label.add_flag(lv.obj.FLAG.CLICKABLE) - self.balance_label.set_width(pct_of_display_width(100)) + self.balance_label.set_width(DisplayMetrics.pct_of_width(100)) # Events label self.events_label = lv.label(self.main_screen) self.events_label.set_text("") self.events_label.align_to(header_line,lv.ALIGN.OUT_BOTTOM_LEFT,0,10) self.update_events_label_font() - self.events_label.set_width(pct_of_display_width(100)) + self.events_label.set_width(DisplayMetrics.pct_of_width(100)) self.events_label.add_flag(lv.obj.FLAG.CLICKABLE) self.events_label.add_event_cb(self.events_label_clicked,lv.EVENT.CLICKED,None) settings_button = lv.button(self.main_screen) diff --git a/internal_filesystem/builtin/apps/com.micropythonos.about/assets/about.py b/internal_filesystem/builtin/apps/com.micropythonos.about/assets/about.py index 9e317719..2ba6ae4a 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.about/assets/about.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.about/assets/about.py @@ -1,4 +1,4 @@ -from mpos import Activity, pct_of_display_width, get_display_width, get_display_height, get_dpi +from mpos import Activity, DisplayMetrics import mpos.info import sys @@ -38,7 +38,7 @@ class About(Activity): screen = lv.obj() screen.set_style_border_width(0, 0) screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) - screen.set_style_pad_all(pct_of_display_width(2), 0) + screen.set_style_pad_all(DisplayMetrics.pct_of_width(2), 0) # Make the screen focusable so it can be scrolled with the arrow keys focusgroup = lv.group_get_default() if focusgroup: @@ -147,10 +147,10 @@ class About(Activity): # Display info try: self._add_label(screen, f"{lv.SYMBOL.IMAGE} Display", is_header=True) - hor_res = get_display_width() - ver_res = get_display_height() + hor_res = DisplayMetrics.width() + ver_res = DisplayMetrics.height() self._add_label(screen, f"Resolution: {hor_res}x{ver_res}") - dpi = get_dpi() + dpi = DisplayMetrics.dpi() self._add_label(screen, f"Dots Per Inch (dpi): {dpi}") except Exception as e: print(f"Could not get display info: {e}") 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 06e117b4..a8e9106f 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.launcher/assets/launcher.py @@ -10,7 +10,7 @@ # Most of this time is actually spent reading and parsing manifests. import lvgl as lv import mpos.apps -from mpos import NOTIFICATION_BAR_HEIGHT, PackageManager, Activity, pct_of_display_width +from mpos import NOTIFICATION_BAR_HEIGHT, PackageManager, Activity, DisplayMetrics import time import uhashlib import ubinascii @@ -29,7 +29,7 @@ class Launcher(Activity): main_screen.set_style_border_width(0, lv.PART.MAIN) main_screen.set_style_radius(0, 0) main_screen.set_pos(0, NOTIFICATION_BAR_HEIGHT) - main_screen.set_style_pad_hor(pct_of_display_width(2), 0) + main_screen.set_style_pad_hor(DisplayMetrics.pct_of_width(2), 0) main_screen.set_style_pad_ver(NOTIFICATION_BAR_HEIGHT, 0) main_screen.set_flex_flow(lv.FLEX_FLOW.ROW_WRAP) self.setContentView(main_screen) diff --git a/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py b/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py index 2d0562c2..25f04688 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.osupdate/assets/osupdate.py @@ -2,7 +2,7 @@ import lvgl as lv import ujson import time -from mpos import Activity, PackageManager, ConnectivityManager, TaskManager, DownloadManager, pct_of_display_width, pct_of_display_height +from mpos import Activity, PackageManager, ConnectivityManager, TaskManager, DownloadManager, DisplayMetrics import mpos.info class OSUpdate(Activity): @@ -39,7 +39,7 @@ class OSUpdate(Activity): def onCreate(self): self.main_screen = lv.obj() - self.main_screen.set_style_pad_all(pct_of_display_width(2), 0) + self.main_screen.set_style_pad_all(DisplayMetrics.pct_of_width(2), 0) # Make the screen focusable so it can be scrolled with the arrow keys if focusgroup := lv.group_get_default(): @@ -51,7 +51,7 @@ class OSUpdate(Activity): self.force_update = lv.checkbox(self.main_screen) self.force_update.set_text("Force Update") self.force_update.add_event_cb(lambda *args: self.force_update_clicked(), lv.EVENT.VALUE_CHANGED, None) - self.force_update.align_to(self.current_version_label, lv.ALIGN.OUT_BOTTOM_LEFT, 0, pct_of_display_height(5)) + self.force_update.align_to(self.current_version_label, lv.ALIGN.OUT_BOTTOM_LEFT, 0, DisplayMetrics.pct_of_height(5)) self.install_button = lv.button(self.main_screen) self.install_button.align(lv.ALIGN.TOP_RIGHT, 0, 0) self.install_button.add_state(lv.STATE.DISABLED) # button will be enabled if there is an update available @@ -72,7 +72,7 @@ class OSUpdate(Activity): check_again_label.center() self.status_label = lv.label(self.main_screen) - self.status_label.align_to(self.force_update, lv.ALIGN.OUT_BOTTOM_LEFT, 0, pct_of_display_height(5)) + self.status_label.align_to(self.force_update, lv.ALIGN.OUT_BOTTOM_LEFT, 0, DisplayMetrics.pct_of_height(5)) self.setContentView(self.main_screen) def _update_ui_for_state(self): diff --git a/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/calibrate_imu.py b/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/calibrate_imu.py index e008f9e7..f138f638 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/calibrate_imu.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/calibrate_imu.py @@ -10,7 +10,7 @@ Guides user through IMU calibration process: import lvgl as lv import time import sys -from mpos import Activity, SensorManager, wait_for_render, pct_of_display_width +from mpos import Activity, SensorManager, wait_for_render, DisplayMetrics class CalibrationState: @@ -40,7 +40,7 @@ class CalibrateIMUActivity(Activity): def onCreate(self): screen = lv.obj() - screen.set_style_pad_all(pct_of_display_width(3), 0) + screen.set_style_pad_all(DisplayMetrics.pct_of_width(3), 0) screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) screen.set_flex_align(lv.FLEX_ALIGN.CENTER, lv.FLEX_ALIGN.START, lv.FLEX_ALIGN.CENTER) focusgroup = lv.group_get_default() diff --git a/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/check_imu_calibration.py b/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/check_imu_calibration.py index c9373c27..ad7ee9d1 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/check_imu_calibration.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.settings/assets/check_imu_calibration.py @@ -7,7 +7,7 @@ variance, expected value comparison, and overall quality score. import lvgl as lv import time import sys -from mpos import Activity, SensorManager, pct_of_display_width +from mpos import Activity, SensorManager, DisplayMetrics class CheckIMUCalibrationActivity(Activity): @@ -34,7 +34,7 @@ class CheckIMUCalibrationActivity(Activity): def onCreate(self): screen = lv.obj() - screen.set_style_pad_all(pct_of_display_width(1), 0) + screen.set_style_pad_all(DisplayMetrics.pct_of_width(1), 0) #screen.set_style_pad_all(0, 0) screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) focusgroup = lv.group_get_default() @@ -96,7 +96,7 @@ class CheckIMUCalibrationActivity(Activity): # Gyroscope section gyro_cont = lv.obj(data_cont) - gyro_cont.set_width(pct_of_display_width(45)) + gyro_cont.set_width(DisplayMetrics.pct_of_width(45)) gyro_cont.set_height(lv.SIZE_CONTENT) gyro_cont.set_style_border_width(0, 0) gyro_cont.set_style_pad_all(0, 0) diff --git a/internal_filesystem/builtin/apps/com.micropythonos.wifi/assets/wifi.py b/internal_filesystem/builtin/apps/com.micropythonos.wifi/assets/wifi.py index 51e8b19f..ddeb9f32 100644 --- a/internal_filesystem/builtin/apps/com.micropythonos.wifi/assets/wifi.py +++ b/internal_filesystem/builtin/apps/com.micropythonos.wifi/assets/wifi.py @@ -2,7 +2,7 @@ import time import lvgl as lv import _thread -from mpos import Activity, Intent, MposKeyboard, WifiService, CameraActivity, pct_of_display_width, CameraManager +from mpos import Activity, Intent, MposKeyboard, WifiService, CameraActivity, DisplayMetrics, CameraManager import mpos.apps class WiFi(Activity): @@ -238,8 +238,8 @@ class EditNetwork(Activity): label.set_text(f"Network name:") self.ssid_ta = lv.textarea(password_page) self.ssid_ta.set_width(lv.pct(100)) - self.ssid_ta.set_style_margin_left(pct_of_display_width(2), lv.PART.MAIN) - self.ssid_ta.set_style_margin_right(pct_of_display_width(2), lv.PART.MAIN) + self.ssid_ta.set_style_margin_left(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) + self.ssid_ta.set_style_margin_right(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) self.ssid_ta.set_one_line(True) self.ssid_ta.set_placeholder_text("Enter the SSID") self.keyboard = MposKeyboard(password_page) @@ -254,8 +254,8 @@ class EditNetwork(Activity): label.set_text(f"Password for '{self.selected_ssid}':") self.password_ta = lv.textarea(password_page) self.password_ta.set_width(lv.pct(100)) - self.password_ta.set_style_margin_left(pct_of_display_width(2), lv.PART.MAIN) - self.password_ta.set_style_margin_right(pct_of_display_width(2), lv.PART.MAIN) + self.password_ta.set_style_margin_left(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) + self.password_ta.set_style_margin_right(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) self.password_ta.set_one_line(True) if known_password: self.password_ta.set_text(known_password) @@ -267,7 +267,7 @@ class EditNetwork(Activity): # Hidden network: self.hidden_cb = lv.checkbox(password_page) self.hidden_cb.set_text("Hidden network (always try connecting)") - self.hidden_cb.set_style_margin_left(pct_of_display_width(2), lv.PART.MAIN) + self.hidden_cb.set_style_margin_left(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) if known_hidden: self.hidden_cb.set_state(lv.STATE.CHECKED, True) diff --git a/internal_filesystem/lib/mpos/__init__.py b/internal_filesystem/lib/mpos/__init__.py index 9648961c..1b2cd401 100644 --- a/internal_filesystem/lib/mpos/__init__.py +++ b/internal_filesystem/lib/mpos/__init__.py @@ -31,11 +31,7 @@ from .ui.testing import ( ) # UI utility functions -from .ui.display import ( - pct_of_display_width, pct_of_display_height, - get_display_width, get_display_height, get_dpi, - min_resolution, max_resolution, get_pointer_xy -) +from .ui.display_metrics import DisplayMetrics from .ui.event import get_event_name, print_event from .ui.view import setContentView, back_screen from .ui.theme import set_theme @@ -73,10 +69,8 @@ __all__ = [ "SettingActivity", "SettingsActivity", "CameraActivity", # UI components "MposKeyboard", - # UI utility functions - "pct_of_display_width", "pct_of_display_height", - "get_display_width", "get_display_height", "get_dpi", - "min_resolution", "max_resolution", "get_pointer_xy", + # UI utility - DisplayMetrics + "DisplayMetrics", "get_event_name", "print_event", "setContentView", "back_screen", "set_theme", diff --git a/internal_filesystem/lib/mpos/ui/__init__.py b/internal_filesystem/lib/mpos/ui/__init__.py index 3d34f33b..dd94e407 100644 --- a/internal_filesystem/lib/mpos/ui/__init__.py +++ b/internal_filesystem/lib/mpos/ui/__init__.py @@ -6,12 +6,7 @@ from .gesture_navigation import handle_back_swipe, handle_top_swipe from .theme import set_theme from .topmenu import open_bar, close_bar, open_drawer, drawer_open, NOTIFICATION_BAR_HEIGHT from .focus import save_and_clear_current_focusgroup -from .display import ( - get_display_width, get_display_height, get_dpi, - pct_of_display_width, pct_of_display_height, - min_resolution, max_resolution, - get_pointer_xy # ← now correct -) +from .display_metrics import DisplayMetrics from .event import get_event_name, print_event from .util import shutdown, set_foreground_app, get_foreground_app from .setting_activity import SettingActivity @@ -28,10 +23,7 @@ __all__ = [ "set_theme", "open_bar", "close_bar", "open_drawer", "drawer_open", "NOTIFICATION_BAR_HEIGHT", "save_and_clear_current_focusgroup", - "get_display_width", "get_display_height", "get_dpi", - "pct_of_display_width", "pct_of_display_height", - "min_resolution", "max_resolution", - "get_pointer_xy", + "DisplayMetrics", "get_event_name", "print_event", "shutdown", "set_foreground_app", "get_foreground_app", "SettingActivity", diff --git a/internal_filesystem/lib/mpos/ui/camera_activity.py b/internal_filesystem/lib/mpos/ui/camera_activity.py index b416786d..f240b2c2 100644 --- a/internal_filesystem/lib/mpos/ui/camera_activity.py +++ b/internal_filesystem/lib/mpos/ui/camera_activity.py @@ -99,11 +99,11 @@ class CameraActivity(Activity): self.status_label_cont = lv.obj(self.main_screen) - width = mpos_ui.pct_of_display_width(70) - height = mpos_ui.pct_of_display_width(60) + width = mpos_ui.DisplayMetrics.pct_of_width(70) + height = mpos_ui.DisplayMetrics.pct_of_width(60) self.status_label_cont.set_size(width,height) - center_w = round((mpos_ui.pct_of_display_width(100) - self.button_width - 5 - width)/2) - center_h = round((mpos_ui.pct_of_display_height(100) - height)/2) + center_w = round((mpos_ui.DisplayMetrics.pct_of_width(100) - self.button_width - 5 - width)/2) + center_h = round((mpos_ui.DisplayMetrics.pct_of_height(100) - height)/2) self.status_label_cont.set_pos(center_w,center_h) self.status_label_cont.set_style_bg_color(lv.color_white(), 0) self.status_label_cont.set_style_bg_opa(66, 0) diff --git a/internal_filesystem/lib/mpos/ui/camera_settings.py b/internal_filesystem/lib/mpos/ui/camera_settings.py index f4b5f647..843b69cc 100644 --- a/internal_filesystem/lib/mpos/ui/camera_settings.py +++ b/internal_filesystem/lib/mpos/ui/camera_settings.py @@ -2,7 +2,7 @@ import lvgl as lv from ..config import SharedPreferences from ..app.activity import Activity -from .display import pct_of_display_width, pct_of_display_height +from .display import DisplayMetrics from . import anim class CameraSettingsActivity(Activity): @@ -125,7 +125,7 @@ class CameraSettingsActivity(Activity): # Create tabview tabview = lv.tabview(screen) - tabview.set_tab_bar_size(pct_of_display_height(15)) + tabview.set_tab_bar_size(DisplayMetrics.pct_of_height(15)) #tabview.set_size(lv.pct(100), pct_of_display_height(80)) # Create Basic tab (always) @@ -239,7 +239,7 @@ class CameraSettingsActivity(Activity): def add_buttons(self, parent): # Save/Cancel buttons at bottom button_cont = lv.obj(parent) - button_cont.set_size(lv.pct(100), pct_of_display_height(20)) + button_cont.set_size(lv.pct(100), DisplayMetrics.pct_of_height(20)) button_cont.remove_flag(lv.obj.FLAG.SCROLLABLE) button_cont.align(lv.ALIGN.BOTTOM_MID, 0, 0) button_cont.set_style_border_width(0, 0) @@ -256,9 +256,9 @@ class CameraSettingsActivity(Activity): save_label.center() cancel_button = lv.button(button_cont) - cancel_button.set_size(pct_of_display_width(25), lv.SIZE_CONTENT) + cancel_button.set_size(DisplayMetrics.pct_of_width(25), lv.SIZE_CONTENT) if self.scanqr_mode: - cancel_button.align(lv.ALIGN.BOTTOM_MID, pct_of_display_width(10), 0) + cancel_button.align(lv.ALIGN.BOTTOM_MID, DisplayMetrics.pct_of_width(10), 0) else: cancel_button.align(lv.ALIGN.BOTTOM_MID, 0, 0) cancel_button.add_event_cb(lambda e: self.finish(), lv.EVENT.CLICKED, None) @@ -267,7 +267,7 @@ class CameraSettingsActivity(Activity): cancel_label.center() erase_button = lv.button(button_cont) - erase_button.set_size(pct_of_display_width(20), lv.SIZE_CONTENT) + erase_button.set_size(DisplayMetrics.pct_of_width(20), lv.SIZE_CONTENT) erase_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0) erase_button.add_event_cb(lambda e: self.erase_and_close(), lv.EVENT.CLICKED, None) erase_label = lv.label(erase_button) diff --git a/internal_filesystem/lib/mpos/ui/display.py b/internal_filesystem/lib/mpos/ui/display.py index 3ee65fe6..22daeb8d 100644 --- a/internal_filesystem/lib/mpos/ui/display.py +++ b/internal_filesystem/lib/mpos/ui/display.py @@ -1,9 +1,12 @@ # lib/mpos/ui/display.py -import lvgl as lv +""" +Display initialization module. -_horizontal_resolution = None -_vertical_resolution = None -_dpi = None +Handles LVGL display initialization and sets up DisplayMetrics. +""" + +import lvgl as lv +from .display_metrics import DisplayMetrics # White text on black logo works (for dark mode) and can be inverted (for light mode) logo_white = "M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296_without_border_266x39.png" @@ -13,20 +16,27 @@ logo_white = "M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296_witho # Even when it's on a white (instead of transparent) background #logo_black = "M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-black-long-w240.png" + def init_rootscreen(): - global _horizontal_resolution, _vertical_resolution, _dpi + """Initialize the root screen and set display metrics.""" screen = lv.screen_active() disp = screen.get_display() - _horizontal_resolution = disp.get_horizontal_resolution() - _vertical_resolution = disp.get_vertical_resolution() - _dpi = disp.get_dpi() - print(f"init_rootscreen set resolution to {_horizontal_resolution}x{_vertical_resolution} at {_dpi} DPI") + width = disp.get_horizontal_resolution() + height = disp.get_vertical_resolution() + dpi = disp.get_dpi() + + # Initialize DisplayMetrics with actual display values + DisplayMetrics.set_resolution(width, height) + DisplayMetrics.set_dpi(dpi) + + print(f"init_rootscreen set resolution to {width}x{height} at {dpi} DPI") + try: img = lv.image(screen) img.set_src(logo_white) img.set_blend_mode(lv.BLEND_MODE.DIFFERENCE) img.center() - except Exception as e: # if image loading fails + except Exception as e: # if image loading fails print(f"ERROR: logo image failed, LVGL will be in a bad state and the UI will hang: {e}") import sys sys.print_exception(e) @@ -35,38 +45,3 @@ def init_rootscreen(): label.set_text("MicroPythonOS") label.set_style_text_font(lv.font_montserrat_20, lv.PART.MAIN) 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): - if pct == 100: - return _horizontal_resolution - return round(_horizontal_resolution * pct / 100) - -def pct_of_display_height(pct): - if pct == 100: - return _vertical_resolution - 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(): - return _horizontal_resolution - -def get_display_height(): - return _vertical_resolution - -def get_dpi(): - print(f"get_dpi_called {_dpi}") - return _dpi - diff --git a/internal_filesystem/lib/mpos/ui/display_metrics.py b/internal_filesystem/lib/mpos/ui/display_metrics.py new file mode 100644 index 00000000..94e3940c --- /dev/null +++ b/internal_filesystem/lib/mpos/ui/display_metrics.py @@ -0,0 +1,81 @@ +# lib/mpos/ui/display_metrics.py +""" +DisplayMetrics - Android-inspired display metrics singleton. + +Provides a clean, unified API for accessing display properties like width, height, and DPI. +All methods are class methods, so no instance creation is needed. +""" + + +class DisplayMetrics: + """ + Display metrics singleton (Android-inspired). + + Provides static/class methods for accessing display properties. + Initialized by display.init_rootscreen() which calls set_resolution() and set_dpi(). + """ + + _width = None + _height = None + _dpi = None + + @classmethod + def set_resolution(cls, width, height): + """Set the display resolution (called by init_rootscreen).""" + cls._width = width + cls._height = height + + @classmethod + def set_dpi(cls, dpi): + """Set the display DPI (called by init_rootscreen).""" + cls._dpi = dpi + + @classmethod + def width(cls): + """Get display width in pixels.""" + return cls._width + + @classmethod + def height(cls): + """Get display height in pixels.""" + return cls._height + + @classmethod + def dpi(cls): + """Get display DPI (dots per inch).""" + return cls._dpi + + @classmethod + def pct_of_width(cls, pct): + """Get percentage of display width.""" + if pct == 100: + return cls._width + return round(cls._width * pct / 100) + + @classmethod + def pct_of_height(cls, pct): + """Get percentage of display height.""" + if pct == 100: + return cls._height + return round(cls._height * pct / 100) + + @classmethod + def min_dimension(cls): + """Get minimum dimension (width or height).""" + return min(cls._width, cls._height) + + @classmethod + def max_dimension(cls): + """Get maximum dimension (width or height).""" + return max(cls._width, cls._height) + + @classmethod + def pointer_xy(cls): + """Get current pointer/touch coordinates.""" + import lvgl as lv + indev = lv.indev_active() + if indev: + p = lv.point_t() + indev.get_point(p) + return p.x, p.y + return -1, -1 diff --git a/internal_filesystem/lib/mpos/ui/gesture_navigation.py b/internal_filesystem/lib/mpos/ui/gesture_navigation.py index df95f6ed..71ff0eff 100644 --- a/internal_filesystem/lib/mpos/ui/gesture_navigation.py +++ b/internal_filesystem/lib/mpos/ui/gesture_navigation.py @@ -3,7 +3,7 @@ from lvgl import LvReferenceError from .anim import smooth_show, smooth_hide from .view import back_screen from mpos.ui import topmenu as topmenu -from .display import get_display_width, get_display_height +from .display import DisplayMetrics downbutton = None backbutton = None @@ -56,7 +56,7 @@ def _back_swipe_cb(event): if backbutton_visible: backbutton_visible = False smooth_hide(backbutton) - if x > get_display_width() / 5: + if x > DisplayMetrics.width() / 5: if topmenu.drawer_open : topmenu.close_drawer() else : @@ -97,7 +97,7 @@ def _top_swipe_cb(event): smooth_hide(downbutton) dx = abs(x - down_start_x) dy = abs(y - down_start_y) - if y > get_display_height() / 5: + if y > DisplayMetrics.height() / 5: topmenu.open_drawer() elif is_short_movement(dx, dy): # print("Short movement - treating as tap") diff --git a/internal_filesystem/lib/mpos/ui/setting_activity.py b/internal_filesystem/lib/mpos/ui/setting_activity.py index da12cd18..20f1c7bd 100644 --- a/internal_filesystem/lib/mpos/ui/setting_activity.py +++ b/internal_filesystem/lib/mpos/ui/setting_activity.py @@ -2,7 +2,7 @@ import lvgl as lv from ..app.activity import Activity from .camera_activity import CameraActivity -from .display import pct_of_display_width +from .display import DisplayMetrics from . import anim from ..camera_manager import CameraManager @@ -80,9 +80,9 @@ class SettingActivity(Activity): ui = "textarea" self.textarea = lv.textarea(settings_screen_detail) self.textarea.set_width(lv.pct(100)) - self.textarea.set_style_pad_all(pct_of_display_width(2), lv.PART.MAIN) - self.textarea.set_style_margin_left(pct_of_display_width(2), lv.PART.MAIN) - self.textarea.set_style_margin_right(pct_of_display_width(2), lv.PART.MAIN) + self.textarea.set_style_pad_all(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) + self.textarea.set_style_margin_left(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) + self.textarea.set_style_margin_right(DisplayMetrics.pct_of_width(2), lv.PART.MAIN) self.textarea.set_one_line(True) if current_setting: self.textarea.set_text(current_setting) diff --git a/internal_filesystem/lib/mpos/ui/settings_activity.py b/internal_filesystem/lib/mpos/ui/settings_activity.py index 6c760edd..a8e2fdc1 100644 --- a/internal_filesystem/lib/mpos/ui/settings_activity.py +++ b/internal_filesystem/lib/mpos/ui/settings_activity.py @@ -17,7 +17,7 @@ class SettingsActivity(Activity): print("creating SettingsActivity ui...") screen = lv.obj() - screen.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0) + screen.set_style_pad_all(mpos.ui.DisplayMetrics.pct_of_width(2), 0) screen.set_flex_flow(lv.FLEX_FLOW.COLUMN) screen.set_style_border_width(0, 0) self.setContentView(screen) @@ -43,7 +43,7 @@ class SettingsActivity(Activity): setting_cont.set_height(lv.SIZE_CONTENT) setting_cont.set_style_border_width(1, 0) #setting_cont.set_style_border_side(lv.BORDER_SIDE.BOTTOM, 0) - setting_cont.set_style_pad_all(mpos.ui.pct_of_display_width(2), 0) + setting_cont.set_style_pad_all(mpos.ui.DisplayMetrics.pct_of_width(2), 0) setting_cont.add_flag(lv.obj.FLAG.CLICKABLE) setting["cont"] = setting_cont # Store container reference for visibility control diff --git a/internal_filesystem/lib/mpos/ui/topmenu.py b/internal_filesystem/lib/mpos/ui/topmenu.py index 7584bc42..83e8a53f 100644 --- a/internal_filesystem/lib/mpos/ui/topmenu.py +++ b/internal_filesystem/lib/mpos/ui/topmenu.py @@ -2,7 +2,7 @@ import lvgl as lv import mpos.time import mpos.battery_voltage -from .display import (get_display_width, get_display_height, pct_of_display_width, pct_of_display_height, get_pointer_xy) +from .display_metrics import DisplayMetrics from .util import (get_foreground_app) from . import focus_direction from .anim import WidgetAnimator @@ -89,14 +89,14 @@ def create_notification_bar(): # Time label time_label = lv.label(notification_bar) time_label.set_text("00:00:00") - time_label.align(lv.ALIGN.LEFT_MID, pct_of_display_width(10), 0) + time_label.align(lv.ALIGN.LEFT_MID, DisplayMetrics.pct_of_width(10), 0) temp_label = lv.label(notification_bar) temp_label.set_text("00°C") - temp_label.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, pct_of_display_width(7) , 0) + temp_label.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, DisplayMetrics.pct_of_width(7) , 0) if False: memfree_label = lv.label(notification_bar) memfree_label.set_text("") - memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, pct_of_display_width(7), 0) + memfree_label.align_to(temp_label, lv.ALIGN.OUT_RIGHT_MID, DisplayMetrics.pct_of_width(7), 0) #style = lv.style_t() #style.init() #style.set_text_font(lv.font_montserrat_8) # tiny font @@ -114,12 +114,12 @@ def create_notification_bar(): 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, -pct_of_display_width(10), 0) + battery_icon.align(lv.ALIGN.RIGHT_MID, -DisplayMetrics.pct_of_width(10), 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, -pct_of_display_width(1), 0) + wifi_icon.align_to(battery_icon, lv.ALIGN.OUT_LEFT_MID, -DisplayMetrics.pct_of_width(1), 0) wifi_icon.add_flag(lv.obj.FLAG.HIDDEN) # Update time def update_time(timer): @@ -366,13 +366,13 @@ 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,get_display_height()) + l2.set_pos(0, DisplayMetrics.height()) def drawer_scroll_callback(event): global scroll_start_y event_code=event.get_code() - x, y = get_pointer_xy() + x, y = DisplayMetrics.pointer_xy() #name = mpos.ui.get_event_name(event_code) #print(f"drawer_scroll: code={event_code}, name={name}, ({x},{y})") if event_code == lv.EVENT.SCROLL_BEGIN and scroll_start_y == None: