diff options
| author | user <user@clank> | 2026-07-18 20:41:27 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-18 20:41:27 +0200 |
| commit | 9e7d5f6e5e01b7473b84bc3449d7f8e0f78e7045 (patch) | |
| tree | 5dcde624f3b366cc9244e08b0d2ca4ab35b338e2 /src/term.asm | |
| parent | kernel: a real timer source (64 Hz tick IRQ) + uptime(1) (diff) | |
| download | gbos-9e7d5f6e5e01b7473b84bc3449d7f8e0f78e7045.tar.gz gbos-9e7d5f6e5e01b7473b84bc3449d7f8e0f78e7045.tar.xz gbos-9e7d5f6e5e01b7473b84bc3449d7f8e0f78e7045.zip | |
term: 7-color terminal (per-glyph fg+bg) + ANSI escapes; colorize irc
Fano-plane palette scheme: the 7 colors map onto 7 CGB BG palettes so
any color pair shares one palette; a tile's palette is picked from the
set of colors its two cells need (tables generated by tools/gencolor.py).
Per cell a packed (bg<<4)|fg byte lives alongside the char shadow, and
the glyph blitter steers glyph/empty pixels to each cell's fg/bg color
slots via plane masks.
An ANSI-ish CSI parser (ESC [ .. m) drives it; usr/ansi.c demos it and
the irc client now renders hashed nick colors, status dimming and a
channel-activity bar.
Diffstat (limited to '')
| -rw-r--r-- | src/term.asm | 523 |
1 files changed, 495 insertions, 28 deletions
diff --git a/src/term.asm b/src/term.asm index 099ca0a..d62310e 100644 --- a/src/term.asm +++ b/src/term.asm @@ -23,6 +23,8 @@ DEF TCOLS EQU 40 ; text columns DEF TROWS EQU 18 ; text rows DEF TSTRIDE EQU 64 ; bytes per line-slot in the buffer (power of 2) DEF TERM_BUF EQU $D600 ; 18*64 = 1152 B (WRAMX, above the FS caches) +DEF COL_BUF EQU $DA80 ; per-cell fg color, parallel to TERM_BUF (18*64 = 1152 B) +DEF COL_OFF EQU COL_BUF - TERM_BUF ; $0480: add to a char addr -> its color addr ; terminal state (WRAMX gap $D580-$D5FF) DEF wGlyphL EQU $D580 ; 2 @@ -53,24 +55,20 @@ term_init:: ld [rSCX], a ld [rSCY], a - ; BG palette 0: color0 = black (bg), color1 = white (text) - dark terminal - ld a, $80 + ; Load all 8 CGB BG palettes from PalData (see tools/gencolor.py). Palettes + ; 0 and 2..7 are the 7 Fano-plane text palettes (each = bg + 3 of our 7 + ; colors); palette 1 is the OSK's white-on-black inverse. Per-glyph color + ; works because each tile picks the one palette that holds both its glyphs' + ; colors, and the two bitplanes steer each glyph to its color's slot. + ld a, $80 ; BCPS index 0, auto-increment ld [rBCPS], a - xor a - ld [rBCPD], a - ld [rBCPD], a ; c0 = $0000 black (background) - ld a, $FF - ld [rBCPD], a - ld a, $7F - ld [rBCPD], a ; c1 = $7FFF white (text) - ld a, $FF - ld [rBCPD], a - ld a, $7F - ld [rBCPD], a ; c2 = white - ld a, $FF + ld hl, PalData + ld b, 8 * 4 * 2 ; 8 palettes * 4 colors * 2 bytes +.palcopy + ld a, [hl+] ld [rBCPD], a - ld a, $7F - ld [rBCPD], a ; c3 = white + dec b + jr nz, .palcopy ; state: assign[sr]=sr, cursor at 0,0 (on) ld hl, wAssign @@ -86,8 +84,11 @@ term_init:: ld [wCurCol], a ld [wViewTop], a ; view starts unshifted (all 18 rows) ld [wOskVisible], a ; OSK hidden until toggled + ld [wAnsiState], a ; ANSI parser idle ld a, 1 ld [wCursorOn], a + ld a, 7 + ld [wCurColor], a ; default fg color = 7 (white) ; clear the buffer (all slots) to spaces ld hl, TERM_BUF @@ -100,6 +101,17 @@ term_init:: or e jr nz, .clr + ; clear the color buffer to the default fg color (7 = white) + ld hl, COL_BUF + ld de, TROWS * TSTRIDE +.clrc + ld a, 7 + ld [hl+], a + dec de + ld a, d + or e + jr nz, .clrc + call term_write_tilemap call term_redraw ; build all tiles (blank + cursor) @@ -134,16 +146,47 @@ term_write_tilemap:: ld [rVBK], a ld c, 0 .a_row - call .slot20 + call .slot20 ; DE = pos, HL = tilemap addr, wTMSlot = slot ld b, TCOLS/2 .a_col - ld a, d ; HIGH(pos): 0 or 1 + ; attribute = palette(from this tile's two cell colors) | bank bit(pos hi). + push hl ; save tilemap write ptr + push de ; save pos + ; color addr = COL_BUF + slot*64 + tcol*2 ; tcol = (TCOLS/2) - b + ld a, [wTMSlot] + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; slot*64 + ld a, TCOLS/2 + sub b + add a ; tcol*2 + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld de, COL_BUF + add hl, de + ld a, [hl+] + ld d, a ; colL + ld a, [hl] + ld e, a ; colR + call color_setup ; -> wPalAttr (preserves B, C) + pop de ; restore pos + pop hl ; restore tilemap write ptr + ld a, d ; HIGH(pos): 0 or 1 -> VRAM bank bit or a jr z, .a0 - ld a, $08 ; VRAM bank 1 attribute + ld a, [wPalAttr] + or $08 ; VRAM bank 1 tile jr .aw .a0 - xor a + ld a, [wPalAttr] .aw ld [hl+], a inc de @@ -170,6 +213,7 @@ term_write_tilemap:: adc 0 ld h, a ld a, [hl] ; L = assign[logical row] + ld [wTMSlot], a ; stash slot for the color-aware attr pass ld l, a ld h, 0 add hl, hl @@ -239,6 +283,135 @@ glyph_ptr: ret ; ----------------------------------------------------------------------------- +; color_setup - D = left cell packed color, E = right cell packed color (each +; (bg<<4)|fg, 0..7 per nibble). Picks the tile's palette (wPalAttr) and the four +; bitplane masks: wFGP0/wFGP1 steer GLYPH pixels to each cell's fg slot, and +; wBGP0/wBGP1 steer EMPTY pixels to each cell's bg slot. Clobbers A, DE, HL; +; preserves B, C (so it runs inside the tilemap column loop). +; ----------------------------------------------------------------------------- +color_setup: + ld a, d + ld [wCSL], a + ld a, e + ld [wCSR], a + ; set = union of the (non-black) colors of both cells + xor a + ld [wCSset], a + ld a, [wCSL] + and $0F + call cs_add ; fg left + ld a, [wCSL] + swap a + and $0F + call cs_add ; bg left + ld a, [wCSR] + and $0F + call cs_add ; fg right + ld a, [wCSR] + swap a + and $0F + call cs_add ; bg right + ; palette = BestPal[set] + ld a, [wCSset] + ld l, a + ld h, 0 + ld de, BestPal + add hl, de + ld a, [hl] + ld [wPalAttr], a + add a + add a + add a ; palette*8 = SlotOf base + ld [wCSpb], a + ; foreground masks from the two fg slots (left in high nibble, right in low) + ld a, [wCSL] + and $0F + call cs_slot + ld d, a + ld a, [wCSR] + and $0F + call cs_slot + ld e, a + call cs_planes + ld a, [wCStmp0] + ld [wFGP0], a + ld a, [wCStmp1] + ld [wFGP1], a + ; background masks from the two bg slots + ld a, [wCSL] + swap a + and $0F + call cs_slot + ld d, a + ld a, [wCSR] + swap a + and $0F + call cs_slot + ld e, a + call cs_planes + ld a, [wCStmp0] + ld [wBGP0], a + ld a, [wCStmp1] + ld [wBGP1], a + ret + +; cs_add - A = color (0..7); OR its set-bit into wCSset (black contributes none) +cs_add: + or a + ret z + ld l, a + ld h, 0 + ld de, BitTab + add hl, de + ld a, [hl] + ld hl, wCSset + or [hl] + ld [hl], a + ret + +; cs_slot - A = color (0..7) -> A = its id (0..3) in the chosen palette. +; Addresses with A/HL only so it preserves DE (the caller holds the left id in D +; across the second call). SlotOf index = wCSpb + color <= 63, so no page carry +; issue beyond the single adc below. +cs_slot: + ld l, a + ld a, [wCSpb] + add l + add LOW(SlotOf) + ld l, a + ld a, 0 + adc HIGH(SlotOf) + ld h, a + ld a, [hl] + ret + +; cs_planes - D = id for the high (left) nibble, E = id for the low (right) +; nibble -> wCStmp0 = plane-0 mask, wCStmp1 = plane-1 mask. An id's bit0 lights +; plane 0, bit1 lights plane 1; the left cell owns $F0, the right owns $0F. +cs_planes: + xor a + bit 0, d + jr z, .p0r + or $F0 +.p0r + bit 0, e + jr z, .p0done + or $0F +.p0done + ld [wCStmp0], a + xor a + bit 1, d + jr z, .p1r + or $F0 +.p1r + bit 1, e + jr z, .p1done + or $0F +.p1done + ld [wCStmp1], a + ret + +; ----------------------------------------------------------------------------- ; build_tile - build tile for tile-position DE (E=index, D=bank) from chars ; B (left) and C (right). ORs wCurMask into the last pixel row (cursor). ; ----------------------------------------------------------------------------- @@ -291,21 +464,39 @@ build_tile:: ld d, a ld a, [de] and $0F - or c ; A = combined pixel row - ld e, a ; save (DE reloaded next iter) + or c ; A = glyph row g (left=hi nibble, right=lo) + ld c, a ; C = g + cpl + ld d, a ; D = ~g (the empty pixels = background) + ; plane1 = (g & wFGP1) | (~g & wBGP1) -> hold in E for the write below + ld a, [wFGP1] + and c + ld e, a + ld a, [wBGP1] + and d + or e + ld e, a ; E = plane 1 + ; plane0 = (g & wFGP0) | (~g & wBGP0) (+cursor underline on the last row) + ld a, [wFGP0] + and c + ld c, a ; C = g & wFGP0 + ld a, [wBGP0] + and d + or c ; A = plane 0 base + ld c, a ld a, b dec a jr nz, .nocur ; last row (b==1)? add cursor underline ld a, [wCurMask] - or e + or c jr .wr .nocur - ld a, e + ld a, c .wr pop hl ld [hl+], a ; plane 0 - xor a - ld [hl+], a ; plane 1 = 0 + ld a, e + ld [hl+], a ; plane 1 ; advance glyph pointers - MUST NOT touch HL (it holds the VRAM dest!) ld a, [wGlyphL] inc a @@ -349,7 +540,16 @@ render_tile: ld a, [hl+] ld [wRT_cL], a ld a, [hl] - ld [wRT_cR], a + ld [wRT_cR], a ; HL = &cellR + ; per-glyph color: read colL/colR from the parallel color buffer, then pick + ; the tile's palette + bitplane masks (D=colL, E=colR) via the Fano table. + ld de, COL_OFF + add hl, de ; HL = &colR + ld a, [hl-] + ld e, a ; E = colR + ld a, [hl] + ld d, a ; D = colL + call color_setup ; cursor mask xor a ld [wCurMask], a @@ -418,7 +618,102 @@ render_cursor_tile: ld a, [wCurCol] srl a ; /2 = tile col ld c, a - jp render_tile + call render_tile + ; fall through: keep the cursor tile's palette attribute in sync with its + ; (possibly just-recolored) cells. + +; ----------------------------------------------------------------------------- +; update_cursor_attr - rewrite the tilemap palette attribute for the tile under +; the cursor, from that tile's two cell colors. The cursor line is always fully +; on-screen (compute_offset guarantees it), so its physical row = wCurRow minus +; the view offset with no clamping. Bank bit follows the tile's VRAM position. +; ----------------------------------------------------------------------------- +update_cursor_attr: + ; slot = assign[wCurRow] + ld a, [wCurRow] + add LOW(wAssign) + ld l, a + ld a, HIGH(wAssign) + adc 0 + ld h, a + ld a, [hl] + ld [wTMSlot], a + ; color addr = COL_BUF + slot*64 + (wCurCol & $FE) + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; slot*64 + ld a, [wCurCol] + and $FE ; left cell of the tile + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld de, COL_BUF + add hl, de + ld a, [hl+] + ld d, a ; colL + ld a, [hl] + ld e, a ; colR + call color_setup ; -> wPalAttr (masks recomputed, harmless) + ; bank bit: pos = slot*20 + (wCurCol/2) + ld a, [wTMSlot] + ld l, a + ld h, 0 + add hl, hl + add hl, hl ; *4 + ld d, h + ld e, l + add hl, hl + add hl, hl ; *16 + add hl, de ; *20 + ld a, [wCurCol] + srl a + add l + ld l, a + ld a, h + adc 0 + ld h, a ; HL = pos + ld a, [wPalAttr] + bit 0, h ; pos >= 256 -> VRAM bank 1 + jr z, .noattr_bank + or $08 +.noattr_bank + ld c, a ; C = attribute byte + ; physical row = wCurRow - wViewTop + ld a, [wCurRow] + ld hl, wViewTop + sub [hl] + ; tilemap attr addr = $9800 + physical*32 + (wCurCol/2), VRAM bank 1 + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; *32 + ld a, h + add $98 + ld h, a + ld a, [wCurCol] + srl a + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld a, 1 + ld [rVBK], a + ld a, c + ld [hl], a + xor a + ld [rVBK], a + ret ; ----------------------------------------------------------------------------- ; term_redraw - rebuild every tile from the buffer (init / full refresh). @@ -479,6 +774,24 @@ term_scroll:: ld [hl+], a dec b jr nz, .clr + ; reset the freed line's colors to the default (1 = white) + ld a, [wScratch] + ld l, a + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; *64 + ld de, COL_BUF + add hl, de + ld b, TCOLS +.clrcol + ld a, 7 + ld [hl+], a + dec b + jr nz, .clrcol ; rebuild logical row 17 ld c, 0 .build @@ -549,6 +862,18 @@ term_putc:: ld [rSVBK], a ret term_putc_body: + ld e, a ; save the incoming byte + ld a, [wAnsiState] + or a + jp nz, .ansi_active + ld a, e + cp 27 ; ESC begins an ANSI sequence + jr nz, .not_esc + ld a, 1 + ld [wAnsiState], a + ret +.not_esc: + ld a, e cp 10 jr z, .newline cp 13 @@ -569,6 +894,13 @@ term_putc_body: ld h, a ld a, [wRT_cL] ld [hl], a + ; record this cell's color in the parallel color buffer + push hl + ld de, COL_OFF + add hl, de + ld a, [wCurColor] + ld [hl], a + pop hl ; redraw current cell WITHOUT cursor (shows the char) xor a ld [wCursorOn], a @@ -636,6 +968,141 @@ term_putc_body: call render_cursor_tile ret +; --- ANSI CSI parser (reached when wAnsiState != 0) -------------------------- +.ansi_active: + dec a ; state 1 (just saw ESC)? + jr nz, .ansi_csi ; else state 2 (inside CSI) + ld a, e + cp 91 ; '[' : ESC '[' opens a CSI; anything else aborts + jr nz, .ansi_off + ld a, 2 + ld [wAnsiState], a + xor a + ld [wAnsiParam], a + ret +.ansi_off: + xor a + ld [wAnsiState], a + ret +.ansi_csi: + ld a, e + cp 48 ; '0' + jr c, .csi_final + cp 58 ; '9'+1 + jr nc, .csi_final + sub 48 ; '0' : accumulate decimal parameter + ld d, a + ld a, [wAnsiParam] + add a, a ; *2 + ld c, a + add a, a + add a, a ; *8 + add c ; *10 + add d + ld [wAnsiParam], a + ret +.csi_final: + ld a, e + cp 109 ; 'm' : set graphics rendition + jr z, .csi_sgr + cp 59 ; ';' : parameter separator + jr z, .csi_sep + xor a ; unknown final byte: end the sequence + ld [wAnsiState], a + ret +.csi_sep: + call apply_sgr + xor a + ld [wAnsiParam], a + ret +.csi_sgr: + call apply_sgr + xor a + ld [wAnsiState], a + ret + +; ----------------------------------------------------------------------------- +; apply_sgr - act on the accumulated SGR parameter in wAnsiParam, updating the +; packed current color (bg<<4)|fg. Foreground 30..37 / 90..97 map through +; SgrColorMap (black-fg -> white so it stays visible on black); background +; 40..47 / 100..107 map straight (0=black..7=white, black bg = our default). +; 0 = reset both; 39 = default fg; 49 = default bg. Others are ignored. +; ----------------------------------------------------------------------------- +apply_sgr: + ld a, [wAnsiParam] + or a + jr z, .reset ; 0 = reset fg+bg + cp 30 + jr c, .done + cp 38 + jr c, .fg ; 30..37 + cp 39 + jr z, .fgdef ; 39 = default fg + cp 40 + jr c, .done ; 38 (256-color intro): ignore + cp 48 + jr c, .bg ; 40..47 + cp 49 + jr z, .bgdef ; 49 = default bg + cp 90 + jr c, .done ; 48, 50..89: ignore + cp 98 + jr c, .fgb ; 90..97 + cp 100 + jr c, .done + cp 108 + jr c, .bgb ; 100..107 +.done: + ret +.fg: + sub 30 + jr .setfg +.fgb: + sub 90 +.setfg: + ld e, a + ld d, 0 + ld hl, SgrColorMap + add hl, de + ld a, [hl] ; A = fg color 1..7 + ld e, a + ld a, [wCurColor] + and $F0 + or e + ld [wCurColor], a + ret +.fgdef: + ld a, [wCurColor] + and $F0 + or 7 + ld [wCurColor], a + ret +.bg: + sub 40 + jr .setbg +.bgb: + sub 100 +.setbg: + swap a ; bg color 0..7 -> high nibble + ld e, a + ld a, [wCurColor] + and $0F + or e + ld [wCurColor], a + ret +.bgdef: + ld a, [wCurColor] + and $0F ; bg -> 0 (black/default) + ld [wCurColor], a + ret +.reset: + ld a, 7 ; fg=7 (white), bg=0 (black) + ld [wCurColor], a + ret +SgrColorMap: + ; blk red grn yel blu mag cyn wht (ANSI 30..37 -> our 1..7; black->white) + db 7, 1, 2, 3, 4, 5, 6, 7 + ; ----------------------------------------------------------------------------- ; term_puts - HL = NUL-terminated string: print via term_putc. ; ----------------------------------------------------------------------------- |
