PULL DOWN MENUS
ZX Computing, February 1987

Toni Baker shows how to give your programs that "sate of the art"
look, with this routine for creating pull-down menus.


This article is all about pull-down menus. Specifically, about how you
can use them in BASIC (with the aid of a morsel of machine code of
course). Before I start, let's begin with some definitions.

A menu is something you normally get in restaurants, which lists the
different choices of food available to you. Computers, however, can't
eat anything except chips. For this reason computer menus are allowed
to list not only food, but in fact anything that it's possible to list
at all. When a menu appears on the screen, what you see is a list of
choices. Usually this list is headed with a title. The computer will
then wait until you're ready to order. You make your selection and
then, once the computer has carried out whatever tasks it's been told
to, the menu will disappear from the screen. Whatever was underneath
the menu will reappear and the screen will be as it was before the
menu appeared.


Nests

Sometimes you see menus happening in nests. When this happens lots of
birds start chirping for worms and things. On the screen you may
witness one menu or information panel appearing on top of a previous
menu. Furthermore, you may then get even more menus layering
themselves on top of these. When all tasks are complete you will see
the menus disappearing, precisely in reverse order - each time leaving
the screen exactly as it was before the particular menu appeared. When
the final (ie. first) menu disappears the screen is as it was to begin
with.

This suggests some sort of STACK, because the first menu in becomes
the last menu out. We are familiar with at least three stacks already
- the machine stack, the calculator stack, and the GOSUB stack. If we
wish to create yet another kind of stack still, we must first of all
decide where to put it.

What I propose to do in this article is to provide a comprehensive
machine code program with access points to make most of its functions
available to BASIC. If this is done then it means that nested menus
and information panels may be created and recalled using only simple
BASIC statements.

Essentially the menu idea is a very simple one. PUSHing a menu
consists of (1) storing the contents of the screen portion which lies
underneath the proposed menu position; (2) printing a list, with a
title and a pretty border, at the designated position; and (3)
providing some means of allowing the user to choose one of the
options.

Alternatively we may wish to use step (1) only or steps (1) and (2)
only, to allow for a variety of types of information panel, as well as
just the standard kind of menu.

The reverse process, that of POPping a menu, has only one step; the
contents of the screen underneath the menu must be restored. This will
cause the menu or information panel to disappear from the screen.


Obscure RAM

For this program, I have decided to use a little-known and seldom-used
region of RAM in which to store the stack of panel information. It is
possible to store information BETWEEN the BASIC program and the
variables area. This region has the following advantages: (1) It is
impossible for the stack to grow too big - error 4, Out of memory will
occur if too much memory is asked for; (2) it will not overwrite any
machine code (or in fact anything); and (3) the information will not
be erased by the ROM between BASIC statements (as the workspace would be).

My new stack grows upwards. Figure one [see PULLDOWN.GIF] shows a
single stack entry diagrammatically. Note that each stack entry may be
of variable length. As you can see from the diagram the print position
and attribute colours are also stacked, so that when the menu
disappears printing may continue as normal.

The first two bytes of information store the address on the screen of
the current print position. Note that this address is stored HIGH BYTE
FIRST. This is unusual in Spectrum machine code, but is done for a
good reason. Remember that to the left (in the diagram) of the panel
information is the BASIC program itself. The RUN and LIST commands
must know where to stop - it would not do if the panel information
somehow got confused with program. The first byte beyond the BASIC
program must be in the range 40 to FF, so as to distinguish it from a
program line. Putting the high byte first ensures this.

Notice also that the very last byte of panel information has bit 7
set. This too is for a reason. The machine code program must be able
to tell whether or not the panel stack is empty. If, directly below
the variables area, it finds a byte with bit 7 set, then the stack is
non-empty. Note that if the stack WERE in fact empty then the byte
directly below the variables area would be 0D (enter) which of course
has bit seven reset. Once the principle of the panel information stack
is understood, the rest of the algorithm is boringly straightforward.
There's a lot of it, but this is because there is lots of work to do -
not because of any untoward complexity.


---
Figure 2. New commands for manipulating menus

To clear menu stack entirely:
RANDOMIZE USR MCLEAR

To create information panel:
RANDOMIZE USR PANEL: PRINT .[control;]. height, width

To create menu panel (without choice):
RANDOMIZE USR MPANEL: PRINT .[control;]. string.|, string.|

To create menu panel and allow choice of options:
LET variable=USR MENU: PRINT .[control;]. string.|, string.|

To undo a menu or information panel:
RANDOMIZE USR MENUOFF
or:
LET variable=USR MENUOFF
(The latter case will assign the variable with 0 if the menu stack is
now empty, or with 1 otherwise.)

"control" may be either: AT y-coordinate,x-coordinate
                         PAPER colour
                         INK colour
                         BRIGHT status
                         FLASH status

.[...]. means "optional, but may be repeated"

.|...|. means "at least one must be included, more repeats are optional"
---


Back to BASIC

Let's look at the BASIC now. How does it operate? Figure two lists the
five new statements which are allowed. Note that the middle three have
rather complicated syntaxes. To begin with each of the three
statements is in two parts, separated by a colon. The second part
always begins "PRINT" - but this is not a PRINT statement - it just
looks like one. You may follow the word PRINT by an optional number of
controls, which may specify the colour of the menu (eg. PAPER 6); or
the position of the menu (eg. AT 8,8;). In the case of USR PANEL you
then have to specify the size of the panel - first the height, then
the width, separated by a comma. In the remaining two cases (USR
MPANEL and USR MENU) you have to supply a list of strings. The first
one is the title, and the rest are the choices available on the menu.

You don't have to specify the menu size once the strings are listed.
The machine code will work that out for itself (ie. height = number of
strings + 1; width = maximum length of string + 2). Although the
syntax looks complicated at first glance, in fact it's remarkably easy
once you're used to it.

Figure Three is an example BASIC program. It doesn't do very much,
apart from show off the menus. Each feature is demonstrated at least
once. Try it - it's very interesting.

You can use the BASIC statements in your own programs. As a guide -
you can CREATE a menu with the LET X = USR MENU: statement, followed
by "PRINT" and a list of strings. You don't have to use X of course -
any variable will do. From then on, in the BASIC program, this menu
will be on the screen, and X will be assigned with 1 if the first item
on the list was chosen, 2 if the second item on the list was chosen,
and so on. To remove this menu from the screen you should use
RANDOMIZE USR MENUOFF. Alternatively, it may be the case that X has
been assigned with zero - if this is so it means that the menu has
been abandoned, and has already been removed from the screen.

From a user's point of view, when you are confronted with a menu you
must manipulate the bar cursor to the appropriate choice using the UP
and DOWN cursor keys, and then press ENTER when the cursor is in the
right place. Alternatively, to abandon the menu altogether, just press
DELETE.

Well, that's all from me for this article. All this talk about menus
has made me hungry. Excuse me while I make myself a microfych supper.



#8000 90909090 MENU_UDGS   DEFB 90909090
#8004 90909090             DEFB 90909090 ;Definition for graphic-A
#8008 09090909             DEFB 09090909
#800C 09090909             DEFB 09090909 ;Definition for graphic-B
#8010 90909090             DEFB 90909090
#8014 9F8080FF             DEFB 9F8080FF ;Definition for graphic-C
#8018 09090909             DEFB 09090909
#801C F90101FF             DEFB F90101FF ;Definition for graphic-D
#8020 00000000             DEFB 00000000
#8024 FF0000FF             DEFB FF0000FF ;Definition for graphic-E
#8028 0000     PANEL_LEN   DEFW 0000
#802A 0000     PANEL_ADDR  DEFW 0000
#802C 00       PANEL_TYPE  DEFB 00
#802D 00       STRING_COUNTDEFB 00
#802E 0000     MENU_AT     DEFW 0000     ;System variables used by program

#8030 7C       ATTR_ADDR   LD   A,H      ;A:= high part of screen address
#8031 E638                 AND  #38      ;Identify screen third
#8033 1F                   RRA
#8034 1F                   RRA
#8035 1F                   RRA           ;A:= screen third number
#8036 F658                 OR   #58
#8038 67                   LD   H,A      ;HL:= corresponding attribute addr
#8039 C9                   RET

#803A F5       DOWN_1      PUSH AF
#803B 24                   INC  H        ;Presume within character square
#803C 7C                   LD   A,H
#803D E607                 AND  #07
#803F 200A                 JR   NZ,DOWN_1_EXIT ;Jump if this is the case
#8041 7D                   LD   A,L
#8042 C620                 ADD  A,#20
#8044 6F                   LD   L,A      ;Presume crossing screen thirds
#8045 3804                 JR   C,DOWN_1_EXIT;Jump if this is the case
#8047 7C                   LD   A,H
#8048 D608                 SUB  #08
#804A 67                   LD   H,A      ;Must be within screen third
#804B F1       DOWN_1_EXIT POP  AF
#804C C9                   RET           ;Return

#804D 2A4B5C   UNSTK_PANEL LD   HL,(VARS);HL: points to variables area
#8050 2B                   DEC  HL       ;HL: points to top of panel stack
#8051 CB7E                 BIT  7,(HL)
#8053 CBBE                 RES  7,(HL)
#8055 C8                   RET  Z        ;Return if panel stack empty
#8056 46                   LD   B,(HL)
#8057 2B                   DEC  HL
#8058 4E                   LD   C,(HL)   ;BC:= length of panel information
#8059 C5                   PUSH BC       ;Stack length of panel information
#805A 23                   INC  HL
#805B 23                   INC  HL       ;HL: points to variables area
#805C A7                   AND  A
#805D ED42                 SBC  HL,BC    ;HL: points to panel information
#805F E5                   PUSH HL       ;Stack this address
#8060 56                   LD   D,(HL)
#8061 23                   INC  HL
#8062 5E                   LD   E,(HL)   ;DE:= previous print position
#8063 23                   INC  HL
#8064 ED53845C             LD   (DF_CC),DE ;Restore previous print position
#8068 5E                   LD   E,(HL)
#8069 23                   INC  HL
#806A 56                   LD   D,(HL)   ;DE:= previous print coordinates
#806B 23                   INC  HL
#806C ED53885C             LD   (S_POSN),DE;Restore previous print coordinates
#8070 5E                   LD   E,(HL)   ;E:= previous attribute byte
#8071 23                   INC  HL
#8072 56                   LD   D,(HL)   ;D:= previous attribute mask
#8073 23                   INC  HL
#8074 ED538D5C             LD   (ATTR_P),DE;Restore previous attribute colours
#8078 ED538F5C             LD   (ATTR_T),DE;and transparent mask
#807C 7E                   LD   A,(HL)   ;A:= previous INVERSE/OVER status
#807D 23                   INC  HL
#807E 32915C               LD   (P_FLAG),A ;Restore INVERSE/OVER status
#8081 5E                   LD   E,(HL)
#8082 23                   INC  HL
#8083 56                   LD   D,(HL)   ;DE:= addr within screen of panel
#8084 23                   INC  HL
#8085 4E                   LD   C,(HL)   ;C:= width of panel in squares
#8086 23                   INC  HL
#8087 0600                 LD   B,#00    ;BC:= width of panel in squares
#8089 7E                   LD   A,(HL)   ;A:= height of panel in squares
#808A 23                   INC  HL       ;HL: points to prev data from scr
#808B F5                   PUSH AF       ;Stack height of panel in squares
#808C 87                   ADD  A,A
#808D 87                   ADD  A,A
#808E 87                   ADD  A,A      ;A:= height of panel in pixels
#808F D5                   PUSH DE       ;Stack panel address within screen
#8090 D5       U_P_LOOP_1  PUSH DE       ;Stack addr of pixel row to restore
#8091 C5                   PUSH BC       ;Stack width of panel
#8092 EDB0                 LDIR          ;Restore one pixel row of panel
#8094 C1                   POP  BC       ;BC:= width of panel
#8095 D1                   POP  DE       ;DE:= address of row just restored
#8096 EB                   EX   DE,HL
#8097 CD3A80               CALL DOWN_1
#809A EB                   EX   DE,HL    ;DE:= addr of next row to restore
#809B 3D                   DEC  A
#809C 20F2                 JR   NZ,U_P_LOOP_1;Restore all pixels to panel
#809E D1                   POP  DE       ;DE:= address of first row
#809F EB                   EX   DE,HL
#80A0 CD3080               CALL ATTR_ADDR
#80A3 EB                   EX   DE,HL    ;DE:= addr of first attribute row
#80A4 F1                   POP  AF       ;A:= height of panel in squares
#80A5 C5       U_P_LOOP_2  PUSH BC       ;Stack width of panel
#80A6 D5                   PUSH DE       ;Stack address of attribute row
#80A7 EDB0                 LDIR          ;Restore next attribute row
#80A9 E3                   EX   (SP),HL  ;HL:= addr of attr row restored
#80AA 012000               LD   BC,#0020
#80AD 09                   ADD  HL,BC    ;HL:= address of next attribute row
#80AE EB                   EX   DE,HL    ;DE:= address of next attribute row
#80AF E1                   POP  HL       ;HL: points into panel information
#80B0 C1                   POP  BC       ;BC:= width of panel
#80B1 3D                   DEC  A
#80B2 20F1                 JR   NZ,U_P_LOOP_2;Restore all attributes to panel
#80B4 E1                   POP  HL       ;HL: points to start of panel info
#80B5 C1                   POP  BC       ;BC:= length of panel information
#80B6 CDE819               CALL RECLAIM-2;Reclaim memory used by panel info
#80B9 224B5C               LD   (VARS),HL;Restore variables-area pointer
#80BC F6FF                 OR   #FF      ;Reset the zero flag
#80BE C9                   RET           ;Return

#80BF CD4D80   MENU_CLEAR  CALL UNSTK_PANEL;Unstack topmost panel if exists
#80C2 20FB                 JR   NZ,MENU_CLEAR;Try again if there may be more
#80C4 C36B0D               JP   CLS      ;Clear the screen and return

#80C7 F5       NEW_PANEL   PUSH AF       ;Stack required attribute colours
#80C8 78                   LD   A,B      ;A:= required height of panel
#80C9 82                   ADD  A,D      ;A:= maximum line number of panel
#80CA 380C                 JR   C,NP_ERROR ;Jump if greater than 255d
#80CC FE19                 CP   #19
#80CE 3008                 JR   NC,NP_ERROR;Jump if height is off screen
#80D0 79                   LD   A,C      ;A:= required width of panel
#80D1 83                   ADD  A,E      ;A:= maximum column number of panel
#80D2 3804                 JR   C,NP_ERROR ;Jump if greater than 255d
#80D4 FE21                 CP   #21
#80D6 3802                 JR   C,#80DA  ;Jump unless width is off screen
#80D8 CF      NP_ERROR     RST  #08      ;Report 5 - Out of screen
#80D9 04                   DEFB 04
#80DA C5      NEW_PANEL_2  PUSH BC       ;Stack size of intended panel
#80DB D5                   PUSH DE       ;Stack position of intended panel
#80DC 110900               LD   DE,#0009 ;DE:= bytes needed per chr scr
#80DF 62                   LD   H,D
#80E0 68                   LD   L,B      ;HL:= height of intended panel
#80E1 CDF82A               CALL MULT-HL*DE ;HL:= HL * DE
#80E4 59                   LD   E,C      ;DE:= width of intended panel
#80E5 CDF82A               CALL MULT-HL*DE ;HL:= number of bytes needed for
                                         ;screen information for whole panel
#80E8 1E0D                 LD   E,#0D
#80EA 19                   ADD  HL,DE    ;HL:= length of required panel info
#80EB 222880               LD   (PANEL_LEN),HL ;Store this length
#80EE 44                   LD   B,H
#80EF 4D                   LD   C,L      ;BC:= length of required panel info
#80F0 2A535C               LD   HL,(PROG);HL points to start of program area
#80F3 E5                   PUSH HL       ;Stack this address
#80F4 2A4B5C               LD   HL,(VARS);HL points to start of variables
#80F7 222A80               LD   (#802A),HL ;Store this as addr of panel info
#80FA 2B                   DEC  HL       ;HL: points to top of panel stack
#80FB C5                   PUSH BC
#80FC CD5516               CALL MAKE_ROOM;Make room for panel information
#80FF C1                   POP  BC
#8100 EB                   EX   DE,HL    ;HL: points to penultimate byte
                                         ;of new room
#8101 71                   LD   (HL),C
#8102 23                   INC  HL
#8103 70                   LD   (HL),B   ;Store length of panel info
#8104 CBFE                 SET  7,(HL)   ;Set bit 7 to "stack not empty"
#8106 E1                   POP  HL       ;HL:= address of program area
#8107 22535C               LD   (PROG),HL;Restore system variable
#810A 2A2A80               LD   HL,(PANEL_ADDR) ;HL:= address of new room
#810D ED5B845C             LD   DE,(DF_CC) ;DE:= current print position
#8111 72                   LD   (HL),D
#8112 23                   INC  HL
#8113 73                   LD   (HL),E   ;Store in panel information
#8114 23                   INC  HL
#8115 ED5B885C             LD   DE,(S_POSN);DE:= current print coordinates
#8119 73                   LD   (HL),E
#811A 23                   INC  HL
#811B 72                   LD   (HL),D   ;Store in panel information
#811C 23                   INC  HL
#811D ED5B8D5C             LD   DE,(ATTR_P);DE:= current attribute byte & mask
#8121 73                   LD   (HL),E
#8122 23                   INC  HL
#8123 72                   LD   (HL),D   ;Store in panel information
#8124 23                   INC  HL
#8125 3A915C               LD   A,(P_FLAG) ;A:= current INVERSE/OVER status
#8128 77                   LD   (HL),A   ;Store in panel information
#8129 23                   INC  HL
#812A 3E02                 LD   A,#02
#812C E5                   PUSH HL
#812D CD0116               CALL CHAN-OPEN;Select stream 2 (the screen)
#8130 E1                   POP  HL
#8131 C1                   POP  BC       ;BC:= position of panel
#8132 E5                   PUSH HL       ;Stack pointer into panel info
#8133 CD9B0A               CALL AT_B_C   ;PRINT AT B,C;
#8136 EB                   EX   DE,HL    ;DE:= addr within screen of panel
#8137 E1                   POP  HL       ;HL: points into panel info
#8138 73                   LD   (HL),E
#8139 23                   INC  HL
#813A 72                   LD   (HL),D   ;Store screen addr in panel info
#813B 23                   INC  HL
#813C C1                   POP  BC       ;BC:= size of panel
#813D 71                   LD   (HL),C
#813E 23                   INC  HL
#813F 70                   LD   (HL),B   ;Store in panel information
#8140 23                   INC  HL
#8141 EB                   EX   DE,HL    ;DE:= addr at which to store data
                                         ;from screen; HL:= screen address
#8142 F1                   POP  AF       ;A:= required attribute byte
#8143 E5                   PUSH HL
#8144 6F                   LD   L,A
#8145 2600                 LD   H,#00
#8147 228D5C               LD   (ATTR_P),HL
#814A 228F5C               LD   (ATTR_T),HL;Set required colours
#814D FD7457               LD   (P_FLAG),H ;Also set OVER 0; INVERSE 0;
#8150 E1                   POP  HL       ;HL:= screen address of panel
#8151 78                   LD   A,B      ;A:= height of panel in squares
#8152 0600                 LD   B,#00    ;BC:= width of panel in squares
#8154 F5                   PUSH AF       ;Stack height of panel in squares
#8155 87                   ADD  A,A
#8156 87                   ADD  A,A
#8157 87                   ADD  A,A      ;A:= height of panel in pixels
#8158 E5                   PUSH HL       ;Stack address of first row
#8159 E5       N_P_LOOP_1  PUSH HL       ;Stack address of row
#815A C5                   PUSH BC
#815B EDB0                 LDIR          ;Store one row of panel
#815D C1                   POP  BC
#815E E1                   POP  HL       ;HL:= address of row just stored
#815F CD3A80               CALL DOWN_1   ;HL:= address of next row to store
#8162 3D                   DEC  A
#8163 20F4                 JR   NZ,N_P_LOOP_1;Store all screen info for panel
#8165 E1                   POP  HL       ;HL:= address of first row
#8166 CD3080               CALL ATTR_ADDR;HL:= address of first attr row
#8169 F1                   POP  AF       ;A:= height of panel in squares
#816A C5       N_P_LOOP_2  PUSH BC       ;Stack width of window
#816B E5                   PUSH HL       ;Stack address of attribute row
#816C EDB0                 LDIR          ;Store one attribute row
#816E E1                   POP  HL       ;HL:= addr of attr row just stored
#816F 012000               LD   BC,#0020
#8172 09                   ADD  HL,BC    ;HL:= address of next attribute row
#8173 C1                   POP  BC       ;BC:= width of window
#8174 3D                   DEC  A
#8175 20F3                 JR   NZ,N_P_LOOP_2;Store all attribute info for panel
#8177 C9                   RET           ;Return

#8178 C5       MENU_NEWLINEPUSH BC
#8179 14                   INC  D        ;Increment y coordinate
#817A D5                   PUSH DE
#817B 42                   LD   B,D      ;B:= required y coordinate
#817C 4B                   LD   C,E      ;C:= required x coordinate
#817D CD9B0A               CALL AT_B_C   ;PRINT AT B,C;
#8180 D1                   POP  DE
#8181 C1                   POP  BC
#8182 C9                   RET           ;Return

#8183 C5       MENU_STRING PUSH BC
#8184 D5                   PUSH DE
#8185 2A5F5C               LD   HL,(X_PTR) ;HL: points to reqd string params
#8188 23                   INC  HL
#8189 5E                   LD   E,(HL)
#818A 23                   INC  HL
#818B 56                   LD   D,(HL)   ;DE:= original address of string
#818C 23                   INC  HL
#818D 46                   LD   B,(HL)   ;B:= length of string
#818E 23                   INC  HL
#818F 23                   INC  HL
#8190 225F5C               LD   (X_PTR),HL ;(X_PTR): pts to nxt string params
#8193 2A655C               LD   HL,(STKEND);HL: pts to end of dynamic RAM
#8196 ED52                 SBC  HL,DE
#8198 380C                 JR   C,M_STRING_2 ;Jump if address after dynamic RAM
#819A 2A2A80               LD   HL,(PANEL_ADDR) ;HL:= addr where info inserted
#819D ED52                 SBC  HL,DE
#819F 3005                 JR   NC,M_STRING_2;Jump if address below panel info
#81A1 2A2880               LD   HL,(PANEL_LEN) ;HL:= length of panel information
#81A4 19                   ADD  HL,DE
#81A5 EB                   EX   DE,HL    ;DE:= new address of string
#81A6 04       M_STRING_2  INC  B        ;B:= length of string plus one
#81A7 1804                 JR   MS_CHR_NEXT;Jump forward
#81A9 1A       MS_CHR_LOOP LD   A,(DE)   ;A:= next chr from string
#81AA 13                   INC  DE       ;DE: points to next chr
#81AB D7                   RST  #10      ;Print the character
#81AC 0D                   DEC  C        ;Decrement number of squares left
#81AD 10FA     MS_CHR_NEXT DJNZ MS_CHR_LOOP;Print whole of string
#81AF 0C                   INC  C        ;C:= number of squares left + 1
#81B0 1803                 JR   MS_SPC_NEXT;Jump forward
#81B2 3E20     MS_SPC_LOOP LD   A,"space"
#81B4 D7                   RST  #10      ;Print a space
#81B5 0D       MS_SPC_NEXT DEC  C
#81B6 20FA                 JR   NZ,MS_SPC_LOOP ;Print all remaining spaces
#81B8 D1                   POP  DE
#81B9 C1                   POP  BC
#81BA C9                   RET

#81BB F5       NEW_MPANEL  PUSH AF         ;Stack required attribute byte
#81BC D5                   PUSH DE         ;Stack position of panel
#81BD 78                   LD   A,B        ;A:= number of strings supplied
#81BE F5                   PUSH AF         ;Stack number of strings
#81BF 2A655C               LD   HL,(STKEND);HL: points to end of calc stack
#81C2 110000               LD   DE,#0000   ;DE:= smallest number possible
#81C5 2B       NMP_LOOP    DEC  HL
#81C6 46                   LD   B,(HL)
#81C7 2B                   DEC  HL
#81C8 4E                   LD   C,(HL)     ;BC:= length of string
#81C9 2B                   DEC  HL
#81CA 2B                   DEC  HL
#81CB 2B                   DEC  HL
#81CC EB                   EX   DE,HL      ;HL:= len of longest string so far
#81CD A7                   AND  A
#81CE ED42                 SBC  HL,BC
#81D0 09                   ADD  HL,BC
#81D1 3002                 JR   NC,NMP_STRLEN;Jump if HL greater than BC
#81D3 60                   LD   H,B
#81D4 69                   LD   L,C        ;HL:= len of longest string so far
#81D5 EB       NMP_STRLEN  EX   DE,HL      ;HL: points into calculator stack
#81D6 3D                   DEC  A
#81D7 20EC                 JR   NZ,NMP_LOOP;Scan all strings
#81D9 A2                   AND  D
#81DA 2005                 JR   NZ,NMP_ERROR ;Error if length gtr than 255d
#81DC 4B                   LD   C,E        ;C:= maximum string length
#81DD CB79                 BIT  7,C
#81DF 2802                 JR   Z,NMP_PANEL;Jump if length less than 128d
#81E1 CF       NMP_ERROR   RST  #08        ;Report 5, Out of screen
#81E2 04                   DEFB 04
#81E3 225F5C   NMP_PANEL   LD   (X_PTR),HL ;Store pointer to string parameters
#81E6 F1                   POP  AF
#81E7 47                   LD   B,A        ;B:= number of strings
#81E8 D1                   POP  DE         ;DE:= intended position for menu
#81E9 F1                   POP  AF         ;A:= required attribute byte
#81EA C5                   PUSH BC
#81EB D5                   PUSH DE
#81EC 04                   INC  B          ;Allow one extra line for border
#81ED 0C                   INC  C
#81EE 0C                   INC  C          ;Allow two extra columns for border
#81EF CDC780               CALL NEW_PANEL  ;Assign panel information area
#81F2 D1                   POP  DE
#81F3 C1                   POP  BC
#81F4 2A5F5C               LD   HL,(X_PTR) ;HL: points to first string param
#81F7 22655C               LD   (STKEND),HL;Empty calc stack from here on
#81FA 3E14                 LD   A,#14
#81FC D7                   RST  #10        ;PRINT INVERSE ...
#81FD 3E01                 LD   A,#01
#81FF D7                   RST  #10        ;1;
#8200 3E20                 LD   A,"space"
#8202 D7                   RST  #10        ;Print a space
#8203 CD8381               CALL MENU_STRING;Print the menu title
#8206 3E20                 LD   A,"space"
#8208 D7                   RST  #10        ;Print a space
#8209 3E14                 LD   A,#14
#820B D7                   RST  #10        ;PRINT INVERSE ...
#820C AF                   XOR  A
#820D D7                   RST  #10        ;0;
#820E 2A7B5C               LD   HL,(UDG)
#8211 E5                   PUSH HL         ;Stack UDGs address
#8212 210080               LD   HL,MENU_UDGS
#8215 227B5C               LD   (UDG),HL   ;Use specified UDGs
#8218 05                   DEC  B          ;B:= number of remaining strings
#8219 C5                   PUSH BC
#821A CD7881   NMP_PRINT   CALL MENU_NEWLINE ;Move print position to next line
                                           ;of menu panel
#821D 3E90                 LD   A,"graphic-A"
#821F D7                   RST  #10        ;Print left-hand border
#8220 CD8381               CALL MENU_STRING;Print next string
#8223 3E91                 LD   A,"graphic-B"
#8225 D7                   RST  #10        ;Print right-hand border
#8226 10F2                 DJNZ NMP_PRINT  ;Print all strings
#8228 FD362600             LD   (X_PTR+1),#00;Clear pointer
#822C CD7881               CALL MENU_NEWLINE ;Move print position to last line
                                           ;of menu panel
#822F 3E92                 LD   A,"graphic-C"
#8231 D7                   RST  #10        ;Print bottom left-hand corner
#8232 3E94     NMP_UNDER   LD   A,"graphic-E"
#8234 D7                   RST  #10        ;Print underside border
#8235 0D                   DEC  C
#8236 20FA                 JR   NZ,NMP_UNDER ;Print whole of underside border
#8238 3E93                 LD   A,"graphic-D"
#823A D7                   RST  #10        ;Print bottom right-hand corner
#823B C1                   POP  BC         ;B:= number of strings, excluding
                                           ;title; C:= max length of string
#823C E1                   POP  HL         ;HL:= original address of UDGs
#823D 227B5C               LD   (UDG),HL   ;Restore system variable
#8240 C9                   RET             ;Return

#8241 D5       NEW_MENU    PUSH DE
#8242 CDBB81               CALL NEW_MPANEL ;Print menu on screen
#8245 D1                   POP  DE
#8246 0C                   INC  C
#8247 0C                   INC  C          ;C:= full width including borders
#8248 3E01                 LD   A,#01      ;A:= initial menu choice
#824A F5       N_M_LOOP    PUSH AF         ;Stack menu choice
#824B 82                   ADD  A,D        ;A:= line number of menu choice
#824C 6F                   LD   L,A
#824D AF                   XOR  A          ;A:= 00
#824E 67                   LD   H,A        ;HL:= line number of menu choice
#824F 29                   ADD  HL,HL
#8250 29                   ADD  HL,HL
#8251 29                   ADD  HL,HL
#8252 29                   ADD  HL,HL
#8253 29                   ADD  HL,HL
#8254 D5                   PUSH DE
#8255 57                   LD   D,A         ;DE:= x coordinate of panel
#8256 19                   ADD  HL,DE
#8257 110058               LD   DE,#5800
#825A 19                   ADD  HL,DE       ;HL:= attr address of menu choice
#825B D1                   POP  DE
#825C C5                   PUSH BC
#825D 7E       N_M_MARK    LD   A,(HL)      ;A:= attribute byte for row
#825E EE10                 XOR  #10         ;Change paper colour
#8260 77                   LD   (HL),A      ;Make change on screen
#8261 23                   INC  HL          ;HL: points to next attr byte in row
#8262 0D                   DEC  C
#8263 20F8                 JR   NZ,N_M_MARK ;Mark entire row
#8265 C1                   POP  BC
#8266 FDCB01AE N_M_WAIT    RES  5,(FLAGS)   ;Signal "Ready for a new key"
#826A FDCB016E N_M_WAIT2   BIT  5,(FLAGS)
#826E 28FA                 JR   Z,N_M_WAIT2 ;Wait until key pressed
#8270 3A085C               LD   A,(LAST_K)  ;A:= chr code of key pressed
#8273 D60A                 SUB  #0A
#8275 2009                 JR   NZ,N_M_KEY_B;Jump unless key is "cursor down"
#8277 F1                   POP  AF          ;A:= current menu choice
#8278 B8                   CP   B
#8279 3C                   INC  A           ;Move one line down
#827A 380C                 JR   C,N_M_NEXT  ;Jump unless below last entry
#827C 3E01                 LD   A,#01       ;Go to first entry
#827E 1808                 JR   N_M_NEXT    ;Jump forward
#8280 3D       N_M_KEY_B   DEC  A
#8281 2013                 JR   NZ,N_M_KEY_C;Jump unless key is "cursor up"
#8283 F1                   POP  AF          ;A:= current menu choice
#8284 3D                   DEC  A           ;Move one line up
#8285 2001                 JR   NZ,N_M_NEXT ;Jump unless above first entry
#8287 78                   LD   A,B         ;Go to last entry
#8288 F5       N_M_NEXT    PUSH AF          ;Stack new menu choice
#8289 C5                   PUSH BC
#828A 2B       N_M_UNMARK  DEC  HL          ;HL: points to next attr byte in row
#828B 7E                   LD   A,(HL)      ;A:= attribute byte from row
#828C EE10                 XOR  #10         ;Restore paper colour
#828E 77                   LD   (HL),A      ;Make change on screen
#828F 0D                   DEC  C
#8290 20F8                 JR   NZ,N_M_UNMARK ;Remove marker entirely
#8292 C1                   POP  BC
#8293 F1                   POP  AF          ;A:= menu choice
#8294 18B4                 JR   N_M_LOOP    ;Loop back for next try
#8296 3D       N_M_KEY_C   DEC  A
#8297 2006                 JR   NZ,N_M_KEY_D;Jump unless key is "delete"
#8299 F1                   POP  AF          ;A:= menu choice
#829A CD4D80               CALL UNSTK_PANEL ;Remove menu from screen
#829D AF                   XOR  A           ;A:= 00
#829E C9                   RET              ;Return with A containing zero
#829F 3D       N_M_KEY_D   DEC  A
#82A0 20C4                 JR   NZ,N_M_WAIT ;Jump back unless key is "enter"
#82A2 F1                   POP  AF          ;A:= menu choice
#82A3 C9                   RET              ;Return with A containing choice

#82A4 DF       SEPARATOR?  RST  #18         ;A:= current character
#82A5 FE27                 CP   "'"
#82A7 C8                   RET  Z           ;Set zero flag for "'"
#82A8 FE2C                 CP   ","
#82AA C8                   RET  Z           ;Set zero flag for ","
#82AB FE3B                 CP   ";"
#82AD C9                   RET              ;Or set zero flag for ";"

#82AE CDA482   SEPARATOR   CALL SEPARATOR?  ;Test current character
#82B1 C8                   RET  Z           ;Return with valid separator
#82B2 CF       ERROR_C     RST  #08         ;Report C, Nonsense in BASIC
#82B3 0B                   DEFB 0B

#82B4 AF       B_PANEL     XOR  A           ;A:= 00
#82B5 1806                 JR   B_INTERPRET
#82B7 3E01     B_MPANEL    LD   A,#01
#82B9 1802                 JR   B_INTERPRET
#82BB 3E03     B_MENU      LD   A,#03
#82BD 210000   B_INTERPRET LD   HL,#0000
#82C0 222E80               LD   (MENU_AT),HL;Initialise menu location
#82C3 6F                   LD   L,A
#82C4 222C80               LD   (PANEL_TYPE),HL ;Define panel type and reset
                                            ;string count
#82C7 3E02                 LD   A,#02
#82C9 CD0116               CALL CHAN-OPEN   ;Select stream 2 (the screen)
#82CC CD4D0D               CALL TEMPS       ;Use permanent colours
#82CF DF                   RST  #18         ;A:= current char from BASIC line
#82D0 FE3A                 CP   ":"
#82D2 20DE                 JR   NZ,ERROR_C  ;Error unless character is ":"
#82D4 E7                   RST  #20         ;A:= next character from BASIC line
#82D5 FEF5                 CP   "PRINT"
#82D7 20D9                 JR   NZ,ERROR_C  ;Error unless character is "PRINT"
#82D9 FD340D               INC  (SUBPPC)    ;Increment statement count
#82DC E7       BI_LOOP     RST  #20         ;A:= next character from BASIC line
#82DD FEAC                 CP   "AT"
#82DF 2010                 JR   NZ,BI_COLOURS ;Jump unless character is "AT" 
#82E1 CD791C               CALL NEXT-2NUM   ;Evaluate AT parameters
#82E4 CD0723               CALL STK-TO-BC   ;C,B:= AT parameters
#82E7 61                   LD   H,C
#82E8 68                   LD   L,B
#82E9 222E80               LD   (MENU_AT),HL;Store required AT coordinates
#82EC CDAE82               CALL SEPARATOR   ;Expect a separator
#82EF 18EB                 JR   BI_LOOP     ;Jump back to continue interpreting
#82F1 CDF221   BI_COLOURS  CALL CO-TEMP-3   ;Evaluate colour commands
#82F4 3805                 JR   C,BI_MENU   ;Jump if no colour commands present
#82F6 CDAE82               CALL SEPARATOR   ;Expect a separator
#82F9 18E1                 JR   BI_LOOP     ;Jump back to continue interpreting
#82FB 212C80   BI_MENU     LD   HL,PANEL_TYPE
#82FE CB46                 BIT  0,(HL)
#8300 280F                 JR   Z,BI_PANEL  ;Jump if panel only requested
#8302 CD8C1C   BI_STR_LOOP CALL EXPT-EXP    ;Expect a string expression
#8305 212D80               LD   HL,STRING_COUNT
#8308 34                   INC  (HL)        ;Increment number of strings counted
#8309 CDA482               CALL SEPARATOR?  ;Is there a separator?
#830C 2016                 JR   NZ,BI_MENU_GO ;Jump if not
#830E E7                   RST  #20         ;Skip over the separator
#830F 18F1                 JR   BI_STR_LOOP ;Loop back to count remaining strs
#8311 CD7A1C   BI_PANEL    CALL EXPT-2NUM   ;Evaluate panel size
#8314 CD0723               CALL STK-TO-BC   ;C,B:= panel size
#8317 78                   LD   A,B         ;A:= panel width
#8318 41                   LD   B,C         ;B:= panel height
#8319 4F                   LD   C,A         ;C:= panel width
#831A ED5B2E80             LD   DE,(MENU_AT);DE:= panel position
#831E 3A8F5C               LD   A,(ATTR_T)  ;A:= colours required
#8321 C3C780               JP   NEW_PANEL   ;Jump to create panel
#8324 ED4B2C80 BI_MENU_GO  LD   BC,(PANEL_TYPE) ;B:= number of strings
#8328 ED5B2E80             LD   DE,(MENU_AT);DE:= panel position
#832C 3A8F5C               LD   A,(ATTR_T)  ;A:= colours required
#832F CB49                 BIT  1,C
#8331 CABB81               JP   Z,NEW_MPANEL;Jump to create menu panel, if reqd
#8334 CD4182               CALL NEW_MENU    ;Else create menu panel and choose
#8337 0600                 LD   B,#00
#8339 4F                   LD   C,A         ;BC:= menu choice
#833A C9                   RET              ;Return

#833B CD4D80   MENU_OFF    CALL UNSTK_PANEL ;Unstack topmost panel, if it exists
#833E 2A4B5C               LD   HL,(VARS)   ;HL: points to variables area
#8341 2B                   DEC  HL          ;HL: points to top of panel stack
#8342 010000               LD   BC,#0000
#8345 CB7E                 BIT  7,(HL)
#8347 C8                   RET  Z           ;Return if no panels left on stack
#8348 03                   INC  BC          ;BC:= one
#8349 C9                   RET              ;Return
