; grid - abstraction for grid-like video output ; ; Collapse OS doesn't support curses-like interfaces: too complicated. However, ; in cases where output don't have to go through a serial interface before ; being displayed, we have usually have access to a grid-like interface. ; ; Direct access to this kind of interface allow us to build an abstraction layer ; that is very much alike curses but is much simpler underneath. This unit is ; this abstraction. ; ; The principle is simple: we have a cell grid of X columns by Y rows and we ; can access those cells by their (X, Y) address. In addition to this, we have ; the concept of an active cursor, which will be indicated visually if possible. ; ; This module provides PutC and GetC routines, suitable for plugging into stdio. ; PutC, for obvious reasons, GetC, for less obvious reasons: We need to wrap ; GetC because we need to update the cursor before calling actual GetC, but ; also, because we need to know when a bulk update ends. ; ; *** Defines *** ; ; GRID_COLS: Number of columns in the grid ; GRID_ROWS: Number of rows in the grid ; GRID_SETCELL: Pointer to routine that sets cell at row D and column E with ; character in A. If C is nonzero, this cell must be displayed, ; if possible, as the cursor. This routine is never called with ; A < 0x20. ; GRID_GETC: Routine that gridGetC will wrap around. ; ; *** Consts *** .equ GRID_SIZE GRID_COLS*GRID_ROWS ; *** Variables *** ; Cursor's column .equ GRID_CURX GRID_RAMSTART ; Cursor's row .equ GRID_CURY @+1 ; Whether we scrolled recently. We don't refresh the screen immediately when ; scrolling in case we have many lines being spit at once (refreshing the ; display is then very slow). Instead, we wait until the next gridGetC call .equ GRID_SCROLLED @+1 ; Grid's in-memory buffer of the contents on screen. Because we always push to ; display right after a change, this is almost always going to be a correct ; representation of on-screen display. ; The buffer is organized as a rows of columns. The cell at row Y and column X ; is at GRID_BUF+(Y*GRID_COLS)+X. .equ GRID_BUF @+1 .equ GRID_RAMEND @+GRID_SIZE ; *** Code *** gridInit: xor a ld b, GRID_RAMEND-GRID_RAMEND ld hl, GRID_RAMSTART jp fill ; Place HL at row D and column E in the buffer ; Destroys A _gridPlaceCell: ld hl, GRID_BUF ld a, d or a jr z, .setcol push de ; --> lvl 1 ld de, GRID_COLS .loop: add hl, de dec a jr nz, .loop pop de ; <-- lvl 1 .setcol: ; We're at the proper row, now let's advance to cell ld a, e jp addHL ; Ensure that A >= 0x20 _gridAdjustA: cp 0x20 ret nc ld a, 0x20 ret ; Push row D in the buffer onto the screen. gridPushRow: push af push bc push de push hl ; Cursor off ld c, 0 ld e, c call _gridPlaceCell ld b, GRID_COLS .loop: ld a, (hl) call _gridAdjustA ; A, C, D and E have proper values call GRID_SETCELL inc hl inc e djnz .loop pop hl pop de pop bc pop af ret ; Clear row D and push contents to screen gridClrRow: push af push bc push de push hl ld e, 0 call _gridPlaceCell ld a, ' ' ld b, GRID_COLS call fill call gridPushRow pop hl pop de pop bc pop af ret gridPushScr: push de ld d, GRID_ROWS-1 .loop: call gridPushRow dec d jp p, .loop pop de ret ; Set character under cursor to A. C is passed to GRID_SETCELL as-is. gridSetCur: push de push hl push af ; --> lvl 1 ld a, (GRID_CURY) ld d, a ld a, (GRID_CURX) ld e, a call _gridPlaceCell pop af \ push af ; <--> lvl 1 ld (hl), a call _gridAdjustA call GRID_SETCELL pop af ; <-- lvl 1 pop hl pop de ret ; Call gridSetCur with C = 1. gridSetCurH: push bc ld c, 1 call gridSetCur pop bc ret ; Call gridSetCur with C = 0. gridSetCurL: push bc ld c, 0 call gridSetCur pop bc ret ; Clear character under cursor gridClrCur: push af ld a, ' ' call gridSetCurL pop af ret gridLF: call gridClrCur push de push af ld a, (GRID_CURY) ; increase A inc a cp GRID_ROWS jr nz, .noscroll ; bottom reached, stay on last line and scroll screen push hl push de push bc ld de, GRID_BUF ld hl, GRID_BUF+GRID_COLS ld bc, GRID_SIZE-GRID_COLS ldir ld hl, GRID_SCROLLED inc (hl) ; mark as scrolled pop bc pop de pop hl dec a .noscroll: ; A has been increased properly ld d, a call gridClrRow ld (GRID_CURY), a xor a ld (GRID_CURX), a pop af pop de ret gridBS: call gridClrCur push af ld a, (GRID_CURX) or a jr z, .lineup dec a ld (GRID_CURX), a pop af ret .lineup: ; end of line, we need to go up one line. But before we do, are we ; already at the top? ld a, (GRID_CURY) or a jr z, .end dec a ld (GRID_CURY), a ld a, GRID_COLS-1 ld (GRID_CURX), a .end: pop af ret gridPutC: cp LF jr z, gridLF cp BS jr z, gridBS cp ' ' ret c ; ignore unhandled control characters call gridSetCurL push af ; --> lvl 1 ; Move cursor ld a, (GRID_CURX) cp GRID_COLS-1 jr z, .incline ; We just need to increase X inc a ld (GRID_CURX), a pop af ; <-- lvl 1 ret .incline: ; increase line and start anew call gridLF pop af ; <-- lvl 1 ret gridGetC: ld a, (GRID_SCROLLED) or a jr z, .nopush ; We've scrolled recently, update screen xor a ld (GRID_SCROLLED), a call gridPushScr .nopush: ld a, ' ' call gridSetCurH jp GRID_GETC