rumpk/libs/membrane/term.nim

227 lines
6.1 KiB
Nim

# Nexus Membrane: Virtual Terminal Emulator
import term_font
import ion_client
const TERM_COLS* = 100
const TERM_ROWS* = 37
# Sovereign Palette (Phased Aesthetics)
const
COLOR_SOVEREIGN_BLUE = 0xFF401010'u32
COLOR_PHOSPHOR_AMBER = 0xFF00B0FF'u32
COLOR_SCANLINE_DIM = 0xFF300808'u32
type
Cell = object
ch: char
fg: uint32
bg: uint32
dirty: bool
var grid: array[TERM_ROWS, array[TERM_COLS, Cell]]
var cursor_x, cursor_y: int
var color_fg: uint32 = COLOR_PHOSPHOR_AMBER
var color_bg: uint32 = COLOR_SOVEREIGN_BLUE
var term_dirty*: bool = true
var fb_ptr: ptr UncheckedArray[uint32]
var fb_w, fb_h, fb_stride: int
# ANSI State Machine
type AnsiState = enum
Normal, Escape, CSI, Param
var cur_state: AnsiState = Normal
var ansi_params: array[8, int]
var cur_param_idx: int
const PALETTE: array[16, uint32] = [
0xFF000000'u32, # 0: Black
0xFF0000AA'u32, # 1: Red
0xFF00AA00'u32, # 2: Green
0xFF00AAAA'u32, # 3: Brown/Yellow
0xFFAA0000'u32, # 4: Blue
0xFFAA00AA'u32, # 5: Magenta
0xFFAAAA00'u32, # 6: Cyan
0xFFAAAAAA'u32, # 7: Gray
0xFF555555'u32, # 8: Bright Black
0xFF5555FF'u32, # 9: Bright Red
0xFF55FF55'u32, # 10: Bright Green
0xFF55FFFF'u32, # 11: Bright Yellow
0xFFFF5555'u32, # 12: Bright Blue
0xFFFF55FF'u32, # 13: Bright Magenta
0xFFFFFF55'u32, # 14: Bright Cyan
0xFFFFFFFF'u32 # 15: White
]
proc handle_sgr() =
## Handle Select Graphic Rendition (m)
if cur_param_idx == 0: # reset
color_fg = COLOR_PHOSPHOR_AMBER
color_bg = COLOR_SOVEREIGN_BLUE
return
for i in 0..<cur_param_idx:
let p = ansi_params[i]
if p == 0:
color_fg = COLOR_PHOSPHOR_AMBER
color_bg = COLOR_SOVEREIGN_BLUE
elif p >= 30 and p <= 37:
color_fg = PALETTE[p - 30]
elif p >= 40 and p <= 47:
color_bg = PALETTE[p - 40]
elif p >= 90 and p <= 97:
color_fg = PALETTE[p - 90 + 8]
elif p >= 100 and p <= 107:
color_bg = PALETTE[p - 100 + 8]
proc term_clear*() =
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
cursor_x = 0
cursor_y = 0
cur_state = Normal
term_dirty = true
proc term_scroll() =
for row in 0..<(TERM_ROWS-1):
grid[row] = grid[row + 1]
for col in 0..<TERM_COLS: grid[row][col].dirty = true
for col in 0..<TERM_COLS:
grid[TERM_ROWS-1][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
term_dirty = true
proc term_putc*(ch: char) =
case cur_state
of Normal:
if ch == '\x1b':
cur_state = Escape
return
if ch == '\n':
cursor_x = 0
cursor_y += 1
elif ch == '\r':
cursor_x = 0
elif ch >= ' ':
if cursor_x >= TERM_COLS:
cursor_x = 0
cursor_y += 1
if cursor_y >= TERM_ROWS:
term_scroll()
cursor_y = TERM_ROWS - 1
grid[cursor_y][cursor_x] = Cell(ch: ch, fg: color_fg, bg: color_bg, dirty: true)
cursor_x += 1
term_dirty = true
of Escape:
if ch == '[':
cur_state = CSI
cur_param_idx = 0
for i in 0..<ansi_params.len: ansi_params[i] = 0
else:
cur_state = Normal
of CSI:
if ch >= '0' and ch <= '9':
ansi_params[cur_param_idx] = (ch.int - '0'.int)
cur_state = Param
elif ch == ';':
if cur_param_idx < ansi_params.len - 1: cur_param_idx += 1
elif ch == 'm':
if cur_state == Param or cur_param_idx > 0 or ch == 'm': # Handle single m or param m
if cur_state == Param: cur_param_idx += 1
handle_sgr()
cur_state = Normal
elif ch == 'H' or ch == 'f': # Cursor Home
cursor_x = 0; cursor_y = 0
cur_state = Normal
elif ch == 'J': # Clear Screen
term_clear()
cur_state = Normal
else:
cur_state = Normal
of Param:
if ch >= '0' and ch <= '9':
ansi_params[cur_param_idx] = ansi_params[cur_param_idx] * 10 + (ch.int - '0'.int)
elif ch == ';':
if cur_param_idx < ansi_params.len - 1: cur_param_idx += 1
elif ch == 'm':
cur_param_idx += 1
handle_sgr()
cur_state = Normal
elif ch == 'H' or ch == 'f':
# pos logic here if we wanted it
cursor_x = 0; cursor_y = 0
cur_state = Normal
else:
cur_state = Normal
# --- THE GHOST RENDERER ---
proc draw_char(cx, cy: int, cell: Cell) =
if fb_ptr == nil: return
let glyph_idx = uint8(cell.ch)
let fg = cell.fg
let bg = cell.bg
let px_start = cx * 8
let py_start = cy * 16
for y in 0..15:
let is_scanline = (y mod 4) == 3
let row_bits = FONT_BITMAP[glyph_idx][y]
let screen_y = py_start + y
if screen_y >= fb_h: break
let row_offset = screen_y * (fb_stride div 4)
for x in 0..7:
let screen_x = px_start + x
if screen_x >= fb_w: break
let pixel_idx = row_offset + screen_x
let is_pixel = ((int(row_bits) shr (7 - x)) and 1) != 0
if is_pixel:
fb_ptr[pixel_idx] = if is_scanline: (fg and 0xFFE0E0E0'u32) else: fg
else:
fb_ptr[pixel_idx] = if is_scanline: COLOR_SCANLINE_DIM else: bg
proc term_render*() =
if fb_ptr == nil or not term_dirty: return
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
if grid[row][col].dirty:
draw_char(col, row, grid[row][col])
grid[row][col].dirty = false
term_dirty = false
proc term_init*() =
let sys = cast[ptr SysTable](SYS_TABLE_ADDR)
fb_ptr = cast[ptr UncheckedArray[uint32]](sys.fb_addr)
fb_w = int(sys.fb_width)
fb_h = int(sys.fb_height)
fb_stride = int(sys.fb_stride)
cursor_x = 0
cursor_y = 0
cur_state = Normal
term_dirty = true
when defined(TERM_PROFILE_minimal):
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
var msg = "[TERM] Profile: MINIMAL (IBM VGA/Hack)\n"
console_write(addr msg[0], uint(msg.len))
elif defined(TERM_PROFILE_standard):
proc console_write(p: pointer, len: uint) {.importc, cdecl.}
var msg = "[TERM] Profile: STANDARD (Spleen/Nerd)\n"
console_write(addr msg[0], uint(msg.len))
for row in 0..<TERM_ROWS:
for col in 0..<TERM_COLS:
grid[row][col] = Cell(ch: ' ', fg: color_fg, bg: color_bg, dirty: true)
# Test Colors
let test_msg = "\x1b[31mN\x1b[32mE\x1b[33mX\x1b[34mU\x1b[35mS\x1b[0m\n"
for ch in test_msg: term_putc(ch)