Here is a useful routine! It is a scrollable menu that can have multiple titles, but works on the graph screen.
Feel free to use any of these!
Scrollable Menu With Multiple Headings
Code:
;This routine is a little complicated to set up!
;It is intended to work in a variety of environments, so it needs a few
;variables, and 17 bytes of RAM.
;===============================================================================
;First, you'll need to define the routine that you are using to display text.
;#define Text() bcall(_VPutS)
;How tall is the text?
;#define TEXT_HEIGHT 6
;Are text coords stored with the upper byte as Y and lower byte as X ?
;If so, uncomment the following! You'll want this for VPutS, for example.
;#define TEXTCOORD_YX ;define if textcoord is Y then X (TI-OS is Y then X for small font, large font and some custom text routines use the opposite)
;Where are the text coordinates stored?
;textcoord = penCol
;How are you updating the LCD? Expect interrupts to be on, but you may turn them off.
; #macro LCD_Update()
; di
; call UpdateLCD
; #endmacro
;We need to draw rectangles, so what routines do you want to use?
;Inputs are (B,C)=(x,y), and (D,E)=(width,height)
; #macro rect_OR()
; ;Draw a black rectangle
; ld hl,plotSScreen
; call rectOR
; #endmacro
;
; #macro rect_XOR()
; ;Draw an inverted rectangle
; ld hl,plotSScreen
; call rectXOR
; #endmacro
;
; #macro rect_Erase()
; ;Draw a white rectangle
; ld hl,plotSScreen
; call rectErase
; #endmacro
;
;Now define where vars are located, 17 bytes required in all.
;textbox_top = $8000
;textbox_left = textbox_top+1 ;needs to follow textbox_top
;textbox_bottom = $8002
;textbox_right = textbox_bottom+1 ;needs to follow textbox_bottom
;menucallptr = $8004
;menutop = $8006
;menucur = $8008
;menuselection = $800A
;menuheader_ptr = $800C
;menuheader_coord= $800E
;menuheader_cur = $8010 ;1 byte
;===============================================================================
menu:
;Input:
; (B,C) = (x,y)
; (D,E) = (width,height)
; HL points to the header
; IX points to the get/select code
; If you are using the TI-OS VPutS routine, you'll need to have the textWrite flag set in sGrFlags
;Notes:
; The header is set up as follows:
; .db number_of_titles
; .db "title 0",0
; .db "title 1",0
; .db "title 2",0
; ...
;
; The get/select code is passed the following information:
; A is the index of the current title (0 is the first, 1 is the second, etc.)
; HL is the index of the currently highlighted item
; carry flag is reset if it needs to return a pointer to the string for the element
; Return carry flag as set if the element is out-of-bounds.
; carry flag is set if it needs to return a pointer to the data for the element
ld (menucallptr),ix
;save box dimensions
push bc
;Save the pointer to the menu headers field
ld (menuheader_ptr),hl
;Set the menu header to header 0
xor a
ld (menuheader_cur),a
;establish the bottom and right edges of the textbox
ld a,c
add a,e
ld (textbox_bottom),a
ld a,b
add a,d
ld (textbox_right),a
; Draw the rectangle for the menu. Black border, white fill.
rect_OR()
; Display the header
; get coords
pop hl
inc h
inc h
inc l
push hl
#ifdef TEXTCOORD_YX
;need to swap the coords order
ld a,h
ld h,l
ld l,a
#endif
ld (menuheader_coord),hl
call draw_header
pop bc
;Before we do too much, let's establish the top-left textbox boundaries.
ld a,TEXT_HEIGHT+2
add a,c
ld c,a
ld (textbox_top),bc
call reset_menu_cursor
;===============================================================================
; Now we have drawn the menu box, we have to populate it.
; We'll call a routine to get the n-th string to be displayed.
; Stop fetching once the next item would go at or past textbox_bottom.
; Draw at least one item.
menu_render:
;Restore the text coordinates top
ld hl,(textbox_top)
#ifndef TEXT_PAD_TOP
inc l
#endif
#ifdef TEXTCOORD_YX
;need to swap the coords order
ld a,h
ld h,l
ld l,a
#endif
ld (textcoord),hl
;rerender the items
call menu_render_sub
menu_render_0:
;highlight the current option
ld bc,(menuselection)
ld hl,(textbox_bottom)
ld a,h
sub b
ld d,a
ld e,TEXT_HEIGHT+1
dec d
push de
push bc
rect_XOR()
LCD_Update()
pop bc
pop de
rect_XOR()
;wait for a keypress
_:
in a,(4)
and 8
jr z,menu_get_select_err
call getKeyDebounce
or a
jr z,-_
cp 9
scf
jr z,menu_get_select
cp 15
jr z,menu_get_select_err
call menu_arrow
jr c,menu_render_0
jr menu_render
menu_get_select:
ld hl,(menucallptr)
ld a,(menuheader_cur)
jp (hl)
menu_render_sub:
; need to clear out the textbox
ld bc,(textbox_top)
ld hl,(textbox_bottom)
ld a,h
sub b
ld d,a
ld a,l
sub c
ld e,a
dec e
dec b
rect_Erase()
xor a
ld bc,(menutop)
menu_render_sub_loop:
push bc
call menu_get_select
pop bc
ret c
ld de,(textcoord)
push de
Text()
pop de
#ifdef TEXTCOORD_YX
ld a,TEXT_HEIGHT
add a,d
ld d,a
ld a,(textbox_bottom)
#ifdef TEXT_PAD_TOP
sub TEXT_HEIGHT+2
#else
sub TEXT_HEIGHT+1
#endif
cp d
#else
ld a,TEXT_HEIGHT
add a,e
ld e,a
ld a,(textbox_bottom)
#ifdef TEXT_PAD_TOP
sub TEXT_HEIGHT+2
#else
sub TEXT_HEIGHT+1
#endif
cp e
#endif
ld (textcoord),de
inc bc
jr nc,menu_render_sub_loop
ret
menu_get_select_err:
;return a null pointer.
;A=0, HL=0
xor a
ld h,a
ld l,a
ret
menu_arrow:
;check arrow keys
dec a
jr z,menu_down
dec a
jr z,menu_left
dec a
jr z,menu_right
add a,-1 ;sets the carry flag if it is not a keypress
ret nz
;if would select oob
; if next element exists
; increment the menutop and rerender the menu
;else
; move menuselection
menu_up:
or a
ld bc,(menucur)
dec bc
push bc
call menu_get_select
pop hl
ret c
ld (menucur),hl
ld a,(menuselection)
ld hl,(textbox_top)
cp l
jr z,+_
sub TEXT_HEIGHT
ld (menuselection),a
scf
ret
_:
ld hl,(menutop)
dec hl
ld (menutop),hl
ret
menu_down:
or a
ld bc,(menucur)
inc bc
push bc
call menu_get_select
pop hl
ret c
ld (menucur),hl
ld a,(menuselection)
add a,TEXT_HEIGHT+TEXT_HEIGHT+1
ld hl,(textbox_bottom)
cp l
jr nc,+_
sub TEXT_HEIGHT+1
ld (menuselection),a
scf
ret
_:
ld hl,(menutop)
inc hl
ld (menutop),hl
ret
;These change to the next menu header
menu_left:
ld a,(menuheader_cur)
dec a
jr +_
menu_right:
ld a,(menuheader_cur)
inc a
_:
ld (menuheader_cur),a
call reset_menu_cursor
draw_header:
;Set up textcoord
ld hl,(menuheader_coord)
#ifndef TEXT_PAD_TOP
#ifdef TEXTCOORD_YX
inc h
#else
inc l
#endif
#endif
ld (textcoord),hl
;Need to erase the current header area
#ifdef TEXTCOORD_YX
;need to swap the coords order
ld b,l
ld c,h
#else
ld b,h
ld c,l
#endif
#ifndef TEXT_PAD_TOP
dec c
#endif
ld de,(textbox_bottom)
ld a,d
sub b
ld d,a
dec b
ld e,TEXT_HEIGHT+1
rect_Erase()
;Verify that the header element is valid, wrap if needed
ld hl,(menuheader_ptr)
ld a,(menuheader_cur)
cp (hl)
jr c,+_
inc a
jr nz,$+5
;cycle to the last header
ld a,(hl)
dec a
.db $FE
;cycle to the first header
xor a
ld (menuheader_cur),a
_:
;A is the header to seek
ld bc,0 ;using CPIR, want to make sure it doesn't exit too soon!
inc hl ;point to the first header
or a
jr draw_header_loopend
draw_header_loop:
push af
xor a
cpir
pop af
dec a
draw_header_loopend:
jr nz,draw_header_loop
;now draw the header
Text()
or a
ret
reset_menu_cursor:
ld hl,(textbox_top)
dec h
ld (menuselection),hl
ld hl,0
ld (menutop),hl
ld (menucur),hl
ret
In my test, for example, I define these:
Code:
#define K_DELAY_DEFAULT 13
#define K_DELAY_ACCEL 3
#define Text() bcall(_VPutS)
#define TEXTCOORD_YX ;comment if textcoord is X then Y (TI-OS is Y then X)
#define TEXT_PAD_TOP ;comment if there is not a row of padding above the text
#define TEXT_HEIGHT 6
textcoord = penCol
textbox_top = $8000
textbox_left = textbox_top+1
textbox_bottom = $8002
textbox_right = textbox_bottom+1
menucallptr = $8004
menutop = $8006
menucur = $8008
menuselection = $800A
menuheader_ptr = $800C
menuheader_coord= $800E
menuheader_cur = $8010 ;1 byte
k_save = $8011 ;3 bytes
#macro rect_OR()
ld hl,plotSScreen
call rectOR
#endmacro
#macro rect_XOR()
ld hl,plotSScreen
call rectXOR
#endmacro
#macro rect_Erase()
ld hl,plotSScreen
call rectErase
#endmacro
#macro LCD_Update()
bcall(_GrBufCpy)
#endmacro
And I include the following routines for getkey and rectangle drawing, and converting ints to a string:
Code:
getKeyDebounce:
k_count = k_save+1
k_delay = k_count+1
ei
halt
call GetKey
ld hl,k_save
cp (hl)
jr nz,newkeypress
;if the keys match, decrement k_count
inc hl
dec (hl)
jr z,+_
xor a
ret
_:
inc hl
ld a,(hl)
sub K_DELAY_ACCEL+1
jr nc,+_
xor a
_:
inc a
ld (hl),a
dec hl
ld (hl),a
dec hl
ld a,(hl)
ret
newkeypress:
ld (hl),a
inc hl
ld (hl),K_DELAY_DEFAULT
inc hl
ld (hl),K_DELAY_DEFAULT
ret
getKey:
;Input:
; Expects that interrupts are either off or won't interfere with port 1.
;Outputs:
; A is a value from 0 to 56 that is the keypress
;Destroys:
; C, DE
; C is actually 1
; E is actually a copy of the keypress
ld c,1
ld de,$FEFF
_:
out (c),d
rlc d
ret nc
inc e
in a,(1)
inc a
jr z,-_
dec a
sla e
sla e
sla e
_:
inc e
rra
jr c,-_
ld a,e
ret
rectOR:
; (B,C) = (x,y) signed
; (D,E) = (w,h) unsigned
; HL points to buf
push hl
call rectSub
pop ix
ret nc
ex de,hl
add ix,de
ex de,hl
push ix
pop hl
dec b
jp m,orrect0
inc b
or_rect_loop:
push bc
push hl
ld a,(hl) \ or d \ ld (hl),a \ inc hl
dec b
jr z,$+8
ld c,-1
ld (hl),c \ inc hl \ djnz $-2
ld a,(hl) \ or e \ ld (hl),a
ld bc,12
pop hl
add hl,bc
pop bc
dec c
jr nz,or_rect_loop
ret
orrect0:
ld a,d
and e
ld b,c
ld c,a
ld de,12
ld a,c
or (hl)
ld (hl),a
add hl,de
djnz $-4
ret
rectXOR:
; (B,C) = (x,y) signed
; (D,E) = (w,h) unsigned
; HL points to buf
push hl
call rectSub
pop ix
ret nc
ex de,hl
add ix,de
ex de,hl
push ix
pop hl
dec b
jp m,xorrect0
inc b
xor_rect_loop:
push bc
push hl
ld a,(hl) \ xor d \ ld (hl),a \ inc hl
dec b
jr z,$+8
ld a,(hl) \ cpl \ ld (hl),a \ inc hl \ djnz $-4
ld a,(hl) \ xor e \ ld (hl),a
ld bc,12
pop hl
add hl,bc
pop bc
dec c
jr nz,xor_rect_loop
ret
xorrect0:
ld a,d
and e
ld b,c
ld c,a
ld de,12
ld a,c
xor (hl)
ld (hl),a
add hl,de
djnz $-4
ret
rectErase:
; (B,C) = (x,y) signed
; (D,E) = (w,h) unsigned
; HL points to buf
push hl
call rectSub
pop ix
ret nc
ex de,hl
add ix,de
ex de,hl
push ix
pop hl
ld a,d
cpl
ld d,a
ld a,e
cpl
ld e,a
dec b
jp m,eraserect0
inc b
erase_rect_loop:
push bc
push hl
ld a,(hl) \ and d \ ld (hl),a \ inc hl
dec b
jr z,$+7
xor a
ld (hl),a \ inc hl \ djnz $-2
ld a,(hl) \ and e \ ld (hl),a
ld bc,12
pop hl
add hl,bc
pop bc
dec c
jr nz,erase_rect_loop
ret
eraserect0:
ld a,d
xor e
ld b,c
ld c,a
ld de,12
ld a,c
and (hl)
ld (hl),a
add hl,de
djnz $-4
ret
rectsub:
;(B,C) = (x,y) signed
;(D,E) = (w,h) unsigned
;Output:
; Start Mask D
; End Mask E
; Byte width B
; Height C
; buf offset HL
bit 7,b
jr z,+_
;Here, b is negative, so we have to add width to x.
;If the result is still negative, the entire box is out of bounds, so return
;otherwise, set width=newvalue,b=0
ld a,d
add a,b
ret nc
ld d,a
ld b,0
_:
bit 7,c
jr z,+_
ld a,e
add a,c
ret nc
ld e,a
ld c,0
_:
;We have clipped all negative areas.
;Now we need to verify that (x,y) are on the screen.
;If they aren't, then the whole rectangle is off-screen so no need to draw.
ld a,b
cp 96
ret nc
ld a,c
cp 64
ret nc
;Let's also verfiy that height and width are non-zero:
ld a,d
or a
ret z
ld a,e
or a
ret z
;Now we need to clip the width and height to be in-bounds
add a,c
cp 65
jr c,+_
;Here we need to set e=64-c
ld a,64
sub c
ld e,a
_:
ld a,d
add a,b
cp 97
jr c,+_
;Here we need to set d=96-b
ld a,96
sub b
ld d,a
_:
;B is starting X
;C is starting Y
;D is width
;E is height
push bc
ld l,b
ld a,b
and 7
ld b,a
ld a,-1
jr z,+_
rra \ djnz $-1
_:
inc a
cpl
ld h,a ;start mask
ld a,l
add a,d
and 7
ld b,a
ld a,-1
jr z,+_
rra \ djnz $-1
_:
inc a
ld l,a ;end mask
ex (sp),hl
ld a,h
ld h,b
add hl,hl
add hl,bc
add hl,hl
add hl,hl
ld b,a
rrca
rrca
rrca
and 31
add a,l
ld l,a
jr nc,$+3
inc h
;B is the starting x, D is width
;Only A,B,D,E are available
ld a,b
add a,d
and $F8
ld d,a
ld a,b
and $F8
ld b,a
ld a,d
sub b
rrca
rrca
rrca
ld b,a
ld c,e
pop de
scf
ret
uitoa_16:
;Input:
; DE is the number to convert
; HL points to where to write the ASCII string (up to 6 bytes needed).
;Output:
; HL points to the null-terminated ASCII string
; NOTE: This isn't necessarily the same as the input HL.
push de
push bc
push af
ex de,hl
ld bc,-10000
ld a,'0'-1
inc a \ add hl,bc \ jr c,$-2
ld (de),a
inc de
ld bc,1000
ld a,'9'+1
dec a \ add hl,bc \ jr nc,$-2
ld (de),a
inc de
ld bc,-100
ld a,'0'-1
inc a \ add hl,bc \ jr c,$-2
ld (de),a
inc de
ld a,l
ld h,'9'+1
dec h \ add a,10 \ jr nc,$-3
add a,'0'
ex de,hl
ld (hl),d
inc hl
ld (hl),a
inc hl
ld (hl),0
;Now strip the leading zeros
ld c,-6
add hl,bc
ld a,'0'
inc hl \ cp (hl) \ jr z,$-2
;Make sure that the string is non-empty!
ld a,(hl)
or a
jr nz,+_
dec hl
_:
pop af
pop bc
pop de
ret
And I call it with:
Code:
ld ix,menulookup
ld bc,$1C02
ld de,$283B
ld hl,menhead
set textWrite, (IY + sGrFlags)
xor a
ld (kbdScanCode),a
call menu
xor a
ld (kbdScanCode),a
res textWrite, (IY + sGrFlags)
...
menhead:
.db 3
.db "Strings",$05,0
.db $CF,"Pictures",$05,0
.db $CF,"GDB",0
menulookup:
jr c,menu_select
menu_get:
ld d,a
ld a,b
add a,-1
ret c
inc bc
ld a,c
sub 10
jr nz,+_
ld b,a
ld c,a
_:
push bc
ld hl,s_str
dec d
jr nz,+_
ld hl,s_pic
_:
dec d
jr nz,+_
ld hl,s_GDB
_:
ld de,OP1
ldi
ldi
ldi
ex de,hl
pop de
push hl
call uitoa_16
pop de
;de is where to write
call strcopy
ld hl,OP1
xor a
ret
menu_select:
ld hl,(menucur-1)
ld l,$AA
dec a \ jr nz,$+4 \ ld l,$60
dec a \ ret nz \ ld l,$61
ret
s_str: .db "Str"
s_pic: .db "Pic"
s_GDB: .db "GDB"
It's a lot of overhead if your project doesn't need those rectangle routines or the getkey routines, but, you can modify those to use the built-in routines (it's just smoother this way). Either way, check out the example:
EDIT: Updated to define if there is padding above the text or not. Here is a screenshot of using the large font (save bandwidth, don't need a gif for this one )