pm-grid: start main.rs modularization (extract fonts module)

Move the 3x5 LED font (DIGITS, glyph, build_name_cols) into src/fonts.rs.
Pure code move, compiler-verified identical behavior; main.rs 1835 -> ~1770 lines.
First step of the recommended main.rs split; further extraction (FAT/MSC storage,
views) to continue incrementally as those areas are touched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Me Here 2026-06-04 11:24:09 -05:00
parent 72dbb2ecd0
commit bdf69cfd30
2 changed files with 80 additions and 75 deletions

78
rust/pm-grid/src/fonts.rs Normal file
View file

@ -0,0 +1,78 @@
//! 3x5 LED font (bit2 = leftmost column) — shared by the views, the scrolling name, and the splash.
//! Same bit convention as pico-scroll/app.py: glyph row value's bit `(1 << (2 - col))` lights a column.
use alloc::vec::Vec;
pub const DIGITS: [[u8; 5]; 10] = [
[7, 5, 5, 5, 7], // 0
[2, 6, 2, 2, 7], // 1
[7, 1, 7, 4, 7], // 2
[7, 1, 7, 1, 7], // 3
[5, 5, 7, 1, 1], // 4
[7, 4, 7, 1, 7], // 5
[7, 4, 7, 5, 7], // 6
[7, 1, 2, 2, 2], // 7
[7, 5, 7, 5, 7], // 8
[7, 5, 7, 1, 7], // 9
];
/// Full 3x5 uppercase glyph for a character (used by the scrolling name + boot splash).
/// Unknown characters render blank. Digits reuse `DIGITS`.
fn glyph(c: char) -> [u8; 5] {
match c {
'0'..='9' => DIGITS[c as usize - '0' as usize],
'A' => [2, 5, 7, 5, 5],
'B' => [6, 5, 6, 5, 6],
'C' => [3, 4, 4, 4, 3],
'D' => [6, 5, 5, 5, 6],
'E' => [7, 4, 6, 4, 7],
'F' => [7, 4, 6, 4, 4],
'G' => [7, 4, 5, 5, 7],
'H' => [5, 5, 7, 5, 5],
'I' => [7, 2, 2, 2, 7],
'J' => [1, 1, 1, 5, 2],
'K' => [5, 6, 4, 6, 5],
'L' => [4, 4, 4, 4, 7],
'M' => [5, 7, 7, 5, 5],
'N' => [5, 7, 7, 7, 5],
'O' => [2, 5, 5, 5, 2],
'P' => [7, 5, 7, 4, 4],
'Q' => [2, 5, 5, 6, 3],
'R' => [7, 5, 7, 6, 5],
'S' => [3, 4, 2, 1, 6],
'T' => [7, 2, 2, 2, 2],
'U' => [5, 5, 5, 5, 7],
'V' => [5, 5, 5, 5, 2],
'W' => [5, 5, 7, 7, 5],
'X' => [5, 5, 2, 5, 5],
'Y' => [5, 5, 2, 2, 2],
'Z' => [7, 1, 2, 4, 7],
'-' => [0, 0, 7, 0, 0],
'/' => [1, 1, 2, 4, 4],
'(' => [1, 2, 2, 2, 1],
')' => [4, 2, 2, 2, 4],
'.' => [0, 0, 0, 0, 2],
'+' => [0, 2, 7, 2, 0],
'&' => [2, 5, 2, 5, 3],
_ => [0, 0, 0, 0, 0], // space + anything unmapped
}
}
/// Render a string into a list of 5-bit column slices (3 columns per glyph + a 1px gap), for the
/// horizontal scrollers (Ticker name + boot splash). Lowercase is folded to uppercase.
pub fn build_name_cols(name: &str) -> Vec<u8> {
let mut cols = Vec::new();
for ch in name.chars() {
let g = glyph(ch.to_ascii_uppercase());
for c in 0..3 {
let mut col = 0u8;
for r in 0..5 {
if g[r] & (1 << (2 - c)) != 0 {
col |= 1 << r;
}
}
cols.push(col);
}
cols.push(0); // 1px gap between glyphs
}
cols
}

View file

@ -19,6 +19,8 @@
#![no_main]
extern crate alloc;
mod fonts;
use fonts::{build_name_cols, DIGITS};
use alloc::collections::VecDeque;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
@ -311,63 +313,6 @@ const APP_VERSION: &str = "0.1.0";
const BRIGHTNESS: u8 = 160; // accent
const NAME_BRIGHT: u8 = 120; // ticker name pixels
// ============================== FONTS (3x5; bit2 = leftmost column) ==============================
// Same bit convention as pico-scroll/app.py: glyph row value's bit (1<<(2-col)) lights that column.
const DIGITS: [[u8; 5]; 10] = [
[7, 5, 5, 5, 7], // 0
[2, 6, 2, 2, 7], // 1
[7, 1, 7, 4, 7], // 2
[7, 1, 7, 1, 7], // 3
[5, 5, 7, 1, 1], // 4
[7, 4, 7, 1, 7], // 5
[7, 4, 7, 5, 7], // 6
[7, 1, 2, 2, 2], // 7
[7, 5, 7, 5, 7], // 8
[7, 5, 7, 1, 7], // 9
];
/// Full 3x5 uppercase glyph for a character (used by the scrolling name + boot splash).
/// Unknown characters render blank. Digits reuse `DIGITS`.
fn glyph(c: char) -> [u8; 5] {
match c {
'0'..='9' => DIGITS[c as usize - '0' as usize],
'A' => [2, 5, 7, 5, 5],
'B' => [6, 5, 6, 5, 6],
'C' => [3, 4, 4, 4, 3],
'D' => [6, 5, 5, 5, 6],
'E' => [7, 4, 6, 4, 7],
'F' => [7, 4, 6, 4, 4],
'G' => [7, 4, 5, 5, 7],
'H' => [5, 5, 7, 5, 5],
'I' => [7, 2, 2, 2, 7],
'J' => [1, 1, 1, 5, 2],
'K' => [5, 6, 4, 6, 5],
'L' => [4, 4, 4, 4, 7],
'M' => [5, 7, 7, 5, 5],
'N' => [5, 7, 7, 7, 5],
'O' => [2, 5, 5, 5, 2],
'P' => [7, 5, 7, 4, 4],
'Q' => [2, 5, 5, 6, 3],
'R' => [7, 5, 7, 6, 5],
'S' => [3, 4, 2, 1, 6],
'T' => [7, 2, 2, 2, 2],
'U' => [5, 5, 5, 5, 7],
'V' => [5, 5, 5, 5, 2],
'W' => [5, 5, 7, 7, 5],
'X' => [5, 5, 2, 5, 5],
'Y' => [5, 5, 2, 2, 2],
'Z' => [7, 1, 2, 4, 7],
'-' => [0, 0, 7, 0, 0],
'/' => [1, 1, 2, 4, 4],
'(' => [1, 2, 2, 2, 1],
')' => [4, 2, 2, 2, 4],
'.' => [0, 0, 0, 0, 2],
'+' => [0, 2, 7, 2, 0],
'&' => [2, 5, 2, 5, 3],
_ => [0, 0, 0, 0, 0], // space + anything unmapped
}
}
// ============================== IS31FL3731 DRIVER (bulk framebuffer) ==============================
// Faithful port of pico-scroll/app.py's `Matrix`: keep a 144-byte PWM framebuffer and push the
// WHOLE thing in one I2C block write per frame (per-pixel I2C is far too slow to animate). The
@ -559,24 +504,6 @@ fn master_bar_ns(track: &track_format::Track, tempo: i64) -> i64 {
beat * beats.max(1)
}
fn build_name_cols(name: &str) -> Vec<u8> {
let mut cols = Vec::new();
for ch in name.chars() {
let g = glyph(ch.to_ascii_uppercase());
for c in 0..3 {
let mut col = 0u8;
for r in 0..5 {
if g[r] & (1 << (2 - c)) != 0 {
col |= 1 << r;
}
}
cols.push(col);
}
cols.push(0); // 1px gap between glyphs
}
cols
}
const SCROLL_GAP: i32 = 13; // blank columns between repeats (>= name region width) for a clean loop
impl App {