; ============================================================================= ; term.asm - a 40x18 text terminal on the CGB LCD, with cursor and fast scroll. ; ; 4x8 font: each 8x8 background tile holds TWO 4-px characters (left+right), ; giving 40 columns. Tiles are LINE-BOUND: line-slot L owns the 20 VRAM tiles ; at tile-positions [L*20 .. L*20+19] (index = LOW(pos), VRAM bank = HIGH(pos); ; positions 256-359 live in bank 1 - hence CGB-only). assign[sr] maps each of ; the 18 screen rows to a line-slot, and the tilemap points each screen row at ; that slot's tiles. Scrolling just rotates assign[], rebuilds the one new ; bottom line, and rewrites the tilemap - O(1 line), not O(screen). ; ; The cursor is an underline OR'd into the current cell's tile (wCurMask). ; ============================================================================= INCLUDE "include/gbos.inc" DEF rSCY EQU $FF42 DEF rSCX EQU $FF43 DEF rVBK EQU $FF4F ; CGB VRAM bank select DEF rBCPS EQU $FF68 ; CGB BG palette index DEF rBCPD EQU $FF69 ; CGB BG palette data 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 DEF wGlyphR EQU $D582 ; 2 DEF wVram EQU $D584 ; 2 DEF wCurRow EQU $D586 ; cursor screen row 0..17 DEF wCurCol EQU $D587 ; cursor screen col 0..39 DEF wCursorOn EQU $D588 ; draw the cursor? DEF wRT_sr EQU $D58A ; render_tile scratch DEF wRT_tcol EQU $D58B DEF wRT_cL EQU $D58C DEF wRT_cR EQU $D58D DEF wScratch EQU $D58E DEF wViewTop EQU $D58F ; view offset: screen row maps to logical row + this DEF wAssign EQU $D590 ; assign[18]: screen row -> line-slot ($D590-$D5A1) DEF CUR_L EQU $E0 ; cursor underline, left cell (3px) DEF CUR_R EQU $0E ; cursor underline, right cell SECTION "term", ROM0 ; ----------------------------------------------------------------------------- ; term_init - CGB palette, identity assign[], clear buffer, build, LCD on. ; ----------------------------------------------------------------------------- term_init:: xor a ld [rLCDC], a ; LCD off ld [rSCX], a ld [rSCY], a ; 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 ld hl, PalData ld b, 8 * 4 * 2 ; 8 palettes * 4 colors * 2 bytes .palcopy ld a, [hl+] ld [rBCPD], a dec b jr nz, .palcopy ; state: assign[sr]=sr, cursor at 0,0 (on) ld hl, wAssign ld b, TROWS xor a .mkid ld [hl+], a inc a dec b jr nz, .mkid xor a ld [wCurRow], a 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 ld de, TROWS * TSTRIDE .clr ld a, $20 ld [hl+], a dec de ld a, d 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) ld a, %10010001 ; LCD on, BG on, tile data $8000, map $9800 ld [rLCDC], a ret ; ----------------------------------------------------------------------------- ; term_write_tilemap - point each screen row's tilemap entries at assign[]'s ; line-slot tiles. Pass 1 = indices (bank 0), pass 2 = attributes (bank 1). ; ----------------------------------------------------------------------------- term_write_tilemap:: call compute_offset xor a ld [rVBK], a ld c, 0 ; screen row .i_row call .slot20 ; DE = assign[c]*20 (pos start), HL = tilemap row ld b, TCOLS/2 .i_col ld a, e ld [hl+], a ; index = LOW(pos) inc de dec b jr nz, .i_col inc c ld a, c cp TROWS jr c, .i_row ld a, 1 ld [rVBK], a ld c, 0 .a_row call .slot20 ; DE = pos, HL = tilemap addr, wTMSlot = slot ld b, TCOLS/2 .a_col ; 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, [wPalAttr] or $08 ; VRAM bank 1 tile jr .aw .a0 ld a, [wPalAttr] .aw ld [hl+], a inc de dec b jr nz, .a_col inc c ld a, c cp TROWS jr c, .a_row xor a ld [rVBK], a ret ; helper: C = screen row -> DE = assign[C]*20, HL = $9800 + C*32 .slot20 ld a, [wViewTop] add c ; logical row = screen row + view offset cp TROWS jr c, .lok ld a, TROWS-1 ; rows the OSK covers clamp to the cursor line .lok add LOW(wAssign) ld l, a ld a, HIGH(wAssign) 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 add hl, hl ; L*4 ld d, h ld e, l add hl, hl add hl, hl ; L*16 add hl, de ; L*20 ld d, h ld e, l ; DE = pos start ld a, c ld l, a ld h, 0 add hl, hl add hl, hl add hl, hl add hl, hl add hl, hl ; row*32 ld a, h add $98 ld h, a ; HL = $9800 + row*32 ret ; ----------------------------------------------------------------------------- ; slot_addr - A = screen row -> HL = TERM_BUF + assign[row]*64 ; ----------------------------------------------------------------------------- slot_addr: add LOW(wAssign) ld l, a ld a, HIGH(wAssign) adc 0 ld h, a ld a, [hl] ; line-slot 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, TERM_BUF add hl, de ret ; ----------------------------------------------------------------------------- ; glyph_ptr - A = char -> HL = FontData + (clamp(A,32,127)-32)*8 ; ----------------------------------------------------------------------------- glyph_ptr: cp 32 jr nc, .ok ld a, 32 .ok cp 128 jr c, .ok2 ld a, 32 .ok2 sub 32 ld l, a ld h, 0 add hl, hl add hl, hl add hl, hl ; *8 ld de, FontData add hl, de 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). ; ----------------------------------------------------------------------------- build_tile:: ld a, d ld [rVBK], a ; VRAM bank for this tile ld h, 0 ld l, e add hl, hl add hl, hl add hl, hl add hl, hl ; E*16 ld a, h add $80 ld h, a ; HL = $8000 + E*16 ld a, l ld [wVram], a ld a, h ld [wVram+1], a ld a, b call glyph_ptr ld a, l ld [wGlyphL], a ld a, h ld [wGlyphL+1], a ld a, c call glyph_ptr ld a, l ld [wGlyphR], a ld a, h ld [wGlyphR+1], a ld b, 8 ; row counter ld a, [wVram] ld l, a ld a, [wVram+1] ld h, a ; HL = VRAM dest .brow push hl ld a, [wGlyphL] ld e, a ld a, [wGlyphL+1] ld d, a ld a, [de] swap a and $F0 ld c, a ; left glyph -> high nibble ld a, [wGlyphR] ld e, a ld a, [wGlyphR+1] ld d, a ld a, [de] and $0F 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 c jr .wr .nocur ld a, c .wr pop hl ld [hl+], a ; plane 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 ld [wGlyphL], a jr nz, .nl ld a, [wGlyphL+1] inc a ld [wGlyphL+1], a .nl ld a, [wGlyphR] inc a ld [wGlyphR], a jr nz, .nr ld a, [wGlyphR+1] inc a ld [wGlyphR+1], a .nr dec b jr nz, .brow ret ; ----------------------------------------------------------------------------- ; render_tile - B = screen row, C = tile column: rebuild that one tile from the ; buffer, adding the cursor underline if the cursor is in it. ; ----------------------------------------------------------------------------- render_tile: ld a, b ld [wRT_sr], a ld a, c ld [wRT_tcol], a ; chars: &buffer[assign[sr]][tcol*2] ld a, b call slot_addr ; HL = &buffer[slot][0] ld a, [wRT_tcol] add a ; tcol*2 add l ld l, a ld a, h adc 0 ld h, a ld a, [hl+] ld [wRT_cL], a ld a, [hl] 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 ld a, [wCursorOn] or a jr z, .pos ld a, [wCurRow] ld b, a ld a, [wRT_sr] cp b jr nz, .pos ld a, [wRT_tcol] add a ; left cell col ld b, a ld a, [wCurCol] cp b jr nz, .cr ld a, CUR_L ld [wCurMask], a jr .pos .cr inc b ; right cell col cp b jr nz, .pos ld a, CUR_R ld [wCurMask], a .pos ; pos = assign[sr]*20 + tcol -> DE (D=bank, E=index) ld a, [wRT_sr] add LOW(wAssign) ld l, a ld a, HIGH(wAssign) adc 0 ld h, a ld a, [hl] ld l, a ld h, 0 add hl, hl add hl, hl ld d, h ld e, l ; L*4 add hl, hl add hl, hl add hl, de ; L*20 ld a, [wRT_tcol] add l ld l, a ld a, h adc 0 ld h, a ; HL = pos ld d, h ld e, l ; DE = pos ld a, [wRT_cL] ld b, a ld a, [wRT_cR] ld c, a call build_tile xor a ld [rVBK], a ret ; render_cursor_tile - rebuild the tile at the cursor position. render_cursor_tile: ld a, [wCurRow] ld b, a ld a, [wCurCol] srl a ; /2 = tile col ld c, a 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). ; ----------------------------------------------------------------------------- term_redraw:: ld b, 0 ; screen row .row ld c, 0 ; tile col .col push bc call render_tile pop bc inc c ld a, c cp TCOLS/2 jr c, .col inc b ld a, b cp TROWS jr c, .row ret ; ----------------------------------------------------------------------------- ; term_scroll - scroll up one line: rotate assign[], blank+build the new bottom ; line, rewrite the tilemap. ; ----------------------------------------------------------------------------- ; term_scroll - scroll all 18 logical rows up one; the new line becomes logical ; row 17. The view offset decides where that lands on screen, so toggling the ; OSK never destroys backlog. term_scroll:: ld a, [wAssign] ld [wScratch], a ; freed line-slot ld hl, wAssign+1 ld de, wAssign ld b, TROWS-1 .rot ld a, [hl+] ld [de], a inc de dec b jr nz, .rot ld a, [wScratch] ld [wAssign + TROWS-1], a ; clear buffer[freed] to spaces 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, TERM_BUF add hl, de ld b, TCOLS .clr ld a, $20 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 push bc ld b, TROWS-1 call render_tile pop bc inc c ld a, c cp TCOLS/2 jr c, .build call term_write_tilemap ret ; cursor_down - advance the cursor row, scrolling when it runs past the bottom. cursor_down: ld a, [wCurRow] inc a cp TROWS jr c, .ok call term_scroll ; rewrites the tilemap (offset recomputed) ld a, TROWS-1 ld [wCurRow], a ret .ok ld [wCurRow], a ; the cursor changed rows; if the OSK is up the view offset may need to move ld a, [wOskVisible] or a ret z jp term_write_tilemap ; compute_offset - set wViewTop so the cursor line is always on screen: 0 when ; the OSK is hidden; otherwise max(0, wCurRow - 14) so the cursor sits at the ; bottom visible row (14) once the terminal has filled past it, but stays put ; while there is little content (no shifting the cursor off the top). compute_offset:: ld a, [wOskVisible] or a jr nz, .osk xor a ld [wViewTop], a ret .osk ld a, [wCurRow] sub TROWS - OSK_DOCK - 1 ; wCurRow - 14 jr nc, .store xor a ; cursor above row 14 -> no shift .store ld [wViewTop], a ret ; ----------------------------------------------------------------------------- ; term_putc - A = character. Print at the cursor, advance, handle \n \r \b. ; ----------------------------------------------------------------------------- ; term_putc guarantees SVBK=1 so the buffer/state at $D5xx/$D6xx are the ones ; we built, no matter which process context KPutc calls us from. The stack is ; in non-banked WRAM ($Cxxx/$Axxx), so saving SVBK across the switch is safe. term_putc:: ld d, a ld a, [rSVBK] push af ld a, 1 ld [rSVBK], a ld a, d call term_putc_body pop af 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 jr z, .cret cp 8 jr z, .bs cp 32 ret c ; ignore other control chars ; printable: buffer[assign[cur]][cc] = char ld [wRT_cL], a ld a, [wCurRow] call slot_addr ld a, [wCurCol] add l ld l, a ld a, h adc 0 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 call render_cursor_tile ; advance ld a, [wCurCol] inc a cp TCOLS jr c, .adv xor a ld [wCurCol], a call cursor_down jr .show .adv ld [wCurCol], a .show ld a, 1 ld [wCursorOn], a call render_cursor_tile ret .newline xor a ld [wCursorOn], a call render_cursor_tile xor a ld [wCurCol], a call cursor_down ld a, 1 ld [wCursorOn], a call render_cursor_tile ret .cret xor a ld [wCursorOn], a call render_cursor_tile xor a ld [wCurCol], a ld a, 1 ld [wCursorOn], a call render_cursor_tile ret .bs ld a, [wCurCol] or a ret z xor a ld [wCursorOn], a call render_cursor_tile ; clear cursor at old ld a, [wCurCol] dec a ld [wCurCol], a ; blank the cell we backed into ld a, [wCurRow] call slot_addr ld a, [wCurCol] add l ld l, a ld a, h adc 0 ld h, a ld a, $20 ld [hl], a ld a, 1 ld [wCursorOn], a 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. ; ----------------------------------------------------------------------------- term_puts:: ld a, [hl+] or a ret z push hl call term_putc pop hl jr term_puts ; ----------------------------------------------------------------------------- ; term_demo - drive the terminal: print enough lines to scroll, end at a prompt. ; ----------------------------------------------------------------------------- term_demo:: ld hl, .msg call term_puts ret .msg: db "GBOS terminal: cursor + fast scroll", 10 db "01 the quick brown fox jumps", 10 db "02 over the lazy dog.", 10 db "03 line three of the scroll test", 10 db "04 line four", 10 db "05 line five", 10 db "06 line six", 10 db "07 line seven", 10 db "08 line eight", 10 db "09 line nine", 10 db "10 line ten", 10 db "11 line eleven", 10 db "12 line twelve", 10 db "13 line thirteen", 10 db "14 line fourteen", 10 db "15 line fifteen", 10 db "16 line sixteen", 10 db "17 line seventeen", 10 db "18 line eighteen", 10 db "19 line nineteen", 10 db "20 line twenty", 10 db "21 line twenty-one", 10 db "22 line twenty-two", 10 db "ready$ ", 0