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