Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
My little menu
#11
Nicely done, I love seeing how we all code so differently...I got me old Menu system from GDK and with Google made it this...hopefully youll be able to glean something from it...works with mouse and KB, title of the window returns the selected item...

Code: (Select All)
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////
'// PRO-CODER MENU SYSTEM (2025 EDITION)
'// Features: Keyboard Debouncing, Mouse Hover/Click, Mouse Wheel, Dynamic Alignment
'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

'--- STRUCTURES ---

TYPE GDK_Vector_2D
    x AS SINGLE
    y AS SINGLE
END TYPE

TYPE GDK_Mouse_State
    X AS INTEGER
    Y AS INTEGER
    L AS _BYTE      ' Left Button
    R AS _BYTE      ' Right Button
    Wheel AS INTEGER ' Scroll Delta
END TYPE

TYPE GDK_Menu
    Position AS GDK_Vector_2D
    Fnt AS LONG
    OFF_CLR AS _UNSIGNED LONG
    ON_CLR AS _UNSIGNED LONG
    Entries AS _UNSIGNED _BYTE
    Ref AS _UNSIGNED _BYTE    ' The index of the currently selected/highlighted item
    Min_Ref AS _UNSIGNED _BYTE ' The index of the first item visible in the "window"
    Max_Ref AS _UNSIGNED _BYTE ' How many items to show at once (scrolling window size)
    Alignment AS _BYTE        ' -1 = Left, 0 = Center, 1 = Right
    LastInput AS SINGLE        ' Timer used to slow down keyboard repeats
    InputDelay AS SINGLE      ' Seconds to wait between keyboard inputs (e.g., 0.15)
END TYPE

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

' Global Mouse State (0 = Current Frame, 1 = Previous Frame)
' This allows us to detect "Clicks" (was not pressed last frame, is pressed this frame)
DIM SHARED GDK_Mouse(1) AS GDK_Mouse_State

SCREEN _NEWIMAGE(800, 600, 32)

'--- MENU SETUP ---
DIM MainMenu AS GDK_Menu
DIM MainStr(10) AS STRING * 32 ' Fixed length strings for memory safety
DIM MenuFnt&

' Load a standard system font
MenuFnt& = _LOADFONT("C:\windows\fonts\arial.ttf", 22)

' Populate Menu Data
MainStr(0) = "Resume Mission"
MainStr(1) = "New Campaign"
MainStr(2) = "Load Game"
MainStr(3) = "Display Settings"
MainStr(4) = "Audio Mix"
MainStr(5) = "Controller Config"
MainStr(6) = "Online Lobby"
MainStr(7) = "Credits"
MainStr(8) = "Bug Reporter"
MainStr(9) = "Privacy Policy"
MainStr(10) = "Exit to Desktop"

' Initialize: X=400, Y=150, Total=11, Align=0(Center), Font, HoverColor, IdleColor, WindowSize=6
GDK_Menu_New MainMenu, 400, 150, 11, 0, MenuFnt&, _RGB32(255, 255, 0), _RGB32(255, 255, 255), 6

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

DO
    _LIMIT 60 ' Limit loop to 60 FPS
    CLS

    GDK_Update_Mouse
    GDK_Menu_Update MainMenu, MainStr()

    ' Handle Selection (Enter Key or Left Mouse Click)
    IF _KEYDOWN(13) OR (GDK_Mouse(0).L AND NOT GDK_Mouse(1).L) THEN
        ' PRO TIP: You would use a SELECT CASE MainMenu.Ref here to trigger game actions
        _TITLE "Selected: " + LTRIM$(RTRIM$(MainStr(MainMenu.Ref)))
    END IF

    GDK_Menu_Render MainMenu, MainStr()

    _DISPLAY
LOOP UNTIL INKEY$ = CHR$(27) ' Exit on Escape

'///////////////////////////////////////////////////////////////////////////////////////////////////////////////

' Constructor for the Menu Object
SUB GDK_Menu_New (Menu AS GDK_Menu, x%, y%, Entries~%%, Alignment%%, Fnt&, Clr_On~&, Clr_Off~&, Max~%%)
    Menu.Position.x = x%
    Menu.Position.y = y%
    Menu.Entries = Entries~%%
    Menu.Alignment = Alignment%%
    Menu.Ref = 0
    Menu.Min_Ref = 0
    Menu.Max_Ref = Max~%%
    Menu.OFF_CLR = Clr_Off~&
    Menu.ON_CLR = Clr_On~&
    Menu.Fnt = Fnt&
    Menu.InputDelay = 0.15 ' Default debounce speed
END SUB

' Handles all Input logic
SUB GDK_Menu_Update (Menu AS GDK_Menu, Strings() AS STRING * 32)
    _FONT Menu.Fnt
    LOCAL_H = _FONTHEIGHT(Menu.Fnt) + 6 ' Vertical spacing including padding
   
    ' 1. KEYBOARD NAVIGATION (with Debounce)
    ' This prevents the selection from "flying" past options when holding a key
    IF TIMER - Menu.LastInput > Menu.InputDelay THEN
        IF _KEYDOWN(18432) THEN ' Arrow Up
            Menu.Ref = (Menu.Ref + Menu.Entries - 1) MOD Menu.Entries
            Menu.LastInput = TIMER
        ELSEIF _KEYDOWN(20480) THEN ' Arrow Down
            Menu.Ref = (Menu.Ref + 1) MOD Menu.Entries
            Menu.LastInput = TIMER
        END IF
    END IF

    ' 2. MOUSE WHEEL NAVIGATION
    ' Wheel logic is "clamped" (doesn't wrap) for better user feel in long lists
    IF GDK_Mouse(0).Wheel <> 0 THEN
        IF GDK_Mouse(0).Wheel < 0 AND Menu.Ref > 0 THEN Menu.Ref = Menu.Ref - 1
        IF GDK_Mouse(0).Wheel > 0 AND Menu.Ref < Menu.Entries - 1 THEN Menu.Ref = Menu.Ref + 1
    END IF

    ' 3. MOUSE HOVER NAVIGATION
    ' We calculate a "Bounding Box" for every visible menu item
    FOR i = 0 TO Menu.Max_Ref - 1
        currIdx = Menu.Min_Ref + i
        IF currIdx >= Menu.Entries THEN EXIT FOR
       
        txt$ = RTRIM$(Strings(currIdx))
        tw = _PRINTWIDTH(txt$) ' Get width in pixels for current font
       
        ' Calculate X boundaries based on Alignment
        SELECT CASE Menu.Alignment
            CASE -1: x1 = Menu.Position.x: x2 = x1 + tw          ' Left
            CASE 0:  x1 = Menu.Position.x - (tw / 2): x2 = x1 + tw ' Center
            CASE 1:  x1 = Menu.Position.x - tw: x2 = Menu.Position.x ' Right
        END SELECT
       
        ' Calculate Y boundaries
        y1 = Menu.Position.y + (i * LOCAL_H)
        y2 = y1 + LOCAL_H

        ' Check if mouse is inside this item's box
        IF GDK_Mouse(0).X >= x1 AND GDK_Mouse(0).X <= x2 AND GDK_Mouse(0).Y >= y1 AND GDK_Mouse(0).Y <= y2 THEN
            ' Only change Ref if mouse actually moves.
            ' This allows keyboard/wheel selection to stay put even if the mouse is sitting on the screen.
            IF GDK_Mouse(0).X <> GDK_Mouse(1).X OR GDK_Mouse(0).Y <> GDK_Mouse(1).Y THEN
                Menu.Ref = currIdx
            END IF
        END IF
    NEXT

    ' 4. SCROLLING VIEWPORT LOGIC
    ' This keeps the "Selected" item inside the visible window of items
    IF Menu.Ref < Menu.Min_Ref THEN
        Menu.Min_Ref = Menu.Ref
    ELSEIF Menu.Ref >= Menu.Min_Ref + Menu.Max_Ref THEN
        Menu.Min_Ref = Menu.Ref - Menu.Max_Ref + 1
    END IF
END SUB

' Handles all Visual logic
SUB GDK_Menu_Render (Menu AS GDK_Menu, Strings() AS STRING * 32)
    _FONT Menu.Fnt
    LineHeight% = _FONTHEIGHT(Menu.Fnt) + 6
   
    FOR i = 0 TO Menu.Max_Ref - 1
        Mnu% = Menu.Min_Ref + i
        IF Mnu% >= Menu.Entries THEN EXIT FOR

        txt$ = RTRIM$(Strings(Mnu%))
        currY% = Menu.Position.y + (i * LineHeight%)

        ' Set colors based on selection
        IF Mnu% = Menu.Ref THEN
            COLOR Menu.ON_CLR
        ELSE
            COLOR Menu.OFF_CLR
        END IF

        ' Render text based on Alignment setting
        SELECT CASE Menu.Alignment
            CASE -1 ' Left Align
                _PRINTSTRING (Menu.Position.x, currY%), txt$
            CASE 0 ' Center Align
                _PRINTSTRING (Menu.Position.x - (_PRINTWIDTH(txt$) / 2), currY%), txt$
            CASE 1 ' Right Align
                _PRINTSTRING (Menu.Position.x - _PRINTWIDTH(txt$), currY%), txt$
        END SELECT
    NEXT
END SUB

' Collects Mouse Data and saves previous state
SUB GDK_Update_Mouse
    GDK_Mouse(1) = GDK_Mouse(0) ' Store last frame for click detection
    GDK_Mouse(0).Wheel = 0      ' Reset wheel delta per frame
   
    DO WHILE _MOUSEINPUT
        GDK_Mouse(0).L = _MOUSEBUTTON(1)
        GDK_Mouse(0).X = _MOUSEX
        GDK_Mouse(0).Y = _MOUSEY
        GDK_Mouse(0).Wheel = GDK_Mouse(0).Wheel + _MOUSEWHEEL
    LOOP
END SUB

Happy Coding

Unseen
Reply
#12
Neat job! You might want to consider changing this...

1) Put mouse on entry #8 and keep it there.
2) Use the arrow keys to go up and highlight entry #2.
3) Without moving the mouse, make a left click.

Now we clicked 8 but since 2 was highlighted, it returned #2.

If you want it that way, great, but if not, just allow a click to first reposition the highlighting to the mouse row, and then your click will return the entry of the mouse row.

Pete
Reply
#13
Are you suggesting that when a menu is active (will need a new flag) that the mouse auto locates itself to the highlighted position? If so, i can see that...also should mouse and KB then be synced? Ie, the KB moves the selected option and the mouse follows it?

I made it as a demo so if you want, please mod away! Also if anyone can show me how to store the menu strings inside the UDT! Then youll get a +1 from me! 

Thanks

John
Reply
#14
Nice menu Mad Aceman,

Following the hint of bplus I rewrote the drawing of the frame with the "chr$(x)" statement:

Code: (Select All)
' draw the border if needed

IF pb = 1 THEN

  COLOR c5, c2
  pb = 0

  LOCATE py, px
  PRINT CHR$(201); '    "Õ";
  FOR i = 1 TO mr
    PRINT CHR$(205); '  "Í";
  NEXT
  PRINT CHR$(187) '    "¸"

  FOR i = 1 TO pt
    LOCATE py + i, px
    PRINT CHR$(186); STRING$(mr, 32); CHR$(186) ' "³"; and  "³"
  NEXT

  LOCATE py + pt + 1, px
  PRINT CHR$(200); '    "Ô";
  FOR i = 1 TO mr
    PRINT CHR$(205); '  "Í";
  NEXT
  PRINT CHR$(188) '    "¾"

  py = py + 1
  px = px + 1

END IF
Reply
#15
Quote:Also if anyone can show me how to store the menu strings inside the UDT! Then youll get a +1 from me! 

Simple, for me, is using long strings. I used them in my very simple GUI for any array I needed, that PLUS  the split routine Yes not exactly most effecient but it works, perfect for Text Property of List Boxes. Long strings are easy to append (no dim _preserve) and easy slice and dice and substitute whole sections of strings. I use them in my Fval() (like Eval of other BASICs) code too which is core to my Interpreter work. UDT will take a variable length string since way back around time of QB64 v 2.0, you may have missed the change, by Luke, I think it was. Alas! they don't store well to file like a fixed string would.

BTW let me plug my drop down menus lecture (because no one else will Smile ) that starts here with exploring menus:
https://qb64phoenix.com/forum/showthread...8#pid17118
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply
#16
@Unseen Machine and @Mad Axeman

John, your post swooped in front of mine. I was actually commenting on Mad's, but I tested your menu and it also picks the highlighted menu option, instead of where the mouse is pointing, when the mouse is clicked. Mine and some others like the QB64 IDE prioritize the mouse position over the highlighted selection. Of course pressing Enter should always pick the highlighted selection, but the mouse? Well, I suppose it is a matter of personal preference.

Now all Mad has to do is change to...

        If menu_mb1 = -1 Then
            ''''' menu = menu_z1 + 1
            menu = menu_my - menu_py + 1 ' This will make it select the mouse position instead of the highlighted menu item.
            Color menu_defc1, menu_defc2
            Return
        End If

However yours is graphics, and I can't apply the same text solution.

Anyway, it's a neat graphics menu! + 1. As far as the strings in UDT, like Mark mentioned, we can use variable length strings...

Type Tremendous
Pete as String
End Type
Dim me as Tremendous

Big Grin 

I won't be completely happy until we can include fixed strings and arrays in UDTs. Actually, I didn't know fixed strings were an issue, as I haven't worked with those in any of my routines with a UDT format. +1 to Mark for that heads up!

Pete
Shoot first and shoot people who ask questions, later.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Menu Demo to be Expanded Pete 29 1,607 01-31-2026, 11:08 PM
Last Post: Pete
  Hardware Popup Menu. Yes, Dragable! Pete 2 682 04-03-2025, 12:15 AM
Last Post: Pete
  Text Menu Library Project Pete 3 728 01-03-2025, 05:55 PM
Last Post: Pete
  EVMS -- Easy Versatile Menu System SMcNeill 8 1,731 11-30-2022, 06:15 AM
Last Post: SMcNeill
  a simple menu system (works) madscijr 0 632 07-28-2022, 11:07 PM
Last Post: madscijr

Forum Jump:


Users browsing this thread: