Author Topic: Menu System  (Read 7554 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
Re: Menu System
« Reply #15 on: December 22, 2019, 04:54:33 am »
Mouse handling has been added:

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, active AS INTEGER)
  61.     'k is for the keyboard
  62.     'active is a return code for which menu we got the result from, in case someone makes multiple menus
  63.     '    with the same options listed in the sub menus, such as PAINT -- Circle as one menu/submenu
  64.     '    and then DRAW -- Circle as another menu/submenu.
  65.     '    Both would just return "Circle" as our final choice, so we'd need to know which menu was active
  66.     '    and where the command came from.
  67.  
  68.     DIM Caption AS STRING
  69.     STATIC oldmouse AS INTEGER 'the old status of our left mouse button (up or down)
  70.     mb = _MOUSEBUTTON(1) 'shortcut key so I don't have to type _mousebutton(1) multiple times.
  71.     mx = _MOUSEX: my = _MOUSEY 'same here.
  72.  
  73.     IF mb AND NOT oldmouse THEN 'we only need to check the menu IF we clicked the mouse button
  74.         oldmouse = mb 'placed here in case we exit early (which we can)
  75.         FOR i = 1 TO UBOUND(menu) 'check for mouse interactions
  76.             IF Menu(i).Layout AND Visible THEN 'only if a menu is visible do we check to see if we can do something with it.
  77.                 x = Menu(i).Left: y = Menu(i).Top 'top and left
  78.                 w = Menu(i).Wide: h = Menu(i).High
  79.                 r = x + w: b = y + h 'bottom and right
  80.  
  81.                 IF mx >= x AND mx <= r AND my >= y AND my <= b THEN 'the mouse in over a visible menu
  82.                     IF Menu(i).Active THEN 'if that window is active, return a result
  83.                         IF Menu(i).Layout AND Hortizontal THEN
  84.                             IF Menu(i).Layout AND Minimized THEN
  85.                                 Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  86.                                 Menu(i).Active = 0
  87.                                 HideSubMenus
  88.                                 EXIT SUB
  89.                             ELSE
  90.                                 FOR j = 1 TO Menu(i).Options
  91.                                     oldpx = px
  92.                                     Caption = SCC(Captions(i, j))
  93.                                     px = px + _PRINTWIDTH(Caption)
  94.                                     IF mx >= oldpx AND mx <= px THEN 'we clicked on an item
  95.                                         Menu(i).Active = j
  96.                                         GOTO itemselected
  97.                                     END IF
  98.                                     px = px + _FONTWIDTH * 2
  99.                                 NEXT
  100.                             END IF
  101.                         ELSE 'It's a vertical menu
  102.                             Menu(i).Active = (my - y) \ _FONTHEIGHT + 1
  103.                             GOTO itemselected
  104.                         END IF
  105.                     ELSE 'if the menu we're over isn't active, then make it the active menu
  106.                         Menu(i).Active = 1
  107.                         FOR j = 1 TO UBOUND(menu)
  108.                             IF j <> i THEN 'close all the other (non-main) menus.
  109.                                 IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  110.                                 Menu(j).Active = 0
  111.                             END IF
  112.                         NEXT
  113.                     END IF
  114.                 END IF
  115.             END IF
  116.         NEXT
  117.     END IF
  118.     oldmouse = mb 'placed here in case we don't go into the mouse checking loop
  119.  
  120.     FOR i = 1 TO UBOUND(menu)
  121.         IF Menu(i).Active THEN EXIT FOR
  122.     NEXT
  123.     IF i > UBOUND(menu) THEN EXIT SUB 'no menus active
  124.     SELECT CASE k
  125.         CASE 13
  126.             itemselected:
  127.             IF Captions(i, Menu(i).Active) = "ð" THEN
  128.                 IF Menu(i).Layout AND Minimized THEN
  129.                     Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  130.                 ELSE
  131.                     Menu(i).Layout = Menu(i).Layout OR Minimized
  132.                 END IF
  133.             ELSE
  134.                 active = i
  135.                 ProcessMenu$ = SCC(Captions(i, Menu(i).Active)) 'Strip the command code for the return name
  136.                 CC$ = GCC(Captions(i, Menu(i).Active), "{", p%) 'but process the command code, if it's available
  137.                 IF CC$ <> "" THEN
  138.                     OpenSubMenu i, CC$
  139.                 ELSE
  140.                     'we clicked on something without a command code (which right now is just submenu links)
  141.                     'hide all the submenus which are open from the screen
  142.                     HideSubMenus
  143.                 END IF
  144.             END IF
  145.             Menu(i).Active = 0
  146.             k = 0 'keyboard process has been handled internally
  147.             EXIT FUNCTION
  148.         CASE 27, 100307, 100308
  149.             FOR i = 1 TO UBOUND(menu) 'Turn all menus inactive
  150.                 Menu(i).Active = 0
  151.             NEXT
  152.             HideSubMenus 'and hide them
  153.             k = 0 'don't return a keycode back to the main program itself
  154.             EXIT FUNCTION
  155.         CASE 19200 'left arrow
  156.             IF Menu(i).Layout AND Hortizontal THEN
  157.                 Menu(i).Active = Menu(i).Active - 1
  158.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  159.             END IF
  160.             k = 0 'keyboard process has been handled internally
  161.         CASE 19712 'right arrow
  162.             IF Menu(i).Layout AND Hortizontal THEN
  163.                 Menu(i).Active = Menu(i).Active + 1
  164.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  165.             END IF
  166.             k = 0 'keyboard process has been handled internally
  167.         CASE 18432 'up arrow
  168.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  169.                 Menu(i).Active = Menu(i).Active - 1
  170.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  171.             END IF
  172.             k = 0 'keyboard process has been handled internally
  173.         CASE 20480 'down arrow
  174.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  175.                 Menu(i).Active = Menu(i).Active + 1
  176.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  177.             END IF
  178.             k = 0 'keyboard process has been handled internally
  179.     END SELECT
  180.     FOR j = 1 TO Menu(i).Options
  181.         CC$ = GCC(Captions(i, j), "@", p%)
  182.         IF CC$ <> "" THEN
  183.             c = ASC(LCASE$(CC$))
  184.             IF k = c OR k = c + 32 THEN
  185.                 ProcessMenu$ = SCC(Captions(i, j)) 'Strip the command code for the return name
  186.                 CC$ = GCC(Captions(i, j), "{", p%) 'but process the command code, if it's available
  187.                 IF CC$ <> "" THEN OpenSubMenu i, CC$ ELSE HideSubMenus
  188.                 Menu(i).Active = 0
  189.                 k = 0
  190.             END IF
  191.         END IF
  192.     NEXT
  193.  
  194. SUB OpenSubMenu (calledfrom AS INTEGER, CC$) 'calledfrom is the previous menu which called this one
  195.     FOR j = 1 TO UBOUND(menu)
  196.         IF Menu(j).Name = CC$ THEN
  197.             Menu(j).Active = 1
  198.             Menu(j).Layout = Menu(j).Layout OR Visible
  199.             IF Menu(calledfrom).Layout AND Hortizontal THEN
  200.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT
  201.                 FOR j1 = 1 TO calledfrom
  202.                     px = px + _PRINTWIDTH(SCC(Captions(calledfrom, j1))) + _FONTWIDTH * 2
  203.                 NEXT
  204.                 Menu(j).Left = Menu(calledfrom).Left + px
  205.             ELSE
  206.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT * (Menu(calledfrom).Active - 1)
  207.                 Menu(j).Left = MaxVW(calledfrom)
  208.             END IF
  209.             EXIT FOR
  210.         END IF
  211.     NEXT
  212.  
  213.  
  214. SUB HideSubMenus
  215.     FOR j = 1 TO UBOUND(menu)
  216.         IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  217.     NEXT
  218.  
  219. SUB DisplayMenus
  220.     DIM BG AS _UNSIGNED LONG, DC AS _UNSIGNED LONG 'background color, default color
  221.     DIM IC AS _UNSIGNED LONG 'internal color
  222.     DIM Caption AS STRING
  223.     FOR Which = 1 TO UBOUND(menu)
  224.         IF Menu(Which).Handle <> 0 AND (Menu(Which).Layout AND Visible) THEN
  225.             x = Menu(Which).Left: y = Menu(Which).Top
  226.             w = Menu(Which).Wide: h = Menu(Which).High
  227.             r = x + w: b = y + h 'right side and bottom side of menu limits
  228.             fw = _FONTWIDTH: fh = _FONTHEIGHT
  229.             COLOR Menu(Which).FC, 0
  230.             IF Menu(Which).Layout AND Hortizontal THEN
  231.                 IF Menu(Which).Layout AND Minimized THEN
  232.                     'we have the menu minimized to begin with
  233.                     Caption = "ð " + Menu(Which).Name
  234.                     pw = _PRINTWIDTH(Caption)
  235.                     IF Menu(Which).Active THEN 'if the menu is active, highlight the squiggle
  236.                         LINE (x, y)-STEP(pw, h), Menu(Which).HC, BF
  237.                     ELSE 'otherwise leave it grayed out and inactive
  238.                         LINE (x, y)-STEP(pw, h), Menu(Which).BGC, BF
  239.                     END IF
  240.                     _PRINTSTRING (x, y), Caption
  241.                 ELSE
  242.                     LINE (x, y)-(r, b), Menu(Which).BGC, BF
  243.                     FOR i = 1 TO Menu(Which).Options
  244.                         Caption = SCC(Captions(Which, i))
  245.                         pw = _PRINTWIDTH(Caption)
  246.                         IF Menu(Which).Active = i THEN
  247.                             LINE (px, y)-STEP(_PRINTWIDTH(Caption), fh), Menu(Which).HC, BF
  248.                         END IF
  249.                         CC$ = GCC(Captions(Which, i), "#", p%)
  250.                         IF CC$ <> "" THEN
  251.                             COLOR VAL("&HFF" + CC$ + "~&"), 0
  252.                         ELSE
  253.                             COLOR Menu(Which).FC, 0
  254.                         END IF
  255.                         _PRINTSTRING (px, y), Caption
  256.                         IF Menu(Which).Active THEN
  257.                             CC$ = GCC(Captions(Which, i), "@", p%)
  258.                             IF p% THEN
  259.                                 COLOR Menu(Which).LC, 0
  260.                                 _PRINTSTRING (px + (p% - 1) * _FONTWIDTH, y), CC$
  261.                             END IF
  262.                         END IF
  263.                         px = px + _PRINTWIDTH(Caption) + fw * 2
  264.                     NEXT
  265.                 END IF
  266.             ELSE 'It's a vertical menu
  267.                 LINE (x, y)-(r, b), Menu(Which).BGC, BF
  268.                 FOR i = 1 TO Menu(Which).Options
  269.                     IF Menu(Which).Active = i THEN LINE (x, y + fh * (i - 1))-STEP(Menu(Which).Wide, fh), Menu(Which).HC, BF
  270.                     CC$ = GCC(Captions(Which, i), "#", p%)
  271.                     IF CC$ <> "" THEN
  272.                         COLOR VAL("&HFF" + CC$ + "~&"), 0
  273.                     ELSE
  274.                         COLOR Menu(Which).FC, 0
  275.                     END IF
  276.                     _PRINTSTRING (x, y + fh * (i - 1)), SCC(Captions(Which, i))
  277.                     IF Menu(Which).Active THEN
  278.                         CC$ = GCC(Captions(Which, i), "@", p%)
  279.                         IF p% THEN
  280.                             COLOR Menu(Which).LC, 0
  281.                             _PRINTSTRING (x + (p% - 1) * _FONTWIDTH, y + fh * (i - 1)), CC$
  282.                         END IF
  283.                     END IF
  284.                 NEXT
  285.             END IF
  286.         END IF
  287.     NEXT
  288.     COLOR DC, BG
  289.  
  290. SUB AutoSetMenuSize (Which AS INTEGER) 'Sub to set the width of a menu
  291.     FOR i = 1 TO Menu(Which).Options
  292.         tw = _PRINTWIDTH(SCC(Captions(Which, i))) 'temp width
  293.         IF tw > Menu(Which).Wide THEN Menu(Which).Wide = tw 'if the width is less than that temp width, correct that
  294.     NEXT
  295.     Menu(Which).Wide = Menu(Which).Wide + _FONTWIDTH
  296.     IF (Menu(Which).Layout AND Hortizontal) = 0 THEN Menu(Which).High = _FONTHEIGHT * Menu(Which).Options
  297.  
  298.  
  299. FUNCTION SCC$ (FromWhat$) 'Strip Command Code
  300.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  301.     ListOfCommand$ = "{@#"
  302.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  303.     '#NNNNNN are used to set 6 character RGB colors.
  304.     '{ are used to designate links
  305.     SCC$ = FromWhat$
  306.     FOR i = 1 TO LEN(ListOfCommand$)
  307.         m$ = MID$(ListOfCommand$, i, 1)
  308.         DO
  309.             l = INSTR(SCC$, m$)
  310.             IF l THEN
  311.                 SELECT CASE m$
  312.                     CASE "{" 'remove anything to the right of {, as the link is our last command
  313.                         'This command should only ever be processed once, as the first time it appears
  314.                         'it strips off everything to the right of it.
  315.                         SCC$ = LEFT$(SCC$, l - 1)
  316.                     CASE "@" 'just strip out the command code
  317.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 1)
  318.                     CASE "#" 'need to strip out the command code and the next 6 color characters
  319.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 7)
  320.                 END SELECT
  321.             END IF
  322.         LOOP UNTIL l = 0
  323.     NEXT
  324.  
  325. FUNCTION SCCL$ (FromWhat$, ListOfCommand$) 'Strip Command Code Lite
  326.     'Requires specifiication of what commands to strip from the text
  327.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  328.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  329.     '#NNNNNN are used to set 6 character RGB colors.
  330.     '{ are used to designate links
  331.     SCCL$ = FromWhat$
  332.     FOR i = 1 TO LEN(ListOfCommand$)
  333.         m$ = MID$(ListOfCommand$, i, 1)
  334.         DO
  335.             l = INSTR(SCCL$, m$)
  336.             IF l THEN
  337.                 SELECT CASE m$
  338.                     CASE "{" 'remove anything to the right of {, as the link is our last command
  339.                         'This command should only ever be processed once, as the first time it appears
  340.                         'it strips off everything to the right of it.
  341.                         SCCL$ = LEFT$(SCCL$, l - 1)
  342.                     CASE "@" 'just strip out the command code
  343.                         SCCL$ = LEFT$(SCCL$, l - 1) + MID$(SCCL$, l + 1)
  344.                     CASE "#" 'need to strip out the command code and the next 6 color characters
  345.                         SCCL$ = LEFT$(SCCL$, l - 1) + MID$(SCCL$, l + 7)
  346.                 END SELECT
  347.             END IF
  348.         LOOP UNTIL l = 0
  349.     NEXT
  350.  
  351.  
  352.  
  353. FUNCTION GCC$ (FromWhat$, WhichCommand$, FromPosition AS INTEGER) 'Get Command Code
  354.     cc = INSTR(FromWhat$, WhichCommand$)
  355.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  356.     SELECT CASE WhichCommand$
  357.         CASE "{" 'return everything between the brackets
  358.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1) 'return "" if there's no link when requested
  359.             cc1 = INSTR(GCC$, "}")
  360.             IF cc1 THEN GCC$ = LEFT$(GCC$, cc1 - 1) 'Remove anything after the link
  361.         CASE "@" 'Return the character after
  362.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1, 1)
  363.             cc = INSTR(SCCL(FromWhat$, "#{"), "@") 'find the position without any other command codes throwing off the location
  364.         CASE "#" 'return the 6 characters after
  365.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1, 6)
  366.     END SELECT
  367.     FromPosition = cc
  368.  
  369. SUB AddMenuItem (Which AS INTEGER, What$)
  370.     CheckHandle Which
  371.     o = Menu(Which).Options + 1
  372.     Menu(Which).Options = o
  373.     Captions(Which, o) = What$
  374.     IF Menu(Which).Layout AND NOT Hortizontal THEN AutoSetMenuSize (Which)
  375.  
  376. SUB MenuActive (Which AS INTEGER, IsActive AS INTEGER)
  377.     CheckHandle Which
  378.     IF IsActive THEN Menu(Which).Active = 1 ELSE Menu(Which).Active = 0
  379.  
  380.  
  381. FUNCTION CreateMenu (fName AS STRING, fLayout AS INTEGER, fCloseable AS INTEGER)
  382.     FOR i = 1 TO UBOUND(Menu)
  383.         IF Menu(i).Handle = 0 THEN EXIT FOR 'it's a freehandle
  384.     NEXT
  385.     IF i > UBOUND(Menu) THEN
  386.         REDIM _PRESERVE Menu(i + 10) AS Menu_Type
  387.         REDIM _PRESERVE Captions(255, i + 10) AS STRING 'each menu can have up to 256 entries max
  388.     END IF
  389.     Menu(i).Handle = i
  390.     Menu(i).Name = fName
  391.     Menu(i).Layout = fLayout
  392.     Menu(i).Closeable = fCloseable
  393.     Menu(i).Top = 0: Menu(i).Left = 0
  394.     IF fLayout AND Hortizontal THEN Menu(i).Wide = _WIDTH: Menu(i).High = _FONTHEIGHT
  395.     IF fCloseable THEN
  396.         Menu(i).Options = 1 'make the collapse button our first option
  397.         Captions(i, 1) = "ð"
  398.     ELSE
  399.         Menu(i).Options = 0 'no options yet
  400.     END IF
  401.     Menu(i).Active = 0
  402.     Menu(i).BGC = DarkGray
  403.     Menu(i).HC = LightGray
  404.     Menu(i).FC = Black
  405.     Menu(i).LC = White
  406.     CreateMenu = i
  407.  
  408. SUB SetMenuPos (Which AS INTEGER, WhereX AS INTEGER, WhereY AS INTEGER)
  409.     CheckHandle Which
  410.     Menu(Which).Top = WhereY
  411.     Menu(Which).Left = WhereX
  412.  
  413. SUB SetMenuSize (Which AS INTEGER, Wide AS INTEGER, High AS INTEGER)
  414.     CheckHandle Which
  415.     IF Wide = 0 THEN Wide = _WIDTH
  416.     IF High = 0 THEN High = _FONTWIDTH + 4
  417.     Menu(Which).Wide = Wide
  418.     Menu(Which).High = High
  419.  
  420. SUB CheckHandle (Which AS INTEGER)
  421.     IF Which < 0 OR Which > UBOUND(Menu) THEN ERROR 5: EXIT SUB
  422.     IF Menu(Which).Handle = 0 THEN ERROR 5: EXIT SUB

Notice that our main loop now contains the following: 
Code: [Select]
    WHILE _MOUSEINPUT: WEND
The menu system does nothing to capture our mouse for us.  In fact, it only interacts with the mouse at all, under a very limited set of conditions.

First:
Code: [Select]
    IF mb AND NOT oldmouse THEN 'we only need to check the menu IF we clicked the mouse button
Only when a mouse button is clicked do we even bother to do anything with the mouse inside the menu processing loop.

Then:
Code: [Select]
        IF Menu(i).Layout AND Visible THEN 'only if a menu is visible
                IF mx >= x AND mx <= r AND my >= y AND my <= b THEN 'the mouse in over a visible menu

Only IF a menu is visible, and we happen to be over that visible menu, do we bother to do anything further with the mouse...

There's nothing in here at all to have the menu take control of your mouse.  It'll only interact with your keyboard once you let it (in my case, it takes pressing to ALT key to activate the main menu for this demo).  The rest of the time, it just quietly sits and does nothing until the proper conditions are met for it to become interactive with us.

As far as I can tell, this fulfills its basic requirements to be a workable menu for me, even now.  It works with the keyboard.  It works with the mouse.  It plays nice and doesn't take control over any other input handler...

I'd call what it does here to be a workable set of code already.  Everything from this point on is just expanding functionality and customization ability.

If any of you guys try it out and have it glitch out on you, for whatever reason, let me know and I'll sort out where/what went wrong.  I won't swear it's 100% bug free, but it's working fairly decently for me, from what little limited testing I've did with it while designing it.  ;)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Menu System
« Reply #16 on: December 22, 2019, 11:17:04 am »
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, "É"
  35. AddMenuItem FileMenu, "º@New"
  36. AddMenuItem FileMenu, "º#FF0000@Open"
  37. AddMenuItem FileMenu, "ºSa@ve"
  38. AddMenuItem FileMenu, "Ì"
  39. AddMenuItem FileMenu, "º@Print"
  40. AddMenuItem FileMenu, "È"
  41.  
  42.  
  43.     CLS
  44.  
  45.     k& = _KEYHIT
  46.     Choice$ = ProcessMenu(k&, active%)
  47.     SELECT CASE k& 'Only when there's an active menu do we intercept those keystrokes
  48.         'If no menus are active, we'll just fall through and preserve our keys as usual
  49.         CASE 27 'ESC in the menu will close the menus.  ESC outside of it will exit our program.
  50.             'I put it here just to use to showcase that we only intercept keystrokes when the menus are active.
  51.             SYSTEM
  52.         CASE 100307, 100308 'I chose ALT as a simple means to make my menu actives.  Feel free to choose your own.
  53.             Menu(MainMenu).Active = 1
  54.     END SELECT
  55.  
  56.     IF Choice$ <> "" THEN LastChoice$ = Choice$
  57.     DisplayMenus
  58.     LOCATE 10, 10: PRINT "Your last choice: "; LastChoice$
  59.  
  60.     _DISPLAY
  61.     _LIMIT 30
  62.  
  63.  
  64. FUNCTION ProcessMenu$ (k AS LONG, active AS INTEGER)
  65.     'k is for the keyboard
  66.     'active is a return code for which menu we got the result from, in case someone makes multiple menus
  67.     '    with the same options listed in the sub menus, such as PAINT -- Circle as one menu/submenu
  68.     '    and then DRAW -- Circle as another menu/submenu.
  69.     '    Both would just return "Circle" as our final choice, so we'd need to know which menu was active
  70.     '    and where the command came from.
  71.  
  72.     DIM Caption AS STRING
  73.     STATIC oldmouse AS INTEGER 'the old status of our left mouse button (up or down)
  74.     mb = _MOUSEBUTTON(1) 'shortcut key so I don't have to type _mousebutton(1) multiple times.
  75.     mx = _MOUSEX: my = _MOUSEY 'same here.
  76.  
  77.     IF mb AND NOT oldmouse THEN 'we only need to check the menu IF we clicked the mouse button
  78.         oldmouse = mb 'placed here in case we exit early (which we can)
  79.         FOR i = 1 TO UBOUND(menu) 'check for mouse interactions
  80.             IF Menu(i).Layout AND Visible THEN 'only if a menu is visible do we check to see if we can do something with it.
  81.                 x = Menu(i).Left: y = Menu(i).Top 'top and left
  82.                 w = Menu(i).Wide: h = Menu(i).High
  83.                 r = x + w: b = y + h 'bottom and right
  84.  
  85.                 IF mx >= x AND mx <= r AND my >= y AND my <= b THEN 'the mouse in over a visible menu
  86.                     IF Menu(i).Active THEN 'if that window is active, return a result
  87.                         IF Menu(i).Layout AND Hortizontal THEN
  88.                             IF Menu(i).Layout AND Minimized THEN
  89.                                 Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  90.                                 Menu(i).Active = 0
  91.                                 HideSubMenus
  92.                                 EXIT SUB
  93.                             ELSE
  94.                                 FOR j = 1 TO Menu(i).Options
  95.                                     oldpx = px
  96.                                     Caption = SCC(Captions(i, j))
  97.                                     px = px + _PRINTWIDTH(Caption)
  98.                                     IF mx >= oldpx AND mx <= px THEN 'we clicked on an item
  99.                                         Menu(i).Active = j
  100.                                         GOTO itemselected
  101.                                     END IF
  102.                                     px = px + _FONTWIDTH * 2
  103.                                 NEXT
  104.                             END IF
  105.                         ELSE 'It's a vertical menu
  106.                             Menu(i).Active = (my - y) \ _FONTHEIGHT + 1
  107.                             GOTO itemselected
  108.                         END IF
  109.                     ELSE 'if the menu we're over isn't active, then make it the active menu
  110.                         Menu(i).Active = 1
  111.                         FOR j = 1 TO UBOUND(menu)
  112.                             IF j <> i THEN 'close all the other (non-main) menus.
  113.                                 IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  114.                                 Menu(j).Active = 0
  115.                             END IF
  116.                         NEXT
  117.                     END IF
  118.                 END IF
  119.             END IF
  120.         NEXT
  121.     END IF
  122.     oldmouse = mb 'placed here in case we don't go into the mouse checking loop
  123.  
  124.     FOR i = 1 TO UBOUND(menu)
  125.         IF Menu(i).Active THEN EXIT FOR
  126.     NEXT
  127.     IF i > UBOUND(menu) THEN EXIT SUB 'no menus active
  128.  
  129.     IF GCC(Captions(i, Menu(i).Active), "É", p%) <> "" THEN
  130.         Menu(i).Active = Menu(i).Active + 1
  131.     ELSEIF GCC(Captions(i, Menu(i).Active), "È", p%) <> "" THEN
  132.         Menu(i).Active = Menu(i).Active - 1
  133.     END IF
  134.     SELECT CASE k
  135.         CASE 13
  136.             itemselected:
  137.             IF Captions(i, Menu(i).Active) = "ð" THEN
  138.                 IF Menu(i).Layout AND Minimized THEN
  139.                     Menu(i).Layout = Menu(i).Layout AND NOT Minimized
  140.                 ELSE
  141.                     Menu(i).Layout = Menu(i).Layout OR Minimized
  142.                 END IF
  143.             ELSE
  144.                 active = i
  145.                 ProcessMenu$ = SCC(Captions(i, Menu(i).Active)) 'Strip the command code for the return name
  146.                 CC$ = GCC(Captions(i, Menu(i).Active), "{", p%) 'but process the command code, if it's available
  147.                 IF CC$ <> "" THEN
  148.                     OpenSubMenu i, CC$
  149.                 ELSE
  150.                     'we clicked on something without a command code
  151.                     'hide all the submenus which are open from the screen
  152.                     'HideSubMenus
  153.                 END IF
  154.             END IF
  155.             Menu(i).Active = 0
  156.             k = 0 'keyboard process has been handled internally
  157.             EXIT FUNCTION
  158.         CASE 27, 100307, 100308
  159.             FOR i = 1 TO UBOUND(menu) 'Turn all menus inactive
  160.                 Menu(i).Active = 0
  161.             NEXT
  162.             HideSubMenus 'and hide them
  163.             k = 0 'don't return a keycode back to the main program itself
  164.             EXIT FUNCTION
  165.         CASE 19200 'left arrow
  166.             IF Menu(i).Layout AND Hortizontal THEN
  167.                 Menu(i).Active = Menu(i).Active - 1
  168.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  169.             END IF
  170.             k = 0 'keyboard process has been handled internally
  171.         CASE 19712 'right arrow
  172.             IF Menu(i).Layout AND Hortizontal THEN
  173.                 Menu(i).Active = Menu(i).Active + 1
  174.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  175.             END IF
  176.             k = 0 'keyboard process has been handled internally
  177.         CASE 18432 'up arrow
  178.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  179.                 Menu(i).Active = Menu(i).Active - 1
  180.                 IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options
  181.                 recheckup:
  182.                 IF GCC(Captions(i, Menu(i).Active), "É", p%) <> "" THEN
  183.                     Menu(i).Active = Menu(i).Active - 1
  184.                     IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options: GOTO recheckup
  185.                 ELSEIF GCC(Captions(i, Menu(i).Active), "È", p%) <> "" THEN
  186.                     Menu(i).Active = Menu(i).Active - 1
  187.                     IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options: GOTO recheckup
  188.                 ELSEIF GCC(Captions(i, Menu(i).Active), "Ì", p%) <> "" THEN
  189.                     Menu(i).Active = Menu(i).Active - 1
  190.                     IF Menu(i).Active < 1 THEN Menu(i).Active = Menu(i).Options: GOTO recheckup
  191.                 END IF
  192.             END IF
  193.             k = 0 'keyboard process has been handled internally
  194.         CASE 20480 'down arrow
  195.             IF (Menu(i).Layout AND Hortizontal) = 0 THEN
  196.                 Menu(i).Active = Menu(i).Active + 1
  197.                 IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1
  198.                 recheckdown:
  199.                 IF GCC(Captions(i, Menu(i).Active), "É", p%) <> "" THEN
  200.                     Menu(i).Active = Menu(i).Active + 1
  201.                     IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1: GOTO recheckdown
  202.                 ELSEIF GCC(Captions(i, Menu(i).Active), "È", p%) <> "" THEN
  203.                     Menu(i).Active = Menu(i).Active + 1
  204.                     IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1: GOTO recheckdown
  205.                 ELSEIF GCC(Captions(i, Menu(i).Active), "Ì", p%) <> "" THEN
  206.                     Menu(i).Active = Menu(i).Active + 1
  207.                     IF Menu(i).Active > Menu(i).Options THEN Menu(i).Active = 1: GOTO recheckdown
  208.                 END IF
  209.             END IF
  210.             k = 0 'keyboard process has been handled internally
  211.     END SELECT
  212.     FOR j = 1 TO Menu(i).Options
  213.         CC$ = GCC(Captions(i, j), "@", p%)
  214.         IF CC$ <> "" THEN
  215.             c = ASC(LCASE$(CC$))
  216.             IF k = c OR k = c + 32 THEN
  217.                 ProcessMenu$ = SCC(Captions(i, j)) 'Strip the command code for the return name
  218.                 CC$ = GCC(Captions(i, j), "{", p%) 'but process the command code, if it's available
  219.                 IF CC$ <> "" THEN OpenSubMenu i, CC$ ELSE HideSubMenus
  220.                 Menu(i).Active = 0
  221.                 k = 0
  222.             END IF
  223.         END IF
  224.     NEXT
  225.  
  226. SUB OpenSubMenu (calledfrom AS INTEGER, CC$) 'calledfrom is the previous menu which called this one
  227.     FOR j = 1 TO UBOUND(menu)
  228.         IF Menu(j).Name = CC$ THEN
  229.             Menu(j).Active = 1
  230.             Menu(j).Layout = Menu(j).Layout OR Visible
  231.             IF Menu(calledfrom).Layout AND Hortizontal THEN
  232.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT
  233.                 FOR j1 = 1 TO calledfrom
  234.                     px = px + _PRINTWIDTH(SCC(Captions(calledfrom, j1))) + _FONTWIDTH * 2
  235.                 NEXT
  236.                 Menu(j).Left = Menu(calledfrom).Left + px
  237.             ELSE
  238.                 Menu(j).Top = Menu(calledfrom).Top + _FONTHEIGHT * (Menu(calledfrom).Active - 1)
  239.                 Menu(j).Left = MaxVW(calledfrom)
  240.             END IF
  241.             EXIT FOR
  242.         END IF
  243.     NEXT
  244.  
  245.  
  246. SUB HideSubMenus
  247.     FOR j = 1 TO UBOUND(menu)
  248.         IF Menu(j).Layout AND SubMenu THEN Menu(j).Layout = Menu(j).Layout AND NOT Visible
  249.     NEXT
  250.  
  251. SUB DisplayMenus
  252.     DIM BG AS _UNSIGNED LONG, DC AS _UNSIGNED LONG 'background color, default color
  253.     DIM IC AS _UNSIGNED LONG 'internal color
  254.     DIM Caption AS STRING
  255.     FOR Which = 1 TO UBOUND(menu)
  256.         IF Menu(Which).Handle <> 0 AND (Menu(Which).Layout AND Visible) THEN
  257.             x = Menu(Which).Left: y = Menu(Which).Top
  258.             w = Menu(Which).Wide: h = Menu(Which).High
  259.             r = x + w: b = y + h 'right side and bottom side of menu limits
  260.             fw = _FONTWIDTH: fh = _FONTHEIGHT
  261.             COLOR Menu(Which).FC, 0
  262.             IF Menu(Which).Layout AND Hortizontal THEN
  263.                 IF Menu(Which).Layout AND Minimized THEN
  264.                     'we have the menu minimized to begin with
  265.                     Caption = "ð " + Menu(Which).Name
  266.                     pw = _PRINTWIDTH(Caption)
  267.                     IF Menu(Which).Active THEN 'if the menu is active, highlight the squiggle
  268.                         LINE (x, y)-STEP(pw, h), Menu(Which).HC, BF
  269.                     ELSE 'otherwise leave it grayed out and inactive
  270.                         LINE (x, y)-STEP(pw, h), Menu(Which).BGC, BF
  271.                     END IF
  272.                     _PRINTSTRING (x, y), Caption
  273.                 ELSE
  274.                     LINE (x, y)-(r, b), Menu(Which).BGC, BF
  275.                     FOR i = 1 TO Menu(Which).Options
  276.                         Caption = SCC(Captions(Which, i))
  277.                         pw = _PRINTWIDTH(Caption)
  278.                         IF Menu(Which).Active = i THEN
  279.                             LINE (px, y)-STEP(_PRINTWIDTH(Caption), fh), Menu(Which).HC, BF
  280.                         END IF
  281.                         CC$ = GCC(Captions(Which, i), "#", p%)
  282.                         IF CC$ <> "" THEN
  283.                             COLOR VAL("&HFF" + CC$ + "~&"), 0
  284.                         ELSE
  285.                             COLOR Menu(Which).FC, 0
  286.                         END IF
  287.                         _PRINTSTRING (px, y), Caption
  288.                         IF Menu(Which).Active THEN
  289.                             CC$ = GCC(Captions(Which, i), "@", p%)
  290.                             IF p% THEN
  291.                                 COLOR Menu(Which).LC, 0
  292.                                 _PRINTSTRING (px + (p% - 1) * _FONTWIDTH, y), CC$
  293.                             END IF
  294.                         END IF
  295.                         px = px + _PRINTWIDTH(Caption) + fw * 2
  296.                     NEXT
  297.                 END IF
  298.             ELSE 'It's a vertical menu
  299.                 LINE (x, y)-(r, b), Menu(Which).BGC, BF
  300.                 pw = Menu(Which).Wide \ _FONTWIDTH - 2
  301.                 FOR i = 1 TO Menu(Which).Options
  302.  
  303.                     Caption = Captions(Which, i)
  304.                     COLOR Menu(Which).FC, 0
  305.                     IF GCC(Caption, "É", p%) <> "" THEN
  306.                         BorderIndent = _FONTWIDTH
  307.                         IF Menu(Which).Active = i THEN LINE (x + BorderIndent, y + fh * (i - 1))-STEP(Menu(Which).Wide - 2 * BorderIndent, fh), Menu(Which).HC, BF
  308.                         _PRINTSTRING (x, y + fh * (i - 1)), "É" + STRING$(pw, "Í") + "»"
  309.                     ELSEIF GCC(Caption, "º", p%) <> "" THEN
  310.                         BorderIndent = _FONTWIDTH
  311.                         IF Menu(Which).Active = i THEN LINE (x + BorderIndent, y + fh * (i - 1))-STEP(Menu(Which).Wide - 2 * BorderIndent, fh), Menu(Which).HC, BF
  312.                         _PRINTSTRING (x, y + fh * (i - 1)), "º" + STRING$(pw, " ") + "º"
  313.                     ELSEIF GCC(Caption, "È", p%) <> "" THEN
  314.                         BorderIndent = _FONTWIDTH
  315.                         IF Menu(Which).Active = i THEN LINE (x + BorderIndent, y + fh * (i - 1))-STEP(Menu(Which).Wide - 2 * BorderIndent, fh), Menu(Which).HC, BF
  316.                         _PRINTSTRING (x, y + fh * (i - 1)), "È" + STRING$(pw, "Í") + "¼"
  317.                     ELSEIF GCC(Caption, "Ì", p%) <> "" THEN
  318.                         BorderIndent = _FONTWIDTH
  319.                         IF Menu(Which).Active = i THEN LINE (x + BorderIndent, y + fh * (i - 1))-STEP(Menu(Which).Wide - 2 * BorderIndent, fh), Menu(Which).HC, BF
  320.                         _PRINTSTRING (x, y + fh * (i - 1)), "Ì" + STRING$(pw, "Í") + "¹"
  321.                     ELSE
  322.                         IF Menu(Which).Active = i THEN LINE (x, y + fh * (i - 1))-STEP(Menu(Which).Wide, fh), Menu(Which).HC, BF
  323.                         BorderIndent = 0
  324.                     END IF
  325.                     CC$ = GCC(Caption, "#", p%)
  326.                     IF CC$ <> "" THEN COLOR VAL("&HFF" + CC$ + "~&"), 0
  327.                     Caption = SCC(Caption)
  328.                     _PRINTSTRING (x + BorderIndent, y + fh * (i - 1)), Caption
  329.                     IF Menu(Which).Active THEN
  330.                         CC$ = GCC(Captions(Which, i), "@", p%)
  331.                         IF p% THEN
  332.                             COLOR Menu(Which).LC, 0
  333.                             _PRINTSTRING (x + (p% - 1) * _FONTWIDTH + BorderIndent, y + fh * (i - 1)), CC$
  334.                         END IF
  335.                     END IF
  336.                 NEXT
  337.             END IF
  338.         END IF
  339.     NEXT
  340.     COLOR DC, BG
  341.  
  342. SUB AutoSetMenuSize (Which AS INTEGER) 'Sub to set the width of a menu
  343.     FOR i = 1 TO Menu(Which).Options
  344.         tw = _PRINTWIDTH(SCC(Captions(Which, i))) 'temp width
  345.         IF tw > Menu(Which).Wide THEN Menu(Which).Wide = tw 'if the width is less than that temp width, correct that
  346.     NEXT
  347.     Menu(Which).Wide = Menu(Which).Wide + _FONTWIDTH
  348.     IF (Menu(Which).Layout AND Hortizontal) = 0 THEN Menu(Which).High = _FONTHEIGHT * Menu(Which).Options
  349.  
  350.  
  351. FUNCTION SCC$ (FromWhat$) 'Strip Command Code
  352.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  353.     ListOfCommand$ = "{@#ɺÈÍÌ"
  354.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  355.     '#NNNNNN are used to set 6 character RGB colors.
  356.     '{ are used to designate links
  357.     SCC$ = FromWhat$
  358.     FOR i = 1 TO LEN(ListOfCommand$)
  359.         m$ = MID$(ListOfCommand$, i, 1)
  360.         DO
  361.             l = INSTR(SCC$, m$)
  362.             IF l THEN
  363.                 SELECT CASE m$
  364.                     CASE "{" 'remove anything to the right of {, as the link is our last command
  365.                         'This command should only ever be processed once, as the first time it appears
  366.                         'it strips off everything to the right of it.
  367.                         SCC$ = LEFT$(SCC$, l - 1)
  368.                     CASE "@", "É", "º", "È", "Í", "Ì" 'just strip out the command code
  369.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 1)
  370.                     CASE "#" 'need to strip out the command code and the next 6 color characters
  371.                         SCC$ = LEFT$(SCC$, l - 1) + MID$(SCC$, l + 7)
  372.                 END SELECT
  373.             END IF
  374.         LOOP UNTIL l = 0
  375.     NEXT
  376.  
  377. FUNCTION SCCL$ (FromWhat$, ListOfCommand$) 'Strip Command Code Lite
  378.     'Requires specifiication of what commands to strip from the text
  379.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  380.     '@ are used for hotkey creation, such as @File will give us an Alt-F hotkey for File.
  381.     '#NNNNNN are used to set 6 character RGB colors.
  382.     '{ are used to designate links
  383.     SCCL$ = FromWhat$
  384.     FOR i = 1 TO LEN(ListOfCommand$)
  385.         m$ = MID$(ListOfCommand$, i, 1)
  386.         DO
  387.             l = INSTR(SCCL$, m$)
  388.             IF l THEN
  389.                 SELECT CASE m$
  390.                     CASE "{" 'remove anything to the right of {, as the link is our last command
  391.                         'This command should only ever be processed once, as the first time it appears
  392.                         'it strips off everything to the right of it.
  393.                         SCCL$ = LEFT$(SCCL$, l - 1)
  394.                     CASE "@", "É", "º", "È", "Í", "Ì" 'just strip out the command code
  395.                         SCCL$ = LEFT$(SCCL$, l - 1) + MID$(SCCL$, l + 1)
  396.                     CASE "#" 'need to strip out the command code and the next 6 color characters
  397.                         SCCL$ = LEFT$(SCCL$, l - 1) + MID$(SCCL$, l + 7)
  398.                 END SELECT
  399.             END IF
  400.         LOOP UNTIL l = 0
  401.     NEXT
  402.  
  403.  
  404.  
  405. FUNCTION GCC$ (FromWhat$, WhichCommand$, FromPosition AS INTEGER) 'Get Command Code
  406.     cc = INSTR(FromWhat$, WhichCommand$)
  407.     'Strip Off any command codes (such as link to sub menu) and just return the caption itself
  408.     SELECT CASE WhichCommand$
  409.         CASE "{" 'return everything between the brackets
  410.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1) 'return "" if there's no link when requested
  411.             cc1 = INSTR(GCC$, "}")
  412.             IF cc1 THEN GCC$ = LEFT$(GCC$, cc1 - 1) 'Remove anything after the link
  413.         CASE "@" 'Return the character after
  414.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1, 1)
  415.             cc = INSTR(SCCL(FromWhat$, "#{ɺÈÍÌ"), "@") 'find the position without any other command codes throwing off the location
  416.         CASE "#" 'return the 6 characters after
  417.             IF cc THEN GCC$ = MID$(FromWhat$, cc + 1, 6)
  418.         CASE "É", "º", "È", "Í", "Ì" 'just return the code as verification that it's there.
  419.             IF cc THEN GCC$ = WhichCommand$
  420.     END SELECT
  421.     FromPosition = cc
  422.  
  423. SUB AddMenuItem (Which AS INTEGER, What$)
  424.     CheckHandle Which
  425.     o = Menu(Which).Options + 1
  426.     Menu(Which).Options = o
  427.     Captions(Which, o) = What$
  428.     IF Menu(Which).Layout AND NOT Hortizontal THEN AutoSetMenuSize (Which)
  429.  
  430. SUB MenuActive (Which AS INTEGER, IsActive AS INTEGER)
  431.     CheckHandle Which
  432.     IF IsActive THEN Menu(Which).Active = 1 ELSE Menu(Which).Active = 0
  433.  
  434.  
  435. FUNCTION CreateMenu (fName AS STRING, fLayout AS INTEGER, fCloseable AS INTEGER)
  436.     FOR i = 1 TO UBOUND(Menu)
  437.         IF Menu(i).Handle = 0 THEN EXIT FOR 'it's a freehandle
  438.     NEXT
  439.     IF i > UBOUND(Menu) THEN
  440.         REDIM _PRESERVE Menu(i + 10) AS Menu_Type
  441.         REDIM _PRESERVE Captions(255, i + 10) AS STRING 'each menu can have up to 256 entries max
  442.     END IF
  443.     Menu(i).Handle = i
  444.     Menu(i).Name = fName
  445.     Menu(i).Layout = fLayout
  446.     Menu(i).Closeable = fCloseable
  447.     Menu(i).Top = 0: Menu(i).Left = 0
  448.     IF fLayout AND Hortizontal THEN Menu(i).Wide = _WIDTH: Menu(i).High = _FONTHEIGHT
  449.     IF fCloseable THEN
  450.         Menu(i).Options = 1 'make the collapse button our first option
  451.         Captions(i, 1) = "ð"
  452.     ELSE
  453.         Menu(i).Options = 0 'no options yet
  454.     END IF
  455.     Menu(i).Active = 0
  456.     Menu(i).BGC = DarkGray
  457.     Menu(i).HC = LightGray
  458.     Menu(i).FC = Black
  459.     Menu(i).LC = White
  460.     CreateMenu = i
  461.  
  462. SUB SetMenuPos (Which AS INTEGER, WhereX AS INTEGER, WhereY AS INTEGER)
  463.     CheckHandle Which
  464.     Menu(Which).Top = WhereY
  465.     Menu(Which).Left = WhereX
  466.  
  467. SUB SetMenuSize (Which AS INTEGER, Wide AS INTEGER, High AS INTEGER)
  468.     CheckHandle Which
  469.     IF Wide = 0 THEN Wide = _WIDTH
  470.     IF High = 0 THEN High = _FONTWIDTH + 4
  471.     Menu(Which).Wide = Wide
  472.     Menu(Which).High = High
  473.  
  474. SUB CheckHandle (Which AS INTEGER)
  475.     IF Which < 0 OR Which > UBOUND(Menu) THEN ERROR 5: EXIT SUB
  476.     IF Menu(Which).Handle = 0 THEN ERROR 5: EXIT SUB

Menu system now allows us to create a nice little framed border around our vertical menus, if we want, as the attached screenshot illustrates, and it allows us to create dividers in the menu simply for spacing and ease of reading/scanning with our eyes.

There's no boxing around horizontal menus (wouldn't they be more "buttons" than a framed box?), but the framework is there to add them if desired now.  :)

 
Screenshot.jpg
* Screenshot.jpg (Filesize: 21.58 KB, Dimensions: 806x629, Views: 188)
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline TempodiBasic

  • Forum Resident
  • Posts: 1792
    • View Profile
Re: Menu System
« Reply #17 on: December 22, 2019, 02:43:45 pm »
Great, an indipendent system to make Menu.

IMHO it can be used also to manage different objects.
Menu or Button or Radiobutton or CheckBox or Switch all need a surface to be visible and interact with user and need one click or one key to be activated.
Surely each object has its own aspect and its own output routine for graphic custom aspect.


PS
my routine is not so far from your way, but it didn't work so I couldn't do a Christmas present to you! But the Uncostant wins! :-)
Code: QB64: [Select]
  1. ' in the main loop there is
  2.     mI = _MOUSEINPUT
  3.     IF mI = True AND k& = 0 THEN GetMouse (k&)
  4.  
  5. SUB GetMouse (Ke AS LONG)
  6.     DIM mX AS INTEGER, mY AS INTEGER, i AS INTEGER, l AS INTEGER
  7.  
  8.     DO
  9.         _PRINTSTRING (1, 100), STR$(_MOUSEX) + " " + STR$(_MOUSEY)
  10.         ' left click
  11.         mX = _MOUSEX
  12.         mY = _MOUSEY
  13.         'searching an active voice
  14.         FOR i = 1 TO UBOUND(Menu)
  15.             IF Menu(i).Handle <> 0 AND (Menu(i).Layout AND Visible) THEN ' se esiste la voce menu
  16.                 IF Menu(i).Top < mY AND (Menu(i).Top + Menu(i).High) > mY THEN
  17.  
  18.                     IF Menu(i).Left < mX AND (Menu(i).Left + Menu(i).Wide) > mX THEN
  19.                         '  PRINT " in the row"; Menu(i).Top, mY, (Menu(i).Top + Menu(i).High):
  20.                         '   PRINT " in the column"; Menu(i).Left, mX, (Menu(i).Left + LEN(Menu(i).NAME) * _FONTWIDTH)
  21.                         '  PRINT Menu(i).NAME: _DISPLAY: SLEEP 4
  22.  
  23.                         IF Menu(i).Active THEN
  24.                             'user choices active voice
  25.                             ' PRINT " selected activated "; Menu(i).NAME; "i": _DISPLAY: SLEEP 1
  26.                             Ke = 13
  27.                             EXIT SUB
  28.                         ELSE
  29.                             '  PRINT " selected item "; Menu(i).NAME; "i": _DISPLAY: SLEEP 1
  30.                             Menu(i).Active = True
  31.                             FOR l = LBOUND(menu) TO i - 1
  32.                                 IF Menu(l).Active THEN Menu(l).Active = False: EXIT SUB
  33.                             NEXT l
  34.                             FOR l = i + 1 TO UBOUND(menu)
  35.                                 IF Menu(l).Active THEN Menu(l).Active = False: EXIT SUB
  36.                             NEXT
  37.                         END IF
  38.                     END IF
  39.                 END IF
  40.             END IF
  41.         NEXT
  42.     END IF
Programming isn't difficult, only it's  consuming time and coffee