more colour, QR codes

This commit is contained in:
Peter D. Gray 2023-06-12 11:26:55 -04:00 committed by scgbckbone
parent 04152d09f5
commit 3ecd474137

View File

@ -12,7 +12,7 @@ import sram2
from st7788 import ST7788
# the one font: fixed-width (except for a few double-width chars)
from font_iosevka import CELL_W, CELL_H, TEXT_PALETTE, TEXT_PALETTE_INV
from font_iosevka import CELL_W, CELL_H, TEXT_PALETTE, TEXT_PALETTE_INV, COL_TEXT
from font_iosevka import FontIosevka
# free unused screen buffers, we don't work that way
@ -33,6 +33,17 @@ CHARS_H = const(10)
# colouuurs: RGB565
COL_WHITE = 0xffff
COL_BLACK = 0x0000
COL_PROGRESS = COL_TEXT
FLAG_INVERT = 0x8000
ATTR_MASK = 0x8000
# text display attributes, ie. colours
AT_INVERT = 0x1
AT_GREY25 = 0x1
AT_GREY50 = 0x1
AT_RED = 0x1
AT_GREEN = 0x1
def grey_level(amt):
# give percent 0..1.0
@ -42,6 +53,14 @@ def grey_level(amt):
return (r<<11) | (g << 5) | r
def rgb(r,g,b):
# as if 24-bit, but we're 16
r = int(r/255 * 0x1f)
g = int(g/255 * 0x3f)
b = int(b/255 * 0x1f)
return (r<<11) | (g << 5) | b
def get_sys_status():
# read current values for all status-bar items
# - normally we update as we go along.
@ -89,10 +108,20 @@ class Display:
def __init__(self):
self.dis = ST7788()
self.last_buf = self.make_buf(0)
self.next_buf = self.make_buf(32)
# state of progress bar
self.last_prog_x = -1
self.next_prog_x = 0
self.last_bar_update = 0
self.dis.fill_screen()
self.draw_status(full=True)
def make_buf(self, ch):
return [array.array('H', (ch for i in range(CHARS_W))) for y in range(CHARS_H)]
def redraw_metakeys(self, new_state):
# called when metakeys have changed state
self.draw_status(**new_state)
@ -128,26 +157,61 @@ class Display:
if meta in kws:
self.image(x, 0, '%s_%d' % (meta, kws[meta]))
def width(self, msg, font):
return sum(font.lookup(ord(ch)).w for ch in msg)
def image(self, x, y, name):
# display a graphics image, immediately
w,h, data = getattr(Graphics, name)
if x == None:
x = max(0, (WIDTH - w) // 2)
self.dis.show_zpixels(x, y, w, h, data)
self.mark_correct(x, y, w, h)
def mark_lines_dirty(self, rng):
# mark a bunch of lines as needing redraw
# - for QR which covers most of screen
# - DELME
for y in rng:
self.last_buf[y] = array.array('H', (0xfffe for i in range(CHARS_W)))
self.next_buf[y] = array.array('H', (0xfffe for i in range(CHARS_W)))
def mark_correct(self, px, py, w, h):
# mark a subset of the screen as already drawn correctly
# - because we drew an image in that spot already (immediate)
# - hard: need to convert from pixel coord space to chars
if py < TOP_MARGIN:
# status icons not a concern
return
cy = (py - TOP_MARGIN) // CELL_H
cx = (px - LEFT_MARGIN) // CELL_W
cw = (w+CELL_W) // CELL_W
ch = (h+CELL_H) // CELL_H
#print('pixel %dx%d @ (%d,%d) => %dx%d @ (%d,%d)' % (w, h, px,py, cw, ch, cx,cy))
for y in range(cy, cy+ch+1):
for x in range(cx, cx+cw+1):
try:
self.last_buf[y][x] = self.next_buf[y][x] = 0xfffe
except IndexError:
pass
self.show()
def icon(self, x, y, name, invert=0):
# XXX plan is these are chars or images
return 10, 10
# plan is these are chars or images
raise NotImplementedError
def text(self, x,y, msg, font=None, invert=0):
def width(self, msg):
# length of text msg in char cells
# - typically 1:1 but we have a few double-width chars
rv = len(msg)
rv += sum(1 for ch in msg if ch in FontIosevka.DOUBLE_WIDE)
return rv
def text(self, x,y, msg, font=None, invert=0, attr=None):
# Draw at x,y (in cell positions, not pixels)
# Use invert=1 to get reverse video
if x is None or x < 0:
w = len(msg)
w = self.width(msg)
if x == None:
# center: also blanks rest of line
x = max(0, (CHARS_W - w) // 2)
@ -165,35 +229,79 @@ class Display:
print("BAD Draw '%s' at y=%d" % (msg, y))
return # past bottom
# convert to pixels
x = LEFT_MARGIN + (x * CELL_W)
y = TOP_MARGIN + (y * CELL_H)
for ch in msg:
fn = FontIosevka.lookup(ch)
if fn is None:
# draw blanks for unknowns
x += CELL_W
continue
if x >= CHARS_W: break
self.next_buf[y][x] = ord(ch) + (FLAG_INVERT if invert else 0)
x += 1
if ch in FontIosevka.DOUBLE_WIDE:
self.next_buf[y][x] = 0
x += 1
self.dis.show_pal_pixels(x, y, fn.w, fn.h,
TEXT_PALETTE if not invert else TEXT_PALETTE_INV, fn.bits)
x += fn.w
if x >= WIDTH: break
def real_clear(self, _internal=False):
# fill to black, but only text area, not status bar
if not _internal:
self.dis.fill_rect(0, TOP_MARGIN, WIDTH, HEIGHT-TOP_MARGIN, 0x0)
self.last_buf = self.make_buf(32)
self.next_buf = self.make_buf(32)
self.next_prog_x = 0
def clear(self):
# fill to black, but only text area, not status bar
self.dis.fill_rect(0, TOP_MARGIN, WIDTH, HEIGHT-TOP_MARGIN, 0x0)
# clear text
self.next_buf = self.make_buf(32)
# clear progress bar
self.next_prog_x = 0
def clear_rect(self, x,y, w,h):
self.dis.fill_rect(x,y, w,h, 0x0000)
def show(self, just_lines=None):
# Push internal screen representation to device, effeciently
lines = just_lines or range(CHARS_H)
for y in lines:
x = 0
while x < CHARS_W:
if self.next_buf[y][x] == self.last_buf[y][x]:
# already correct
x += 1
continue
def show(self):
# used to be critical, pushing internal screen representation to device
# however, we can be more selective/auto now...
# - but might still use... idk
pass
py = TOP_MARGIN + (y * CELL_H)
px = LEFT_MARGIN + (x * CELL_W)
ch = chr(self.next_buf[y][x] & ~ATTR_MASK)
attr = (self.next_buf[y][x] & ATTR_MASK)
if ch == ' ':
# space - look for horz runs & fill w/ blank
run = 1
for x2 in range(x+1, CHARS_W):
if self.next_buf[y][x] != self.next_buf[y][x2]:
break
run += 1
self.dis.fill_rect(px, py, run*CELL_W, CELL_H,
COL_TEXT if attr == FLAG_INVERT else 0)
x += run
continue
fn = FontIosevka.lookup(ch)
if not fn:
# unknown char
x += 1
continue
self.dis.show_pal_pixels(px, py, fn.w, fn.h,
TEXT_PALETTE if not (attr == FLAG_INVERT) else TEXT_PALETTE_INV, fn.bits)
x += fn.w // CELL_W
self.last_buf[y][:] = self.next_buf[y]
# maybe update progress bar
if self.next_prog_x != self.last_prog_x:
x = self.next_prog_x
if x:
self.dis.fill_rect(0, HEIGHT-3, x, 3, COL_PROGRESS)
if x != WIDTH:
self.dis.fill_rect(x, HEIGHT-3, WIDTH-x, 3, COL_BLACK)
self.last_prog_x = x
# rather than clearing and redrawing, use this buffer w/ fixed parts of screen
# - obsolete concept
@ -201,6 +309,8 @@ class Display:
pass
def restore(self):
pass
def clear_rect(self, x,y, w,h):
raise NotImplementedError
def hline(self, y):
self.dis.fill_rect(0,y, WIDTH, 1, 0xffff)
@ -222,29 +332,49 @@ class Display:
if percent is not None:
self.progress_bar(percent)
def DELME_splash(self):
# test code
from qrs import QRDisplaySingle
import glob, time
glob.dis = self
#q = QRDisplaySingle(['mtHSVByP9EYZmB26jASDdPVm19gvpecb5R'], is_alnum=True)
#q2 = QRDisplaySingle(['R5bcepvg91mVPdDSAj62BmZYE9PyBVSHtm'], is_alnum=True)
q = QRDisplaySingle(['a'*2953], is_alnum=False)
q2 = QRDisplaySingle(['b'*2953], is_alnum=False)
q.redraw()
while 1:
#time.sleep_ms(250)
q2.redraw()
#time.sleep_ms(250)
q.redraw()
assert False
def splash(self):
# display a splash screen with some version numbers
self.clear()
self.image(None, 24, 'splash')
self.real_clear()
self.image(None, 80, 'splash')
self.text(None, 1, "Don't Trust. Verify.")
from version import get_mpy_version
timestamp, label, *_ = get_mpy_version()
y = HEIGHT-CELL_H-2
self.text(0, -1, 'Version '+label)
self.text(-1, -1, timestamp)
self.show([CHARS_H-1])
def splash_text(self, msg):
# additional progress during splash/startup screen
self.text(None, 6, msg)
y = 6
self.text(None, y, msg)
self.show([y])
def progress_bar(self, percent):
# Horizontal progress bar
# takes 0.0 .. 1.0 as fraction of doneness
percent = max(0, min(1.0, percent))
x = int(WIDTH * percent)
self.dis.fill_rect(0, HEIGHT-3, x, 3, COL_WHITE)
self.dis.fill_rect(x, HEIGHT-3, WIDTH-x, 3, COL_BLACK)
self.next_prog_x = int(WIDTH * percent)
def progress_sofar(self, done, total):
# Update progress bar, but only if it's been a while since last update
@ -256,18 +386,19 @@ class Display:
def progress_bar_show(self, percent):
# useful as a callback
self.progress_bar(percent)
self.show()
def mark_sensitive(self, from_y, to_y):
return # XXX maybe TODO ? or remove ... LCD doesnt have issue
wx = WIDTH-4 # avoid scroll bar
for y in range(from_y, to_y):
ln = max(2, ckcc.rng() % 32)
self.dis.line(wx-ln, y, wx, y, 1)
# XXX maybe TODO ? or remove ... LCD doesnt have issue
return
def busy_bar(self, enable, speed_code=5):
# TODO: activate the GPU to render/animate this.
print("busy_bar: %s" % enable)
return
#print("busy_bar: %s" % enable)
# impt, this show() is relied-upon by callers
self.next_prog_x = 0
self.show()
def set_brightness(self, val):
# normal = 0x7f, brightness=0xff, dim=0x00 (but they are all very similar)
@ -280,18 +411,33 @@ class Display:
if ry >= CHARS_H:
# higher layer tries to draw partial line past bottom, and that's
# ok because needed on mk4
# ok because the mk4 had a 5th, half-line as a hint
return
if msg[0] == ' ' and space_indicators:
msg = '_' + msg[1:] # XXX improve me w/ special char
# unused, but might need?
msg = '' + msg[1:]
ln = ('' if is_sel else ' ') + ('%-32s' % msg)
x = 0
self.text(x, ry, ' '+msg+' ', invert=is_sel)
if is_checked:
ln = ln[:CHARS_W-3] + ''
#self.text(CHARS_W-3, ry, '✔︎')
self.text(len(msg)+2, ry, '✔︎')
self.text(0, ry, ln, invert=is_sel)
if 0:
if is_sel:
#ln = '▶ %s ◀' % msg
#ln = '█▌%s▐█' % msg
ln = '█▌%-29s▐█' % msg
else:
ln = ' ' + msg
if is_checked:
ln = '%-34s' % ln
ln = ln[:CHARS_W-3] + ''
self.text(0, ry, ln)
def show_yikes(self, lines):
@ -317,13 +463,15 @@ class Display:
self.text(1 if second else 0, y, l)
y += 1
self.show()
def draw_story(self, lines, top, num_lines, is_sensitive):
self.clear()
y=0
for ln in lines:
if ln == 'EOT':
self.hline( TOP_MARGIN + (y*CELL_H) )
self.text(0, y, ''*CHARS_W, attr=AT_GREY25)
continue
elif ln and ln[0] == '\x01':
# title ... but we have no special font?
@ -379,12 +527,13 @@ class Display:
y = (ACTIVE_H - (num_lines * CELL_H) - qw) // 2
x = (WIDTH - qw) // 2
# send packed pixel data to C level to decode and exand to LCD
# send packed pixel data to C level to decode and expand onto LCD
# - 8-bit aligned rows of data
scan_w, _, data = qr_data.packed()
self.clear()
self.real_clear(_internal=True)
self.dis.show_qr_data(x, TOP_MARGIN + y, w, expand, scan_w, data)
self.mark_correct(x, TOP_MARGIN + y, qw, qw)
if num_lines:
# centered text under that
@ -397,7 +546,7 @@ class Display:
# show path index number: just 1 or 2 digits
self.text(-1, 0, idx_hint)
self.busy_bar(False) # includes show
self.busy_bar(False)
# here for mpy reasons