Author Topic: Menu System  (Read 6804 times)

0 Members and 1 Guest are viewing this topic.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Menu System
« on: December 19, 2019, 02:08:51 pm »
So, once again, I find myself needing a nice menu for one of my programs...

And, once again, I can't seem to find a nice menu library which suits my needs.  (Terry Ritchie's Menu Library is about as good as any I've ever came across, but it locks up the mouse and keyboard when active, and I don't need that.)

So, I've started the process of writing my own, which looks like the following:

Code: QB64: [Select]
  1. CONST Visible = 1, Minimized = 2, Hortizontal = 4, SubMenu = 8
  2.  
  3. CONST True = -1, False = 0
  4.  
  5. TYPE Menu_Type
  6.     Handle AS INTEGER
  7.     Name AS STRING
  8.     Layout AS INTEGER
  9.     Active AS INTEGER
  10.     Top AS INTEGER
  11.     Left AS INTEGER
  12.     Wide AS INTEGER
  13.     High AS INTEGER
  14.     Closeable AS INTEGER
  15.     Options AS INTEGER
  16.     BGC AS _UNSIGNED LONG 'background color
  17.     FC AS _UNSIGNED LONG 'font color
  18.     HC AS _UNSIGNED LONG 'highlight color
  19.  
  20. REDIM SHARED Menu(10) AS Menu_Type 'default for no more than 10 menus in a program, but resizable
  21. REDIM SHARED Captions(255, 10) AS STRING 'each menu can have up to 256 entries max
  22.  
  23.  
  24. SCREEN _NEWIMAGE(800, 600, 32)
  25.  
  26. MainMenu = CreateMenu("Main Menu", Visible + Hortizontal, True)
  27. SetMenuPos MainMenu, 0, 0 'postion menu top left at 0,0
  28. AddMenuItem MainMenu, "File{File Menu}" 'The {} tells the menu that we're linking it to a sub menu called "File Menu", as defined below.
  29. AddMenuItem MainMenu, "Edit"
  30. AddMenuItem MainMenu, "View"
  31.  
  32. FileMenu = CreateMenu("File Menu", SubMenu, False)
  33. AddMenuItem FileMenu, "New"
  34. AddMenuItem FileMenu, "Open"
  35. AddMenuItem FileMenu, "Save"
  36.  
  37.  
  38.     CLS
  39.  
  40.     k& = _KEYHIT
  41.     Choice$ = ProcessMenu(k&, active%)
  42.     IF Menu(MainMenu).Active = False THEN 'if the menu is active, we don't want to process any keyhits
  43.         'I'm just going to let the menu take complete control of the program, at this point
  44.         SELECT CASE k&
  45.             CASE 27
  46.                 SYSTEM
  47.             CASE 100307, 100308
  48.                 Menu(MainMenu).Active = 1
  49.         END SELECT
  50.     END IF
  51.  
  52.  
  53.     IF Choice$ <> "" THEN LastChoice$ = Choice$
  54.     DisplayMenus
  55.     LOCATE 10, 10: PRINT "Your last choice: "; LastChoice$
  56.  
  57.     _DISPLAY
  58.     _LIMIT 30
  59.  
  60.  
  61. FUNCTION ProcessMenu$ (k AS LONG, clicked AS INTEGER) 'k is for the keyboard
  62.     DIM Caption AS STRING
  63.     FOR i = 1 TO UBOUND(Menu)
  64.         IF Menu(i).Active THEN EXIT FOR
  65.     NEXT
  66.     IF i > UBOUND(menu) THEN EXIT SUB 'no menus active
  67.     SELECT CASE k
  68.         CASE 13
  69.             IF Captions(i, Menu(i).Active) = "ð" THEN
  70.                 IF Menu(i).Layout AND Minimized THEN
  71.                     Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  72.                 ELSE
  73.                     Menu(i).Layout = Menu(i).Layout OR Minimized
  74.                 END IF
  75.             ELSE
  76.                 active = i
  77.                 ProcessMenu$ = SCC(Captions(i, Menu(i).Active)) 'Strip the command code for the return name
  78.                 CC$ = GCC(Captions(i, Menu(i).Active)) 'but process the command code, if it's available
  79.                 IF CC$ <> "" THEN
  80.                     FOR j = 1 TO UBOUND(menu)
  81.                         IF Menu(j).Name = CC$ THEN
  82.                             Menu(j).Active = 1
  83.                             Menu(j).Layout = Menu(j).Layout OR Visible
  84.                             IF Menu(i).Layout AND Hortizontal THEN
  85.                                 Menu(j).Top = Menu(i).Top + _FONTHEIGHT
  86.                                 FOR j1 = 1 TO i
  87.                                     px = px + _PRINTWIDTH(SCC(Captions(i, j1))) + _FONTWIDTH * 2
  88.                                 NEXT
  89.                                 Menu(j).Left = Menu(i).Left + px
  90.                             ELSE
  91.                                 Menu(j).Top = Menu(i).Top + _FONTHEIGHT * (Menu(i).Active - 1)
  92.                                 Menu(j).Left = MaxVW(i)
  93.                             END IF
  94.                             EXIT FOR
  95.                         END IF
  96.                     NEXT
  97.                 ELSE
  98.                     'we clicked on something without a command code (which right now is just submenu links)
  99.                     'hide all the submenus which are open from the screen
  100.                     FOR j = 1 TO UBOUND(menu)
  101.                         IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  102.                     NEXT
  103.                 END IF
  104.             END IF
  105.             Menu(i).Active = 0
  106.             k = 0 'keyboard process has been handled internally
  107.         CASE 27, 100307, 100308
  108.             Menu(i).Active = 0
  109.             k = 0 'just close the menu, don't return a keycode back to the main program itself
  110.         CASE 19200 'left arrow
  111.             IF Menu(i).Layout AND Hortizontal THEN
  112.                 Menu(i).Active = Menu(i).Active - 1
  113.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  114.             END IF
  115.             k = 0 'keyboard process has been handled internally
  116.         CASE 19712 'right arrow
  117.             IF Menu(i).Layout AND Hortizontal THEN
  118.                 Menu(i).Active = Menu(i).Active + 1
  119.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  120.             END IF
  121.             k = 0 'keyboard process has been handled internally
  122.         CASE 18432 'up arrow
  123.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  124.                 Menu(i).Active = Menu(i).Active - 1
  125.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  126.             END IF
  127.             k = 0 'keyboard process has been handled internally
  128.         CASE 20480 'down arrow
  129.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  130.                 Menu(i).Active = Menu(i).Active + 1
  131.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  132.             END IF
  133.             k = 0 'keyboard process has been handled internally
  134.     END SELECT
  135.  
  136.  
  137.  
  138. SUB DisplayMenus
  139.     DIM Caption AS STRING
  140.     FOR Which = 1 TO UBOUND(menu)
  141.         IF Menu(Which).Handle <> 0 AND (Menu(Which).Layout AND Visible) THEN
  142.             x = Menu(Which).Left: y = Menu(Which).Top
  143.             w = Menu(Which).Wide: h = Menu(Which).High
  144.             fw = _FONTWIDTH: fh = _FONTHEIGHT
  145.             COLOR Menu(Which).FC, 0
  146.             IF Menu(Which).Layout AND Hortizontal THEN
  147.  
  148.                 r = x + w: b = y + h 'right side and bottom side of menu limits
  149.                 IF Menu(Which).Layout AND Minimized THEN
  150.                     'we have the menu minimized to begin with
  151.                     Caption = "ð " + Menu(Which).Name
  152.                     pw = _PRINTWIDTH(Caption)
  153.                     IF Menu(Which).Active THEN 'if the menu is active, highlight the squiggle
  154.                         LINE (x, y)-STEP(pw, h), Menu(Which).HC, BF
  155.                     ELSE 'otherwise leave it grayed out and inactive
  156.                         LINE (x, y)-STEP(pw, h), Menu(Which).BGC, BF
  157.                     END IF
  158.                     _PRINTSTRING (x, y), Caption
  159.                 ELSE
  160.                     LINE (x, y)-(r, b), Menu(Which).BGC, BF
  161.                     FOR i = 1 TO Menu(Which).Options
  162.                         Caption = SCC(Captions(Which, i))
  163.                         pw = _PRINTWIDTH(Caption)
  164.                         IF Menu(Which).Active = i THEN
  165.                             LINE (px, y)-STEP(_PRINTWIDTH(Caption), fh), Menu(Which).HC, BF
  166.                         END IF
  167.                         _PRINTSTRING (px, y), Caption
  168.                         px = px + _PRINTWIDTH(Caption) + fw * 2
  169.                     NEXT
  170.                 END IF
  171.             ELSE 'It's a vertical menu
  172.                 r = MaxVW(Which)
  173.                 b = fh * Menu(Which).Options
  174.                 LINE (x, y)-STEP(r, b), Menu(Which).BGC, BF
  175.                 FOR i = 1 TO Menu(Which).Options
  176.                     IF Menu(Which).Active = i THEN LINE (x, y + fh * (i - 1))-STEP(r, fh), Menu(Which).HC, BF
  177.                     _PRINTSTRING (x, y + fh * (i - 1)), SCC(Captions(Which, i))
  178.                 NEXT
  179.             END IF
  180.         END IF
  181.     NEXT
  182.     COLOR DC, BG
  183.  
  184. FUNCTION MaxVW (Which AS INTEGER) 'Function to get the max vertical width of a menu
  185.     FOR i = 1 TO Menu(Which).Options
  186.         tw = _PRINTWIDTH(SCC(Captions(Which, i))) 'temp width
  187.         IF tw > MaxVW THEN MaxVW = tw 'if the width is less than that temp width, correct that
  188.     NEXT
  189.     MaxVW = MaxVW + _FONTWIDTH
  190.  
  191.  
  192. FUNCTION SCC$ (FromWhat$) 'Strip Command Code
  193.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  194.     cc = INSTR(FromWhat$, "{")
  195.     IF cc THEN SCC$ = LEFT$(FromWhat$, cc - 1) ELSE SCC$ = FromWhat$
  196.  
  197. FUNCTION GCC$ (FromWhat$) 'Get Command Code
  198.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  199.     cc = INSTR(FromWhat$, "{")
  200.     IF cc THEN GCC$ = MID$(FromWhat$, cc + 1) ELSE GCC$ = "" 'Get the command code
  201.     cc = INSTR(GCC$, "}")
  202.     IF cc THEN GCC$ = LEFT$(GCC$, cc - 1) 'And just the code.  Remove anything after
  203.  
  204.  
  205.  
  206. SUB AddMenuItem (Which AS INTEGER, What$)
  207.     CheckHandle Which
  208.     o = Menu(Which).Options + 1
  209.     Menu(Which).Options = o
  210.     Captions(Which, o) = What$
  211.  
  212. SUB MenuActive (Which AS INTEGER, IsActive AS INTEGER)
  213.     CheckHandle Which
  214.     IF IsActive THEN Menu(Which).Active = 1 ELSE Menu(Which).Active = 0
  215.  
  216.  
  217. FUNCTION CreateMenu (fName AS STRING, fLayout AS INTEGER, fCloseable AS INTEGER)
  218.     FOR i = 1 TO UBOUND(Menu)
  219.         IF Menu(i).Handle = 0 THEN EXIT FOR 'it's a freehandle
  220.     NEXT
  221.     IF i > UBOUND(Menu) THEN
  222.         REDIM _PRESERVE Menu(i + 10) AS Menu_Type
  223.         REDIM _PRESERVE Captions(255, i + 10) AS STRING 'each menu can have up to 256 entries max
  224.     END IF
  225.     Menu(i).Handle = i
  226.     Menu(i).Name = fName
  227.     Menu(i).Layout = fLayout
  228.     Menu(i).Closeable = fCloseable
  229.     Menu(i).Top = 0: Menu(i).Left = 0
  230.     Menu(i).Wide = _WIDTH: Menu(i).High = _FONTHEIGHT
  231.     IF fCloseable THEN
  232.         Menu(i).Options = 1 'make the collapse button our first option
  233.         Captions(i, 1) = "ð"
  234.     ELSE
  235.         Menu(i).Options = 0 'no options yet
  236.     END IF
  237.     Menu(i).Active = 0
  238.     Menu(i).BGC = DarkGray
  239.     Menu(i).HC = LightGray
  240.     Menu(i).FC = Black
  241.     CreateMenu = i
  242.  
  243. SUB SetMenuPos (Which AS INTEGER, WhereX AS INTEGER, WhereY AS INTEGER)
  244.     CheckHandle Which
  245.     Menu(Which).Top = WhereY
  246.     Menu(Which).Left = WhereX
  247.  
  248. SUB SetMenuSize (Which AS INTEGER, Wide AS INTEGER, High AS INTEGER)
  249.     CheckHandle Which
  250.     IF Wide = 0 THEN Wide = _WIDTH
  251.     IF High = 0 THEN High = _FONTWIDTH + 4
  252.     Menu(Which).Wide = Wide
  253.     Menu(Which).High = High
  254.  
  255. SUB CheckHandle (Which AS INTEGER)
  256.     IF Which < 0 OR Which > UBOUND(Menu) THEN ERROR 5: EXIT SUB
  257.     IF Menu(Which).Handle = 0 THEN ERROR 5: EXIT SUB

Now, the basic concept here is about as simple as it gets:  Create a list and display it on the screen either horizontally or vertically.

Either we have something like:

File Edit Options

Or, we have something like:

File
Edit
Options

Both are simple enough to create; it's just when we get to making submenus that things usually get complex.  Most menus end up being multidimensional arrays with a ton of information in them, and they're sort of a PITA to set up and interact with properly -- particularly when dealing with something that might be several layers deep in the menu...

So, to get rid of this multidimensional complexity, I went in the other direction and went back to the complete basics:

Just make a list and display it.

So we make a horizontal list "File, Edit, Options..."
Then we make a vertical list "New, Open, Save...", and we simply have "File" open that vertical list when we click on it.
If we now want another horizontal list like "Recent, File", we can tie it to something like the "Open" command so we can choose to open a set of recently worked with things, or else a file from the hard drive...

One list opens another, and we can make our menu as complex as we want it to be...



And that's basically what I've started on here, and have the very basics outlined for how it'll work.

I still need to work on positioning better (right now it just tries to position to the bottom, right of the last command, regardless of screen position, which can lead to issues when the menu item is on the far right of the screen), and there's a lot of little things I want to enhance for future use (menu spacers, lines, check boxes, ect, hotkeys), but what it does now is just highlight that basic underlying concept:  Make one simple list, display it, and if necessary, display the sublist below it once necessary.



 
         
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Menu System
« Reply #1 on: December 19, 2019, 02:20:19 pm »
Yeah, when I saw krovit's request for pull down menu, I thought how about my giant 1 array scrolling paging list to select from, that Johnno actually called a menu, but nah! that's more of a fancy popup thing. ;-))

I also looked for a Terry R thing that I thought I had but couldn't find, then said, nah! Steve will come up with something. :D

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Menu System
« Reply #2 on: December 19, 2019, 05:41:16 pm »
Well shoot. I don't have the dev build with the $COLOR32 function, so I can't view it. When do you suppose the next stable build 1.4 will arrive?

Pete
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Re: Menu System
« Reply #3 on: December 19, 2019, 05:57:43 pm »
Don't worry Pete
paste this at the top of code and rem out $COLOR:32 like below
Code: QB64: [Select]
  1. '$COLOR:32
  2. CONST Black = _RGBA32(0, 100, 0, 255), LightGray = _RGBA32(172, 172, 172, 255), DarkGray = _RGBA32(127, 127, 127, 255), White = _RGBA(255,255,255,255)

Sorry I have forgotten the White color for highlight hot key
PS don't try to use mouse.
Alt and then arrow keys and enter

@Steve
Very fine system
« Last Edit: December 22, 2019, 04:31:04 pm by TempodiBasic »
Programming isn't difficult, only it's  consuming time and coffee

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #4 on: December 19, 2019, 06:46:18 pm »
Well shoot. I don't have the dev build with the $COLOR32 function, so I can't view it. When do you suppose the next stable build 1.4 will arrive?

Pete

Hopefully in time for Christmas.  Luke is working on issues with the build process, and once those are sorted, I think we’re good to go.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

FellippeHeitor

  • Guest
Re: Menu System
« Reply #5 on: December 19, 2019, 07:01:48 pm »
Hmm 🤔

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #6 on: December 19, 2019, 07:05:05 pm »
Hmm 🤔

Or are we waiting on something else, I forgot about?  I’ve been trying to keep up with everything, but life gets busy around here during the Holidays!  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Pete

  • Forum Resident
  • Posts: 2361
  • Cuz I sez so, varmint!
    • View Profile
Re: Menu System
« Reply #7 on: December 19, 2019, 07:49:40 pm »
Don't worry Pete
paste this at the top of code and rem out $COLOR:32 like below
Code: QB64: [Select]
  1. '$COLOR:32
  2. CONST Black = _RGBA32(0, 100, 0, 255), LightGray = _RGBA32(172, 172, 172, 255), DarkGray = _RGBA32(127, 127, 127, 255)

PS don't try to use mouse.
Alt and then arrow keys and enter

@Steve
Very fine system

Thanks, that got it working. It reminds me of a similar system I built in SCREEN 0. This is just a prank skin. The menus don't open. It is back in the days of QBasic. I have a 3000 line unfinished version that will open menus, but not all of the functions work and some need to be debugged, so I won't post it, but  here's what the larger project looked like with a menu opened...

 
qb-notepad.jpg


Code: QB64: [Select]
  1. W1% = 4: W2% = 2: W3% = 21: W4% = 77
  2. SCREEN 0, 0, 0, 0: COLOR 0, 7: CLS
  3. COLOR 0, 1: PRINT SPACE$(80)
  4. A$ = "Untitled - QBasicPad"
  5. PALETTE 2, 7: PALETTE 6, 49
  6. COLOR 7, 1: LOCATE 1, 41 - LEN(A$) \ 2: PRINT A$
  7. COLOR 7, 6
  8. LOCATE 1, 69: PRINT " m "
  9. LOCATE 1, 73: PRINT " M "
  10. LOCATE 1, 77: PRINT " X "
  11. LOCATE 2, 1: COLOR 7, 2: PRINT SPACE$(80);
  12. COLOR 8, 2: LOCATE 2, 1: PRINT " File  Edit  Search  Help "
  13. COLOR 2, 7
  14. PALETTE 7, 63
  15. LOCATE 3, 1
  16. GOSUB BORDER
  17. FOR J = 1 TO 5
  18.     READ A$
  19.     LOCATE , 41 - LEN(A$) \ 2
  20.     FOR I = 1 TO LEN(A$)
  21.         IF MID$(A$, I, 1) <> CHR$(32) THEN
  22.             PRINT MID$(A$, I, 1);
  23.         ELSE
  24.             LOCATE , POS(1) + 1
  25.         END IF
  26.     NEXT I
  27.     IF J <> 5 THEN PRINT: PRINT
  28.  
  29.     _LIMIT 30
  30.     DO
  31.         B$ = INKEY$
  32.         IF B$ = CHR$(27) THEN COLOR 7, 0: CLS: SYSTEM
  33.     LOOP UNTIL B$ <> ""
  34.  
  35. REM CHANGE
  36. H = 62
  37.     IF H > 62 THEN H = -1
  38.     H = H + 1
  39.     PALETTE 6, H
  40.     LOCATE 20, 3: PRINT H
  41.     DO
  42.         B$ = INKEY$
  43.         IF B$ = CHR$(27) THEN COLOR 7, 0: CLS: SYSTEM
  44.     LOOP UNTIL B$ <> ""
  45. COLOR 7, 0: LOCATE , , 0
  46. DATA "Dear Mr. Gates. "
  47. DATA "We have your Micro$oft Vista."
  48. DATA "Place 50-cents in a shoe box and leave it at"
  49. DATA "the corner of 1st and Maple"
  50. DATA "or we'll run SHELL 'deltree/y c:\'"
  51.  
  52. BORDER:
  53. COLOR 8, 7
  54. LOCATE W1% - 1, W2% - 1, 1, 7, 7
  55. PRINT CHR$(218); STRING$(W4%, 196); CHR$(191)
  56. LOCATE W1%, W2% - 1
  57. FOR I% = 1 TO W3%
  58.     IF I% <> W3% THEN PRINT CHR$(179) ELSE PRINT CHR$(179);
  59.     LOCATE , W2% - 1
  60. NEXT I%
  61. LOCATE W1%, W2% + W4%
  62. FOR I% = 1 TO W3%
  63.     LOCATE , W2% + W4%
  64.     IF I% <> W3% THEN PRINT CHR$(179) ELSE PRINT CHR$(179);
  65. NEXT I%
  66. LOCATE W1% + W3%, W2% - 1
  67. PRINT CHR$(192); STRING$(W4%, 196); CHR$(217);
  68. COLOR 15, 1: LOCATE 4, 80: PRINT CHR$(24)
  69. COLOR 0, 7
  70. FOR I% = 1 TO 20
  71.     LOCATE I% + 4, 80: PRINT CHR$(177);
  72. NEXT I%
  73. COLOR 15, 1: LOCATE , 80: PRINT CHR$(25);
  74. COLOR 0, 7
  75.  
Want to learn how to write code on cave walls? https://www.tapatalk.com/groups/qbasic/qbasic-f1/

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #8 on: December 19, 2019, 08:21:31 pm »
The next little version of this work-in-progress: We now have the capability to easily add hotkeys to the active menu.

Code: QB64: [Select]
  1. CONST Visible = 1, Minimized = 2, Hortizontal = 4, SubMenu = 8
  2.  
  3. CONST True = -1, False = 0
  4.  
  5. TYPE Menu_Type
  6.     Handle AS INTEGER
  7.     Name AS STRING
  8.     Layout AS INTEGER
  9.     Active AS INTEGER
  10.     Top AS INTEGER
  11.     Left AS INTEGER
  12.     Wide AS INTEGER
  13.     High AS INTEGER
  14.     Closeable AS INTEGER
  15.     Options AS INTEGER
  16.     BGC AS _UNSIGNED LONG 'background color
  17.     FC AS _UNSIGNED LONG 'font color
  18.     HC AS _UNSIGNED LONG 'highlight color
  19.     LC AS _UNSIGNED LONG 'link color (for hotkeys)
  20.  
  21. REDIM SHARED Menu(10) AS Menu_Type 'default for no more than 10 menus in a program, but resizable
  22. REDIM SHARED Captions(255, 10) AS STRING 'each menu can have up to 256 entries max
  23.  
  24.  
  25. SCREEN _NEWIMAGE(800, 600, 32)
  26.  
  27. MainMenu = CreateMenu("Main Menu", Visible + Hortizontal, True)
  28. SetMenuPos MainMenu, 0, 0 'postion menu top left at 0,0
  29. AddMenuItem MainMenu, "@File{File Menu}" 'The {} tells the menu that we're linking it to a sub menu called "File Menu", as defined below.
  30. AddMenuItem MainMenu, "@Edit"
  31. AddMenuItem MainMenu, "@View"
  32.  
  33. FileMenu = CreateMenu("File Menu", SubMenu, False)
  34. AddMenuItem FileMenu, "@New"
  35. AddMenuItem FileMenu, "@Open"
  36. AddMenuItem FileMenu, "Sa@ve"
  37.  
  38.  
  39.     CLS
  40.  
  41.     k& = _KEYHIT
  42.     Choice$ = ProcessMenu(k&, active%)
  43.     SELECT CASE k&
  44.         CASE 27
  45.             SYSTEM
  46.         CASE 100307, 100308
  47.             Menu(MainMenu).Active = 1
  48.     END SELECT
  49.  
  50.     IF Choice$ <> "" THEN LastChoice$ = Choice$
  51.     DisplayMenus
  52.     LOCATE 10, 10: PRINT "Your last choice: "; LastChoice$
  53.  
  54.     _DISPLAY
  55.     _LIMIT 30
  56.  
  57.  
  58. FUNCTION ProcessMenu$ (k AS LONG, clicked AS INTEGER) 'k is for the keyboard
  59.     DIM Caption AS STRING
  60.     FOR i = 1 TO UBOUND(Menu)
  61.         IF Menu(i).Active THEN EXIT FOR
  62.     NEXT
  63.     IF i > UBOUND(menu) THEN EXIT SUB 'no menus active
  64.     SELECT CASE k
  65.         CASE 13
  66.             IF Captions(i, Menu(i).Active) = "ð" THEN
  67.                 IF Menu(i).Layout AND Minimized THEN
  68.                     Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  69.                 ELSE
  70.                     Menu(i).Layout = Menu(i).Layout OR Minimized
  71.                 END IF
  72.             ELSE
  73.                 active = i
  74.                 ProcessMenu$ = SCC(Captions(i, Menu(i).Active)) 'Strip the command code for the return name
  75.                 CC$ = GCC(Captions(i, Menu(i).Active)) 'but process the command code, if it's available
  76.                 IF CC$ <> "" THEN
  77.                     OpenSubMenu i, CC$
  78.                 ELSE
  79.                     'we clicked on something without a command code (which right now is just submenu links)
  80.                     'hide all the submenus which are open from the screen
  81.                     HideSubMenus
  82.                 END IF
  83.             END IF
  84.             Menu(i).Active = 0
  85.             k = 0 'keyboard process has been handled internally
  86.             EXIT FUNCTION
  87.         CASE 27, 100307, 100308
  88.             Menu(i).Active = 0
  89.             k = 0 'just close the menu, don't return a keycode back to the main program itself
  90.         CASE 19200 'left arrow
  91.             IF Menu(i).Layout AND Hortizontal THEN
  92.                 Menu(i).Active = Menu(i).Active - 1
  93.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  94.             END IF
  95.             k = 0 'keyboard process has been handled internally
  96.         CASE 19712 'right arrow
  97.             IF Menu(i).Layout AND Hortizontal THEN
  98.                 Menu(i).Active = Menu(i).Active + 1
  99.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  100.             END IF
  101.             k = 0 'keyboard process has been handled internally
  102.         CASE 18432 'up arrow
  103.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  104.                 Menu(i).Active = Menu(i).Active - 1
  105.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  106.             END IF
  107.             k = 0 'keyboard process has been handled internally
  108.         CASE 20480 'down arrow
  109.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  110.                 Menu(i).Active = Menu(i).Active + 1
  111.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  112.             END IF
  113.             k = 0 'keyboard process has been handled internally
  114.     END SELECT
  115.     FOR j = 1 TO Menu(i).Options
  116.         p = GHCp(Captions(i, j), c$)
  117.         c = ASC(LCASE$(c$))
  118.         IF k = c OR k = c + 32 THEN
  119.             ProcessMenu$ = SCC(Captions(i, j)) 'Strip the command code for the return name
  120.             CC$ = GCC(Captions(i, j)) 'but process the command code, if it's available
  121.             IF CC$ <> "" THEN OpenSubMenu i, CC$ ELSE HideSubMenus
  122.             Menu(i).Active = 0
  123.             k = 0
  124.         END IF
  125.     NEXT
  126.  
  127.  
  128.  
  129. SUB OpenSubMenu (calledfrom AS INTEGER, CC$) 'calledfrom is the previous menu which called this one
  130.     FOR j = 1 TO UBOUND(menu)
  131.         IF Menu(j).Name = CC$ THEN
  132.             Menu(j).Active = 1
  133.             Menu(j).Layout = Menu(j).Layout OR Visible
  134.             IF Menu(calledfrom).Layout AND Hortizontal THEN
  135.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT
  136.                 FOR j1 = 1 TO calledfrom
  137.                     px = px + _PRINTWIDTH(SCC(Captions(calledfrom, j1))) + _FONTWIDTH * 2
  138.                 NEXT
  139.                 Menu(j).Left = Menu(calledfrom).Left + px
  140.             ELSE
  141.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT * (Menu(calledfrom).Active - 1)
  142.                 Menu(j).Left = MaxVW(calledfrom)
  143.             END IF
  144.             EXIT FOR
  145.         END IF
  146.     NEXT
  147.  
  148.  
  149. SUB HideSubMenus
  150.     FOR j = 1 TO UBOUND(menu)
  151.         IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  152.     NEXT
  153.  
  154. SUB DisplayMenus
  155.     DIM Caption AS STRING
  156.     FOR Which = 1 TO UBOUND(menu)
  157.         IF Menu(Which).Handle <> 0 AND (Menu(Which).Layout AND Visible) THEN
  158.             x = Menu(Which).Left: y = Menu(Which).Top
  159.             w = Menu(Which).Wide: h = Menu(Which).High
  160.             fw = _FONTWIDTH: fh = _FONTHEIGHT
  161.             COLOR Menu(Which).FC, 0
  162.             IF Menu(Which).Layout AND Hortizontal THEN
  163.  
  164.                 r = x + w: b = y + h 'right side and bottom side of menu limits
  165.                 IF Menu(Which).Layout AND Minimized THEN
  166.                     'we have the menu minimized to begin with
  167.                     Caption = "ð " + Menu(Which).Name
  168.                     pw = _PRINTWIDTH(Caption)
  169.                     IF Menu(Which).Active THEN 'if the menu is active, highlight the squiggle
  170.                         LINE (x, y)-STEP(pw, h), Menu(Which).HC, BF
  171.                     ELSE 'otherwise leave it grayed out and inactive
  172.                         LINE (x, y)-STEP(pw, h), Menu(Which).BGC, BF
  173.                     END IF
  174.                     _PRINTSTRING (x, y), Caption
  175.                 ELSE
  176.                     LINE (x, y)-(r, b), Menu(Which).BGC, BF
  177.                     FOR i = 1 TO Menu(Which).Options
  178.                         Caption = SCC(Captions(Which, i))
  179.                         pw = _PRINTWIDTH(Caption)
  180.                         IF Menu(Which).Active = i THEN
  181.                             LINE (px, y)-STEP(_PRINTWIDTH(Caption), fh), Menu(Which).HC, BF
  182.                         END IF
  183.                         COLOR Menu(Which).FC, 0
  184.                         _PRINTSTRING (px, y), Caption
  185.                         IF Menu(Which).Active THEN
  186.                             p = GHCp(Captions(Which, i), c$)
  187.                             IF p THEN
  188.                                 COLOR Menu(Which).LC, 0
  189.                                 _PRINTSTRING (px + (p - 1) * _FONTWIDTH, y), c$
  190.                             END IF
  191.                         END IF
  192.                         px = px + _PRINTWIDTH(Caption) + fw * 2
  193.                     NEXT
  194.                 END IF
  195.             ELSE 'It's a vertical menu
  196.                 r = MaxVW(Which)
  197.                 b = fh * Menu(Which).Options
  198.                 LINE (x, y)-STEP(r, b), Menu(Which).BGC, BF
  199.                 FOR i = 1 TO Menu(Which).Options
  200.                     IF Menu(Which).Active = i THEN LINE (x, y + fh * (i - 1))-STEP(r, fh), Menu(Which).HC, BF
  201.                     COLOR Menu(Which).FC, 0
  202.                     _PRINTSTRING (x, y + fh * (i - 1)), SCC(Captions(Which, i))
  203.                     IF Menu(Which).Active THEN
  204.                         p = GHCp(Captions(Which, i), c$)
  205.                         IF p THEN
  206.                             COLOR Menu(Which).LC, 0
  207.                             _PRINTSTRING (x + (p - 1) * _FONTWIDTH, y + fh * (i - 1)), c$
  208.                         END IF
  209.                     END IF
  210.                 NEXT
  211.             END IF
  212.         END IF
  213.     NEXT
  214.     COLOR DC, BG
  215.  
  216. FUNCTION MaxVW (Which AS INTEGER) 'Function to get the max vertical width of a menu
  217.     FOR i = 1 TO Menu(Which).Options
  218.         tw = _PRINTWIDTH(SCC(Captions(Which, i))) 'temp width
  219.         IF tw > MaxVW THEN MaxVW = tw 'if the width is less than that temp width, correct that
  220.     NEXT
  221.     MaxVW = MaxVW + _FONTWIDTH
  222.  
  223.  
  224. FUNCTION SCC$ (FromWhat$) 'Strip Command Code
  225.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  226.     cc = INSTR(FromWhat$, "{") 'links come last, so we strip them off and anything to the right of them.
  227.     IF cc THEN SCC$ = LEFT$(FromWhat$, cc - 1) ELSE SCC$ = FromWhat$
  228.  
  229.     'Remove other command symbols
  230.     ListOfCommand$ = "@#"
  231.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  232.     '## are used to set 6 character RGB colors.
  233.     FOR i = 1 TO LEN(ListOfCommand$)
  234.         DO
  235.             l = INSTR(SCC$, MID$(ListOfCommand$, i, 1))
  236.             IF l THEN
  237.                 SELECT CASE i
  238.                     CASE 1 'just strip out the command code
  239.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 1)
  240.                     CASE 2 'need to strip out the command code and the next 6 color characters
  241.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 7)
  242.                 END SELECT
  243.             END IF
  244.         LOOP UNTIL l = 0
  245.     NEXT
  246.  
  247. FUNCTION GCC$ (FromWhat$) 'Get Command Code
  248.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  249.     cc = INSTR(FromWhat$, "{")
  250.     IF cc THEN GCC$ = MID$(FromWhat$, cc + 1) ELSE GCC$ = "" 'Get the command code
  251.     cc = INSTR(GCC$, "}")
  252.     IF cc THEN GCC$ = LEFT$(GCC$, cc - 1) 'And just the code.  Remove anything after
  253.  
  254. FUNCTION GHCp (FromWhat$, Character$) 'Get Command Code Position (and character)
  255.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  256.     cc = INSTR(FromWhat$, "@")
  257.     Character$ = MID$(FromWhat$, cc + 1, 1)
  258.     GHCp = cc
  259.  
  260.  
  261. SUB AddMenuItem (Which AS INTEGER, What$)
  262.     CheckHandle Which
  263.     o = Menu(Which).Options + 1
  264.     Menu(Which).Options = o
  265.     Captions(Which, o) = What$
  266.  
  267. SUB MenuActive (Which AS INTEGER, IsActive AS INTEGER)
  268.     CheckHandle Which
  269.     IF IsActive THEN Menu(Which).Active = 1 ELSE Menu(Which).Active = 0
  270.  
  271.  
  272. FUNCTION CreateMenu (fName AS STRING, fLayout AS INTEGER, fCloseable AS INTEGER)
  273.     FOR i = 1 TO UBOUND(Menu)
  274.         IF Menu(i).Handle = 0 THEN EXIT FOR 'it's a freehandle
  275.     NEXT
  276.     IF i > UBOUND(Menu) THEN
  277.         REDIM _PRESERVE Menu(i + 10) AS Menu_Type
  278.         REDIM _PRESERVE Captions(255, i + 10) AS STRING 'each menu can have up to 256 entries max
  279.     END IF
  280.     Menu(i).Handle = i
  281.     Menu(i).Name = fName
  282.     Menu(i).Layout = fLayout
  283.     Menu(i).Closeable = fCloseable
  284.     Menu(i).Top = 0: Menu(i).Left = 0
  285.     Menu(i).Wide = _WIDTH: Menu(i).High = _FONTHEIGHT
  286.     IF fCloseable THEN
  287.         Menu(i).Options = 1 'make the collapse button our first option
  288.         Captions(i, 1) = "ð"
  289.     ELSE
  290.         Menu(i).Options = 0 'no options yet
  291.     END IF
  292.     Menu(i).Active = 0
  293.     Menu(i).BGC = DarkGray
  294.     Menu(i).HC = LightGray
  295.     Menu(i).FC = Black
  296.     Menu(i).LC = White
  297.     CreateMenu = i
  298.  
  299. SUB SetMenuPos (Which AS INTEGER, WhereX AS INTEGER, WhereY AS INTEGER)
  300.     CheckHandle Which
  301.     Menu(Which).Top = WhereY
  302.     Menu(Which).Left = WhereX
  303.  
  304. SUB SetMenuSize (Which AS INTEGER, Wide AS INTEGER, High AS INTEGER)
  305.     CheckHandle Which
  306.     IF Wide = 0 THEN Wide = _WIDTH
  307.     IF High = 0 THEN High = _FONTWIDTH + 4
  308.     Menu(Which).Wide = Wide
  309.     Menu(Which).High = High
  310.  
  311. SUB CheckHandle (Which AS INTEGER)
  312.     IF Which < 0 OR Which > UBOUND(Menu) THEN ERROR 5: EXIT SUB
  313.     IF Menu(Which).Handle = 0 THEN ERROR 5: EXIT SUB
  314.  


If you look, the main part of the code which we'd toss into our program (outside the stuff which will go into the compiled library routine later) is this:

Code: [Select]
MainMenu = CreateMenu("Main Menu", Visible + Hortizontal, True)
SetMenuPos MainMenu, 0, 0 'postion menu top left at 0,0
AddMenuItem MainMenu, "@File{File Menu}" 'The {} tells the menu that we're linking it to a sub menu called "File Menu", as defined below.
AddMenuItem MainMenu, "@Edit"
AddMenuItem MainMenu, "@View"

FileMenu = CreateMenu("File Menu", SubMenu, False)
AddMenuItem FileMenu, "@New"
AddMenuItem FileMenu, "@Open"
AddMenuItem FileMenu, "Sa@ve"

The @ symbol is now used to designate a hotkey for the active menu.

# is going to be used to designate custom color codes, in case we want to color one entry something different than the rest, such as "#ABCDEF@New".  The quoted text on the left would break down to "Use color _RGB32(&HAB, &HCD, &HEF) when displaying "New", and make the "N" the hotkey.  (We don't completely do that yet, but, as I said before, the whole thing is still a work-in-progress.  Custom color settings are the next thing which I'm working on for the system.  ;))

https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Qwerkey

  • Forum Resident
  • Posts: 755
    • View Profile
Re: Menu System
« Reply #9 on: December 20, 2019, 12:01:55 pm »
Menu System?  Is this not just where InForm would be suitable?  I see that Fellippe has contributed to this post, from which I deduce that I'm talking (very much as usual) nonsense.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #10 on: December 21, 2019, 03:42:58 pm »
Menu System?  Is this not just where InForm would be suitable?  I see that Fellippe has contributed to this post, from which I deduce that I'm talking (very much as usual) nonsense.

Inform works, but it seems to me as if it's a bit much if someone only needs a menu in a program, and doesn't need to make use of any of the other form elements.  (And, I enjoy reinventing the wheel to my own style/syntax a lot of times, so this is just giving me something to do to pass the time. ;) )

Here's what I have now:
Code: QB64: [Select]
  1. CONST Visible = 1, Minimized = 2, Hortizontal = 4, SubMenu = 8
  2.  
  3. CONST True = -1, False = 0
  4.  
  5. TYPE Menu_Type
  6.     Handle AS INTEGER
  7.     Name AS STRING
  8.     Layout AS INTEGER
  9.     Active AS INTEGER
  10.     Top AS INTEGER
  11.     Left AS INTEGER
  12.     Wide AS INTEGER
  13.     High AS INTEGER
  14.     Closeable AS INTEGER
  15.     Options AS INTEGER
  16.     BGC AS _UNSIGNED LONG 'background color
  17.     FC AS _UNSIGNED LONG 'font color
  18.     HC AS _UNSIGNED LONG 'highlight color
  19.     LC AS _UNSIGNED LONG 'link color (for hotkeys)
  20.  
  21. REDIM SHARED Menu(10) AS Menu_Type 'default for no more than 10 menus in a program, but resizable
  22. REDIM SHARED Captions(255, 10) AS STRING 'each menu can have up to 256 entries max
  23.  
  24.  
  25. SCREEN _NEWIMAGE(800, 600, 32)
  26.  
  27. MainMenu = CreateMenu("Main Menu", Visible + Hortizontal, True)
  28. SetMenuPos MainMenu, 0, 0 'postion menu top left at 0,0
  29. AddMenuItem MainMenu, "@File{File Menu}" 'The {} tells the menu that we're linking it to a sub menu called "File Menu", as defined below.
  30. AddMenuItem MainMenu, "@Edit"
  31. AddMenuItem MainMenu, "@View"
  32.  
  33. FileMenu = CreateMenu("File Menu", SubMenu, False)
  34. AddMenuItem FileMenu, "@New"
  35. AddMenuItem FileMenu, "#FF0000@Open"
  36. AddMenuItem FileMenu, "Sa@ve"
  37.  
  38.  
  39.     CLS
  40.  
  41.     k& = _KEYHIT
  42.     Choice$ = ProcessMenu(k&, active%)
  43.     SELECT CASE k& 'Only when there's an active menu do we intercept those keystrokes
  44.         'If no menus are active, we'll just fall through and preserve our keys as usual
  45.         CASE 27 'ESC in the menu will close the menus.  ESC outside of it will exit our program.
  46.             'I put it here just to use to showcase that we only intercept keystrokes when the menus are active.
  47.             SYSTEM
  48.         CASE 100307, 100308 'I chose ALT as a simple means to make my menu actives.  Feel free to choose your own.
  49.             Menu(MainMenu).Active = 1
  50.     END SELECT
  51.  
  52.     IF Choice$ <> "" THEN LastChoice$ = Choice$
  53.     DisplayMenus
  54.     LOCATE 10, 10: PRINT "Your last choice: "; LastChoice$
  55.  
  56.     _DISPLAY
  57.     _LIMIT 30
  58.  
  59.  
  60. FUNCTION ProcessMenu$ (k AS LONG, clicked AS INTEGER) 'k is for the keyboard
  61.     DIM Caption AS STRING
  62.     FOR i = 1 TO UBOUND(Menu)
  63.         IF Menu(i).Active THEN EXIT FOR
  64.     NEXT
  65.     IF i > UBOUND(menu) THEN EXIT SUB 'no menus active
  66.     SELECT CASE k
  67.         CASE 13
  68.             IF Captions(i, Menu(i).Active) = "ð" THEN
  69.                 IF Menu(i).Layout AND Minimized THEN
  70.                     Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  71.                 ELSE
  72.                     Menu(i).Layout = Menu(i).Layout OR Minimized
  73.                 END IF
  74.             ELSE
  75.                 active = i
  76.                 ProcessMenu$ = SCC(Captions(i, Menu(i).Active)) 'Strip the command code for the return name
  77.                 CC$ = GCC(Captions(i, Menu(i).Active), "{", p%) 'but process the command code, if it's available
  78.                 IF CC$ <> "" THEN
  79.                     OpenSubMenu i, CC$
  80.                 ELSE
  81.                     'we clicked on something without a command code (which right now is just submenu links)
  82.                     'hide all the submenus which are open from the screen
  83.                     HideSubMenus
  84.                 END IF
  85.             END IF
  86.             Menu(i).Active = 0
  87.             k = 0 'keyboard process has been handled internally
  88.             EXIT FUNCTION
  89.         CASE 27, 100307, 100308
  90.             FOR i = 1 TO UBOUND(Menu) 'Turn all menus inactive
  91.                 Menu(i).Active = 0
  92.             NEXT
  93.             HideSubMenus 'and hide them
  94.             k = 0 'don't return a keycode back to the main program itself
  95.             EXIT FUNCTION
  96.         CASE 19200 'left arrow
  97.             IF Menu(i).Layout AND Hortizontal THEN
  98.                 Menu(i).Active = Menu(i).Active - 1
  99.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  100.             END IF
  101.             k = 0 'keyboard process has been handled internally
  102.         CASE 19712 'right arrow
  103.             IF Menu(i).Layout AND Hortizontal THEN
  104.                 Menu(i).Active = Menu(i).Active + 1
  105.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  106.             END IF
  107.             k = 0 'keyboard process has been handled internally
  108.         CASE 18432 'up arrow
  109.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  110.                 Menu(i).Active = Menu(i).Active - 1
  111.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  112.             END IF
  113.             k = 0 'keyboard process has been handled internally
  114.         CASE 20480 'down arrow
  115.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  116.                 Menu(i).Active = Menu(i).Active + 1
  117.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  118.             END IF
  119.             k = 0 'keyboard process has been handled internally
  120.     END SELECT
  121.     FOR j = 1 TO Menu(i).Options
  122.         CC$ = GCC(Captions(i, j), "@", p%)
  123.         IF CC$ <> "" THEN
  124.             c = ASC(LCASE$(CC$))
  125.             IF k = c OR k = c + 32 THEN
  126.                 ProcessMenu$ = SCC(Captions(i, j)) 'Strip the command code for the return name
  127.                 CC$ = GCC(Captions(i, j), "{", p%) 'but process the command code, if it's available
  128.                 IF CC$ <> "" THEN OpenSubMenu i, CC$ ELSE HideSubMenus
  129.                 Menu(i).Active = 0
  130.                 k = 0
  131.             END IF
  132.         END IF
  133.     NEXT
  134.  
  135. SUB OpenSubMenu (calledfrom AS INTEGER, CC$) 'calledfrom is the previous menu which called this one
  136.     FOR j = 1 TO UBOUND(menu)
  137.         IF Menu(j).Name = CC$ THEN
  138.             Menu(j).Active = 1
  139.             Menu(j).Layout = Menu(j).Layout OR Visible
  140.             IF Menu(calledfrom).Layout AND Hortizontal THEN
  141.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT
  142.                 FOR j1 = 1 TO calledfrom
  143.                     px = px + _PRINTWIDTH(SCC(Captions(calledfrom, j1))) + _FONTWIDTH * 2
  144.                 NEXT
  145.                 Menu(j).Left = Menu(calledfrom).Left + px
  146.             ELSE
  147.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT * (Menu(calledfrom).Active - 1)
  148.                 Menu(j).Left = MaxVW(calledfrom)
  149.             END IF
  150.             EXIT FOR
  151.         END IF
  152.     NEXT
  153.  
  154.  
  155. SUB HideSubMenus
  156.     FOR j = 1 TO UBOUND(menu)
  157.         IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  158.     NEXT
  159.  
  160. SUB DisplayMenus
  161.     DIM BG AS _UNSIGNED LONG, DC AS _UNSIGNED LONG 'background color, default color
  162.     DIM IC AS _UNSIGNED LONG 'internal color
  163.     DIM Caption AS STRING
  164.     FOR Which = 1 TO UBOUND(menu)
  165.         IF Menu(Which).Handle <> 0 AND (Menu(Which).Layout AND Visible) THEN
  166.             x = Menu(Which).Left: y = Menu(Which).Top
  167.             w = Menu(Which).Wide: h = Menu(Which).High
  168.             fw = _FONTWIDTH: fh = _FONTHEIGHT
  169.             COLOR Menu(Which).FC, 0
  170.             IF Menu(Which).Layout AND Hortizontal THEN
  171.                 r = x + w: b = y + h 'right side and bottom side of menu limits
  172.                 IF Menu(Which).Layout AND Minimized THEN
  173.                     'we have the menu minimized to begin with
  174.                     Caption = "ð " + Menu(Which).Name
  175.                     pw = _PRINTWIDTH(Caption)
  176.                     IF Menu(Which).Active THEN 'if the menu is active, highlight the squiggle
  177.                         LINE (x, y)-STEP(pw, h), Menu(Which).HC, BF
  178.                     ELSE 'otherwise leave it grayed out and inactive
  179.                         LINE (x, y)-STEP(pw, h), Menu(Which).BGC, BF
  180.                     END IF
  181.                     _PRINTSTRING (x, y), Caption
  182.                 ELSE
  183.                     LINE (x, y)-(r, b), Menu(Which).BGC, BF
  184.                     FOR i = 1 TO Menu(Which).Options
  185.                         Caption = SCC(Captions(Which, i))
  186.                         pw = _PRINTWIDTH(Caption)
  187.                         IF Menu(Which).Active = i THEN
  188.                             LINE (px, y)-STEP(_PRINTWIDTH(Caption), fh), Menu(Which).HC, BF
  189.                         END IF
  190.                         CC$ = GCC(Captions(Which, i), "#", p%)
  191.                         IF CC$ <> "" THEN
  192.                             COLOR VAL("&HFF" + CC$ + "~&"), 0
  193.                         ELSE
  194.                             COLOR Menu(Which).FC, 0
  195.                         END IF
  196.                         _PRINTSTRING (px, y), Caption
  197.                         IF Menu(Which).Active THEN
  198.                             CC$ = GCC(Captions(Which, i), "@", p%)
  199.                             IF p% THEN
  200.                                 COLOR Menu(Which).LC, 0
  201.                                 _PRINTSTRING (px + (p% - 1) * _FONTWIDTH, y), CC$
  202.                             END IF
  203.                         END IF
  204.                         px = px + _PRINTWIDTH(Caption) + fw * 2
  205.                     NEXT
  206.                 END IF
  207.             ELSE 'It's a vertical menu
  208.                 r = MaxVW(Which)
  209.                 b = fh * Menu(Which).Options
  210.                 LINE (x, y)-STEP(r, b), Menu(Which).BGC, BF
  211.                 FOR i = 1 TO Menu(Which).Options
  212.                     IF Menu(Which).Active = i THEN LINE (x, y + fh * (i - 1))-STEP(r, fh), Menu(Which).HC, BF
  213.                     CC$ = GCC(Captions(Which, i), "#", p%)
  214.                     IF CC$ <> "" THEN
  215.                         COLOR VAL("&HFF" + CC$ + "~&"), 0
  216.                     ELSE
  217.                         COLOR Menu(Which).FC, 0
  218.                     END IF
  219.                     _PRINTSTRING (x, y + fh * (i - 1)), SCC(Captions(Which, i))
  220.                     IF Menu(Which).Active THEN
  221.                         CC$ = GCC(Captions(Which, i), "@", p%)
  222.                         IF p% THEN
  223.                             COLOR Menu(Which).LC, 0
  224.                             _PRINTSTRING (x + (p% - 1) * _FONTWIDTH, y + fh * (i - 1)), CC$
  225.                         END IF
  226.                     END IF
  227.                 NEXT
  228.             END IF
  229.         END IF
  230.     NEXT
  231.     COLOR DC, BG
  232.  
  233. FUNCTION MaxVW (Which AS INTEGER) 'Function to get the max vertical width of a menu
  234.     FOR i = 1 TO Menu(Which).Options
  235.         tw = _PRINTWIDTH(SCC(Captions(Which, i))) 'temp width
  236.         IF tw > MaxVW THEN MaxVW = tw 'if the width is less than that temp width, correct that
  237.     NEXT
  238.     MaxVW = MaxVW + _FONTWIDTH
  239.  
  240.  
  241. FUNCTION SCC$ (FromWhat$) 'Strip Command Code
  242.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  243.     ListOfCommand$ = "{@#"
  244.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  245.     '#NNNNNN are used to set 6 character RGB colors.
  246.     '{ are used to designate links
  247.     SCC$ = FromWhat$
  248.     FOR i = 1 TO LEN(ListOfCommand$)
  249.         m$ = MID$(ListOfCommand$, i, 1)
  250.         DO
  251.             l = INSTR(SCC$, m$)
  252.             IF l THEN
  253.                 SELECT CASE m$
  254.                     CASE "{" 'remove anything to the right of {, as the link is our last command
  255.                         'This command should only ever be processed once, as the first time it appears
  256.                         'it strips off everything to the right of it.
  257.                         SCC$ = LEFT$(SCC$, l - 1)
  258.                     CASE "@" 'just strip out the command code
  259.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 1)
  260.                     CASE "#" 'need to strip out the command code and the next 6 color characters
  261.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 7)
  262.                 END SELECT
  263.             END IF
  264.         LOOP UNTIL l = 0
  265.     NEXT
  266.  
  267. FUNCTION SCCL$ (FromWhat$, ListOfCommand$) 'Strip Command Code Lite
  268.     'Requires specifiication of what commands to strip from the text
  269.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  270.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  271.     '#NNNNNN are used to set 6 character RGB colors.
  272.     '{ are used to designate links
  273.     SCCL$ = FromWhat$
  274.     FOR i = 1 TO LEN(ListOfCommand$)
  275.         m$ = MID$(ListOfCommand$, i, 1)
  276.         DO
  277.             l = INSTR(SCCL$, m$)
  278.             IF l THEN
  279.                 SELECT CASE m$
  280.                     CASE "{" 'remove anything to the right of {, as the link is our last command
  281.                         'This command should only ever be processed once, as the first time it appears
  282.                         'it strips off everything to the right of it.
  283.                         SCCL$ = LEFT$(SCCL$, l - 1)
  284.                     CASE "@" 'just strip out the command code
  285.                         SCCL$ = LEFT$(SCCL$, l - 1) + MID$(SCCL$, l + 1)
  286.                     CASE "#" 'need to strip out the command code and the next 6 color characters
  287.                         SCCL$ = LEFT$(SCCL$, l - 1) + MID$(SCCL$, l + 7)
  288.                 END SELECT
  289.             END IF
  290.         LOOP UNTIL l = 0
  291.     NEXT
  292.  
  293.  
  294.  
  295. FUNCTION GCC$ (FromWhat$, WhichCommand$, FromPosition AS INTEGER) 'Get Command Code
  296.     cc = INSTR(FromWhat$, WhichCommand$)
  297.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  298.     SELECT CASE WhichCommand$
  299.         CASE "{" 'return everything between the brackets
  300.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1) 'return "" if there's no link when requested
  301.             cc1 = INSTR(GCC$, "}")
  302.             IF cc1 THEN GCC$ = LEFT$(GCC$, cc1 - 1) 'Remove anything after the link
  303.         CASE "@" 'Return the character after
  304.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1, 1)
  305.             cc = INSTR(SCCL(FromWhat$, "#{"), "@") 'find the position without any other command codes throwing off the location
  306.         CASE "#" 'return the 6 characters after
  307.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1, 6)
  308.     END SELECT
  309.     FromPosition = cc
  310.  
  311. SUB AddMenuItem (Which AS INTEGER, What$)
  312.     CheckHandle Which
  313.     o = Menu(Which).Options + 1
  314.     Menu(Which).Options = o
  315.     Captions(Which, o) = What$
  316.  
  317. SUB MenuActive (Which AS INTEGER, IsActive AS INTEGER)
  318.     CheckHandle Which
  319.     IF IsActive THEN Menu(Which).Active = 1 ELSE Menu(Which).Active = 0
  320.  
  321.  
  322. FUNCTION CreateMenu (fName AS STRING, fLayout AS INTEGER, fCloseable AS INTEGER)
  323.     FOR i = 1 TO UBOUND(Menu)
  324.         IF Menu(i).Handle = 0 THEN EXIT FOR 'it's a freehandle
  325.     NEXT
  326.     IF i > UBOUND(Menu) THEN
  327.         REDIM _PRESERVE Menu(i + 10) AS Menu_Type
  328.         REDIM _PRESERVE Captions(255, i + 10) AS STRING 'each menu can have up to 256 entries max
  329.     END IF
  330.     Menu(i).Handle = i
  331.     Menu(i).Name = fName
  332.     Menu(i).Layout = fLayout
  333.     Menu(i).Closeable = fCloseable
  334.     Menu(i).Top = 0: Menu(i).Left = 0
  335.     Menu(i).Wide = _WIDTH: Menu(i).High = _FONTHEIGHT
  336.     IF fCloseable THEN
  337.         Menu(i).Options = 1 'make the collapse button our first option
  338.         Captions(i, 1) = "ð"
  339.     ELSE
  340.         Menu(i).Options = 0 'no options yet
  341.     END IF
  342.     Menu(i).Active = 0
  343.     Menu(i).BGC = DarkGray
  344.     Menu(i).HC = LightGray
  345.     Menu(i).FC = Black
  346.     Menu(i).LC = White
  347.     CreateMenu = i
  348.  
  349. SUB SetMenuPos (Which AS INTEGER, WhereX AS INTEGER, WhereY AS INTEGER)
  350.     CheckHandle Which
  351.     Menu(Which).Top = WhereY
  352.     Menu(Which).Left = WhereX
  353.  
  354. SUB SetMenuSize (Which AS INTEGER, Wide AS INTEGER, High AS INTEGER)
  355.     CheckHandle Which
  356.     IF Wide = 0 THEN Wide = _WIDTH
  357.     IF High = 0 THEN High = _FONTWIDTH + 4
  358.     Menu(Which).Wide = Wide
  359.     Menu(Which).High = High
  360.  
  361. SUB CheckHandle (Which AS INTEGER)
  362.     IF Which < 0 OR Which > UBOUND(Menu) THEN ERROR 5: EXIT SUB
  363.     IF Menu(Which).Handle = 0 THEN ERROR 5: EXIT SUB
  364.  

We can now make our horizontal and vertical menus, set hotkeys for them, and even set custom colors for them (in case we want to grey one out, or color it differently to show it's currently unavailable, or such). 

I suppose mouse support should be something which I add real soonish, and then after that point, it's simply a matter of expanding customization/features one step at a time.  (Do I want a pretty box around the menu?  Do I want to include line separators?  Toggle style commands so one can Turn ON/OFF a feature?  Have the option for different colored backgrounds?  Highlight colors?  On so on...)

The core process seems to be working as intended now (except for mouse usage), so from here on out, it's just small tweaks to expand the options involved with the process, as far as I can tell.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: Menu System
« Reply #11 on: December 21, 2019, 04:02:41 pm »
Back from the road and returned to my armchair. Where do I jump into all this recent forum activity?

I'll run this code when I don't get a syntax error on line 1. Definitely my fault for not staying current on downloads; usually I let the devs play with dev builds though. ;-)

While the community needs no reminding that InForm already handles menus, every possible kind of widget, button, scrollbar, toolbox, and canvas, and has been stable for several years and developed by one of the most skilled and THE most humble guy I've ever met, my question arises from elsewhere. 'Cause my fellow American Steve has the same level of talent and dedication and I 100% encourage parallel efforts within the community we have here.

So the question is: in what program will this menu library be implemented? We place plenty of emphasis on the means to an end, but rarely the end itself. It reminds me of a bigger question: is there any established `software', besides QB64.exe, that is written in QB64 and actually used? I wanna see where even 10% of all this is going and and eventually immortalize it as a Sample, a Toolbox entry (I'm way behind on those, I know)... or a new section on the forums that we've needed for a while: a "made with QB64" section. This should be for finished stuff and it's weird that we don't have much to show off.


« Last Edit: December 21, 2019, 04:08:13 pm by STxAxTIC »
You're not done when it works, you're done when it's right.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #12 on: December 21, 2019, 04:40:36 pm »
So the question is: in what program will this menu library be implemented? We place plenty of emphasis on the means to an end, but rarely the end itself. It reminds me of a bigger question: is there any established `software', besides QB64.exe, that is written in QB64 and actually used?

Once it's all finished, I'll probably end up using it as a simple plug-in routine for all sorts of future stuff.  At the moment, it's just a tool in the making for me, without any set and pressing purpose for it, which is something I try and do when I'm not actually engrossed in some other project.

For ages, I've made use of Terry's menu library when I needed one, but unfortunately, I often found it limiting for my own needs.  The one real issue I have with it is that it reminds me more of INPUT than it does INKEY$ -- it locks focus from the keyboard and mouse when it's active.  They keyboard I can kind of understand, as you never know what keys someone might assign to become hot keys, but the mouse issue has always bugged me.   Clicking on the screen, in an unrelated spot from the menu, shouldn't ignore my click completely.  At the most intrusive, it should close the menu and restore focus back to the main program, but it doesn't do that...

Terry's menu steals the mouse focus completely when it's active, and that's the main reason why I finally decided to write my own little tool to handle the job without that drawback.  Several times, it's interfered with various program flow for me, so I'm just working on making a tool which works with the syntax and capabilities which I want to give it -- which is what we see here, so far. 

Though to be honest, I tend to enjoy creating the tools to do things almost more than I actually end up enjoying the  true doing of things...   Tools are flexible; programs are rigid and set; and I enjoy that flexibility of possibility more than I do the actual realization of that possibility.   ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Re: Menu System
« Reply #13 on: December 21, 2019, 07:42:27 pm »
Steve's Menu System: SMS :-)

I think that doing something in another way is at the basis of programming and if  you like you can build a generalized system to use again in different programs.

IMHO this kind of approach to menu with the possibility to choose the horizontal or vertical position and moreover to gain additional submenus let me think that in the same manner you can manage other multiple objects.

You can keep separately the different managing of multiple kind of objects if you don't want to make a duplicate of Inform also without the RAD tool.

about mouse
Quote
I suppose mouse support should be something which I add real soonish
I have tried to use the simple position of mouse to interact with menu, but I failed. It seems that the positional information are not stored only in Left Top Wide and High so the GetMouse Function is able only to activate the main menu without selections.
I'm waiting to see how do you develop this function.
Programming isn't difficult, only it's  consuming time and coffee

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #14 on: December 21, 2019, 08:35:22 pm »
about mouse I have tried to use the simple position of mouse to interact with menu, but I failed. It seems that the positional information are not stored only in Left Top Wide and High so the GetMouse Function is able only to activate the main menu without selections.

They’re not (unless we manually set them later). 

If you look, there’s only one single menu which we currently do any positioning at all on:  SetMenuPos MainMenu, 0, 0 'postion menu top left at 0,0

Everything else auto-positions and auto-sizes itself to fit the contents of our menus.  In time, I’m going to add better support for set sizes/locations for the menus (maybe you want one to pop up centered on the screen each time to be certain to focus user attention on it, or such), but for now they do most of the positioning themselves if they’re a submenu.

Mouse usage really shouldn’t be that hard to implement when I get a little free time.  I picture the process being this simple:

Check mouse X/Y position.
If we’re over an active menu, then
     see what item we clicked on
Else
    If we’re over a visible menu, then
        set that menu active, close any other sub-menus, make them inactive
    ELSE
        close all sub menus, make everything inactive as we clicked outside the screen
     END IF
END IF
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!