Skip to content

Commit be5a86f

Browse files
Update docs
1 parent 4207671 commit be5a86f

5 files changed

Lines changed: 108 additions & 14 deletions

File tree

docs/apps/app-lifecycle.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,32 @@ def onPause(self, screen):
140140

141141
**Important:** Call `super().onPause(screen)` to properly track foreground state.
142142

143+
### onBackPressed(screen)
144+
145+
Called when the user performs the back/close gesture (e.g. swiping from the left edge) **before** the activity is paused. This is the right place to ask the user for confirmation when there is unsaved work.
146+
147+
Return `True` to consume the event and keep the activity in the foreground. In that case the activity is responsible for calling `finish()` later when it is ready to close. Return `False` (the default) to let the framework finish the activity normally.
148+
149+
```python
150+
def onBackPressed(self, screen):
151+
if self._has_unsaved_changes():
152+
# Show a dialog; return True so the activity stays alive
153+
self._show_exit_confirm()
154+
return True
155+
return False
156+
```
157+
158+
In the dialog callback, call `self.finish()` to actually close the activity:
159+
160+
```python
161+
def _on_exit_confirmed(self, dialog):
162+
dialog.close()
163+
self._save_changes()
164+
self.finish()
165+
```
166+
167+
**Important:** `finish()` does **not** call `onBackPressed()` again, so your dialog callbacks can safely call it.
168+
143169
### onStop(screen)
144170

145171
Called when the activity is no longer visible (fully covered by another activity).
@@ -189,7 +215,7 @@ Activity Stack:
189215

190216
1. **Starting a new activity:** Current activity receives `onPause()``onStop()`, new activity receives `onCreate()``onStart()``onResume()`
191217

192-
2. **Going back:** Current activity receives `onPause()``onStop()``onDestroy()`, previous activity receives `onResume()`
218+
2. **Going back:** Framework first calls `onBackPressed()`. If it returns `True`, the activity stays foreground and must call `finish()` itself when ready. If it returns `False`, the current activity receives `onPause()``onStop()``onDestroy()`, and the previous activity receives `onResume()`
193219

194220
## Starting Activities
195221

@@ -404,6 +430,8 @@ class NetworkAwareActivity(Activity):
404430
✅ Check `has_foreground()` before updating UI from async operations
405431
✅ Use `setContentView()` at the end of `onCreate()`
406432
✅ Clean up resources in `onDestroy()`
433+
✅ Use `onBackPressed()` to ask before discarding unsaved changes
434+
407435

408436
### Don'ts
409437

@@ -412,6 +440,8 @@ class NetworkAwareActivity(Activity):
412440
❌ Don't update UI from background threads without `update_ui_threadsafe_if_foreground()`
413441
❌ Don't assume the activity is still visible after async operations
414442
❌ Don't store references to LVGL objects after `onDestroy()`
443+
❌ Don't show back-navigation confirmation dialogs in `onPause()` - use `onBackPressed()` instead
444+
415445

416446
## Services — Background Components
417447

@@ -460,6 +490,7 @@ Services are declared either programmatically (system services) or via `"service
460490
| `onStart(screen)` | `onStart()` | MicroPythonOS passes screen |
461491
| `onResume(screen)` | `onResume()` | MicroPythonOS passes screen |
462492
| `onPause(screen)` | `onPause()` | MicroPythonOS passes screen |
493+
| `onBackPressed(screen)` | `onBackPressed()` | MicroPythonOS passes screen; called before `onPause()` on back gesture |
463494
| `onStop(screen)` | `onStop()` | MicroPythonOS passes screen |
464495
| `onDestroy(screen)` | `onDestroy()` | MicroPythonOS passes screen |
465496
| `finish()` | `finish()` | Same purpose |

docs/apps/appstore.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ App discovery is currently done by downloading the app list from [apps.micropyth
1111
- **Image Viewer**: Displays images stored in `/data/images/`.
1212
- **IMU**: Visualize data from the Intertial Measurement Unit, also known as the accellerometer.
1313

14+
## Image Viewer
15+
16+
The **Image Viewer** app displays images stored in `/data/images/`. See [Supported File Formats](../other/supported-file-formats.md) for the list of image formats the OS can decode.
17+
1418
## Screenshots
1519

1620
<div class="grid">

docs/frameworks/font-manager.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# FontManager
22

3-
FontManager is a singleton framework for loading, caching, and composing LVGL fonts — including built-in bitmap fonts, TrueType fonts, and emoji image fonts. It uses LVGL's imgfont fallback mechanism to render 20×20 emoji PNGs inline with text, with nearest-neighbour scaling to match any font size.
3+
FontManager is a singleton framework for loading, caching, and composing LVGL fonts — including built-in bitmap fonts, TrueType fonts, and emoji image fonts. It uses LVGL's imgfont fallback mechanism to render 32×32 emoji PNGs inline with text, with nearest-neighbour scaling to match any font size.
44

55
## Overview
66

77
FontManager centralizes all font concerns in a single class:
88

99
- **Unified API** - One call (`getFont`) to get any font, with or without emoji support
10-
- **Emoji Compositing** - Transparently layers 20×20 emoji PNGs via LVGL's imgfont fallback, with nearest-neighbour scaling to match any font size
10+
- **Emoji Compositing** - Transparently layers 32×32 emoji PNGs via LVGL's imgfont fallback, with nearest-neighbour scaling to match any font size
1111
- **Lazy Caching** - Fonts and scaled image descriptors are cached on first use; no redundant work on subsequent calls
1212
- **Android-Inspired** - Follows the same singleton/class-method pattern as other MicroPythonOS frameworks
1313

@@ -29,9 +29,13 @@ ttf_font = FontManager.getFont(size=42, ttf="M:apps/com.myapp/assets/MyFont.ttf"
2929
for info in FontManager.listFonts():
3030
print(info["name"], info["size"])
3131

32-
# Get all available emoji codepoints
32+
# Get all available emoji codepoints (base codepoints only)
3333
for cp in FontManager.getEmojiCodepoints():
3434
print(hex(cp))
35+
36+
# Get all available emoji strings (full sequences, including flag pairs)
37+
for s in FontManager.getEmojiStrings():
38+
print(s)
3539
```
3640

3741
## Architecture
@@ -56,17 +60,18 @@ Emoji PNGs are stored in `builtin/res/emojis/`:
5660

5761
| Directory | Used for |
5862
|-----------|----------|
59-
| `20x20/` | All fonts — emoji are rendered at 20×20 px and nearest-neighbour scaled up or down by LVGL as needed |
63+
| `32x32/` | All fonts — emoji are rendered at 32×32 px and nearest-neighbour scaled up or down by LVGL as needed |
6064

61-
`_imgfont_path_cb` receives the rendering font's pixel height and returns the 20×20 emoji source. LVGL's software renderer performs nearest-neighbour scaling to fit the target font size.
65+
`_imgfont_path_cb` receives the rendering font's pixel height and returns the 32×32 emoji source. LVGL's software renderer performs nearest-neighbour scaling to fit the target font size.
6266

6367
### Caching layers
6468

6569
| Cache | Key | Value |
6670
|-------|-----|-------|
6771
| `_composed_font_cache` | `(font_id, emoji_size)` | Composed imgfont object |
6872
| `_ttf_font_cache` | `(path, size)` | `lv.tiny_ttf_create_file` result |
69-
| `_emoji_maps` | `dir_name` | `{codepoint: src_path}` dict |
73+
| `_emoji_map` | (none) | `{codepoint: src_path}` dict |
74+
| `_emoji_strings` | (none) | Sorted list of complete emoji strings |
7075
| `_imgfont_scaled_src_cache` | `(src, target_height)` | Scaled `lv.image_dsc_t` or original src |
7176
| `_imgfont_source_size_cache` | `src` | `(width, height)` tuple |
7277
| `_imgfont_empty_src_cache` | `target_height` | 1×h transparent `lv.image_dsc_t` |
@@ -128,7 +133,9 @@ for info in FontManager.listFonts(emojis=True):
128133

129134
### `getEmojiCodepoints()`
130135

131-
Return a sorted list of all available emoji codepoints.
136+
Return a sorted list of the base emoji codepoints available in the emoji map.
137+
138+
For multi-codepoint emoji such as flag sequences (e.g. `"🇸🇻"`), only the first codepoint is returned. Use `getEmojiStrings()` when you need complete, renderable emoji sequences.
132139

133140
**Returns:** list of int
134141

@@ -143,6 +150,25 @@ for cp in FontManager.getEmojiCodepoints():
143150

144151
---
145152

153+
### `getEmojiStrings()`
154+
155+
Return a sorted list of all available, complete emoji strings.
156+
157+
Unlike `getEmojiCodepoints()`, this returns full sequences: flag emoji include both regional indicators, and emoji with variation selectors keep their trailing selector. This is the preferred API for building a visual list of every supported emoji.
158+
159+
**Returns:** list of str
160+
161+
**Example:**
162+
163+
```python
164+
from mpos import FontManager
165+
166+
for s in FontManager.getEmojiStrings():
167+
print(s)
168+
```
169+
170+
---
171+
146172
### `normalizeEmojiText(text)`
147173

148174
Strip Unicode variation selectors (U+FE0E text selector, U+FE0F emoji selector) from a string. Useful before storing or comparing text that may have been pasted from a source that appends these codepoints.
@@ -167,20 +193,21 @@ To keep firmware image size small, the OS bundles ~50 of the most frequently-use
167193

168194
```
169195
builtin/res/emojis/
170-
└── 20x20/ # Pre-rendered at 20×20 px
196+
└── 32x32/ # Pre-rendered at 32×32 px
171197
├── 1F600.png
172-
├── 263A.png
198+
├── 1F3CE-FE0F.png
199+
├── 1F1F8-1F1FB.png
173200
└── ...
174201
```
175202

176-
Files are named by their Unicode codepoint in uppercase hex (e.g. `1F600.png` for 😀). FontManager scans the directory at runtime and builds a `{codepoint: path}` map.
203+
Files are named by their Unicode codepoint(s) in uppercase hex, with multiple codepoints joined by `-` (e.g. `1F600.png` for 😀, `1F3CE-FE0F.png` for 🏎️, `1F1F8-1F1FB.png` for 🇸🇻). FontManager scans the directory at runtime and builds a `{codepoint: path}` map.
177204

178205
### Adding new emoji
179206

180-
1. Add a PNG named `<CODEPOINT_HEX>.png` to `20x20/`:
207+
1. Add a PNG named `<CODEPOINT_HEX>.png` to `32x32/`:
181208

182209
```bash
183-
cp original.png 20x20/CODEPOINT.png
210+
cp original.png 32x32/CODEPOINT.png
184211
```
185212

186213
2. The new emoji will be picked up automatically on next boot — no code changes needed.
@@ -196,7 +223,7 @@ internal_filesystem/
196223
├── lib/mpos/ui/
197224
│ └── font_manager.py # FontManager class
198225
└── builtin/res/emojis/
199-
└── 20x20/ # 20×20 px emoji PNGs
226+
└── 32x32/ # 32×32 px emoji PNGs
200227
```
201228

202229
## Related Frameworks
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Supported File Formats
2+
3+
MicroPythonOS uses LVGL's built-in image decoders and its own audio stack. The formats below are supported out of the box for apps such as the Image Viewer and Music Player.
4+
5+
## Image formats
6+
7+
| Format | Extensions | Notes |
8+
|--------|------------|-------|
9+
| PNG | `.png` | Fully supported via LodePNG. |
10+
| Baseline JPEG | `.jpg`, `.jpeg` | Fully supported via TJpgD. |
11+
| Progressive JPEG | `.jpg`, `.jpeg` | **Not supported.** Files will decode as `0x0` and appear blank. |
12+
| RAW | `.raw` | Supported by the Image Viewer app if named as `<name>_<w>x<h>_RGB565.raw` or `<name>_<w>x<h>_GRAY.raw`. |
13+
14+
## Audio formats
15+
16+
| Format | Extensions | Notes |
17+
|--------|------------|-------|
18+
| Plain PCM WAV | `.wav` | Standard RIFF/WAVE with `WAVE_FORMAT_PCM` (0x0001) or `WAVE_FORMAT_EXTENSIBLE` (0xFFFE) and 16-bit samples. |
19+
| IMA ADPCM WAV | `.wav` | RIFF/WAVE with `WAVE_FORMAT_ADPCM` (0x0011), decoded by the built-in `adpcm_ima` module. Must have 4 or 16 bits per sample. |
20+
21+
### JPEG conversion
22+
23+
The built-in JPEG decoder only supports baseline JPEGs. The file extension is also treated case-sensitively, so `.JPG` or `.JPEG` will **not** be recognised.
24+
25+
To convert an image to a supported baseline JPEG:
26+
27+
```bash
28+
convert input.jpg -interlace none output.jpg
29+
```
30+
31+
`input.jpg` can be any image format that ImageMagick supports (including PNG), and `-interlace none` forces a non-progressive JPEG.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ nav:
9292
- Other:
9393
- Release Process: other/release-checklist.md
9494
- Merge Checklist: other/merge-checklist.md
95+
- Supported File Formats: other/supported-file-formats.md
9596
extra:
9697
social:
9798
- icon: fontawesome/brands/github

0 commit comments

Comments
 (0)