QB64 Phoenix Edition
LIGHTBAR Menu - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Code and Stuff (https://qb64phoenix.com/forum/forumdisplay.php?fid=3)
+---- Forum: Programs (https://qb64phoenix.com/forum/forumdisplay.php?fid=7)
+---- Thread: LIGHTBAR Menu (/showthread.php?tid=1870)

Pages: 1 2


LIGHTBAR Menu - grymmjack - 07-31-2023

Knocked up a little lightbar driven menu routine which will go in my library soon.

[Image: lightbar-routine-demo.png]

Code: (Select All)

''
' LIGHTBAR Menu
'
' Creating lightbar driven menus using arrow keys to choose, enter to select, and
' making it reusable and modular.
'
' Code is a bit long, I'm sure someone can do this better! Regardless, this will end
' up in my QB64_GJ_LIB library soon.
'
' @author Rick Christy <grymmjack@gmail.com>
'
OPTION _EXPLICIT
SCREEN 0 : _BLINK OFF : _CONTROLCHR OFF
_TITLE "LIGHTBAR Menu Routine DEMO"

TYPE LIGHTBAR ' bg|b = background, fg|f = foreground, k = key
opt_bg_color AS INTEGER
opt_fg_color AS INTEGER
bar_bg_color AS INTEGER
bar_fg_color AS INTEGER
bar_kf_color AS INTEGER
bar_kb_color AS INTEGER
key_bg_color AS INTEGER
key_fg_color AS INTEGER
opt_selected AS INTEGER
delimeter AS STRING
END TYPE

DIM menu AS LIGHTBAR : DIM opts(5) AS STRING : DIM choice AS INTEGER
menu.opt_bg_color = 0
menu.opt_fg_color = 12
menu.bar_bg_color = 3
menu.bar_fg_color = 11
menu.bar_kf_color = 14
menu.bar_kb_color = 11
menu.key_bg_color = 3
menu.key_fg_color = 14
menu.opt_selected = 0
menu.delimeter = "|"
opts$(0) = " |P|izza "
opts$(1) = " |R|ibs "
opts$(2) = " |W|ings "
opts$(3) = " |S|alad "
opts$(4) = " |B|readsticks "
opts$(5) = " |Q|uit "

COLOR 12, 0: PRINT "----------------------------------------"
COLOR 7, 0 : PRINT " Welcome to";
COLOR 12, 0: PRINT " ANTONIOS"; : COLOR 10, 0: PRINT " PIZZERIA!"
COLOR 7, 0 : PRINT " Pick your favorite food from our menu!"
COLOR 14, 0: PRINT "----------------------------------------"
COLOR 2, 0 : PRINT " ..if you're not hungry press ESCAPE.. "
COLOR 12, 0: PRINT "----------------------------------------"
PRINT
COLOR 9, 0 : PRINT " UP and DOWN choose and ENTER picks!"
PRINT

choice% = LIGHTBAR%(menu, opts$())
IF choice% <> -1 THEN
PRINT
COLOR 11, 0 : PRINT "You chose option ";
COLOR 14, 0 : PRINT UCASE$(_TRIM$(STR$(choice%)));
COLOR 11, 0 : PRINT ": ";
COLOR 12, 0 : PRINT _TRIM$(opts$(choice%))
IF choice% = 0 THEN
COLOR 10, 0 : PRINT "An excellent choice! It is also my favorite!"
END IF
ELSE
PRINT
COLOR 3, 0 : PRINT "Not hungry eh? OK you come back later!"
END IF
PRINT
COLOR 12, 0 : PRINT "Thank you! Come again!"



FUNCTION LIGHTBAR%(menu AS LIGHTBAR, options$())
DIM AS STRING opt_l, opt_r, k, opt_sel_l, opt_sel_r, opt_sel_k
DIM AS INTEGER obg, ofg, bbg, bfg, bkf, bkb, kbg, kfg, key_pos_s, key_pos_e
DIM AS INTEGER row, col, orig_bg, orig_fg, lb, ub, i, selected, choice_made
lb% = LBOUND(options$) : ub% = UBOUND(options$)

' fetch convenience colors
obg% = menu.opt_bg_color : ofg% = menu.opt_fg_color
bbg% = menu.bar_bg_color : bfg% = menu.bar_fg_color
bkf% = menu.bar_kf_color : bkb% = menu.bar_kb_color
kbg% = menu.key_bg_color : kfg% = menu.key_fg_color

DIM keys(lb% TO ub%) AS STRING ' holds hot keys (chars in delimeters)
row% = CSRLIN : col% = POS(0) ' store initial cursor position
orig_fg% = SCREEN(row%, col%, 1) AND 15 ' store initial foreground color
orig_bg% = SCREEN(row%, col%, 1) \ 16 ' store initial background color
selected% = menu.opt_selected ' get selected option

LIGHTBAR_draw:
LOCATE row%, col%
FOR i% = lb% TO ub%
key_pos_s% = INSTR(0, options$(i%), menu.delimeter)
key_pos_e% = INSTR(key_pos_s%, options$(i%), menu.delimeter)
keys$(i%) = MID$(options$(i%), key_pos_s% + 1, 1)
opt_l$ = MID$(options$(i%), 0, key_pos_s%)
opt_r$ = MID$(options$(i%), key_pos_s% + 3)
COLOR ofg%, obg% : PRINT opt_l$;
COLOR kfg%, kbg% : PRINT keys$(i%);
COLOR ofg%, obg% : PRINT opt_r$
IF i% = selected% THEN
opt_sel_l$ = opt_l$ : opt_sel_r$ = opt_r$ : opt_sel_k$ = keys$(i%)
END IF
NEXT i%

' draw selected option
LOCATE row% + selected%, col%
COLOR bfg%, bbg% : PRINT opt_sel_l$;
COLOR bkf%, bkb% : PRINT opt_sel_k$;
COLOR bfg%, bbg% : PRINT opt_sel_r$

LIGHTBAR_get_choice:
DO:
' handle arrow keys
k$ = INKEY$
SELECT CASE k$
CASE CHR$(27): ' escape to abort
selected% = -1
CASE CHR$(0) + CHR$(71): ' home to jump to first option
selected% = lb%
GOTO LIGHTBAR_draw
CASE CHR$(0) + CHR$(79): ' end to jump to last option
selected% = ub%
GOTO LIGHTBAR_draw
CASE CHR$(0) + CHR$(80): ' down arrow to go down an option
selected% = selected% + 1
IF selected% > ub% THEN selected% = lb%
GOTO LIGHTBAR_draw
CASE CHR$(0) + CHR$(72): ' up arrow to go up an option
selected% = selected% - 1
IF selected% < lb% THEN selected% = ub%
GOTO LIGHTBAR_draw
END SELECT
' handle hot keys
FOR i% = lb% TO ub%
IF LCASE$(k$) = LCASE$(keys$(i%)) THEN
selected% = i%
choice_made% = 1
GOTO LIGHTBAR_draw
END IF
NEXT i%
LOOP UNTIL k$ = CHR$(13) OR k$ = CHR$(27) OR choice_made% = 1

COLOR orig_fg%, orig_bg% ' restore original colors
LOCATE row% + (ub% - lb%) + 1, col% ' position cursor under menu
LIGHTBAR% = selected%
END FUNCTION



RE: LIGHTBAR Menu - bplus - 07-31-2023

Nice for Screen 0, just a few mods away from any screen pop-up.

So Rick are you a screen 0 hero? Smile  Pete was!


RE: LIGHTBAR Menu - Dimster - 07-31-2023

That's a nice highlighter. I'm still working on a drop down menu which b+ posted however I haven't yet learned enough on mouse controls to adapt it to my pet project and I think this lightbar would compliment the drop down menu very nicely. Thanks of this code.


RE: LIGHTBAR Menu - grymmjack - 08-01-2023

(07-31-2023, 01:59 PM)bplus Wrote: Nice for Screen 0, just a few mods away from any screen pop-up.

So Rick are you a screen 0 hero? Smile  Pete was!
Thanks. 

I love screen 0 yes. Smile


RE: LIGHTBAR Menu - grymmjack - 08-01-2023

(07-31-2023, 03:51 PM)Dimster Wrote: That's a nice highlighter. I'm still working on a drop down menu which b+ posted however I haven't yet learned enough on mouse controls to adapt it to my pet project and I think this lightbar would compliment the drop down menu very nicely. Thanks of this code.

Hey my pleasure! I'd love to see what ya make.


RE: LIGHTBAR Menu - James D Jarvis - 08-02-2023

Easy to modify and make use of.


RE: LIGHTBAR Menu - grymmjack - 08-06-2023

I have modified the LIGHTBAR to accommodate vertical and horizontal.

This works fine as long as the options in horizontal (menu.opt_vertical% = 0) do not exceed the screen width.

There is a really strange bug in here, and I'd love some help looking at it and seeing if we can figure out what's going on.

Thanks, and a video explains the issue here:
https://app.screencast.com/VvSodu5cncwDc

[Image: lightbar-bug.png]

Code: (Select All)

''
' LIGHTBAR Menu
'
' Creating lightbar driven menus using arrow keys to choose, enter to select, and
' making it reusable and modular.
'
' Code is a bit long, I'm sure someone can do this better! Regardless, this will end
' up in my QB64_GJ_LIB library soon.
'
' @author Rick Christy <grymmjack@gmail.com>
'
OPTION _EXPLICIT
_TITLE "LIGHTBAR Menu Routine DEMO"

SCREEN 0 ' this uses SCREEN 0 (text mode)
_BLINK OFF ' allow high intensity background colors > 7
_CONTROLCHR OFF ' allow printing of any characters including control chars

' LIGHTBAR configuration UDT
TYPE LIGHTBAR ' bg|b = background, fg|f = foreground, k = key
opt_bg_color AS INTEGER ' unselected background color
opt_fg_color AS INTEGER ' unselected foreground color
bar_bg_color AS INTEGER ' selected background color
bar_fg_color AS INTEGER ' selected foreground color
bar_kf_color AS INTEGER ' selected hot key foreground color
bar_kb_color AS INTEGER ' selected hot key background color
key_bg_color AS INTEGER ' unselected hot key background color
key_fg_color AS INTEGER ' unselected hot key foreground color
opt_selected AS INTEGER ' selected option index
opt_vertical AS INTEGER ' 1 = true (then vertical) 0 = false (then horizontal)
delimeter AS STRING ' Single character used to surround hot key
END TYPE

DIM menu AS LIGHTBAR ' Create a LIGHTBAR menu
DIM opts(13) AS STRING ' Our menu will contain 6 options (0 indexed array)

DIM choice AS INTEGER ' This variable will contain the choice the user made
' using either the hot key directly, or by using the
' home, end, or arrow keys to select with ENTER.
' This variable will be -1 if the user hit ESC to abort.

' Configure the LIGHTBAR menu
menu.opt_bg_color% = 0 : menu.opt_fg_color% = 12 ' Unselected option colors
menu.bar_bg_color% = 3 : menu.bar_fg_color% = 11 ' Selected option colors
menu.bar_kf_color% = 14 : menu.bar_kb_color% = 11 ' Selected hot key colors
menu.key_bg_color% = 0 : menu.key_fg_color% = 14 ' Unselected hot key colors
' Select the first option by default
menu.opt_selected% = 0
' Use vertical orientation (new lines after each option)
menu.opt_vertical% = 1
' Use vertical orientation (new lines after each option)
menu.opt_vertical = 0
' Delimiter can be any single character but must be same char on both sides
' OK: "|F|oo", Not OK: "[F]oo"
menu.delimeter$ = "|"

' Populate the LIGHTBAR options - NOTE: spaces and formatting are kept intact
' If you want longer bars, add more spaces or whatever characters that bar is made of.
IF menu.opt_vertical% = 1 THEN ' vertical LIGHTBAR menu
opts$(0) = " |P|izza " ' | = delimeter, so |P| for Pizza
opts$(1) = " |R|ibs " ' Notice that Ribs, ...
opts$(2) = " |W|ings " ' Wings, ...
opts$(3) = " |S|alad " ' Salad, ...
opts$(4) = " |B|readsticks " ' ...
opts$(5) = " |A|pple Pie " ' ...
opts$(6) = " |C|innamon Sticks " ' ...
opts$(7) = " |K|rispy Kreme Donuts " ' ...
opts$(8) = " |D|eluxe Pepperoni Bread " ' ...
opts$(9) = " |E|ggplant Parmesan " ' ...
opts$(10) = " |F|ettucinni Alfredo Bowl " ' ...
opts$(11) = " |J|uice for the Bambinos " ' ...
opts$(12) = " |W|ine for Padre + Madre " ' All of the same length
opts$(13) = " |Q|uit " ' However, each option can have a different bar length like this.
END IF

' Populate the LIGHTBAR options - NOTE: spaces and formatting are kept intact
IF menu.opt_vertical% = 0 THEN ' horizontal LIGHTBAR menu
opts$(0) = " |P|izza "
opts$(1) = " |R|ibs "
opts$(2) = " |W|ings "
opts$(3) = " |S|alad "
opts$(4) = " |B|readsticks "
opts$(5) = " |A|pple Pie "
opts$(6) = " |C|innamon Sticks "
opts$(7) = " |K|rispy Kreme Donut "
opts$(8) = " |D|eluxe Pepperoni Bread "
opts$(9) = " |E|ggplant Parmesan "
opts$(10) = " |F|ettucinni Alfredo Bowl "
opts$(11) = " |J|uice for the Bambinos "
opts$(12) = " |W|ine for Padre + Madre "
opts$(13) = " |Q|uit "
END IF

' Draw some basic goofy screen under which we will have a LIGHTBAR menu
COLOR 12, 0: PRINT "----------------------------------------"
COLOR 7, 0 : PRINT " Welcome to";
COLOR 12, 0: PRINT " ANTONIOS"; : COLOR 10, 0: PRINT " PIZZERIA!"
COLOR 7, 0 : PRINT " Pick your favorite food from our menu!"
COLOR 14, 0: PRINT "----------------------------------------"
COLOR 2, 0 : PRINT " ..if you're not hungry press ESCAPE.. "
COLOR 12, 0: PRINT "----------------------------------------"
COLOR 9, 0 : PRINT " PRESS ARROWS & HOME/END to choose..."
COLOR 9, 0 : PRINT " PRESS ENTER or HOT KEY to PICK..."
COLOR 9, 0 : PRINT " PRESS ESC to abort!"

' Draw the LIGHTBAR menu, and store the result chosen in choice%
choice% = LIGHTBAR%(menu, opts$())

' If user did not press ESC to abort show which option they chose.
IF choice% <> -1 THEN
PRINT
COLOR 11, 0 : PRINT "You chose option ";
COLOR 14, 0 : PRINT UCASE$(_TRIM$(STR$(choice%)));
COLOR 11, 0 : PRINT ": ";
COLOR 12, 0 : PRINT _TRIM$(opts$(choice%))
IF choice% = 0 THEN
COLOR 10, 0 : PRINT "An excellent choice! It is also my favorite!"
END IF
' User pressed ESC to abort, so show something else
ELSE
PRINT
COLOR 3, 0 : PRINT "Not hungry eh? OK you come back later!"
END IF
PRINT
COLOR 12, 0 : PRINT "Thank you! Come again!"



''
' Render a LIGHTBAR menu using options$
' @param LIGHTBAR menu UDT
' @param string array of menu options
' @return integer choice made (-1 if abort with ESC)
'
FUNCTION LIGHTBAR%(menu AS LIGHTBAR, options$())
DIM AS STRING k, o
DIM AS INTEGER key_pos_s, key_pos_e, ks, fg, bg, kf, kb
DIM AS INTEGER row, col, orig_bg, orig_fg, lb, ub, sel, choice_made
DIM AS INTEGER rows_drawn, cols_drawn, i, j
lb% = LBOUND(options$) : ub% = UBOUND(options$)

' UDT for option data
TYPE LIGHTBAR_OPTION
row AS INTEGER ' Option row
col AS INTEGER ' Option column
opt_left AS STRING ' Option left side text
opt_key AS STRING ' Option hot key
opt_right AS STRING ' Option right side text
opt_len AS INTEGER ' Option length (left + key + right)
is_selected AS INTEGER ' Is this option selected? 0 = no, 1 = yes
END TYPE

' Define key constants
CONST KEY_ESC = 27
CONST KEY_HOME = 71 : CONST KEY_END = 79
CONST KEY_LEFT = 75 : CONST KEY_RIGHT = 77
CONST KEY_UP = 72 : CONST KEY_DOWN = 80
CONST KEY_ENTER = 13

DIM keys(lb% TO ub%) AS STRING ' holds hot keys (chars in delimeters)
row% = CSRLIN : col% = POS(0) ' store initial cursor position
orig_fg% = SCREEN(row%, col%, 1) AND 15 ' store initial foreground color
orig_bg% = SCREEN(row%, col%, 1) \ 16 ' store initial background color
cols_drawn% = 0 ' init columns drawn

LIGHTBAR_get_options:
DIM opt(lb% TO ub%) AS LIGHTBAR_OPTION
FOR i% = lb% to ub%
' Extract hot key start and end positions
key_pos_s% = INSTR(0, options$(i%), menu.delimeter$)
key_pos_e% = INSTR(key_pos_s%, options$(i%), menu.delimeter$)

' Extract left and right part of option without hotkey or delimeter
opt(i%).opt_left$ = MID$(options$(i%), 0, key_pos_s%)
opt(i%).opt_right$ = MID$(options$(i%), key_pos_s% + 3)

' Capture hot key into arrays
opt(i%).opt_key$ = MID$(options$(i%), key_pos_s% + 1, 1)

' Capture visible option length
o$ = opt(i%).opt_left$ + opt(i%).opt_key$ + opt(i%).opt_right$

opt(i%).opt_len% = LEN(o$)

' Check if option is selected
opt(i%).is_selected% = 0
IF i% = menu.opt_selected% THEN
sel% = i%
opt(i%).is_selected% = 1
END IF

' Calculate row and col positions for option
IF menu.opt_vertical% = 1 THEN
opt(i%).row% = row% + i%
opt(i%).col% = col%
ELSE
opt(i%).row% = row%
cols_drawn% = cols_drawn% + opt(i%).opt_len%
opt(i%).col% = cols_drawn% - opt(i%).opt_len%
END IF
NEXT i%

LIGHTBAR_draw:
FOR j% = lb% TO ub% ' Walk the array of menu options
safe_locate opt(j%).row%, opt(j%).col%
IF j% = sel% THEN ' selected colors
fg% = menu.bar_fg_color% : bg% = menu.bar_bg_color%
kf% = menu.bar_kf_color% : kb% = menu.bar_kb_color%
ELSE ' unselected colors
fg% = menu.opt_fg_color% : bg% = menu.opt_bg_color%
kf% = menu.key_fg_color% : kb% = menu.key_bg_color%
END IF
COLOR fg%, bg% : PRINT opt(j%).opt_left$; ' draw opt left
COLOR kf%, kb% : PRINT opt(j%).opt_key$; ' draw opt hot key
COLOR fg%, bg% : PRINT opt(j%).opt_right$ ' draw opt right
NEXT j%

' Wait for the user to choose an option
LIGHTBAR_get_choice:
DO:
_LIMIT 30
k$ = INKEY$
IF k$ <> "" THEN
IF LEFT$(k$, 1) = CHR$(0) THEN ' handle SPECIAL keys
ks% = ASC(RIGHT$(k$, 1)) ' get the char code minus the CHR$(0)
SELECT CASE ks%
CASE KEY_HOME:
sel% = lb%
GOTO LIGHTBAR_draw
CASE KEY_END:
sel% = ub%
GOTO LIGHTBAR_draw
CASE KEY_DOWN, KEY_RIGHT:
sel% = sel% + 1
IF sel% > ub% THEN sel% = lb%
GOTO LIGHTBAR_draw
CASE KEY_UP, KEY_LEFT:
sel% = sel% - 1
IF sel% < lb% THEN sel% = ub%
GOTO LIGHTBAR_draw
END SELECT
END IF

FOR i% = lb% TO ub% ' handle option hot keys
IF LCASE$(k$) = LCASE$(opt(i%).opt_key$) THEN
sel% = i%
choice_made% = 1
GOTO LIGHTBAR_draw
END IF
NEXT i%

IF k$ = CHR$(KEY_ESC) THEN ' ESCAPE to abort
sel% = -1
END IF

END IF
LOOP UNTIL k$ = CHR$(KEY_ENTER) OR k$ = CHR$(KEY_ESC) OR choice_made% = 1

' User chose option or aborted with ESC
COLOR orig_fg%, orig_bg% ' restore original colors

' position cursor under menu
IF menu.opt_vertical = 1 THEN
LOCATE row% + (ub% - lb%) + 1, col% ' if vertical
ELSE
LOCATE opt(ub%).row% + 1, col% ' if not vertical
END IF

LIGHTBAR% = sel%
END FUNCTION


''
' Safely locates within boundaries
' @param INTEGER row%
' @param INTEGER col%
'
SUB safe_locate(row%, col%)
IF col% > _WIDTH(0) THEN
row% = row% + (col% \ _WIDTH(0))
col% = col% MOD _WIDTH(0)
END IF
LOCATE clamp_int(row%, 1, _HEIGHT(0)), clamp_int(col%, 1, _WIDTH(0))
END SUB


''
' Clamps a value between a minimum and a maximum
' Clamp = do not be less than min, or more than max.
' @param INTEGER var% to clamp
' @param INTEGER min% minimum value allowed
' @param INTEGER max% maximum value allowed
' @return INTEGER clamped value between min and max range
'
FUNCTION clamp_int%(var%, min%, max%)
DIM r AS INTEGER
IF var% < min% THEN
r% = min%
ELSEIF var% > max% THEN
r% = max%
ELSE
r% = var%
END IF
clamp_int = r%
END FUNCTION




RE: LIGHTBAR Menu - bplus - 08-06-2023

I tried video and turn all volumes to max and still can't hear what is being said.

Can you give quick summary of problem? My little test only found the up down arrow acted like left right, not a big problem may be on purpose.


RE: LIGHTBAR Menu - grymmjack - 08-06-2023

OK I've fixed the LIGHTBAR menu.

Now it works regardless of width. Thanks to @offbyone for the help in the Discord.

You can set the menu max_width and when vertical menu is not used, it will wrap according to that max.

Code: (Select All)

''
' LIGHTBAR Menu
'
' Creating lightbar driven menus using arrow keys to choose, enter to select,
' and making it reusable and modular.
'
' Code is a bit long, I'm sure someone can do this better! Regardless, this
' will end up in my QB64_GJ_LIB library soon.
'
' @author Rick Christy <grymmjack@gmail.com>
'
OPTION _EXPLICIT
_TITLE "LIGHTBAR Menu Routine DEMO"

SCREEN 0 ' This uses SCREEN 0 (text mode)
_BLINK OFF ' Allow high intensity background colors > 7
_CONTROLCHR OFF ' Allow printing of any characters including control chars

' LIGHTBAR configuration UDT
TYPE LIGHTBAR
opt_bg_color AS INTEGER ' Unselected background color
opt_fg_color AS INTEGER ' Unselected foreground color
bar_bg_color AS INTEGER ' Selected background color
bar_fg_color AS INTEGER ' Selected foreground color
bar_kf_color AS INTEGER ' Selected hot key foreground color
bar_kb_color AS INTEGER ' Selected hot key background color
key_bg_color AS INTEGER ' Unselected hot key background color
key_fg_color AS INTEGER ' Unselected hot key foreground color
opt_selected AS INTEGER ' Selected option index
opt_vertical AS INTEGER ' 1 = true (then vertical) 0 = false (then horiz)
max_width AS INTEGER ' Maximum width for horizontal options
delimeter AS STRING ' Single character used to surround hot key
use_sounds AS INTEGER ' 1 = true (use sounds) 0 = false (no sounds)
snd_move_frq AS SINGLE ' Frequency for SOUND movement
snd_move_dur AS SINGLE ' Duration for SOUND movement
snd_move_vol AS SINGLE ' Volume for SOUND movement
snd_pick_frq AS SINGLE ' Frequency for SOUND pick
snd_pick_dur AS SINGLE ' Duration for SOUND pick
snd_pick_vol AS SINGLE ' Volume for SOUND pick

END TYPE

DIM menu AS LIGHTBAR ' Create a LIGHTBAR menu
DIM opts(13) AS STRING ' Our menu will contain 6 options (0 indexed array)

' choice variable will contain the choice the user made using either the
' hot key directly, or by using the home, end, or arrow keys to select
' and confirm with ENTER. This var will be -1 if the user hit ESC to abort.
DIM choice AS INTEGER

' Configure the LIGHTBAR menu
menu.opt_bg_color% = 0 : menu.opt_fg_color% = 12 ' Unselected option colors
menu.bar_bg_color% = 3 : menu.bar_fg_color% = 11 ' Selected option colors
menu.bar_kf_color% = 14 : menu.bar_kb_color% = 3 ' Selected hot key colors
menu.key_bg_color% = 0 : menu.key_fg_color% = 14 ' Unselected hot key colors
' Select the first option by default
menu.opt_selected% = 0
' Use vertical orientation (new lines after each option)
menu.opt_vertical% = 1
' Delimiter can be any single char but must be same char on both sides of key
' OK: "|F|oo", Not OK: "[F]oo"
menu.delimeter$ = "|"
' Set max width to screen width
menu.max_width% = _WIDTH(0)
' Setup sounds
menu.use_sounds% = 1
menu.snd_move_frq! = 200
menu.snd_move_dur! = 0.2
menu.snd_move_vol! = 1.0
menu.snd_pick_frq! = 100
menu.snd_pick_dur! = 1
menu.snd_pick_vol! = 1.0

' Populate the LIGHTBAR options - NOTE: Vertical options are padded with spaces
' If you want longer bars, add more spaces or characters that bar is made of
IF menu.opt_vertical% = 1 THEN ' Vertical LIGHTBAR menu
opts$(0) = " |P|izza " ' | = delimeter, so |P| for Pizza
opts$(1) = " |R|ibs " ' Notice that Ribs, ...
opts$(2) = " |H|ot Wings " ' Hot Wings, ...
opts$(3) = " |S|alad " ' Salad, ...
opts$(4) = " |B|readsticks " ' ...
opts$(5) = " |A|pple Pie " ' ...
opts$(6) = " |C|innamon Sticks " ' ...
opts$(7) = " |K|rispy Kreme Donuts " ' ...
opts$(8) = " |D|eluxe Pepperoni Bread " ' ...
opts$(9) = " |E|ggplant Parmesan " ' ...
opts$(10) = " |F|ettucinni Alfredo Bowl " ' ...
opts$(11) = " |J|uice for the Bambinos " ' ...
opts$(12) = " |W|ine for Padre + Madre " ' All of the same length
opts$(13) = " |Q|uit " ' However, each can have diff bar length like this.
END IF

' Populate the LIGHTBAR options - NOTE: Horizontal options aren't padded.
IF menu.opt_vertical% = 0 THEN ' Horizontal LIGHTBAR menu
opts$(0) = " |P|izza "
opts$(1) = " |R|ibs "
opts$(2) = " |H|ot Wings "
opts$(3) = " |S|alad "
opts$(4) = " |B|readsticks "
opts$(5) = " |A|pple Pie "
opts$(6) = " |C|innamon Sticks "
opts$(7) = " |K|rispy Kreme Donut "
opts$(8) = " |D|eluxe Pepperoni Bread "
opts$(9) = " |E|ggplant Parmesan "
opts$(10) = " |F|ettucinni Alfredo Bowl "
opts$(11) = " |J|uice for the Bambinos "
opts$(12) = " |W|ine for Padre + Madre "
opts$(13) = " |Q|uit "
END IF

' Draw some basic goofy screen under which we will have a LIGHTBAR menu
COLOR 12, 0: PRINT "----------------------------------------"
COLOR 7, 0 : PRINT " Welcome to";
COLOR 12, 0: PRINT " ANTONIOS"; : COLOR 10, 0: PRINT " PIZZERIA!"
COLOR 7, 0 : PRINT " Pick your favorite food from our menu!"
COLOR 14, 0: PRINT "----------------------------------------"
COLOR 2, 0 : PRINT " ..if you're not hungry press ESCAPE.. "
COLOR 12, 0: PRINT "----------------------------------------"
COLOR 9, 0 : PRINT " PRESS ARROWS & HOME/END to choose..."
COLOR 9, 0 : PRINT " PRESS ENTER or HOT KEY to PICK..."
COLOR 9, 0 : PRINT " PRESS ESC to abort!"
PRINT

' Draw the LIGHTBAR menu, and store the result chosen in choice%
choice% = LIGHTBAR%(menu, opts$())

' If user did not press ESC to abort show which option they chose.
IF choice% <> -1 THEN
PRINT
COLOR 11, 0 : PRINT "You chose option ";
COLOR 14, 0 : PRINT UCASE$(_TRIM$(STR$(choice%)));
COLOR 11, 0 : PRINT ": ";
COLOR 12, 0 : PRINT _TRIM$(opts$(choice%))
IF choice% = 0 THEN
COLOR 10, 0 : PRINT "An excellent choice! It is also my favorite!"
END IF
' User pressed ESC to abort, so show something else
ELSE
PRINT
COLOR 3, 0 : PRINT "Not hungry eh? OK you come back later!"
END IF
PRINT
COLOR 12, 0 : PRINT "Thank you! Come again!"



''
' Render a LIGHTBAR menu using options$
' @param LIGHTBAR menu UDT
' @param string array of menu options
' @return integer choice made (-1 if abort with ESC)
'
FUNCTION LIGHTBAR%(menu AS LIGHTBAR, options$())
DIM AS STRING k
DIM AS INTEGER key_pos_s, key_pos_e, key_code
DIM AS INTEGER orig_bg, orig_fg, fg, bg, kf, kb
DIM AS INTEGER row, col, cur_row, cur_col, lb, ub, sel, chose, w, i

' UDT for option data
TYPE LIGHTBAR_OPTION
row AS INTEGER ' Option row
col AS INTEGER ' Option column
lft AS STRING ' Option left side text
key AS STRING ' Option hot key
rgt AS STRING ' Option right side text
len AS INTEGER ' Option length (left + key + right)
sel AS INTEGER ' Is this option selected? 0 = no, 1 = yes
END TYPE

' Define key constants
CONST KEY_ESC = 27
CONST KEY_HOME = 71 : CONST KEY_END = 79
CONST KEY_LEFT = 75 : CONST KEY_RIGHT = 77
CONST KEY_UP = 72 : CONST KEY_DOWN = 80
CONST KEY_ENTER = 13

' Get lower and upper bounds of options array
lb% = LBOUND(options$) : ub% = UBOUND(options$)

' Capture initial state for cursor and colors
row% = CSRLIN : col% = POS(0) ' Store initial cursor position
orig_fg% = SCREEN(row%, col%, 1) AND 15 ' Store initial foreground color
orig_bg% = SCREEN(row%, col%, 1) \ 16 ' Store initial background color
cur_row% = row% : cur_col% = col% ' Init current row and current col
w% = menu.max_width% ' Get the max width for horiz menu
DIM o(lb% TO ub%) AS LIGHTBAR_OPTION

LIGHTBAR_get_options:
FOR i% = lb% to ub%
' Extract hot key start and end positions
key_pos_s% = INSTR(0, options$(i%), menu.delimeter$)
key_pos_e% = INSTR(key_pos_s%, options$(i%), menu.delimeter$)

' Extract left and right part of option without key or delimeter
o(i%).lft$ = MID$(options$(i%), 0, key_pos_s%)
o(i%).rgt$ = MID$(options$(i%), key_pos_s% + 3)

' Capture hot key into arrays
o(i%).key$ = MID$(options$(i%), key_pos_s% + 1, 1)

' Capture visible option length
o(i%).len% = LEN(o(i%).lft$ + o(i%).key$ + o(i%).rgt$)

' Check if option is selected
o(i%).sel% = 0
IF i% = menu.opt_selected% THEN sel% = i% : o(i%).sel% = 1

' Calculate row and col positions for option
IF menu.opt_vertical% = 1 THEN ' Vertical
o(i%).row% = row% + i% ' In vert LIGHTBAR menu, 1 opt per row
o(i%).col% = col% ' In vert LIGHTBAR menu, column is same
ELSE ' Horizontal
IF cur_col% + o(i%).len% >= w% THEN ' Option WILL wrap
o(i%).col% = col% ' Reset col to init col
cur_col% = col% + o(i%).len% ' Reset cur_col counter
cur_row% = cur_row% + 1 ' Increment cur_row
o(i%).row% = cur_row% ' Set row to cur_row
ELSE ' Option will NOT wrap
o(i%).col% = cur_col% ' Set col to current col
o(i%).row% = cur_row% ' Set row to current row
cur_col% = cur_col% + o(i%).len% ' Increment current col
END IF
END IF
NEXT i%

LIGHTBAR_draw:
FOR i% = lb% TO ub% ' Walk the array of menu options
LOCATE o(i%).row%, o(i%).col% ' Position the option
IF i% = sel% THEN ' Selected colors
fg% = menu.bar_fg_color% : bg% = menu.bar_bg_color%
kf% = menu.bar_kf_color% : kb% = menu.bar_kb_color%
ELSE ' Unselected colors
fg% = menu.opt_fg_color% : bg% = menu.opt_bg_color%
kf% = menu.key_fg_color% : kb% = menu.key_bg_color%
END IF
' Draw the option
COLOR fg%, bg% : PRINT o(i%).lft$; ' Draw opt left
COLOR kf%, kb% : PRINT o(i%).key$; ' Draw opt hot key
COLOR fg%, bg% : PRINT o(i%).rgt$; ' Draw opt right
NEXT i%

' Wait for the user to choose an option
LIGHTBAR_get_choice:
DO:
_LIMIT 30
k$ = INKEY$
IF k$ <> "" THEN
IF LEFT$(k$, 1) = CHR$(0) THEN ' Handle SPECIAL keys
key_code% = ASC(RIGHT$(k$, 1)) ' Get char code sans CHR$(0)
SELECT CASE key_code%
CASE KEY_HOME:
sel% = lb%
GOSUB LIGHTBAR_sound_move
GOTO LIGHTBAR_draw
CASE KEY_END:
sel% = ub%
GOSUB LIGHTBAR_sound_move
GOTO LIGHTBAR_draw
CASE KEY_DOWN, KEY_RIGHT:
sel% = sel% + 1
IF sel% > ub% THEN sel% = lb%
GOSUB LIGHTBAR_sound_move
GOTO LIGHTBAR_draw
CASE KEY_UP, KEY_LEFT:
sel% = sel% - 1
IF sel% < lb% THEN sel% = ub%
GOSUB LIGHTBAR_sound_move
GOTO LIGHTBAR_draw
END SELECT
END IF

FOR i% = lb% TO ub% ' Handle option hot keys
IF LCASE$(k$) = LCASE$(o(i%).key$) THEN
sel% = i%
chose% = 1
GOSUB LIGHTBAR_sound_pick
GOTO LIGHTBAR_draw
END IF
NEXT i%

IF k$ = CHR$(KEY_ESC) THEN ' ESCAPE to abort
sel% = -1
END IF

END IF
LOOP UNTIL k$ = CHR$(KEY_ENTER) OR k$ = CHR$(KEY_ESC) OR chose% = 1

' Restore original colors
COLOR orig_fg%, orig_bg%

' Position cursor under menu
IF menu.opt_vertical = 1 THEN
LOCATE row% + (ub% - lb%) + 1, col% ' Vertical
ELSE
LOCATE o(ub%).row% + 1, col% ' Horizontal
END IF

GOSUB LIGHTBAR_sound_pick
LIGHTBAR% = sel%
EXIT FUNCTION

LIGHTBAR_sound_move:
IF menu.use_sounds% = 1 THEN
SOUND menu.snd_move_frq!, menu.snd_move_dur!, menu.snd_move_vol!
END IF
RETURN
LIGHTBAR_sound_pick:
IF menu.use_sounds% = 1 THEN
SOUND menu.snd_pick_frq!, menu.snd_pick_dur!, menu.snd_pick_vol!
END IF
RETURN

END FUNCTION



RE: LIGHTBAR Menu - grymmjack - 08-06-2023

(08-06-2023, 12:23 AM)bplus Wrote: I tried video and turn all volumes to max and still can't hear what is being said.

Can you give quick summary of problem? My little test only found the up down arrow acted like left right, not a big problem may be on purpose.
It's OK, if you look at the screenshot, you can see it's wrapping.

I was caught off guard because I did not realize that PRINT would wrap against the _WIDTH(0). 

I've taken the time to remove the safe_locate and clamp_int kludges, and the code is better for it. Thanks again to @offbyone in discord for the peer review and advice.