1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
const FW = 8 # glyph width in pixels
const FH = 16 # glyph height in pixels (8x16 vga bitmap font)
const FG = 0xd0d0d0
const CON_BG = 0x101018
global con = { col = 0, row = 0, scale = 1, shown = :false, cur_x = 0, cur_y = 0, blink = 0 }
# cell size in pixels at the current zoom. named cell_* (not cw/ch) so they dont
# collide with draw_glyphs `ch` character parameter
fn cell_w() FW * con.scale
fn cell_h() FH * con.scale
fn con_cols() fb_width() / cell_w()
fn con_rows() fb_height() / cell_h()
fn draw_glyph(col, row, ch) do
const x0 = col * cell_w()
const y0 = row * cell_h()
fill_rect(x0, y0, cell_w(), cell_h(), CON_BG) # clear the cell
const code = ch:ascii()
if code < 32 return :nil
if code > 127 return :nil
const glyph = FONT[code - 32]
for fy in 0..FH do
const bits = glyph[fy]
for fx in 0..FW do
if (bits shr fx) band 1 == 1 fill_rect(x0 + fx * con.scale, y0 + fy * con.scale, con.scale, con.scale, FG)
end
end
end
fn con_newline() do
con.col = 0
con.row += 1
if con.row >= con_rows() do
fb_scroll(cell_h(), CON_BG)
con.row = con_rows() - 1
end
end
# we dont do shift/cntrl
# thats a job for future me lmao
fn con_putc(ch) do
const code = ch:ascii()
if code == 10 do con_newline() return :nil end # \n
if code == 13 do con.col = 0 return :nil end # \r
if code == 8 do # backspace: move left
if con.col > 0 con.col -= 1
return :nil
end
if code < 32 return :nil
draw_glyph(con.col, con.row, ch)
con.col += 1
if con.col >= con_cols() con_newline()
end
fn puts(str) serial_puts(str)
const CUR = 0x50fa7b
# the cursor is an overlay block. its cleared before any drawing and shown after,
# so it never sits under a glyph, cur_x/cur_y remember where it was actually drawn
# so we always clear the right cell even after the head moved (backspace etc)
fn cursor_clear() do
if con.shown do
fill_rect(con.cur_x * cell_w(), con.cur_y * cell_h(), cell_w(), cell_h(), CON_BG)
con.shown = :false
end
end
fn cursor_show() do
con.cur_x = con.col
con.cur_y = con.row
fill_rect(con.cur_x * cell_w(), con.cur_y * cell_h(), cell_w(), cell_h(), CUR)
con.shown = :true
end
fn drain() do
let byte = serial_next()
while byte >= 0 do con_putc(string.of_ascii(byte)) byte = serial_next() end
end
global flush_console = fn() do
cursor_clear()
drain()
con.blink = 0
cursor_show()
end
# called each pit tick from the event loop (scheduler task 0). render any output a
# running process left in the serial mirror (this is how its writes reach the
# screen while time sliced), reprint the prompt when one exits, else blink
global on_tick = fn(ticks) do
let byte = serial_next()
if byte >= 0 do
cursor_clear()
con_putc(string.of_ascii(byte))
drain()
con.blink = 0
cursor_show()
return :nil
end
if proc_exited() do
cursor_clear()
puts("% ")
drain()
con.blink = 0
cursor_show()
return :nil
end
con.blink += 1
if con.blink >= 50 do
con.blink = 0
if con.shown cursor_clear() else cursor_show()
end
end
|