MMBasic menu launcher for PicoCalc

Here’s the code for a much better menu launcher for the pico calc.

’ ============================================
’ File Browser / Program Launcher
’ MMBasic for PicoCalc (320x320)
’ ============================================
’ Features:
’ - Lists *.bas files and directories
’ - Cursor navigation with sliding window
’ - Enter/Right: run program or enter folder
’ - Left: go up one directory level
’ - ESC: exit menu
’ ============================================
OPTION EXPLICIT
OPTION DEFAULT FLOAT

’ ============================================
’ Constants
’ ============================================
CONST SCR_W = 320
CONST SCR_H = 320

’ Key codes
CONST KEY_UP = 128
CONST KEY_DOWN = 129
CONST KEY_LEFT = 130
CONST KEY_RIGHT = 131
CONST KEY_DEL = 127
CONST KEY_BKSP = 8
CONST KEY_TAB = 9
CONST KEY_ENTER = 13
CONST KEY_ESC = 27
CONST KEY_HOME = 134
CONST KEY_END = 135
CONST KEY_PGUP = 136
CONST KEY_PGDN = 137

’ Colors - dark theme
CONST COL_BG = RGB(24, 28, 36)
CONST COL_FG = RGB(200, 205, 210)
CONST COL_HEADER = RGB(60, 70, 90)
CONST COL_HEADER_TXT = RGB(220, 225, 230)
CONST COL_CURSOR_BG = RGB(70, 130, 180)
CONST COL_CURSOR_FG = RGB(255, 255, 255)
CONST COL_DIR_FG = RGB(100, 180, 255)
CONST COL_FILE_FG = RGB(200, 205, 210)
CONST COL_FOOTER = RGB(40, 45, 55)
CONST COL_FOOTER_TXT = RGB(150, 155, 160)

’ Layout constants - using Font 1 (8x12)
CONST FONT_W = 8
CONST FONT_H = 12
CONST HEADER_H = 16
CONST FOOTER_H = 14
CONST LINE_H = 14
CONST LIST_TOP = 18
CONST LIST_LEFT = 4
CONST MAX_NAME_LEN = 38

’ Sliding window parameters
CONST SCROLL_MARGIN = 3

’ ============================================
’ Global Variables
’ ============================================
’ File list storage
DIM gFileNames$(200) LENGTH 50
DIM gFileIsDir%(200)
DIM gFileCount%

’ Navigation state
DIM gCursorPos%
DIM gWindowTop%
DIM gVisibleLines%

’ Display state for minimal redraw
DIM gPrevCursor%
DIM gPrevWindow%
DIM gNeedFullRedraw%
DIM gLastPath$

’ Current path display
DIM gCurrentPath$

’ ============================================
’ Initialization
’ ============================================
FONT 1
gVisibleLines% = (SCR_H - LIST_TOP - FOOTER_H) \ LINE_H
gNeedFullRedraw% = 1
gLastPath$ = “”

’ ============================================
’ Main Program
’ ============================================
Main:
LoadFileList
gCursorPos% = 0
gWindowTop% = 0
gPrevCursor% = -1
gPrevWindow% = -1
gNeedFullRedraw% = 1

DO
gCurrentPath$ = CWD$

IF gNeedFullRedraw% THEN
  DrawFullScreen
  gNeedFullRedraw% = 0
  gPrevCursor% = gCursorPos%
  gPrevWindow% = gWindowTop%
ELSEIF gPrevCursor% <> gCursorPos% OR gPrevWindow% <> gWindowTop% THEN
  UpdateDisplay
  gPrevCursor% = gCursorPos%
  gPrevWindow% = gWindowTop%
ENDIF

HandleInput
PAUSE 20

LOOP

END

’ ============================================
’ Load File List - directories and .bas files
’ ============================================
SUB LoadFileList
LOCAL dirEntry$, nIdx%

gFileCount% = 0

’ First, load directories
dirEntry$ = DIR$(“*”, DIR)
DO WHILE dirEntry$ <> “” AND gFileCount% < 200
IF dirEntry$ <> “.” AND dirEntry$ <> “..” THEN
gFileNames$(gFileCount%) = dirEntry$
gFileIsDir%(gFileCount%) = 1
gFileCount% = gFileCount% + 1
ENDIF
dirEntry$ = DIR$()
LOOP

’ Then load .bas files
dirEntry$ = DIR$(“*.bas”, FILE)
DO WHILE dirEntry$ <> “” AND gFileCount% < 200
gFileNames$(gFileCount%) = dirEntry$
gFileIsDir%(gFileCount%) = 0
gFileCount% = gFileCount% + 1
dirEntry$ = DIR$()
LOOP

’ Also check for .BAS (uppercase)
dirEntry$ = DIR$(“*.BAS”, FILE)
DO WHILE dirEntry$ <> “” AND gFileCount% < 200
’ Check if already added (avoid duplicates on case-insensitive systems)
nIdx% = 0
DO WHILE nIdx% < gFileCount%
IF UCASE$(gFileNames$(nIdx%)) = UCASE$(dirEntry$) THEN EXIT DO
nIdx% = nIdx% + 1
LOOP
IF nIdx% >= gFileCount% THEN
gFileNames$(gFileCount%) = dirEntry$
gFileIsDir%(gFileCount%) = 0
gFileCount% = gFileCount% + 1
ENDIF
dirEntry$ = DIR$()
LOOP

SortFileList
END SUB

’ ============================================
’ Simple bubble sort - directories first, then files alphabetically
’ ============================================
SUB SortFileList
LOCAL iOuter%, swapped%, tempName$, tempIsDir%

IF gFileCount% < 2 THEN EXIT SUB

DO
swapped% = 0
FOR iOuter% = 0 TO gFileCount% - 2
’ Directories come before files
IF gFileIsDir%(iOuter%) = 0 AND gFileIsDir%(iOuter% + 1) = 1 THEN
’ Swap: file before directory
tempName$ = gFileNames$(iOuter%)
tempIsDir% = gFileIsDir%(iOuter%)
gFileNames$(iOuter%) = gFileNames$(iOuter% + 1)
gFileIsDir%(iOuter%) = gFileIsDir%(iOuter% + 1)
gFileNames$(iOuter% + 1) = tempName$
gFileIsDir%(iOuter% + 1) = tempIsDir%
swapped% = 1
ELSEIF gFileIsDir%(iOuter%) = gFileIsDir%(iOuter% + 1) THEN
’ Same type: sort alphabetically
IF UCASE$(gFileNames$(iOuter%)) > UCASE$(gFileNames$(iOuter% + 1)) THEN
tempName$ = gFileNames$(iOuter%)
gFileNames$(iOuter%) = gFileNames$(iOuter% + 1)
gFileNames$(iOuter% + 1) = tempName$
swapped% = 1
ENDIF
ENDIF
NEXT iOuter%
LOOP WHILE swapped% = 1
END SUB

’ ============================================
’ Draw the complete screen
’ ============================================
SUB DrawFullScreen
LOCAL iLine%, fileIdx%, yPos%

CLS COL_BG

’ Draw header with current path
BOX 0, 0, SCR_W, HEADER_H, 0, COL_HEADER, COL_HEADER
DrawPathHeader

’ Draw footer with instructions
BOX 0, SCR_H - FOOTER_H, SCR_W, FOOTER_H, 0, COL_FOOTER, COL_FOOTER
DrawFooter

’ Draw file list or empty message
IF gFileCount% = 0 THEN
TEXT SCR_W\2, SCR_H\2, “(No .bas files or folders)”, “CM”, 1, 1, COL_FG, COL_BG
ELSE
FOR iLine% = 0 TO gVisibleLines% - 1
fileIdx% = gWindowTop% + iLine%
IF fileIdx% < gFileCount% THEN
yPos% = LIST_TOP + iLine% * LINE_H
DrawFileLine fileIdx%, yPos%, (fileIdx% = gCursorPos%)
ENDIF
NEXT iLine%
ENDIF
END SUB

’ ============================================
’ Draw path in header (truncated if needed)
’ ============================================
SUB DrawPathHeader
LOCAL dispPath$, countStr$, maxChars%, countLen%

’ Show file count on right side
countStr$ = “[” + STR$(gFileCount%) + “]”
countLen% = LEN(countStr$) * FONT_W + 8

maxChars% = (SCR_W - countLen% - 8) \ FONT_W
dispPath$ = gCurrentPath$

IF LEN(dispPath$) > maxChars% THEN
dispPath$ = “…” + RIGHT$(dispPath$, maxChars% - 3)
ENDIF

TEXT 4, 2, dispPath$, “LT”, 1, 1, COL_HEADER_TXT, COL_HEADER
TEXT SCR_W - 4, 2, countStr$, “RT”, 1, 1, COL_FOOTER_TXT, COL_HEADER
END SUB

’ ============================================
’ Draw footer with key instructions
’ ============================================
SUB DrawFooter
LOCAL helpStr$

helpStr$ = “Arrows/PgUp/Dn/Home/End Enter:Open Del:Delete Esc”
TEXT 4, SCR_H - FOOTER_H + 1, helpStr$, “LT”, 1, 1, COL_FOOTER_TXT, COL_FOOTER
END SUB

’ ============================================
’ Update display with minimal redraw
’ Only redraws lines that changed
’ ============================================
SUB UpdateDisplay
LOCAL iLine%, fileIdx%, yPos%

’ If window scrolled, redraw all visible lines
IF gPrevWindow% <> gWindowTop% THEN
FOR iLine% = 0 TO gVisibleLines% - 1
fileIdx% = gWindowTop% + iLine%
yPos% = LIST_TOP + iLine% * LINE_H
IF fileIdx% < gFileCount% THEN
DrawFileLine fileIdx%, yPos%, (fileIdx% = gCursorPos%)
ELSE
’ Clear empty line
BOX 0, yPos%, SCR_W, LINE_H, 0, COL_BG, COL_BG
ENDIF
NEXT iLine%
ELSE
’ Only cursor moved within same window - redraw old and new cursor lines
IF gPrevCursor% >= gWindowTop% AND gPrevCursor% < gWindowTop% + gVisibleLines% THEN
iLine% = gPrevCursor% - gWindowTop%
yPos% = LIST_TOP + iLine% * LINE_H
DrawFileLine gPrevCursor%, yPos%, 0
ENDIF

IF gCursorPos% >= gWindowTop% AND gCursorPos% < gWindowTop% + gVisibleLines% THEN
  iLine% = gCursorPos% - gWindowTop%
  yPos% = LIST_TOP + iLine% * LINE_H
  DrawFileLine gCursorPos%, yPos%, 1
ENDIF

ENDIF
END SUB

’ ============================================
’ Draw a single file line
’ ============================================
SUB DrawFileLine(fileIdx%, yPos%, isSelected%)
LOCAL dispName$, bgCol%, fgCol%, prefix$

’ Determine colors
IF isSelected% THEN
bgCol% = COL_CURSOR_BG
fgCol% = COL_CURSOR_FG
ELSE
bgCol% = COL_BG
IF gFileIsDir%(fileIdx%) THEN
fgCol% = COL_DIR_FG
ELSE
fgCol% = COL_FILE_FG
ENDIF
ENDIF

’ Clear the line background
BOX 0, yPos%, SCR_W, LINE_H, 0, bgCol%, bgCol%

’ Build display string with prefix for directories
IF gFileIsDir%(fileIdx%) THEN
prefix$ = "[D] "
ELSE
prefix$ = " "
ENDIF

dispName$ = prefix$ + gFileNames$(fileIdx%)

’ Truncate if too long
IF LEN(dispName$) > MAX_NAME_LEN THEN
dispName$ = LEFT$(dispName$, MAX_NAME_LEN - 3) + “…”
ENDIF

’ Draw the text
TEXT LIST_LEFT, yPos% + 1, dispName$, “LT”, 1, 1, fgCol%, bgCol%
END SUB

’ ============================================
’ Handle keyboard input
’ ============================================
SUB HandleInput
LOCAL keyIn$, keyCode%

keyIn$ = INKEY$
IF keyIn$ = “” THEN EXIT SUB

keyCode% = ASC(keyIn$)

SELECT CASE keyCode%
CASE KEY_UP
MoveCursorUp
CASE KEY_DOWN
MoveCursorDown
CASE KEY_PGUP
PageUp
CASE KEY_PGDN
PageDown
CASE KEY_HOME
JumpToTop
CASE KEY_END
JumpToBottom
CASE KEY_ENTER, KEY_RIGHT
ActivateItem
CASE KEY_LEFT
GoUpDirectory
CASE KEY_DEL
DeleteItem
CASE KEY_ESC
ExitMenu
END SELECT
END SUB

’ ============================================
’ Move cursor up with sliding window
’ ============================================
SUB MoveCursorUp
IF gFileCount% = 0 THEN EXIT SUB
IF gCursorPos% <= 0 THEN EXIT SUB

gCursorPos% = gCursorPos% - 1

’ Slide window if cursor near top
IF gCursorPos% < gWindowTop% + SCROLL_MARGIN THEN
IF gWindowTop% > 0 THEN
gWindowTop% = gWindowTop% - 1
IF gWindowTop% < 0 THEN gWindowTop% = 0
ENDIF
ENDIF
END SUB

’ ============================================
’ Move cursor down with sliding window
’ ============================================
SUB MoveCursorDown
IF gFileCount% = 0 THEN EXIT SUB
IF gCursorPos% >= gFileCount% - 1 THEN EXIT SUB

gCursorPos% = gCursorPos% + 1

’ Slide window if cursor near bottom
IF gCursorPos% >= gWindowTop% + gVisibleLines% - SCROLL_MARGIN THEN
IF gWindowTop% + gVisibleLines% < gFileCount% THEN
gWindowTop% = gWindowTop% + 1
ENDIF
ENDIF
END SUB

’ ============================================
’ Page Up - move up by nearly a full page
’ ============================================
SUB PageUp
LOCAL pageSize%

IF gFileCount% = 0 THEN EXIT SUB
IF gCursorPos% = 0 THEN EXIT SUB

pageSize% = gVisibleLines% - 2
IF pageSize% < 1 THEN pageSize% = 1

gCursorPos% = gCursorPos% - pageSize%
IF gCursorPos% < 0 THEN gCursorPos% = 0

gWindowTop% = gWindowTop% - pageSize%
IF gWindowTop% < 0 THEN gWindowTop% = 0
END SUB

’ ============================================
’ Page Down - move down by nearly a full page
’ ============================================
SUB PageDown
LOCAL pageSize%, maxWindow%

IF gFileCount% = 0 THEN EXIT SUB
IF gCursorPos% >= gFileCount% - 1 THEN EXIT SUB

pageSize% = gVisibleLines% - 2
IF pageSize% < 1 THEN pageSize% = 1

gCursorPos% = gCursorPos% + pageSize%
IF gCursorPos% >= gFileCount% THEN gCursorPos% = gFileCount% - 1

gWindowTop% = gWindowTop% + pageSize%
maxWindow% = gFileCount% - gVisibleLines%
IF maxWindow% < 0 THEN maxWindow% = 0
IF gWindowTop% > maxWindow% THEN gWindowTop% = maxWindow%
END SUB

’ ============================================
’ Jump to top of list
’ ============================================
SUB JumpToTop
IF gFileCount% = 0 THEN EXIT SUB

gCursorPos% = 0
gWindowTop% = 0
END SUB

’ ============================================
’ Jump to bottom of list
’ ============================================
SUB JumpToBottom
LOCAL maxWindow%

IF gFileCount% = 0 THEN EXIT SUB

gCursorPos% = gFileCount% - 1
maxWindow% = gFileCount% - gVisibleLines%
IF maxWindow% < 0 THEN maxWindow% = 0
gWindowTop% = maxWindow%
END SUB

’ ============================================
’ Activate selected item (run file or enter directory)
’ ============================================
SUB ActivateItem
LOCAL selectedName$

IF gFileCount% = 0 THEN EXIT SUB

selectedName$ = gFileNames$(gCursorPos%)

IF gFileIsDir%(gCursorPos%) THEN
’ Enter directory
EnterDirectory selectedName$
ELSE
’ Run the BASIC program
RunProgram selectedName$
ENDIF
END SUB

’ ============================================
’ Delete selected item with confirmation
’ ============================================
SUB DeleteItem
LOCAL selectedName$, confirmMsg$, keyIn$, confirmed%

IF gFileCount% = 0 THEN EXIT SUB

selectedName$ = gFileNames$(gCursorPos%)

’ Don’t allow deleting directories (for safety)
IF gFileIsDir%(gCursorPos%) THEN
ShowMessage “Cannot delete folders”
PAUSE 1000
RestoreFooter
EXIT SUB
ENDIF

’ Build confirmation message
confirmMsg$ = "Delete " + selectedName$ + “? (Y/N)”
IF LEN(confirmMsg$) > 39 THEN
confirmMsg$ = “Delete …? (Y/N)”
ENDIF

’ Show confirmation prompt
BOX 0, SCR_H - FOOTER_H, SCR_W, FOOTER_H, 0, COL_FOOTER, COL_FOOTER
TEXT 4, SCR_H - FOOTER_H + 1, confirmMsg$, “LT”, 1, 1, RGB(255,200,100), COL_FOOTER

’ Wait for Y or N
confirmed% = 0
DO
keyIn$ = INKEY$
IF keyIn$ <> “” THEN
IF UCASE$(keyIn$) = “Y” THEN
confirmed% = 1
EXIT DO
ELSEIF UCASE$(keyIn$) = “N” OR ASC(keyIn$) = KEY_ESC THEN
EXIT DO
ENDIF
ENDIF
PAUSE 20
LOOP

IF confirmed% THEN
’ Attempt to delete the file
ON ERROR SKIP 1
KILL selectedName$
IF MM.ERRNO = 0 THEN
’ Successfully deleted - reload file list
LoadFileList
’ Adjust cursor if it’s now past the end
IF gCursorPos% >= gFileCount% THEN
gCursorPos% = gFileCount% - 1
IF gCursorPos% < 0 THEN gCursorPos% = 0
ENDIF
’ Adjust window if needed
IF gWindowTop% + gVisibleLines% > gFileCount% THEN
gWindowTop% = gFileCount% - gVisibleLines%
IF gWindowTop% < 0 THEN gWindowTop% = 0
ENDIF
gNeedFullRedraw% = 1
ELSE
ShowMessage “Delete failed!”
PAUSE 1000
ENDIF
ON ERROR CLEAR
ENDIF

RestoreFooter
END SUB

’ ============================================
’ Show a message on the footer line
’ ============================================
SUB ShowMessage(msg$)
BOX 0, SCR_H - FOOTER_H, SCR_W, FOOTER_H, 0, COL_FOOTER, COL_FOOTER
TEXT 4, SCR_H - FOOTER_H + 1, msg$, “LT”, 1, 1, RGB(255,100,100), COL_FOOTER
END SUB

’ ============================================
’ Restore the normal footer
’ ============================================
SUB RestoreFooter
BOX 0, SCR_H - FOOTER_H, SCR_W, FOOTER_H, 0, COL_FOOTER, COL_FOOTER
DrawFooter
END SUB

’ ============================================
’ Enter a subdirectory
’ ============================================
SUB EnterDirectory(dirName$)
ON ERROR SKIP 1
CHDIR dirName$
IF MM.ERRNO = 0 THEN
’ Successfully changed directory
LoadFileList
gCursorPos% = 0
gWindowTop% = 0
gPrevCursor% = -1
gPrevWindow% = -1
gNeedFullRedraw% = 1
ENDIF
ON ERROR CLEAR
END SUB

’ ============================================
’ Go up one directory level
’ ============================================
SUB GoUpDirectory
LOCAL curPath$

curPath$ = CWD$
IF curPath$ = “/” OR curPath$ = “” OR curPath$ = “A:/” OR curPath$ = “A:” THEN
’ Already at root
EXIT SUB
ENDIF

ON ERROR SKIP 1
CHDIR “..”
IF MM.ERRNO = 0 THEN
LoadFileList
gCursorPos% = 0
gWindowTop% = 0
gPrevCursor% = -1
gPrevWindow% = -1
gNeedFullRedraw% = 1
ENDIF
ON ERROR CLEAR
END SUB

’ ============================================
’ Run the selected BASIC program
’ Uses RUN to load and execute the program
’ ============================================
SUB RunProgram(fileName$)
’ Clear screen before running
CLS RGB(BLACK)
TEXT 10, 10, "Loading: " + fileName$, “LT”, 1, 1, RGB(WHITE), RGB(BLACK)

’ Use RUN to load and execute the program
’ This clears all variables, avoiding conflicts with other programs
RUN fileName$
END SUB

’ ============================================
’ Clean exit from menu
’ ============================================
SUB ExitMenu
CLS RGB(BLACK)
TEXT 10, 10, “Exiting menu…”, “LT”, 1, 1, RGB(WHITE), RGB(BLACK)
PAUSE 500
CLS
END
END SUB

3 Likes