aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-18 20:41:27 +0200
committeruser <user@clank>2026-07-18 20:41:27 +0200
commit9e7d5f6e5e01b7473b84bc3449d7f8e0f78e7045 (patch)
tree5dcde624f3b366cc9244e08b0d2ca4ab35b338e2 /src
parentkernel: a real timer source (64 Hz tick IRQ) + uptime(1) (diff)
downloadgbos-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/color.asm40
-rw-r--r--src/osk.asm8
-rw-r--r--src/programs.asm7
-rw-r--r--src/term.asm523
4 files changed, 549 insertions, 29 deletions
diff --git a/src/color.asm b/src/color.asm
new file mode 100644
index 0000000..5c12569
--- /dev/null
+++ b/src/color.asm
@@ -0,0 +1,40 @@
+; auto-generated by tools/gencolor.py - Fano-plane color tables
+; (foreground + background). DO NOT EDIT.
+SECTION "color", ROM0
+
+; 8 CGB BG palettes, 4 colors each (c0=black). Loaded at BCPS $80.
+PalData::
+ db $00,$00,$FF,$7F,$1F,$00,$E0,$7F ; palette 0
+ db $FF,$7F,$00,$00,$00,$00,$00,$00 ; palette 1
+ db $00,$00,$1F,$00,$E0,$03,$FF,$03 ; palette 2
+ db $00,$00,$1F,$00,$00,$7C,$1F,$7C ; palette 3
+ db $00,$00,$E0,$03,$00,$7C,$E0,$7F ; palette 4
+ db $00,$00,$E0,$03,$1F,$7C,$FF,$7F ; palette 5
+ db $00,$00,$FF,$03,$00,$7C,$FF,$7F ; palette 6
+ db $00,$00,$FF,$03,$1F,$7C,$E0,$7F ; palette 7
+
+; BitTab[color] = bit for that color in the 'colors present' set (black=0)
+BitTab::
+ db $00,$01,$02,$04,$08,$10,$20,$40
+
+; BestPal[set] = palette (0..7) covering the most colors in a 7-bit set
+BestPal::
+ db $00,$00,$02,$02,$02,$02,$02,$02,$03,$03,$04,$02,$06,$02,$02,$02 ; 0..15
+ db $03,$03,$05,$02,$07,$02,$02,$02,$03,$03,$03,$03,$03,$03,$02,$02 ; 16..31
+ db $00,$00,$04,$00,$07,$00,$02,$02,$04,$00,$04,$04,$04,$00,$04,$02 ; 32..47
+ db $07,$00,$04,$00,$07,$07,$07,$02,$03,$03,$04,$03,$07,$03,$04,$02 ; 48..63
+ db $00,$00,$05,$00,$06,$00,$02,$02,$06,$00,$04,$00,$06,$06,$06,$02 ; 64..79
+ db $05,$00,$05,$05,$05,$00,$05,$02,$03,$03,$05,$03,$06,$03,$05,$02 ; 80..95
+ db $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$04,$00,$06,$00,$04,$00 ; 96..111
+ db $00,$00,$05,$00,$07,$00,$05,$00,$00,$00,$04,$00,$06,$00,$04,$00 ; 112..127
+
+; SlotOf[palette*8 + color] = that color's slot/id (0..3) in the palette
+SlotOf::
+ db $00,$02,$01,$01,$01,$01,$03,$01 ; palette 0
+ db $00,$01,$01,$01,$01,$01,$01,$01 ; palette 1
+ db $00,$01,$02,$03,$01,$01,$01,$01 ; palette 2
+ db $00,$01,$01,$01,$02,$03,$01,$01 ; palette 3
+ db $00,$01,$01,$01,$02,$01,$03,$01 ; palette 4
+ db $00,$01,$01,$01,$01,$02,$01,$03 ; palette 5
+ db $00,$01,$01,$01,$02,$01,$01,$03 ; palette 6
+ db $00,$01,$01,$01,$01,$02,$03,$01 ; palette 7
diff --git a/src/osk.asm b/src/osk.asm
index f5d99ac..d5edad7 100644
--- a/src/osk.asm
+++ b/src/osk.asm
@@ -80,10 +80,16 @@ osk_init::
ld [rBCPD], a
ld [rBCPD], a ; c1 = $0000 black
- ; build the key tiles (no cursor underline)
+ ; build the key tiles (no cursor underline). Glyph pixels -> color-id 1
+ ; (palette slot 1: white normal / black inverted); empty pixels -> id 0.
xor a
ld [wCurMask], a
ld [wOskBuild], a
+ ld [wFGP1], a
+ ld [wBGP0], a
+ ld [wBGP1], a
+ ld a, $FF
+ ld [wFGP0], a
.build
ld a, [wOskBuild]
ld e, a
diff --git a/src/programs.asm b/src/programs.asm
index 7c17fb5..c6228f3 100644
--- a/src/programs.asm
+++ b/src/programs.asm
@@ -136,6 +136,9 @@ ProgIrc:
SECTION "prog_uptime", ROMX[$4000], BANK[33]
ProgUptime:
INCBIN "build/usr/uptime.bin"
+SECTION "prog_ansi", ROMX[$4000], BANK[34]
+ProgAnsi:
+ INCBIN "build/usr/ansi.bin"
; -----------------------------------------------------------------------------
; PROG_SH (bank 3) - the shell, now written in C (usr/sh.c): parses >/</| and
@@ -242,6 +245,8 @@ ProgramTable::
dw ProgIrc
db LOW(BANK(ProgUptime)), HIGH(BANK(ProgUptime))
dw ProgUptime
+ db LOW(BANK(ProgAnsi)), HIGH(BANK(ProgAnsi))
+ dw ProgAnsi
ProgramTableEnd::
; -----------------------------------------------------------------------------
@@ -313,4 +318,6 @@ NameTable::
db PROG_IRC
db "uptime", 0
db PROG_UPTIME
+ db "ansi", 0
+ db PROG_ANSI
db 0
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.
; -----------------------------------------------------------------------------