PM_X-1 0.0.2: portrait flip + Kit-style layout (no more overlapping bits)
The 320x240 landscape layout was too cramped vertically; multiple elements sat on the same Y rows, especially the BPM big-number area and the bar/time meters, plus the run dot collided with the MIDI/USB icons. Switching to portrait at 240x320 (display.rotation = 270; user holds the device with A/B/C buttons along the top) gives the same vertical real estate the PM_K-1 Kit's portrait UI uses, just narrower. Layout now mirrors the Kit: - y 0..28: header (logo + version + MIDI/USB icons + run dot) - y 44: BPM big (right) - y 50: elapsed time (left, FONT_M) - y 78: bar counter (left, FONT_M) - y 100: ramp / gap-trainer indicators - y 118: setlist tab + CONT (single row) - y 134: track title (FONT_M) - y 138+: pad grid (up to 6 lanes, taller rowh ceiling now 30px) Plus: - DISPLAY_ROTATION constant near the top of CONFIG so the user can flip it to 90 / 180 if their orientation differs. - Pad grid uses px0=48 (was 60) since the lane label column has less horizontal room at 240 width; max 6-char labels. - Removed the inline modal hints (e.g. "X/Z move, A select, C close") that would have collided with the modal titles at 240 width. The Help screen documents the modal nav pattern, which is consistent across modals. - HELP_PAGES page 1 leads with "Hold portrait with A/B/C on top." - README documents the rotation flag. Bumps Explorer to 0.0.2. .mpy can be pushed via the editor's Update firmware flow (device id reply = X;0.0.1 -> editor fetches the right .mpy). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
05ce1d5ce4
commit
51c81b45e0
2 changed files with 61 additions and 53 deletions
|
|
@ -4,10 +4,16 @@ The **CircuitPython** firmware for the [Pimoroni Explorer Kit (PIM744)](https://
|
||||||
set up as a self-contained appliance. Sibling to the PM_K-1 build in `../pico-cp/` (the 52Pi EP-0172
|
set up as a self-contained appliance. Sibling to the PM_K-1 build in `../pico-cp/` (the 52Pi EP-0172
|
||||||
kit) — same engine, same program strings, same `programs.json`, same web editor.
|
kit) — same engine, same program strings, same `programs.json`, same web editor.
|
||||||
|
|
||||||
This board is a **2.8″ ST7789V 320×240 LCD + 6 user buttons (A/B/C on the left, X/Y/Z on the right)
|
This board is a **2.8″ ST7789V LCD + 6 user buttons + piezo speaker** built around an RP2350B
|
||||||
+ piezo speaker** built around an RP2350B (Pico 2 class chip). **No touchscreen, no joystick, no
|
(Pico 2 class chip). **No touchscreen, no joystick, no RGB LED.** Editing is done in the web
|
||||||
RGB LED.** Editing is done in the web editor with **Live sync** on; the device mirrors changes in
|
editor with **Live sync** on; the device mirrors changes in real time and emits its own
|
||||||
real time and emits its own play/stop/bpm/sel deltas back.
|
play/stop/bpm/sel deltas back.
|
||||||
|
|
||||||
|
**Hold the device in portrait** with the A/B/C buttons along the top and X/Y/Z along the
|
||||||
|
bottom. The firmware drives the LCD as a **240 × 320 portrait** at `display.rotation = 270` —
|
||||||
|
same UI shape as the PM_K-1 Kit, just shorter. If the screen comes up upside-down on your
|
||||||
|
unit, change `DISPLAY_ROTATION` near the top of `app.py` to `90` (or `180` if rotated 180°)
|
||||||
|
and re-flash.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
import board, busio, digitalio, pwmio, displayio, vectorio, time, json, gc, os, supervisor
|
import board, busio, digitalio, pwmio, displayio, vectorio, time, json, gc, os, supervisor
|
||||||
supervisor.runtime.autoreload = False # we write our own files (log + pushed programs); never self-restart
|
supervisor.runtime.autoreload = False # we write our own files (log + pushed programs); never self-restart
|
||||||
APP_VERSION = "0.0.1" # firmware version (the A/B updater pushes/compares this)
|
APP_VERSION = "0.0.2" # firmware version (the A/B updater pushes/compares this)
|
||||||
DEVICE_ID = "X" # 'X' = Explorer, 'K' = 52Pi kit (per docs/livesync-protocol.md and the version reply)
|
DEVICE_ID = "X" # 'X' = Explorer, 'K' = 52Pi kit (per docs/livesync-protocol.md and the version reply)
|
||||||
try:
|
try:
|
||||||
import rtc # set from the editor's clock SysEx so the log has real timestamps
|
import rtc # set from the editor's clock SysEx so the log has real timestamps
|
||||||
|
|
@ -37,6 +37,8 @@ MIDI_CLOCK_IN = False # follow an external 24 PPQN clock
|
||||||
MIDI_CLOCK_IN_TRANSPORT = True
|
MIDI_CLOCK_IN_TRANSPORT = True
|
||||||
MUTE_SPEAKER = False # always silence the on-board piezo
|
MUTE_SPEAKER = False # always silence the on-board piezo
|
||||||
SPEAKER_AUTO_MUTE = True # auto-mute the piezo when a MIDI host is listening
|
SPEAKER_AUTO_MUTE = True # auto-mute the piezo when a MIDI host is listening
|
||||||
|
DISPLAY_ROTATION = 270 # 0=native landscape; 90/270 = portrait. Hold the device with A/B/C
|
||||||
|
# on top: try 270 first; if upside-down try 90; 180 = flipped landscape.
|
||||||
|
|
||||||
# ----- pins (Pimoroni Explorer board layout) -----
|
# ----- pins (Pimoroni Explorer board layout) -----
|
||||||
P_AUDIO = board.GP12 # piezo PWM (variable frequency)
|
P_AUDIO = board.GP12 # piezo PWM (variable frequency)
|
||||||
|
|
@ -45,13 +47,14 @@ P_BTNA, P_BTNB, P_BTNC = board.GP16, board.GP15, board.GP14 # left-side button
|
||||||
P_BTNX, P_BTNY, P_BTNZ = board.GP17, board.GP18, board.GP19 # right-side buttons (top to bottom)
|
P_BTNX, P_BTNY, P_BTNZ = board.GP17, board.GP18, board.GP19 # right-side buttons (top to bottom)
|
||||||
P_SDA, P_SCL = board.GP20, board.GP21 # QwSTEMMA (unused by the firmware - future expansion)
|
P_SDA, P_SCL = board.GP20, board.GP21 # QwSTEMMA (unused by the firmware - future expansion)
|
||||||
|
|
||||||
# Display is initialised by the board definition (8-bit parallel bus, 320x240 landscape).
|
# Display is initialised by the board definition (8-bit parallel bus). We grab board.DISPLAY +
|
||||||
# We grab it via board.DISPLAY rather than rolling our own init sequence.
|
# call display.rotation = DISPLAY_ROTATION (above) to turn it portrait so the layout has the same
|
||||||
WIDTH, HEIGHT = 320, 240
|
# shape as the PM_K-1 Kit's portrait UI but in 240x320 instead of 320x480.
|
||||||
GRID_TOP = 100 # top of the pad grid (header + meters fit above)
|
WIDTH, HEIGHT = 240, 320
|
||||||
|
GRID_TOP = 138 # top of the pad grid (header + meters + title fit above)
|
||||||
MAXLANES = 6 # lanes shown on the pad grid (parser still accepts more; they just play silent visually)
|
MAXLANES = 6 # lanes shown on the pad grid (parser still accepts more; they just play silent visually)
|
||||||
MIN_LOG_SEC = 5 # don't log plays shorter than this
|
MIN_LOG_SEC = 5 # don't log plays shorter than this
|
||||||
LOG_MENU_ROWS = 7 # log entries shown in the Practice-log menu screen
|
LOG_MENU_ROWS = 8 # log entries shown in the Practice-log menu screen (more vertical room in portrait)
|
||||||
|
|
||||||
# ----- BUILT-IN playlists: same defaults as the Kit so the two firmwares feel identical -----
|
# ----- BUILT-IN playlists: same defaults as the Kit so the two firmwares feel identical -----
|
||||||
BUILTIN_SETLISTS = [
|
BUILTIN_SETLISTS = [
|
||||||
|
|
@ -100,13 +103,14 @@ SOUND_GM = {"kick":36,"kick808":36,"kick909":36, "snare":38,"snare808":38,"snare
|
||||||
GM_DEFAULT = 37
|
GM_DEFAULT = 37
|
||||||
HELP_PAGES = (
|
HELP_PAGES = (
|
||||||
("Transport & Navigation", (
|
("Transport & Navigation", (
|
||||||
|
"Hold portrait with A/B/C on top.",
|
||||||
"A: play / stop",
|
"A: play / stop",
|
||||||
"B: tap tempo",
|
"B: tap tempo",
|
||||||
"C: menu (this)",
|
"C: menu (this)",
|
||||||
"X: prev track (hold to repeat)",
|
"X: prev track (hold to repeat)",
|
||||||
"Z: next track (hold to repeat)",
|
"Z: next track (hold to repeat)",
|
||||||
"Y: tempo -1 (hold = -5 after 1.5s)",
|
"Y: tempo -1 (-5 after 1.5s held)",
|
||||||
"X+Z chord: tempo +1 (same hold rule)",
|
"X+Z chord: tempo +1",
|
||||||
)),
|
)),
|
||||||
("Menu navigation", (
|
("Menu navigation", (
|
||||||
"X / Z: move cursor up / down",
|
"X / Z: move cursor up / down",
|
||||||
|
|
@ -321,6 +325,8 @@ def rect(x, y, w, h, color):
|
||||||
class App:
|
class App:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.display = board.DISPLAY # board.c built the BusDisplay; we just use it
|
self.display = board.DISPLAY # board.c built the BusDisplay; we just use it
|
||||||
|
try: self.display.rotation = DISPLAY_ROTATION # turn portrait (240x320) - same shape as the Kit's UI
|
||||||
|
except Exception: pass
|
||||||
try: self.display.auto_refresh = False # we manage refresh in run() (predictive skip + ~20Hz throttle)
|
try: self.display.auto_refresh = False # we manage refresh in run() (predictive skip + ~20Hz throttle)
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
self.i2c = busio.I2C(scl=P_SCL, sda=P_SDA, frequency=400_000) # QwSTEMMA - unused by the firmware, available to user code
|
self.i2c = busio.I2C(scl=P_SCL, sda=P_SDA, frequency=400_000) # QwSTEMMA - unused by the firmware, available to user code
|
||||||
|
|
@ -388,40 +394,38 @@ class App:
|
||||||
d = digitalio.DigitalInOut(pin); d.direction = digitalio.Direction.INPUT; d.pull = digitalio.Pull.UP
|
d = digitalio.DigitalInOut(pin); d.direction = digitalio.Direction.INPUT; d.pull = digitalio.Pull.UP
|
||||||
return d
|
return d
|
||||||
|
|
||||||
# ---------- scene graph (320x240 landscape) ----------
|
# ---------- scene graph (240x320 portrait; same shape as the Kit's UI, shorter) ----------
|
||||||
def _build_scene(self):
|
def _build_scene(self):
|
||||||
root = displayio.Group(); self.display.root_group = root
|
root = displayio.Group(); self.display.root_group = root
|
||||||
root.append(rect(0, 0, WIDTH, HEIGHT, C_BG))
|
root.append(rect(0, 0, WIDTH, HEIGHT, C_BG))
|
||||||
# Header (y 0..28): VARASYS logo + version + run dot + MIDI/USB badges
|
# Header (y 0..28): VARASYS logo + version + (right edge) MIDI/USB badges + run dot
|
||||||
if LOGO:
|
if LOGO:
|
||||||
tg, _p, lw, lh = make_glyph(LOGO, C_CYAN, C_BG); tg.x = 8; tg.y = 6; root.append(tg)
|
tg, _p, lw, lh = make_glyph(LOGO, C_CYAN, C_BG); tg.x = 8; tg.y = 8; root.append(tg)
|
||||||
lx = 8 + lw
|
lx = 8 + lw
|
||||||
else:
|
else:
|
||||||
tg, w, h = make_text("VARASYS", FONT_M, C_CYAN, C_BG); tg.x = 8; tg.y = 6; root.append(tg)
|
tg, w, h = make_text("VARASYS", FONT_M, C_CYAN, C_BG); tg.x = 8; tg.y = 8; root.append(tg)
|
||||||
lx = 8 + w
|
lx = 8 + w
|
||||||
vtg, vw, vh = make_text("v" + APP_VERSION, FONT_S, C_DIM, C_BG); vtg.x = lx + 6; vtg.y = 8; root.append(vtg)
|
vtg, vw, vh = make_text("v" + APP_VERSION, FONT_S, C_DIM, C_BG); vtg.x = lx + 4; vtg.y = 10; root.append(vtg)
|
||||||
# MIDI/USB icons + run dot at the right of the header
|
# Run dot + MIDI/USB icons packed at the right edge (replaces the Kit's hamburger; C button opens the menu)
|
||||||
x = WIDTH - 10
|
self.run_dot_pal = displayio.Palette(1); self.run_dot_pal[0] = C_RUN_IDLE
|
||||||
|
self.run_dot = vectorio.Circle(pixel_shader=self.run_dot_pal, radius=4, x=WIDTH - 12, y=14)
|
||||||
|
root.append(self.run_dot)
|
||||||
|
x = WIDTH - 22 # icons live to the LEFT of the run dot
|
||||||
for asset, attr in ((ICON_USB, "ic_usb_pal"), (ICON_MIDI, "ic_midi_pal")):
|
for asset, attr in ((ICON_USB, "ic_usb_pal"), (ICON_MIDI, "ic_midi_pal")):
|
||||||
if asset:
|
if asset:
|
||||||
tg, pal, w, h = make_glyph(asset, C_DIM, C_BG); x -= w; tg.x = x; tg.y = 6; x -= 6
|
tg, pal, w, h = make_glyph(asset, C_DIM, C_BG); x -= w; tg.x = x; tg.y = 8; x -= 6
|
||||||
root.append(tg); setattr(self, attr, pal)
|
root.append(tg); setattr(self, attr, pal)
|
||||||
# Run dot at the far right
|
root.append(rect(0, 28, WIDTH, 1, C_PANEL)) # header divider
|
||||||
self.run_dot_pal = displayio.Palette(1); self.run_dot_pal[0] = C_RUN_IDLE
|
# Dynamic groups - layout order matches the Kit (BPM big at top-right + meters left;
|
||||||
x -= 10
|
# then ramp/trainer indicators; then setlist tab + CONT; then track title; then pad grid).
|
||||||
self.run_dot = vectorio.Circle(pixel_shader=self.run_dot_pal, radius=4, x=x, y=14)
|
self.g_bpm = displayio.Group(); root.append(self.g_bpm) # big BPM (right, y ~44)
|
||||||
root.append(self.run_dot)
|
self.g_time = displayio.Group(); root.append(self.g_time) # elapsed [of total] (left, y ~50)
|
||||||
# Header divider
|
self.g_bar = displayio.Group(); root.append(self.g_bar) # bar [of total] (left, y ~78)
|
||||||
root.append(rect(0, 28, WIDTH, 1, C_PANEL))
|
self.g_train = displayio.Group(); root.append(self.g_train) # ramp / gap-trainer indicators (y ~100)
|
||||||
# dynamic groups
|
self.g_idx = displayio.Group(); root.append(self.g_idx) # set-list tab (y ~118)
|
||||||
self.g_idx = displayio.Group(); root.append(self.g_idx) # set-list tab (left of title row)
|
self.g_cont = displayio.Group(); root.append(self.g_cont) # CONT (auto-advance) toggle (y ~118)
|
||||||
self.g_cont = displayio.Group(); root.append(self.g_cont) # CONT (auto-advance) toggle indicator
|
self.g_name = displayio.Group(); root.append(self.g_name) # track title (y ~134)
|
||||||
self.g_name = displayio.Group(); root.append(self.g_name) # track title
|
self.g_grid = displayio.Group(); root.append(self.g_grid) # lanes x step pads (y >= GRID_TOP=138)
|
||||||
self.g_bpm = displayio.Group(); root.append(self.g_bpm) # big tempo (right)
|
|
||||||
self.g_time = displayio.Group(); root.append(self.g_time) # elapsed [of total] (left)
|
|
||||||
self.g_bar = displayio.Group(); root.append(self.g_bar) # bar [of total] (left)
|
|
||||||
self.g_train = displayio.Group(); root.append(self.g_train) # ramp / gap-trainer indicators
|
|
||||||
self.g_grid = displayio.Group(); root.append(self.g_grid) # lanes x step pads
|
|
||||||
self.g_overlay = displayio.Group(); root.append(self.g_overlay) # modals (drawn on top)
|
self.g_overlay = displayio.Group(); root.append(self.g_overlay) # modals (drawn on top)
|
||||||
|
|
||||||
def _place(self, group, s, x, y, fg, bg, font, right_edge=None):
|
def _place(self, group, s, x, y, fg, bg, font, right_edge=None):
|
||||||
|
|
@ -503,8 +507,6 @@ class App:
|
||||||
PH = 24 + len(rows) * RH + 18
|
PH = 24 + len(rows) * RH + 18
|
||||||
g.append(rect(PX, PY, PW, PH, C_PANEL)); g.append(rect(PX, PY, PW, 2, C_CYAN))
|
g.append(rect(PX, PY, PW, PH, C_PANEL)); g.append(rect(PX, PY, PW, 2, C_CYAN))
|
||||||
t, w, h = make_text("Menu", FONT_M, C_TXT, C_PANEL); t.x = PX + 12; t.y = PY + 6; g.append(t)
|
t, w, h = make_text("Menu", FONT_M, C_TXT, C_PANEL); t.x = PX + 12; t.y = PY + 6; g.append(t)
|
||||||
ht, hw, hh = make_text("X / Z move, A select, C close", FONT_S, C_DIM, C_PANEL)
|
|
||||||
ht.x = PX + PW - hw - 12; ht.y = PY + 10; g.append(ht)
|
|
||||||
for i, (label, _v, _act) in enumerate(rows):
|
for i, (label, _v, _act) in enumerate(rows):
|
||||||
yy = PY + 26 + i * RH
|
yy = PY + 26 + i * RH
|
||||||
sel = (i == self._modal_cursor)
|
sel = (i == self._modal_cursor)
|
||||||
|
|
@ -538,8 +540,6 @@ class App:
|
||||||
PH = 24 + len(rows) * RH + 14
|
PH = 24 + len(rows) * RH + 14
|
||||||
g.append(rect(PX, PY, PW, PH, C_PANEL)); g.append(rect(PX, PY, PW, 2, C_CYAN))
|
g.append(rect(PX, PY, PW, PH, C_PANEL)); g.append(rect(PX, PY, PW, 2, C_CYAN))
|
||||||
t, w, h = make_text("Settings", FONT_M, C_TXT, C_PANEL); t.x = PX + 12; t.y = PY + 5; g.append(t)
|
t, w, h = make_text("Settings", FONT_M, C_TXT, C_PANEL); t.x = PX + 12; t.y = PY + 5; g.append(t)
|
||||||
ht, hw, hh = make_text("X/Z row, Y -, A +, B back", FONT_S, C_DIM, C_PANEL)
|
|
||||||
ht.x = PX + PW - hw - 12; ht.y = PY + 10; g.append(ht)
|
|
||||||
for i, (label, value, _adj) in enumerate(rows):
|
for i, (label, value, _adj) in enumerate(rows):
|
||||||
yy = PY + 26 + i * RH
|
yy = PY + 26 + i * RH
|
||||||
sel = (i == self._modal_cursor)
|
sel = (i == self._modal_cursor)
|
||||||
|
|
@ -1128,17 +1128,19 @@ class App:
|
||||||
def draw_bpm(self):
|
def draw_bpm(self):
|
||||||
if self.bpm == self._displayed_bpm: return
|
if self.bpm == self._displayed_bpm: return
|
||||||
self._displayed_bpm = self.bpm
|
self._displayed_bpm = self.bpm
|
||||||
self._place(self.g_bpm, str(self.bpm), 0, 56, C_TXT, C_BG, FONT_L, right_edge=WIDTH-10)
|
self._place(self.g_bpm, str(self.bpm), 0, 44, C_TXT, C_BG, FONT_L, right_edge=WIDTH-10)
|
||||||
def draw_status(self):
|
def draw_status(self):
|
||||||
sl = self.setlists[self.sl]
|
sl = self.setlists[self.sl]
|
||||||
self._place(self.g_idx, "%s %d/%d" % (sl['title'][:14], self.idx + 1, len(sl['items'])),
|
# setlist tab line at y=118 (matches the Kit's spacing); muted = built-in, cyan = your own
|
||||||
10, 32, C_MUTE if sl['builtin'] else C_CYAN, C_BG, FONT_S)
|
self._place(self.g_idx, "%s %d/%d" % (sl['title'][:13], self.idx + 1, len(sl['items'])),
|
||||||
self._place(self.g_cont, "CONT", 0, 32, C_GREEN if self.continue_on else C_DIM, C_BG, FONT_S, right_edge=WIDTH-10)
|
10, 118, C_MUTE if sl['builtin'] else C_CYAN, C_BG, FONT_S)
|
||||||
self._place(self.g_name, self.name[:22], 10, 48, C_TXT, C_BG, FONT_M)
|
self._place(self.g_cont, "CONT", 0, 118, C_GREEN if self.continue_on else C_DIM, C_BG, FONT_S, right_edge=WIDTH-10)
|
||||||
|
# track title at y=134 (FONT_M; ~16 px tall, fits above GRID_TOP=138)
|
||||||
|
self._place(self.g_name, self.name[:20], 10, 134, C_TXT, C_BG, FONT_M)
|
||||||
def draw_train(self):
|
def draw_train(self):
|
||||||
g = self.g_train
|
g = self.g_train
|
||||||
while len(g): g.pop()
|
while len(g): g.pop()
|
||||||
x = 10; y = 84
|
x = 10; y = 100 # ramp / gap-trainer indicators on a single row above the setlist tab
|
||||||
if self.ramp:
|
if self.ramp:
|
||||||
up = self.ramp['amt'] >= 0
|
up = self.ramp['amt'] >= 0
|
||||||
pts = [(0, 9), (12, 9), (12, 0)] if up else [(0, 0), (0, 9), (12, 9)]
|
pts = [(0, 9), (12, 9), (12, 0)] if up else [(0, 0), (0, 9), (12, 9)]
|
||||||
|
|
@ -1173,9 +1175,9 @@ class App:
|
||||||
else:
|
else:
|
||||||
ts = self._fmt_t(el); bs = "bar %s" % cur
|
ts = self._fmt_t(el); bs = "bar %s" % cur
|
||||||
if ts != self._lastTs:
|
if ts != self._lastTs:
|
||||||
self._place(self.g_time, ts, 10, 66, C_TXT, C_BG, FONT_S); self._lastTs = ts
|
self._place(self.g_time, ts, 10, 50, C_TXT, C_BG, FONT_M); self._lastTs = ts
|
||||||
if bs != self._lastBs:
|
if bs != self._lastBs:
|
||||||
self._place(self.g_bar, bs, 10, 80, C_MUTE, C_BG, FONT_S); self._lastBs = bs
|
self._place(self.g_bar, bs, 10, 78, C_MUTE, C_BG, FONT_M); self._lastBs = bs
|
||||||
|
|
||||||
# ---------- pad grid (chunked rebuild; per-pad chunks so audio interleaves) ----------
|
# ---------- pad grid (chunked rebuild; per-pad chunks so audio interleaves) ----------
|
||||||
def _padbase(self, L, s):
|
def _padbase(self, L, s):
|
||||||
|
|
@ -1188,8 +1190,8 @@ class App:
|
||||||
self.lane_pads = []; self.lane_lit = []
|
self.lane_pads = []; self.lane_lit = []
|
||||||
gc.collect()
|
gc.collect()
|
||||||
n = min(len(self.lanes), MAXLANES)
|
n = min(len(self.lanes), MAXLANES)
|
||||||
top = GRID_TOP; rowh = min(22, ((HEIGHT - 6) - top) // max(1, n))
|
top = GRID_TOP; rowh = min(30, ((HEIGHT - 6) - top) // max(1, n)) # more vertical room in portrait -> taller rows
|
||||||
px0 = 60; usable = WIDTH - 8 - px0 - 8; gridh = n * rowh
|
px0 = 48; usable = WIDTH - 8 - px0 - 8; gridh = n * rowh # narrower screen -> tighter lane-label column
|
||||||
self._grid = {'top': top, 'rowh': rowh, 'px0': px0, 'usable': usable, 'n': n}
|
self._grid = {'top': top, 'rowh': rowh, 'px0': px0, 'usable': usable, 'n': n}
|
||||||
m = self.lanes[0] if self.lanes else None
|
m = self.lanes[0] if self.lanes else None
|
||||||
if m is not None:
|
if m is not None:
|
||||||
|
|
@ -1211,10 +1213,10 @@ class App:
|
||||||
y = top + li * rowh; cy = y + rowh // 2
|
y = top + li * rowh; cy = y + rowh // 2
|
||||||
st = self._grid_lane_st
|
st = self._grid_lane_st
|
||||||
if st is None:
|
if st is None:
|
||||||
tg, w, h = make_text((L.get('sound', '') or '?')[:7], FONT_S, C_MUTE, C_BG)
|
tg, w, h = make_text((L.get('sound', '') or '?')[:6], FONT_S, C_MUTE, C_BG) # 6-char label fits the 48px lane column
|
||||||
tg.x = 6; tg.y = cy - h // 2; self.g_grid.append(tg)
|
tg.x = 4; tg.y = cy - h // 2; self.g_grid.append(tg)
|
||||||
steps = L['steps']; sub = L['sub']; stepw = max(1, usable // steps)
|
steps = L['steps']; sub = L['sub']; stepw = max(1, usable // steps)
|
||||||
side = max(4, min(12, stepw - 1, rowh - 6))
|
side = max(5, min(14, stepw - 1, rowh - 6)) # squares can be bigger in portrait
|
||||||
rad = max(2, min(side // 2, stepw // 2 - 1))
|
rad = max(2, min(side // 2, stepw // 2 - 1))
|
||||||
self._grid_lane_st = (cy, steps, sub, stepw, side, rad)
|
self._grid_lane_st = (cy, steps, sub, stepw, side, rad)
|
||||||
self._grid_pi = 0; self._grid_pads = []; self.dirty = True
|
self._grid_pi = 0; self._grid_pads = []; self.dirty = True
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue