$PAGINATE
$title(Arnold 5 test)
$subtitle(Routine to allow drawing of and selection from menus)
$copyright(Copyright (c) 1989, 1990, Amstrad plc.)
$pagewidth=131


        PUBLIC  ProcessAMenu            ;called from TESTPACK

        EXTERN  ScreenSetMode           ;in SUPPORT
        EXTERN  SetCursorPos            ;in SUPPORT
        EXTERN  HighlightLine           ;in SUPPORT
        EXTERN  PrintStringHL           ;in SUPPORT
        EXTERN  KeyboardRead            ;in SUPPORT
        EXTERN  PrintLoopLimit          ;in COUNTER

        EXTERN  ?MenuTopRow             ;in TESTVARS
        EXTERN  ?MenuBottomRow          ;in TESTVARS
        EXTERN  ?MenuHighlightedRow     ;in TESTVARS
        EXTERN  ?MenuCursorPos          ;in TESTVARS
        EXTERN  ?MenuAddrTable          ;in TESTVARS
        EXTERN  ?MenuTimeout            ;in TESTVARS
        EXTERN  ?MenuLastKeyPress       ;in TESTVARS
        EXTERN  ?MenuDebounceCount      ;in TESTVARS

        DEFSEG  TestCode, CLASS=CODE

        SEG     TestCode

ProcessAMenu:
;
; A holds 0 if a full clear screen and re-draw is in order. If we just want to
; go back to processing a menu already displayed then enter with A=1
;
; This routine is entered with HL holding the address of a "menu table". This
; table has the following format:
;
;       defb    rowforline1
;       defb    colforline1
;
;       defb    "Text of line 1"
;       defb    "$"
;
;       defb    "Text of line 2"
;       defb    "$"
;        :       :      :
;       defb    "Text of line N"
;       defb    "$"
;
;       defb    0
;
;       defw    address of routine to run if 1 selected
;       defw    address of routine to run if 2 selected
;       defw    address of routine to run if N selected
;
; The routine then draws the lines as described. The first line is started at
; the given row/column then subsequent lines start at the same column but
; on incrementing lines.
;
; A highlighting bar is drawn over the first line and then a loop is entered
; that scans for keys (joystick). If an up movement is made the current row
; counter is decremented unless it is equal to the top row. If it is then it
; is set equal to the bottom row. The opposite also applies.
;
; If the user presses [SPACE], [ENTER], [RETURN] or the joystick fire button
; then the line the highlight is currently has the top line subtracted from
; it to give a line offset. This is doubled and used to index into the table
; of routine addresses. The value there is picked up and returned in HL.
;
        push    af
        push    hl
        ld      hl,200h                 ;timeout value (about 30 secs)
        ld      (?MenuTimeout),hl
        ld      a,0
        ld      (?MenuLastKeyPress),a   ;reset debounce key
        ld      (?MenuDebounceCount),a
        pop     hl
        pop     af

        cp      1                       ;should we skip re-draw
        jp      z,_MenuKeyScanLoop

        push    af
        ld      a,0FFh
        ld      (?MenuHighlightedRow),a      ;mark it as unset
        pop     af
        cp      2                       ;parm of 2 means B holds highlightedRow
        jr      nz,_MenuNoKeepHighlight
        ld      a,b
        ld      (?MenuHighlightedRow),a

_MenuNoKeepHighlight:
        push    hl
        ld      a,1
        call    ScreenSetMode
        pop     hl

        ld      d,(hl)                  ;get row number
        inc     hl
        ld      e,(hl)                  ;and column number
        inc     hl
        ld      (?MenuCursorPos),de     ;save that top cursor position
        ld      a,d                     ;stupid Z80 wont allow ld (nn),d
        ld      (?MenuTopRow),a

        call    SetCursorPos            ;a SUPPORT routine

_MenuPrintStrings:
        ld      a,(hl)                  ;get current char
        cp      0                       ;a zero marks end of text
        jr      z,_MenuStringsPrinted   ;leave loop when found
        ld      b,1                     ;output to screen only
        call    PrintStringHL           ;print the current string
        inc     hl
        ld      de,(?MenuCursorPos)
        inc     d                       ;step row on
        ld      (?MenuCursorPos),de
        call    SetCursorPos            ;move to new line
        jr      _MenuPrintStrings

_MenuStringsPrinted:
        inc     hl                      ;step past the 0 byte
        ld      (?MenuAddrTable),hl     ;it currently points at address table

        ld      de,(?MenuCursorPos)
        ld      a,d                     ;the bottom row
        dec     a                       ;cos we'll have stepped passed the bot.
        ld      (?MenuBottomRow),a

        call    PrintLoopLimit

        ld      a,(?MenuHighlightedRow)
        cp      0FFh                    ;has it been preset ?
        jr      nz,_MenuNoSetHighlight
        ld      a,(?MenuTopRow)
        ld      (?MenuHighlightedRow),a
_MenuNoSetHighlight:
        ld      d,a                     ;stupid Z80 wont allow ld d,(nn)
        call    HighlightLine           ;this highlights the top menu selection (SUPPORT)

_MenuKeyScanLoop:
        call    KeyboardRead            ;(SUPPORT) keynumber into A or 0FFh
        ld      hl,?MenuLastKeyPress
        cp      (hl)                    ;same key pushed again ?
        jr      z,_MenuIncDebounce
        ld      (hl),a                  ;just remember the new key pressed
        ld      a,0
        ld      (?MenuDebounceCount),a  ;and start debounce count again
        jr      _MenuKeyScanLoop

_MenuIncDebounce:
        ld      l,a                     ;save key pressed
        ld      a,(?MenuDebounceCount)
        inc     a
        ld      (?MenuDebounceCount),a
        cp      1                       ;has same key been pressed 8 times ?
        jr      z,_MenuGotAKey
        jr      _MenuKeyScanLoop

_MenuGotAKey:
        ld      a,l                     ;recover key pressed
        ld      hl,?MenuLastKeyPress
        ld      (hl),0                  ;reset debounce key
        ld      hl,?MenuDebounceCount
        ld      (hl),0
        push    af                      ;save keycode
        ld      hl,(?MenuTimeout)
        dec     hl
        ld      (?MenuTimeout),hl
        ld      a,h
        or      l
        jr      nz,_MenuNoTimeout
        pop     af                      ;clear stack of saved key code
        scf                             ;carry set means time out
        ld      a,(?MenuHighlightedRow)
        ret

_MenuNoTimeout:
        pop     af                      ;recover saved key code
        cp      0FFH                    ;if no key...
        jr      z,_MenuKeyScanLoop      ; ...then just loop waiting for one
        cp      0                       ;up key
        jr      z,_MenuBarUp
        cp      72                      ;joy0 up
        jr      z,_MenuBarUp
        cp      48                      ;joy1 up
        jr      z,_MenuBarUp
        cp      2                       ;down key
        jr      z,_MenuBarDown
        cp      73                      ;joy0 down
        jr      z,_MenuBarDown
        cp      49                      ;joy1 down
        jr      z,_MenuBarDown
        cp      47                      ;space
        jr      z,_MenuSelect
        cp      6                       ;enter
        jr      z,_MenuSelect
        cp      18                      ;return
        jr      z,_MenuSelect
        cp      77                      ;joy0 fire1
        jr      z,_MenuSelect
        cp      76                      ;joy0 fire2
        jr      z,_MenuSelect
        cp      53                      ;joy1 fire1
        jr      z,_MenuSelect
        cp      52                      ;joy1 fire2
        jr      z,_MenuSelect
        jr      _MenuKeyScanLoop        ;unidentified key so get another

_MenuBarUp:
;
; Call highlight on current line (a second time) - this removes it. Then
; decrement the MenuHighlightedRow var but if this becomes less than
; MenuTopRow then set it to MenuBottomRow. Finally call highlight row.
;
        ld      hl,0FFFFh
        ld      (?MenuTimeout),hl       ;cursor moved so huge timeout
        ld      a,(?MenuHighlightedRow)
        ld      d,a
        call    HighlightLine           ;remove current highlight with 2nd XOR
        dec     d                       ;attempt to move up a line
        ld      a,(?MenuTopRow)
        dec     a
        cp      d                       ;have we gone over the top ?
        jr      nc,_MenuUpAtTop         ;if yes then jump to set to bottom
        ld      a,d                     ;else
        ld      (?MenuHighlightedRow),a ;update counter
        call    HighlightLine           ;and highlight the new line
        jp      _MenuKeyScanLoop        ;then back round for another key

_MenuUpAtTop:
;
; The up movement has taken us OTT so loop round to the bottom.
;
        ld      a,(?MenuBottomRow)
        ld      (?MenuHighlightedRow),a
        ld      d,a
        call    HighlightLine
        jp      _MenuKeyScanLoop

_MenuBarDown:
;
; Call highlight on current line (a second time) - this removes it. Then
; increment the MenuHighlightedRow var but if this becomes more than
; MenuBottomRow then set it to MenuTopRow. Finally call highlight row.
;
        ld      hl,0FFFFh
        ld      (?MenuTimeout),hl       ;cursor moved so "cancel" timeout
        ld      a,(?MenuHighlightedRow)
        ld      d,a
        call    HighlightLine           ;remove current highlight with 2nd XOR
        inc     d                       ;attempt to move down a line
        ld      a,(?MenuBottomRow)
        cp      d                       ;have we gone past the bottom ?
        jr      c,_MenuDownAtBottom     ;if yes then jump to set to top
        ld      a,d                     ;else
        ld      (?MenuHighlightedRow),a ;update counter
        call    HighlightLine           ;and highlight the new line
        jp      _MenuKeyScanLoop        ;then back round for another key

_MenuDownAtBottom:
;
; The down movement has taken us past the bottom so loop round to the top.
;
        ld      a,(?MenuTopRow)
        ld      (?MenuHighlightedRow),a
        ld      d,a
        call    HighlightLine
        jp      _MenuKeyScanLoop        ;then back round for another key

_MenuSelect:
;
; Work out which line we are on by subtracting the MenuTopRow from row value
; in MenuHighlightedRow. Double this to give a word offset then use this to
; index into the table of routine addresses whose base is pointed to by
; 'MenuAddrTable'
;
        ld      a,(?MenuTopRow)
        ld      d,a                             ;D=top of menu
        ld      a,(?MenuHighlightedRow)         ;A=current row
        sub     d                               ;take D from A to give actual menu line
        ld      l,a                             ;gonna use HL for 16 bit sums
        ld      h,0                             ;clear top half
        add     hl,hl                           ;make line into word offset
        ld      de,(?MenuAddrTable)             ;get pointer to address table
        add     hl,de                           ;index into the table
        ld      a,(hl)                          ;get low byte of target routine
        inc     hl
        ld      h,(hl)                          ;then the high byte
        ld      l,a                             ;make full address in HL (returned)
        xor     a                               ;clear CARRY flag
        ld      a,(?MenuHighlightedRow)         ;return current selection
        ret

        END
