aboutsummaryrefslogtreecommitdiffstats
path: root/src/osk.asm
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-16 16:07:01 +0200
committeruser <user@clank>2026-07-16 16:07:01 +0200
commita5557c75767f7201d5df8c6f95c162044072ef10 (patch)
treeed4ddd29ba3e6411b1b20f41fcbf8f67c023c93e /src/osk.asm
parentsyscall: mirror console output to the LCD terminal (diff)
downloadgbos-a5557c75767f7201d5df8c6f95c162044072ef10.tar.gz
gbos-a5557c75767f7201d5df8c6f95c162044072ef10.tar.xz
gbos-a5557c75767f7201d5df8c6f95c162044072ef10.zip
osk: toggle-able on-screen keyboard on the window layer
A joypad-driven keyboard that costs zero permanent screen space: it lives on the GB window layer, so SELECT just flips LCDC bit 5 (window enable) and the terminal's background layer underneath is never disturbed. - src/joypad.asm: read $FF00 with edge detection (wPadCur/Prev/New). - src/osk.asm: 3x20 key grid (letters, digits, space, punctuation, plus Enter/Backspace) built once into spare bank-1 VRAM tiles (104+) and laid out on the $9C00 window map, docked to the bottom 3 rows. The highlighted key is a palette swap on its window attribute byte (CGB BG palette 1 = inverted), so moving the cursor is 1-2 attribute writes with no tile rebuilding. SELECT toggles, d-pad moves, A types. - KGetc's console poll loop now polls the joypad and osk_handle each iteration; a key press returns its byte to the reader exactly like a serial byte, so the shell is oblivious to the input source. Verified in the emulator (via --keys): SELECT shows the keyboard, the highlight tracks the d-pad, and typing "ls"+Enter runs the command and lists the files; a second SELECT hides it and reclaims the full 18 rows. Known limitation: the OSK overlays the bottom 3 terminal rows, so if the prompt has scrolled to the very bottom the current input line can be hidden. A follow-up can add a scroll region (terminal uses rows 0-14 while the OSK is up). Input over serial still works unchanged.
Diffstat (limited to 'src/osk.asm')
-rw-r--r--src/osk.asm339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/osk.asm b/src/osk.asm
new file mode 100644
index 0000000..b8ee611
--- /dev/null
+++ b/src/osk.asm
@@ -0,0 +1,339 @@
+; =============================================================================
+; osk.asm - on-screen keyboard on the WINDOW layer (CGB).
+;
+; The terminal owns the BG layer ($9800 map, tiles 0-359). The OSK lives on the
+; window ($9C00 map) so toggling it is just LCDC bit 5 (window enable) - the
+; terminal underneath is never touched. Key glyphs are built once into spare
+; bank-1 VRAM tiles (104+). The highlighted key is a palette swap on its window
+; attribute byte (palette 1 = inverted), so moving the cursor is 1-2 byte writes
+; with no tile rebuilding. SELECT toggles; d-pad moves; A types into KGetc.
+; =============================================================================
+INCLUDE "include/gbos.inc"
+
+DEF rVBK EQU $FF4F
+DEF rBCPS EQU $FF68
+DEF rBCPD EQU $FF69
+DEF rWY EQU $FF4A
+DEF rWX EQU $FF4B
+
+DEF OSK_ROWS EQU 3
+DEF OSK_COLS EQU 20
+DEF OSK_KEYS EQU OSK_ROWS * OSK_COLS
+DEF OSK_TILE0 EQU 104 ; first OSK tile index (bank 1; terminal uses 0-103)
+DEF OSK_MAP EQU $9C00 ; window tilemap
+
+SECTION "osk_state", WRAM0
+wOskVisible:: DS 1
+wOskRow:: DS 1
+wOskCol:: DS 1
+wOskBuild:: DS 1 ; scratch for the init build loop
+
+SECTION "osk_keys", ROM0
+; type values, row-major (OSK_ROWS x OSK_COLS)
+osk_keys:
+ db "abcdefghijklmnopqrst"
+ db "uvwxyz0123456789 .-/"
+ db 10, 8, ":;,=+*_!?()[]<>@#~"
+
+SECTION "osk", ROM0
+
+; -----------------------------------------------------------------------------
+; osk_disp - A = key type value -> B = left glyph, C = right glyph (2-char label)
+; -----------------------------------------------------------------------------
+osk_disp:
+ cp 10
+ jr nz, .n1
+ ld b, "C"
+ ld c, "R"
+ ret ; enter
+.n1
+ cp 8
+ jr nz, .n2
+ ld b, "<"
+ ld c, "-"
+ ret ; backspace
+.n2
+ cp " "
+ jr nz, .n3
+ ld b, "S"
+ ld c, "P"
+ ret ; space
+.n3
+ ld b, a ; regular: char in left cell
+ ld c, " "
+ ret
+
+; -----------------------------------------------------------------------------
+; osk_init - build key tiles, lay out the window map, set up palette 1. Window
+; starts disabled.
+; -----------------------------------------------------------------------------
+osk_init::
+ ; CGB BG palette 1 = inverted: color0 black, color1 white
+ ld a, $88 ; BCPS index 8 (palette 1, color 0), auto-inc
+ ld [rBCPS], a
+ xor a
+ ld [rBCPD], a
+ ld [rBCPD], a ; c0 = $0000 black
+ ld a, $FF
+ ld [rBCPD], a
+ ld a, $7F
+ ld [rBCPD], a ; c1 = $7FFF white
+
+ ; build the key tiles (no cursor underline)
+ xor a
+ ld [wCurMask], a
+ ld [wOskBuild], a
+.build
+ ld a, [wOskBuild]
+ ld e, a
+ ld d, 0
+ ld hl, osk_keys
+ add hl, de
+ ld a, [hl]
+ call osk_disp ; B=left, C=right
+ ld a, [wOskBuild]
+ add OSK_TILE0
+ ld e, a
+ ld d, 1 ; VRAM bank 1
+ call build_tile
+ ld a, [wOskBuild]
+ inc a
+ ld [wOskBuild], a
+ cp OSK_KEYS
+ jr c, .build
+ xor a
+ ld [rVBK], a
+
+ ; window map indices (bank 0): [r,c] = OSK_TILE0 + r*20 + c
+ ld c, 0
+.i_row
+ ld a, c
+ call .rowbase ; A = OSK_TILE0 + row*20 (clobbers HL/DE)
+ ld e, a
+ call .maprow ; HL = OSK_MAP + row*32 (preserves E)
+ ld b, OSK_COLS
+.i_col
+ ld a, e
+ ld [hl+], a
+ inc e
+ dec b
+ jr nz, .i_col
+ inc c
+ ld a, c
+ cp OSK_ROWS
+ jr c, .i_row
+
+ ; window map attributes (bank 1): $08 = bank-1 tile, palette 0
+ ld a, 1
+ ld [rVBK], a
+ ld c, 0
+.a_row
+ call .maprow
+ ld b, OSK_COLS
+.a_col
+ ld a, $08
+ ld [hl+], a
+ dec b
+ jr nz, .a_col
+ inc c
+ ld a, c
+ cp OSK_ROWS
+ jr c, .a_row
+ xor a
+ ld [rVBK], a
+
+ ; window position: 3 rows at the bottom (screen rows 15-17)
+ ld a, 7
+ ld [rWX], a
+ ld a, 120
+ ld [rWY], a
+ ; select window map $9C00 (LCDC bit 6); leave window disabled (bit 5 = 0)
+ ld a, [rLCDC]
+ or %01000000
+ ld [rLCDC], a
+
+ xor a
+ ld [wOskVisible], a
+ ld [wOskRow], a
+ ld [wOskCol], a
+ call osk_highlight
+ ret
+
+; HL = OSK_MAP + C*32
+.maprow
+ ld a, c
+ ld h, 0
+ ld l, a
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ ld a, h
+ add HIGH(OSK_MAP)
+ ld h, a
+ ret
+; A(row) -> A = OSK_TILE0 + row*20
+.rowbase
+ ld h, 0
+ ld l, a
+ 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, l
+ add OSK_TILE0
+ ret
+
+; -----------------------------------------------------------------------------
+; osk_set_pal - write palette A (0 or 1) into the current (wOskRow,wOskCol)
+; window attribute.
+; -----------------------------------------------------------------------------
+osk_set_pal:
+ or $08 ; bank-1 bit
+ ld e, a ; save attr byte
+ ld a, 1
+ ld [rVBK], a
+ ld a, [wOskRow]
+ ld h, 0
+ ld l, a
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl
+ add hl, hl ; row*32
+ ld a, h
+ add HIGH(OSK_MAP)
+ ld h, a
+ ld a, [wOskCol]
+ add l
+ ld l, a
+ ld a, h
+ adc 0
+ ld h, a
+ ld a, e
+ ld [hl], a
+ xor a
+ ld [rVBK], a
+ ret
+
+osk_highlight:
+ ld a, 1
+ jr osk_set_pal
+osk_unhighlight:
+ xor a
+ jr osk_set_pal
+
+; -----------------------------------------------------------------------------
+; osk_toggle - show/hide the keyboard (window enable, LCDC bit 5).
+; -----------------------------------------------------------------------------
+osk_toggle::
+ ld a, [wOskVisible]
+ xor 1
+ ld [wOskVisible], a
+ or a
+ jr z, .hide
+ ld a, [rLCDC]
+ or %00100000
+ ld [rLCDC], a
+ ret
+.hide
+ ld a, [rLCDC]
+ and %11011111
+ ld [rLCDC], a
+ ret
+
+; movement (with clamping); each moves the highlight
+osk_up:
+ ld a, [wOskRow]
+ or a
+ ret z
+ call osk_unhighlight
+ ld hl, wOskRow
+ dec [hl]
+ jr osk_highlight
+osk_down:
+ ld a, [wOskRow]
+ cp OSK_ROWS-1
+ ret z
+ call osk_unhighlight
+ ld hl, wOskRow
+ inc [hl]
+ jr osk_highlight
+osk_left:
+ ld a, [wOskCol]
+ or a
+ ret z
+ call osk_unhighlight
+ ld hl, wOskCol
+ dec [hl]
+ jr osk_highlight
+osk_right:
+ ld a, [wOskCol]
+ cp OSK_COLS-1
+ ret z
+ call osk_unhighlight
+ ld hl, wOskCol
+ inc [hl]
+ jr osk_highlight
+
+; -> A = type value of the selected key
+osk_selected:
+ ld a, [wOskRow]
+ ld h, 0
+ ld l, a
+ add hl, hl
+ add hl, hl
+ ld d, h
+ ld e, l
+ add hl, hl
+ add hl, hl
+ add hl, de ; row*20
+ ld a, [wOskCol]
+ add l
+ ld l, a
+ ld a, h
+ adc 0
+ ld h, a
+ ld de, osk_keys
+ add hl, de
+ ld a, [hl]
+ ret
+
+; -----------------------------------------------------------------------------
+; osk_handle - process the latest pad edges. Returns CF set + A = typed byte if
+; the user pressed A over a key, else CF clear. (SELECT toggles; d-pad moves.)
+; -----------------------------------------------------------------------------
+osk_handle::
+ ld a, [wPadNew]
+ bit 2, a ; SELECT
+ jr z, .nosel
+ call osk_toggle
+.nosel
+ ld a, [wOskVisible]
+ or a
+ jr z, .none
+ ld a, [wPadNew]
+ bit 6, a
+ call nz, osk_up
+ ld a, [wPadNew]
+ bit 7, a
+ call nz, osk_down
+ ld a, [wPadNew]
+ bit 5, a
+ call nz, osk_left
+ ld a, [wPadNew]
+ bit 4, a
+ call nz, osk_right
+ ld a, [wPadNew]
+ bit 0, a ; A: type the selected key
+ jr z, .none
+ call osk_selected
+ scf
+ ret
+.none
+ or a ; CF = 0
+ ret