'Qbasic Highlighted Menu
Does anyone have an example code or instructions for making this work? I've just never been able to accomplish a highlighted menu that uses the arrow keys and enter for selections. Thanks in advance!
I anticipate this working by drawing boxes for each option, and redrawing the box in color while coloring the text when an option is selected. I'm just unsure how to design a loop to accomplish this. I'm pretty comfortable with the INKEY$
function and the SELECT CASE
statement, but I don't know how to factor them in.
Solution 1:[1]
A highlighted menu will draw the menu and wait for a key press in a loop or using SLEEP
. A common alternative was to simply change the text color of one of the first few letters, informing the user to press the corresponding key to select the corresponding menu option. For example, the letters Q in "Quit" and N in "New Game" would be a different color from the rest of the text in the line.
However, you're asking to use the arrow keys, so clearly you don't want to do it that way. How you highlight the current menu item depends on the screen mode in use. Screen modes 11, 12, and 13 don't allow you to specify a background color, and I can't get DOSBox to render a background with modes 7, 8, and 9. As a workaround to this issue, you could instead simply draw a box next to the current selection and erase the box (draw a black one or whatever your screen's background color is). Or you could just use an asterisk to avoid graphics/text size issues and simplify the code. Here's an example of the box style with arrow keys, WASD keys, and Vim-style keys (H=Left, J=Down, K=Up, L=Right) all supported, assuming a US-QWERTY keyboard is used. If you only wanted arrow keys, then you'd merely need to change the first (outer) SELECT CASE...END SELECT
block to simply IF LEFT$(k$, 1) = CHR$(0) THEN...END IF
while preserving the inner SELECT CASE...END SELECT
block that works with extended keys.
'size% is used in the selIncDec subroutine.
DIM text$(0 TO 3)
DIM SHARED size%
size% = UBOUND(text$) - LBOUND(text$) + 1
selected% = 0
text$(0) = "Example 1"
text$(1) = "Example 2"
text$(2) = "Example 3"
text$(3) = "Example 4"
SCREEN 12
' Width and height of a text cell in pixels.
' I use 8x8 text cells for max screen compatibility, despite 8x16 looking better.
xpxText% = 8
ypxText% = 8
' See the documentation for SCREEN to determine which screen sizes are
' available with the screen mode you want to use.
' 80x60 for mode 12 results in 8x8 text cells. 80x30 results in 8x16 text cells.
WIDTH 80, 60
DO
LOCATE 1, 1
FOR i% = LBOUND(text$) TO UBOUND(text$)
PRINT TAB(3); text$(i%)
' selected% = i%
' is an equality comparison, resulting in -1 for true and 0 for false.
' If false, -(0) * 2 = 0; if true, -(-1) * 2 = 2.
LINE (0, i% * ypxText%)-STEP(xpxText% - 1, xpxText% - 1), -(selected% = i%) * 2, BF
NEXT i%
SLEEP
k$ = INKEY$
SELECT CASE UCASE$(LEFT$(k$, 1))
' Left -- does nothing
CASE "H", "A"
' Right -- does nothing
CASE "L", "D"
' Up
CASE "K", "W"
CALL selIncDec(selected%, -1)
' Down
CASE "J", "S"
CALL selIncDec(selected%, 1)
' Enter key
CASE CHR$(13)
EXIT DO
' Extended key, such as arrows.
CASE CHR$(0)
SELECT CASE RIGHT$(k$, 1)
' Left
CASE "K"
' Right
CASE "M"
' Up
CASE "H"
CALL selIncDec(selected%, -1)
' Down
CASE "P"
CALL selIncDec(selected%, 1)
END SELECT
END SELECT
LOOP
PRINT USING "You selected option #"; selected% + 1
END
SUB selIncDec (selected%, amtInc%)
selected% = selected% + amtInc%
IF selected% >= size% THEN
selected% = selected% - size%
ELSEIF selected% < 0 THEN
selected% = selected% + size%
END IF
END SUB
If you were using a screen mode that supports background colors or highlighting in some form, such as screen 0, you might be able to get away with simply "highlighting" the entire line's background in text mode. You need not specify the width of the screen to have a "reverse video" effect acting as highlighting, but it looks better when you have an entire line highlighted rather than just the text. After that menu item is printed, just change the colors back to the default and continue printing as usual. Below shows a few changes to the above code (screen mode, screen width setting, and menu display code), but it remains the same otherwise:
SCREEN 0
'8x8 text cells in SCREEN 0 for VGA adapters.
WIDTH 80, 43
...
FOR i% = LBOUND(text$) TO UBOUND(text$)
' "Reverse video" highlighting.
IF selected% = i% THEN COLOR 0, 7 ELSE COLOR 7, 0
PRINT TAB(3); text$(i%); SPACE$(78 - LEN(text$(i%)))
NEXT i%
' The screen will turn "white" when the last menu item is selected.
' This fixes the issue.
COLOR 7, 0
SLEEP
...
Note that I assumed a VGA adapter with a color display for all of the code above, which has long since been superseded by various other display adapter standards that are in use even on devices as small as smart watches.
You should be able to adapt the code to fit your needs. I designed it such that you could simply add menu items as you wished. Also, the display code itself is contained entirely in the FOR...NEXT
loop with the functionality immediately following, so all you'd need to change is the stuff inside the FOR...NEXT
loop to change how things are displayed.
Solution 2:[2]
I'm in the same boat, wanting to use SCREEN 12 for a menu, with background colour. As per http://www.qb64.net/wiki/index_title_COLOR/
SCREEN modes 12 and 13 can use the foreground parameter only in QB 4.5! Background color 0 can be changed using OUT.
I also came across this SUB:
SCREEN 12
ColourPrint "Hello", 4, 9
PRINT
ColourPrint "bob", 3, 10
SUB ColourPrint (t$, fg%, bg%)
' t$ = printing text
' fg% = foreground colour
' bg% = background colour
DIM h%(1 + 32 * LEN(t$))
x1% = 8 * (POS(0) - 1)
y1% = 16 * (CSRLIN - 1)
x2% = x1% + 8 * LEN(t$) - 1
y2% = y1% + 15
LINE (x1%, y1%)-(x2%, y2%), bg%, BF
GET (x1%, y1%)-(x2%, y2%), h%()
COLOR fg% XOR bg%
PRINT t$;
PUT (x1%, y1%), h%(), XOR
ERASE h%
END SUB
This site has a bunch of menu examples that might help too:
Solution 3:[3]
Basically you need a loop that:
- Keeps track of the current menu item selected (usually an index of the menu item: 1, 2, 3, ...) within a variable - let's call it SELECTED
- Draws the menu on the screen with a highlight for the selected item (you'll need to iterate over all menu items and highlight when SELECTED = current menu item index)
- Wait for a key to be pressed:
- If the key is UP then select previous menu item (SELECTED = SELECTED - 1)
- If the key is DOWN then select next menu item (SELECTED = SELECTED + 1)
- If the key is ENTER then exit menu and return the selected item
I have a full example here but for a text-based menu. You can adapt it to your problem. The main functionality is on the "menu" function:
DECLARE FUNCTION countItems% (items$)
DECLARE FUNCTION menu% (row%, col%, items$)
DECLARE FUNCTION widestItemLength% (items$)
DEFINT A-Z
CONST SEPARATOR = ";"
CLS
selectedMenuOption = menu(1, 1, "APPLE;BANANA;ORANGE;EXIT")
PRINT
PRINT
PRINT "Selected menu option:"; selectedMenuOption
' Returns how much items we have in the menu
'
FUNCTION countItems (items$)
count = 1
DO
i = INSTR(i + 1, items$, SEPARATOR)
IF i > 0 THEN count = count + 1
LOOP UNTIL i = 0
countItems = count
END FUNCTION
' Main menu functionality
' 1. shows the menu highlighting the selected item
' 2. wait for a key to be pressed
' a. UP -> select previous item
' b. DOWN -> select next
' c. ENTER -> exit and returns the index of the current selected item
'
FUNCTION menu (row%, col%, items$)
widestLength = widestItemLength(items$)
itemCount = countItems(items$)
selected = 1 ' menu item index starting by 1
DO
' Prints the menu on the screen
itemEnd = 0
LOCATE row%, col%
FOR i = 1 TO itemCount
itemStart = itemEnd + 1
itemEnd = INSTR(itemStart, items$, SEPARATOR)
IF itemEnd = 0 THEN itemEnd = LEN(items$) + 1
item$ = MID$(items$, itemStart, itemEnd - itemStart)
item$ = " " + item$ + SPACE$(widestLength - LEN(item$)) + " "
IF selected = i THEN COLOR 0, 7 ELSE COLOR 7, 0
LOCATE CSRLIN + 1, col: PRINT item$;
NEXT
WHILE INKEY$ <> "": WEND ' Clears the keyboard buffer
DO: k$ = INKEY$: LOOP WHILE k$ = "" ' Waits for a key to be pressed
SELECT CASE k$
' Up key pressed - select previous menu item
CASE CHR$(0) + "H": IF selected > 1 THEN selected = selected - 1
' Down key pressed - select next menu item
CASE CHR$(0) + "P": IF selected < itemCount THEN selected = selected + 1
END SELECT
LOOP UNTIL k$ = CHR$(13) ' Loops until the key pressed is enter
menu = selected ' returns the selected menu item index
COLOR 7, 0
END FUNCTION
' Returns the size of the widest menu item
'
FUNCTION widestItemLength (items$)
widestLen = 0
itemEnd = 0
DO
itemStart = itemEnd + 1
itemEnd = INSTR(itemStart, items$, SEPARATOR)
IF itemEnd = 0 THEN itemEnd = LEN(items$) + 1
itemLen = itemEnd - itemStart
IF itemLen > widestLen THEN widestLen = itemLen
LOOP UNTIL itemEnd = LEN(items$) + 1
widestItemLength = widestLen
END FUNCTION
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | Hiro |