mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Introduce DisplayMetrics framework
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user