Menus

Basics

A useful method of having a program interacting with the user is through a menu. However, menu's fall under a broad category. So what is a menu? A menu must have:

  • List(s) of possible actions that the user can perform
  • A method of selecting items from the list(s)

Here's a chunk of code that will create a simple menu with a header(Test Menu), 4 items (A,B,C, and Quit), and allow the user to select an item. All the other optional stuff will be added later.

;Hard-coded menu routine
;
;inputs: none
;
;outputs: menu
;
;destroyed: all
;
menuStart: ;Start of menu routine
 bcall(_ClrLCDFull) ;Display the Header
 ld hl,txtHeader
 call dispHeader
 ld hl,txtItem1 ;Display Items
 call dispItem
 ld hl,txtItem2
 call dispItem
 ld hl,txtItem3
 call dispItem
 ld hl,txtItemQuit
 call dispItem
menuLoop: ;Scan key loop to get user input
 bcall(_GetKey)
 cp k1 ;If user pressed 1, do action 1
 jr z,item1
 cp k2 ;If user pressed 2, do action 2
 jr z,item2
 cp k3 ;If user pressed 3, do action 3
 jr z,item3
 cp k4 ;If user pressed 4, quit
 jr z,quit
 jr menuLoop ;Else, invalid input. Wait for user to input new key
item1: ;Action 1
 bcall(_ClrLCDFull)
 ld bc,0
 ld (curRow),bc
 ld hl,txtSelect1
 bcall(_PutS)
 bcall(_GetKey)
 jr menuStart
item2: ;Action 2
 bcall(_ClrLCDFull)
 ld bc,0
 ld (curRow),bc
 ld hl,txtSelect2
 bcall(_PutS)
 bcall(_GetKey)
 jr menuStart
item3: ;Action 3
 bcall(_ClrLCDFull)
 ld bc,0
 ld (curRow),bc
 ld hl,txtSelect3
 bcall(_PutS)
 bcall(_GetKey)
 jr menuStart
quit: ;quit the program
 bcall(_ClrLCDFull)
 ret
;dispHeader
;
;displays the header centered and at the top
;
;Inputs: HL points to null-terminating string
;
;Output: text displayed to string, top center right and inverse text 
;
;Destroyed: bc,hl
;
;Note: text string must be large text and take up the full line
;(use blank spaces to fill in gaps)
;
dispHeader: ;displays the header centered and at the top
 ld bc,0ドル
 ld (curRow),bc
 set textInverse,(IY+TextFlags)
 bcall(_PutS)
 res textInverse,(IY+TextFlags)
 ld hl,0
 ld (curRow),hl
 ret
;dispItem
;
;displays menu items at the start of the next line
;
;Inputs: HL points to null-terminating string
;
;Output: text displayed
;
;Destroyed: all
;
dispItem: ;displays menu items at the start of the next line
 push hl
 bcall(_NewLine)
 pop hl
 bcall(_PutS)
 ret
;========================================
;data
;========================================
txtHeader:
 .db " Test Menu ",0
txtItem1:
 .db "1:A",0
txtItem2:
 .db "2:B",0
txtItem3:
 .db "3:C",0
txtSelect1:
 .db "You have selected A",0
txtSelect2:
 .db "You have selected B",0
txtSelect3:
 .db "You have selected C",0
txtItemQuit:
 .db "4:Quit",0

Hopefully from the code you'll be able to understand the general flow.
First, the program runs menuStart, which displays the menu header and items to the display with the sub-routines dispHeader and dispItem.
Once the menu has been displayed, wait for a user input.
If the user presses "1", do action 1 (display "You have selected A")
If the user presses "2", do action 2
If the user presses "3", do action 3
If the user presses "4", quit

For a simple menu, this isn't too bad. However, it's bland, and void of features. So, let's add some features.

Cursor

To give the user some convenience, we'll add a cursor. The cursor allows the user to input up or down and use enter to select an item besides just pressing the corresponding number.

To do so, we'll need some code that will draw the cursor:

;curDraw
;
;Draws the cursor
;
;Inputs: C holds the highlighted item
;
;Outputs: Cursor displayed
;
;Destroyed: A
;
curDraw:
 set textInverse,(IY+TextFlags) ;set inverse text
 xor a
 ld (curCol),a
 ld a,c
 ld (curRow),a
 add a,30ドル ;Character offset
 bcall(_PutC)
 ld a,':'
 bcall(_PutC)
 res textInverse,(IY+TextFlags)
 ret

And, we'll also need some code to erase the cursor:

;curErase
;
;Erase the cursor
;
;Inputs: C highlighted item
;
;Ouputs: Cursor erased
;
;Destroyed: A
;
curErase:
 xor a
 ld (curCol),a
 ld a,c
 ld (curRow),a
 add a,30ドル ;Character offset
 bcall(_PutC)
 ld a,':'
 bcall(_PutC)
 ret

What happens if the user presses up/down/enter? We'll need to add code to deal with the new key presses.

;Updated menuloop
;Stuff with asterisks are new
*ld c,1* ;add this to menuStart to set initial cursor location
menuLoop: ;Scan key loop to get user input
*call curDraw*
*push bc* ;save C for later
 bcall(_GetKey)
*pop bc* ;we'll need this for some routines with the new keypresses
*cp kup*
*jr z, mUp*
*cp kdown*
*jr z, mDown*
*cp kenter*
*jr z, selection*
 cp k1 ;If user pressed 1, do action 1
 jr z,item1
 cp k2 ;If user pressed 2, do action 2
 jr z,item2
 cp k3 ;If user pressed 3, do action 3
 jr z,item3
 cp k4 ;If user pressed 4, quit
 jr z,quit
 jr menuLoop ;Else, invalid input. Wait for user to input new key

Move the cursor up:

;mUp
;
;moves cursor up
;
;inputs: C highlighted item
;
;Ouputs: updated cursor place stored in C
;
;Destroyed: A
;
;Notes: still need to call curDraw to re-draw the cursor
;
mUp:
 call curErase ;erase the cursor
 ld a,c ;check if the cursor is out of bounds
 cp 1
 jr z,menuLoop
 dec c ;if not, decrease and return
 jr menuLoop

…And, down:

;mDown
;
;moves cursor down
;
;Inputs: C highlighted item
;
;Outputs: updated cursor place sctored in C
;
;Destroyed:
;
;Notes: still need to call curDraw to re-draw the cursor
;
mDown:
 call curErase ;erase the cursor
 ld a,c ;check if the cursor is out of bounds
 cp 4
 jr z,menuLoop
 inc c ;if not, increase and return
 jr menuLoop

This bit of code will be called when the user presses enter:

selection:
 ld a,c
 cp 1
 jr z,item1
 cp 2
 jr z,item2
 cp 3
 jr z,item3
 jr quit

"2-D" Menus

Instead of only having the choice up and down, why not categorize items and then display the categories left to right? An example of this is the OS's math menu.

2dmenu.jpg

The code for 2 dimensional menus is much more complicated than the standard 1 dimensional menu, but is still manageable for the calculator. Not only do you have to keep track of which item is currently highlighted, you also need to keep track of which group that item is part of.

Since there are enough differences between the code for a 1 dimensional and 2 dimensional menu, I'll post the code in it's entirety. Be aware that it's a lot longer.

This menu has 4 items in each group, with 3 groups.

menuStart: ;Start of menu routine
 bcall(_ClrLCDFull) ;Display the Header
 ld b,1 ;Which group is being displayed
dispMenu: ;display the menu
 ld c,1 ;Which item is highlighted
 push bc
 ld hl,txtHeader ;display the header
 call dispHeader
 ld a,b
 cp 1
 jr nz,dispMenu2
 ld b,3
 ld hl,txtItem1_1 ;Display Items in GA
 call dispItems
 jr dispMenu4
dispMenu2:
 cp 2
 jr nz,dispMenu3
 ld b,3
 ld hl,txtItem2_1 ;Display Items in GB
 call dispItems
 jr dispMenu4
dispMenu3:
 ld b,3
 ld hl,txtItem3_1 ;Display Items in GC
 call dispItems
dispMenu4: ;Since every group has a quit, display it here
 bcall(_NewLine)
 ld hl,txtItemQuit
 bcall(_PutS)
 pop bc
menuLoop: ;Scan key loop to get user input
 call curDraw
 push bc ;save BC for later
 bcall(_GetKey)
 pop bc ;we'll need this for some routines with the new keypresses
 cp kleft
 jr z,mLeft
 cp kright
 jr z,mRight
 cp kup
 jr z, mUp
 cp kdown
 jr z, mDown
 cp kenter
 jr z, selection
 cp k1 ;If user pressed 1, do action 1
 jr z,item1
 cp k2 ;If user pressed 2, do action 2
 jr z,item2
 cp k3 ;If user pressed 3, do action 3
 jp z,item3
 cp k4 ;If user pressed 4, quit
 jp z,quit
 jr menuLoop ;Else, invalid input. Wait for user to input new key
mUp:
 call curErase ;erase the cursor
 ld a,c ;check if the cursor is out of bounds
 cp 1
 jr z,menuLoop
 dec c ;if not, decrease and return
 jr menuLoop
mDown:
 call curErase ;erase the cursor
 ld a,c ;check if the cursor is out of bounds
 cp 4
 jr z,menuLoop
 inc c ;if not, increase and return
 jr menuLoop
mRight: ;change group
 ld a,b ;check if already as far right as possible
 cp 3
 jr z,menuLoop
 inc b
 jp dispMenu
mLeft: ;change group
 ld a,b ;check if already as far right as possible
 cp 1
 jr z,menuLoop
 dec b
 jp dispMenu
selection:
 ld a,c
 cp 1
 jr z,item1
 cp 2
 jr z,item2
 cp 3
 jr z,item3
 jp quit
item1: ;Action 1
 push bc
 bcall(_ClrLCDFull)
 pop bc
 ld de,0
 ld (curRow),de
 ld a,b
 cp 1
 jr nz,item1B
 ld hl,txtSelect1_1 ;action 1 for group A
 jr item1Done
item1B:
 cp 2
 jr nz,item1C
 ld hl,txtSelect2_1 ;action 1 for group B
 jr item1Done
item1C:
 ld hl,txtSelect3_1 ;action 1 for group C
item1Done:
 bcall(_PutS) ;we'll display the text now
 bcall(_GetKey)
 jp menuStart
item2: ;Action 2
 push bc
 bcall(_ClrLCDFull)
 pop bc
 ld de,0
 ld (curRow),de
 ld a,b
 cp 1
 jr nz,item2B
 ld hl,txtSelect1_2 ;action 2 for group A
 jr item2Done
item2B:
 cp 2
 jr nz,item2C
 ld hl,txtSelect2_2 ;action 2 for group B
 jr item2Done
item2C:
 ld hl,txtSelect3_2 ;action 2 for group C
item2Done:
 bcall(_PutS)
 bcall(_GetKey)
 jp menuStart
item3: ;Action 3
 push bc
 bcall(_ClrLCDFull)
 pop bc
 ld de,0
 ld (curRow),de
 ld a,b
 cp 1
 jr nz,item3B
 ld hl,txtSelect1_3 ;action 3 for group A
 jr item2Done
item3B:
 cp 2
 jr nz,item3C
 ld hl,txtSelect2_3 ;action 3 for group B
 jr item1Done
item3C:
 ld hl,txtSelect3_3 ;action 3 for group C
item3Done:
 bcall(_PutS)
 bcall(_GetKey)
 jp menuStart
quit: ;quit the program
 bcall(_ClrLCDFull)
 ret
;dispHeader
;
;displays the header centered and at the top
;
;Inputs: HL points to null-terminating string
;
;Output: text displayed, with the current group in inverse text 
;
;Destroyed: a,de,hl
;
;Note: text string must be large text and take up the full line
;(use blank spaces to fill in gaps)
;
dispHeader: ;displays the header centered and at the top
 ld de,0
 ld (curRow),de
 ld a,b
 cp 1
 jr nz,dispHeader1
 set textInverse,(IY+TextFlags)
dispHeader1: ;group A
 bcall(_PutS)
 res textInverse,(IY+TextFlags)
 bcall(_PutS)
 cp 2
 jr nz,dispHeader2
 set textInverse,(IY+TextFlags)
dispHeader2: ;group B
 bcall(_PutS)
 res textInverse,(IY+TextFlags)
 bcall(_PutS)
 cp 3
 jr nz,dispHeader3
 set textInverse,(IY+TextFlags)
dispHeader3: ;group C
 bcall(_PutS)
 res textInverse,(IY+TextFlags)
 ld hl,0
 ld (curRow),hl
 ret
;dispItems
;
;displays menu items at the start of the next line
;
;Inputs: HL points to null-terminating string
;
;Output: text displayed
;
;Destroyed: all
;
dispItems: ;displays menu items at the start of the next line
 push bc
 push hl
 bcall(_NewLine)
 pop hl
 pop bc
 bcall(_PutS)
 djnz dispItems
 ret
;curDraw
;
;Draws the cursor
;
;Inputs: C holds the highlighted item
;
;Outputs: Cursor displayed
;
;Destroyed: A
;
curDraw:
 set textInverse,(IY+TextFlags) ;set inverse text
 xor a
 ld (curCol),a
 ld a,c
 ld (curRow),a
 add a,30ドル ;Character offset
 bcall(_PutC)
 ld a,':'
 bcall(_PutC)
 res textInverse,(IY+TextFlags)
 ret
;curErase
;
;Erase the cursor
;
;Inputs: C highlighted item
;
;Ouputs: Cursor erased
;
;Destroyed: A
;
curErase:
 xor a
 ld (curCol),a
 ld a,c
 ld (curRow),a
 add a,30ドル ;Character offset
 bcall(_PutC)
 ld a,':'
 bcall(_PutC)
 ret
;========================================
;data
;========================================
txtHeader:
 .db "GA",0
 .db " ",0
 .db "GB",0
 .db " ",0
 .db "GC",0
txtItem1_1:
 .db "1:A1",0
txtItem1_2:
 .db "2:A2",0
txtItem1_3:
 .db "3:A3",0
txtItem2_1:
 .db "1:B1",0
txtItem2_2:
 .db "2:B2",0
txtItem2_3:
 .db "3:B3",0
txtItem3_1:
 .db "1:C1",0
txtItem3_2:
 .db "2:C2",0
txtItem3_3:
 .db "3:C3",0
txtSelect1_1:
 .db "You selected A1",0
txtSelect1_2:
 .db "You selected A2",0
txtSelect1_3:
 .db "You selected A3",0
txtSelect2_1:
 .db "You selected B1",0
txtSelect2_2:
 .db "You selected B2",0
txtSelect2_3:
 .db "You selected B3",0
txtSelect3_1:
 .db "You selected C1",0
txtSelect3_2:
 .db "You selected C2",0
txtSelect3_3:
 .db "You selected C3",0
txtItemQuit:
 .db "4:Quit",0

Scrolling menus

What if you have more items than will fit in one screen? A solution to this is to display items on the screen and then when the user scrolls past the limits of the screen, it will "move" the items up and display the other items that wouldn't fit.

scrollingmenu.gif

Summary

There are so many variations of a menu interface that showing them all would be impractical and impossible. Use your imagination and come up with variations, like displaying pictures in the background, having a custom cursor, or anything else you can think of.

page revision: 22, last edited: 27 Jun 2007 20:12
Unless otherwise stated, the content of this page is licensed under GNU Free Documentation License.
Click here to edit contents of this page.
Click here to toggle editing of individual sections of the page (if possible). Watch headings for an "edit" link when available.
Append content without editing the whole page source.
Check out how this page has evolved in the past.
If you want to discuss contents of this page - this is the easiest way to do it.
View and manage file attachments for this page.
A few useful tools to manage this Site.
Change the name (also URL address, possibly the category) of the page.
View wiki source for this page without editing.
View/set parent page (used for creating breadcrumbs and structured layout).
Notify administrators if there is objectionable content in this page.
Something does not work as expected? Find out what you can do.
General Wikidot.com documentation and help section.
Wikidot.com Terms of Service - what you can, what you should not etc.
Wikidot.com Privacy Policy.

AltStyle によって変換されたページ (->オリジナル) /