Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions src/launcher/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@


_TRANS = const("""[
{"en": "Paste", "zh": "粘贴", "ja": "貼り付け"},
{"en": "New Directory", "zh": "新建目录", "ja": "新しいディレクトリ"},
{"en": "New File", "zh": "新建文件", "ja": "新しいファイル"},
{"en": "Refresh", "zh": "刷新", "ja": "更新"},
{"en": "Exit to launcher", "zh": "退出到启动器", "ja": "ランチャーに戻る"},
{"en": "Directory name:", "zh": "目录名称:", "ja": "ディレクトリ名:"},
{"en": "File name:", "zh": "文件名称:", "ja": "ファイル名:"},
{"en": "Exiting...", "zh": "正在退出...", "ja": "終了中..."},
{"en": "open", "zh": "打开", "ja": "開く"},
{"en": "copy", "zh": "复制", "ja": "コピー"},
{"en": "rename", "zh": "重命名", "ja": "名前を変更"},
{"en": "delete", "zh": "删除", "ja": "削除"},
{"en": "Opening...", "zh": "正在打开...", "ja": "開いています..."}
{"en": "Paste", "zh": "粘贴", "ja": "貼り付け", "ua": "Вставити"},
{"en": "New Directory", "zh": "新建目录", "ja": "新しいディレクトリ", "ua": "Нова папка"},
{"en": "New File", "zh": "新建文件", "ja": "新しいファイル", "ua": "Новий файл"},
{"en": "Refresh", "zh": "刷新", "ja": "更新", "ua": "Оновити"},
{"en": "Exit to launcher", "zh": "退出到启动器", "ja": "ランチャーに戻る", "ua": "Вийти в меню"},
{"en": "Directory name:", "zh": "目录名称:", "ja": "ディレクトリ名:", "ua": "Назва папки"},
{"en": "File name:", "zh": "文件名称:", "ja": "ファイル名:", "ua": "Назва файлу"},
{"en": "Exiting...", "zh": "正在退出...", "ja": "終了中...", "ua": "Закриваємо..."},
{"en": "open", "zh": "打开", "ja": "開く", "ua": "відкрити"},
{"en": "copy", "zh": "复制", "ja": "コピー", "ua": "копіювати"},
{"en": "rename", "zh": "重命名", "ja": "名前を変更", "ua": "перейменувати"},
{"en": "delete", "zh": "删除", "ja": "削除", "ua": "видалити"},
{"en": "Opening...", "zh": "正在打开...", "ja": "開いています...", "ua": "Відкриваємо..."}
]""")


Expand Down
36 changes: 19 additions & 17 deletions src/launcher/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from launcher.icons import appicons
from lib import battlevel, display, sdcard, userinput
from lib.display.rawbitmap import RawBitmap
from lib.display.char_util import square_char
from lib.hydra import beeper, loader, statusbar
from lib.hydra.config import Config
from lib.hydra.i18n import I18n
Expand Down Expand Up @@ -91,19 +92,16 @@
_SCROLL_ANIMATION_QUICK = const(150)


_ASCII_MAX = const(128)


_TRANS = const("""[
{"en": "Loading...", "zh": "加载中...", "ja": "読み込み中..."},
{"en": "Files", "zh": "文件", "ja": "ファイル"},
{"en": "Terminal", "zh": "终端", "ja": "端末"},
{"en": "Get Apps", "zh": "应用商店", "ja": "アプリストア"},
{"en": "Reload Apps", "zh": "重新加载应用", "ja": "アプリ再読"},
{"en": "UI Sound", "zh": "界面声音", "ja": "UIサウンド"},
{"en": "Settings", "zh": "设置", "ja": "設定"},
{"en": "On", "zh": "开", "ja": "オン"},
{"en": "Off", "zh": "关", "ja": "オフ"}
{"en": "Loading...", "zh": "加载中...", "ja": "読み込み中...", "ua": "Завантаження..."},
{"en": "Files", "zh": "文件", "ja": "ファイル", "ua": "Файли"},
{"en": "Terminal", "zh": "终端", "ja": "端末", "ua": "Термінал"},
{"en": "Get Apps", "zh": "应用商店", "ja": "アプリストア", "ua": "Отримати ПЗ"},
{"en": "Reload Apps", "zh": "重新加载应用", "ja": "アプリ再読", "ua": "Оновити список"},
{"en": "UI Sound", "zh": "界面声音", "ja": "UIサウンド", "ua": "Звуки меню"},
{"en": "Settings", "zh": "设置", "ja": "設定", "ua": "Налаштування"},
{"en": "On", "zh": "开", "ja": "オン", "ua": "Ввімк"},
{"en": "Off", "zh": "关", "ja": "オフ", "ua": "Вимк"}
]""")


Expand Down Expand Up @@ -288,7 +286,7 @@ def launch_app(app_path):
if app_path.endswith(".cli.py"):
loader.launch_app(APP_PATHS['Terminal'], f"${app_path}")
loader.launch_app(app_path)


def center_text_x(text: str) -> int:
"""Calculate the x coordinate to draw a text string, to make it horizontally centered.
Expand All @@ -298,7 +296,7 @@ def center_text_x(text: str) -> int:
# calculate length
x = _DISPLAY_WIDTH_HALF
for char in text:
if ord(char) > _ASCII_MAX:
if square_char(ord(char)):
x -= _FONT_WIDTH
else:
x -= _FONT_WIDTH_HALF
Expand Down Expand Up @@ -506,7 +504,8 @@ def draw(self):
self.drawn_icon = self.next_icon

# if this is a custom icon, it needs to be loaded
if isinstance(self.drawn_icon, str) and self.drawn_icon.endswith(".raw"):
if isinstance(self.drawn_icon, str) and \
(self.drawn_icon.endswith(".wbmp") or self.drawn_icon.endswith(".raw")):
with open(self.drawn_icon, 'rb') as f:
f.readinto(self.buf)

Expand Down Expand Up @@ -545,8 +544,10 @@ def _choose_icon(self) -> int|str:
if not (current_app_path.endswith('.py') or current_app_path.endswith('.mpy')):
# too many ways for `os.listdir` to fail here, so just capture the error:
try:
if 'icon.raw' in os.listdir(current_app_path):
return RawBitmap(f"{current_app_path}/icon.raw", 32, 32, (CONFIG.palette[2], CONFIG.palette[8]))
if 'icon.wbmp' in os.listdir(current_app_path):
return RawBitmap(f"{current_app_path}/icon.wbmp", (CONFIG.palette[2], CONFIG.palette[8]))
elif 'icon.raw' in os.listdir(current_app_path):
return RawBitmap(f"{current_app_path}/icon.raw", (CONFIG.palette[2], CONFIG.palette[8]))
except OSError:
pass

Expand Down Expand Up @@ -823,3 +824,4 @@ def main_loop():

# run the main loop!
main_loop()

24 changes: 13 additions & 11 deletions src/launcher/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@

# this defines the translations passed to hydra.menu and hydra.popup
_TRANS = const("""[
{"en": "language", "zh": "语言/Lang", "ja": "言語/Lang"},
{"en": "volume", "zh": "音量", "ja": "音量"},
{"en": "ui_color", "zh": "UI颜色", "ja": "UIの色"},
{"en": "bg_color", "zh": "背景颜色", "ja": "背景色"},
{"en": "wifi_ssid", "zh": "WiFi名称", "ja": "WiFi名前"},
{"en": "wifi_pass", "zh": "WiFi密码", "ja": "WiFiパスワード"},
{"en": "sync_clock", "zh": "同步时钟", "ja": "時計同期"},
{"en": "24h_clock", "zh": "24小时制", "ja": "24時間制"},
{"en": "timezone", "zh": "时区", "ja": "タイムゾーン"},
{"en": "Confirm", "zh": "确认", "ja": "確認"}
{"en": "language", "zh": "语言/Lang", "ja": "言語/Lang", "ua": "Мова/Lang"},
{"en": "volume", "zh": "音量", "ja": "音量", "ua": "Гучність"},
{"en": "ui_color", "zh": "UI颜色", "ja": "UIの色", "ua": "Колір меню"},
{"en": "bg_color", "zh": "背景颜色", "ja": "背景色", "ua": "Колір фону"},
{"en": "wifi_ssid", "zh": "WiFi名称", "ja": "WiFi名前", "ua": "Мережа WiFi"},
{"en": "wifi_pass", "zh": "WiFi密码", "ja": "WiFiパスワード", "ua": "Пароль WiFi"},
{"en": "sync_clock", "zh": "同步时钟", "ja": "時計同期", "ua": "Синхр. часу"},
{"en": "24h_clock", "zh": "24小时制", "ja": "24時間制", "ua": "24 год. час"},
{"en": "timezone", "zh": "时区", "ja": "タイムゾーン", "ua": "Часовий пояс"},
{"en": "Confirm", "zh": "确认", "ja": "確認", "ua": "Зберегти"}
]""")


Expand All @@ -44,7 +44,7 @@
I18N = I18n(_TRANS)
overlay = UIOverlay(i18n=I18N)

LANGS = ['en', 'zh', 'ja']
LANGS = ['en', 'zh', 'ja', 'ua']
LANGS.sort()

# try mounting SDCard for settings import/export
Expand Down Expand Up @@ -189,3 +189,5 @@ def build_menu() -> hydramenu.Menu:
# this loop lets us restart the new menu if it is stopped/recreated by the callbacks above
while True:
menu.main()


25 changes: 25 additions & 0 deletions src/lib/display/char_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
def square_char(char: int) -> bool:
"""Checks if the character is square"""
# CJK Unified Ideographs
if 0x4E00 <= char <= 0x9FFF:
return True
# Hiragana
if 0x3040 <= char <= 0x309F:
return True
# Katakana
if 0x30A0 <= char <= 0x30FF:
return True
# Halfwidth and Fullwidth Forms (used for Katakana and punctuation)
if 0xFF00 <= char <= 0xFFEF:
return True
# CJK Compatibility Ideographs
if 0xF900 <= char <= 0xFAFF:
return True
# CJK Unified Ideographs Extension A
if 0x3400 <= char <= 0x4DBF:
return True
return False

def ascii_char(char: int) -> bool:
"""Checks if the character is in ASCII range"""
return char < 128
10 changes: 7 additions & 3 deletions src/lib/display/displaycore.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import framebuf
from .palette import Palette
from .char_util import square_char, ascii_char
import lib.hydra.config
from lib.hydra.utils import get_instance
from machine import PWM
Expand Down Expand Up @@ -412,8 +413,10 @@ def _bitmap_text(self, font, text, x:int, y:int, color:int):
@micropython.viper
def _utf8_putc(self, char:int, x:int, y:int, color:int, scale:int) -> int:
"""Render a single UTF8 character on the screen."""
width = 4 if char < 128 else 8
width = 4 if ascii_char(char) else 8
height = 8
if scale > 1 and not square_char(char):
scale = scale >> 1

if not 0x0000 <= char <= 0xFFFF:
return width * scale
Expand Down Expand Up @@ -494,7 +497,7 @@ def _utf8_text(self, text, x:int, y:int, color:int):
while idx < str_len:
char = text[idx]
ch_ord = int(ord(char))
if ch_ord >= 128:
if not ascii_char(ch_ord):
x += int(self._utf8_putc(ch_ord, x, y, color, 1))
else:
self.fbuf.text(char, x, y, color)
Expand Down Expand Up @@ -637,7 +640,7 @@ def _bitmap(self, bitmap, x:int, y:int, draw_width:int, draw_height:int, index:i
btmp_x = (x_idx - x)*btmp_width // draw_width

# Find the start bit for the pixel we want
btmp_bit_idx = starting_bit + (btmp_y*btmp_width + btmp_x) * bpp
btmp_bit_idx = starting_bit + (btmp_y * ((btmp_width * bpp + 7) & ~7)) + btmp_x * bpp
# calculate the byte, and byte shift needed to read that bit
btmp_byte_idx = btmp_bit_idx // 8
byte_shift = 7 - (btmp_bit_idx % 8)
Expand Down Expand Up @@ -669,3 +672,4 @@ def _bitmap(self, bitmap, x:int, y:int, draw_width:int, draw_height:int, index:i

x_idx += 1
y_idx+=1

38 changes: 34 additions & 4 deletions src/lib/display/rawbitmap.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
"""Object class for loading/structuring a raw bitmap file for use with the Display driver."""
"""Object class for loading/structuring a bitmap file for use with the Display driver."""
import os


class RawBitmap:
"""Open a raw bitmap file for use with the Display core."""

cached_path = None
cached_w = None
cached_h = None
cache = None

def __init__(self, file_path: str, width: int, height: int, palette: list[int, ...]):
def __init__(self, file_path: str, palette: list[int, ...]):
"""Construct the bitmap from given file."""
self.WIDTH = width
self.HEIGHT = height
self.PALETTE = palette

# This assumes that the bits per pixel is always the minimum possible:
Expand All @@ -23,19 +23,49 @@ def __init__(self, file_path: str, width: int, height: int, palette: list[int, .
# Use the cached buffer rather than reloading
self.size = len(RawBitmap.cache)
self.BITMAP = RawBitmap.cache
self.WIDTH = RawBitmap.cached_w
self.HEIGHT = RawBitmap.cached_h
else:
# Load and cache a new buffer
self.size = os.stat(file_path)[6]
with open(file_path, 'rb') as f:
buf = bytearray(self.size)
f.readinto(buf)
if file_path.endswith('.wbmp'):
self.wbmp2bitmap(buf)
else:
self.BITMAP = memoryview(buf)
self.WIDTH = 32
self.HEIGHT = 32

RawBitmap.cached_path = file_path
RawBitmap.cache = self.BITMAP
RawBitmap.cached_w = self.WIDTH
RawBitmap.cached_h = self.HEIGHT

def wbmp2bitmap(self, buf):
idx = 2
self.WIDTH, idx = self.read_mbi(buf, idx)
self.HEIGHT, idx = self.read_mbi(buf, idx)
self.BITMAP = memoryview(buf[idx:])

@staticmethod
def read_mbi(data, index):
"""Reads multi-byte integer starting at index."""
value = 0
while True:
byte = data[index]
index += 1
value = (value << 7) | (byte & 0x7F)
if not (byte & 0x80): # if high bit is 0, stop reading
break
return value, index

@classmethod
def clean(cls):
"""Clear the bitmap cache."""
cls.cached_path = None
cls.cached_w = None
cls.cached_h = None
cls.cache = None