VT — Virtual Text Screen Library

DOS-style text screen for FreeBASIC. Two backends: an SDL2 windowed renderer (full feature set) and a headless TTY renderer for direct terminal output (no SDL2 needed). Single include, QBasic-feel API. Target: Windows, Linux, FreeBASIC 1.10.1.

' SDL2 backend (default — full features, requires SDL2.dll):
#include once "vt/vt.bi"

' TTY backend (no SDL2, outputs to terminal — experimental):
#Define VT_TTY
#include once "vt/vt.bi"

Table of Contents


Quick Start

' Minimal hello-world program.
#include once "vt/vt.bi"

vt_title "Hello"
vt_screen VT_SCREEN_0       ' 80x25, 8x16 font

vt_color VT_YELLOW, VT_BLACK
vt_print_center 12, "Hello, World!"

vt_color VT_LIGHT_GREY, VT_BLACK
vt_print_center 14, "Press any key..."
vt_present()

vt_sleep 0   ' wait for keypress

Backends

VT selects a rendering backend at compile time via a #Define placed before the include. Without any define the SDL2 backend is used.

Backend Defines

DefineBackendDescription
(none)SDL2Full feature set. Opens an SDL2 window with CP437 font, palette, mouse, sound, copy/paste. Requires SDL2.dll / libsdl2.
VT_USE_ANSISDL2 + ANSI parserSDL2 backend plus ANSI escape sequence parsing inside vt_print. Useful when string data already contains ANSI color codes. Supported: SGR (ESC[…m), CUP (ESC[row;colH), ED (ESC[2J).
VT_TTYTTY (headless)Outputs directly to the terminal via ANSI escape sequences. No SDL2 required. Implies VT_USE_ANSI. Experimental.
VT_TTY implies VT_USE_ANSI automatically — you do not need both.
#define VT_USE_SOUND combined with #define VT_TTY produces a compile error (intentional).

VT_TTY — Headless Terminal Backend

The TTY backend uses ANSI escape sequences to render the cell grid directly in the calling terminal. It shares the same API as the SDL2 backend; functions that have no TTY equivalent silently return -1 or no-op.

Platform support

PlatformStatus
Linux — any terminal emulatorTested, fully working
Windows 10 build 1511+Implemented — community testing welcome
Windows < 10 (Win7 etc.)Graceful no-op: console opens, does nothing, no crash. vt_screen returns -1.

Functions unavailable in TTY mode

Function / FeatureBehaviour in TTY mode
vt_mouse, vt_getmouse, vt_setmouse, vt_mouselockReturn -1 / no-op silently
vt_copypasteNo-op silently
vt_loadfont, vt_font_resetReturn -1 silently
vt_key_heldAlways returns 0 (no live key-state query in termios / ReadConsoleInput)
VT_USE_SOUND + VT_TTYCompile error (intentional)
VT_BLINKEmitted but depends on terminal support — TERM=linux raw console does not implement it
CP437 chars ≥ 128 (box-drawing, shade blocks…)Sent as raw bytes; terminals expect UTF-8, so they show as garbage. UTF-8 translation is a planned future improvement.

Minimal TTY example

' Compile with: fbc -d VT_TTY myprogram.bas
#Define VT_TTY
#include once "vt/vt.bi"

vt_screen VT_SCREEN_0
vt_cls()
vt_color VT_BRIGHT_GREEN
vt_locate 1, 1
vt_print "Hello from TTY!"
vt_color VT_YELLOW
vt_locate 3, 1
vt_print "Your name: "
vt_present()
Dim s As String = vt_input(20)
vt_locate 5, 1
vt_print "Got: " & s
vt_present()
vt_sleep 0
vt_shutdown()

Screen Modes

Passed as the mode argument to vt_screen.

ConstantValueColumns × RowsGlyphLogical resolution
VT_SCREEN_0080 × 258 × 16640 × 400
VT_SCREEN_2280 × 258 × 8640 × 200
VT_SCREEN_9980 × 258 × 14640 × 350
VT_SCREEN_121280 × 308 × 16640 × 480
VT_SCREEN_131340 × 258 × 8320 × 200
VT_SCREEN_EGA434380 × 438 × 8640 × 344
VT_SCREEN_VGA505080 × 508 × 8640 × 400
VT_SCREEN_TILES10040 × 2516 × 16640 × 400

VT_SCREEN_TILES uses the 8x8 font data scaled to 16x16 glyphs; useful for tile-based games where square cells are desirable.

Window Flags

Passed as the flags argument to vt_screen. Combine with Or.

ConstantValueDescription
VT_WINDOWED1Start in a resizable window (default).
VT_FULLSCREEN_ASPECT2Fullscreen desktop, integer-scaled, black letterbox bars.
VT_FULLSCREEN_STRETCH4Fullscreen, stretches to fill the entire display.
VT_NO_RESIZE8Prevent the user from manually resizing the window.
VT_VSYNC16Enable vertical sync. Off by default.
VT_WINDOWED_MAX32Start maximized (regular window, not fullscreen). Avoid combining with VT_NO_RESIZE.

Color Constants

Used with vt_color, vt_cls, vt_set_cell, etc. These are values of the VT_COLOR_IDX enum (0–15). OR any foreground color with VT_BLINK to enable character blinking.

ConstantValueConstantValue
VT_BLACK0VT_DARK_GREY8
VT_BLUE1VT_BRIGHT_BLUE9
VT_GREEN2VT_BRIGHT_GREEN10
VT_CYAN3VT_BRIGHT_CYAN11
VT_RED4VT_BRIGHT_RED12
VT_MAGENTA5VT_BRIGHT_MAGENTA13
VT_BROWN6VT_YELLOW14
VT_LIGHT_GREY7VT_WHITE15
VT_BLINK16OR with any fg to make it blink.
' Example: blinking white text on blue:
vt_color VT_WHITE Or VT_BLINK, VT_BLUE

Key Macros

vt_inkey and vt_getkey return a packed ULong. Use these macros to extract fields from it.

MacroDescription
VT_CHAR(k)ASCII character code (0 if non-printable).
VT_SCAN(k)VT scancode (bits 16–27). Always compare via this macro, never directly against k.
VT_REPEAT(k)1 if this is an auto-repeat event (key held down), 0 otherwise.
VT_SHIFT(k)1 if Shift was held when the key was pressed.
VT_CTRL(k)1 if Ctrl was held when the key was pressed.
VT_ALT(k)1 if Alt was held when the key was pressed.
' Typical usage pattern:
Dim k As ULong = vt_getkey()

Select Case VT_SCAN(k)
    Case VT_KEY_UP    : ' move up
    Case VT_KEY_DOWN  : ' move down
    Case VT_KEY_ENTER : ' confirm
    Case Else
        If VT_CHAR(k) = Asc("q") Then End
End Select

Key Scancodes

Compare these against VT_SCAN(k). Never compare them directly against the raw key value returned by vt_inkey.

ConstantValueConstantValue
VT_KEY_F159VT_KEY_F765
VT_KEY_F260VT_KEY_F866
VT_KEY_F361VT_KEY_F967
VT_KEY_F462VT_KEY_F1068
VT_KEY_F563VT_KEY_F11133
VT_KEY_F664VT_KEY_F12134
ConstantValueConstantValue
VT_KEY_UP72VT_KEY_HOME71
VT_KEY_DOWN80VT_KEY_END79
VT_KEY_LEFT75VT_KEY_PGUP73
VT_KEY_RIGHT77VT_KEY_PGDN81
VT_KEY_INS82VT_KEY_DEL83
ConstantValueConstantValue
VT_KEY_ESC1VT_KEY_LSHIFT42
VT_KEY_ENTER28VT_KEY_RSHIFT54
VT_KEY_BKSP14VT_KEY_LCTRL29
VT_KEY_TAB15VT_KEY_RCTRL157
VT_KEY_SPACE57VT_KEY_LALT56
VT_KEY_LWIN219VT_KEY_RALT184
VT_KEY_RWIN220  

Mouse Button Constants

Bitmask values returned in the btns parameter of vt_getmouse.

ConstantValueDescription
VT_MOUSE_BTN_LEFT1Left button (bit 0).
VT_MOUSE_BTN_RIGHT2Right button (bit 1).
VT_MOUSE_BTN_MIDDLE4Middle button (bit 2).

Copy/Paste Flags

Passed to vt_copypaste. Combinable with Or.

ConstantValueDescription
VT_CP_DISABLED0Default. No keys reserved, no mouse events captured by VT.
VT_CP_MOUSE1LMB drag selects, RMB copies selection, MMB pastes (paste: vt_input only).
VT_CP_KBD2Shift+arrows select, Ctrl+INS copies, Shift+INS pastes (paste: vt_input only).

Miscellaneous Constants

ConstantDescription
VT_NEWLINEChr(10) — newline character for use with vt_print.
VT_VIDEOValue 0. The display page index. Always page 0.

Math Macros opt-in

Available when #define VT_USE_MATH is placed before #include once "vt/vt.bi". See the Math section for the full function reference.

Macros use IIf() which evaluates both sides — avoid passing expressions with side effects (function calls, increments) as arguments.
MacroDescription
VT_MIN(a, b)Returns the smaller of two values.
VT_MAX(a, b)Returns the larger of two values.
VT_CLAMP(v, lo, hi)Clamps v to the range [lo .. hi].
VT_SIGN(x)Returns -1, 0, or 1 depending on the sign of x.

Sound Constants opt-in

Available when #define VT_USE_SOUND is placed before #include once "vt/vt.bi". See the Sound section for full details.

Waveforms — passed as the wave parameter to vt_sound

ConstantValueDescription
VT_WAVE_SQUARE0PC speaker / chiptune square wave. Default.
VT_WAVE_TRIANGLE1NES triangle channel — softer, bass feel.
VT_WAVE_SINE2Pure sine tone.
VT_WAVE_NOISE315-bit LFSR noise — NES percussion / static feel.

Blocking mode — passed as the blocking parameter to vt_sound

ConstantValueDescription
VT_SOUND_BLOCKING1Wait for the note to finish. The window stays alive during the wait. Default.
VT_SOUND_BACKGROUND0Queue samples and return immediately. Use vt_sound_wait to sync.

Sort Constants opt-in

Available when #define VT_USE_SORT is placed before #include once "vt/vt.bi". See the Sort section for the full function reference.

ConstantValueDescription
VT_ASCENDING0Sort direction for vt_sort: smallest value first.
VT_DESCENDING1Sort direction for vt_sort: largest value first.

TUI Constants opt-in

Available when #define VT_USE_TUI is placed before #include once "vt/vt.bi". See the TUI Widgets section for the full function reference.

Return codes — VT_TUI_RET enum

Returned by vt_tui_dialog and used as .ret values in form items.

ConstantValueDescription
VT_RET_CANCEL0Cancel / No action taken.
VT_RET_OK1OK confirmed.
VT_RET_YES2Yes confirmed.
VT_RET_NO3No confirmed.

Dialog button set flags — passed to vt_tui_dialog

ConstantValueDescription
VT_DLG_OK1Single OK button.
VT_DLG_OKCANCEL2OK and Cancel buttons.
VT_DLG_YESNO3Yes and No buttons.
VT_DLG_YESNOCANCEL4Yes, No, and Cancel buttons.
VT_DLG_NO_ESC8Combinable modifier: Escape does nothing. Forces an explicit button choice.

Widget flags

ConstantValueApplies toDescription
VT_TUI_WIN_CLOSEBTN1vt_tui_windowRender [x] at the right end of the title bar. Visual only — caller checks mouse with vt_tui_mouse_in_rect.
VT_TUI_WIN_SHADOW2vt_tui_windowDraw a one-cell drop shadow (right strip + bottom strip).
VT_TUI_PROG_LABEL1vt_tui_progressPrint a centred nn% label on the bar.
VT_TUI_FD_DIRS1vt_tui_file_dialogDefined but redundant — directories are always included in the listing.
VT_TUI_FD_DIRSONLY2vt_tui_file_dialogShow only subdirectories. Enter or OK on a directory entry confirms it.
VT_TUI_ED_READONLY1vt_tui_editorView and scroll only — no editing. Text cursor is hidden.

Form item kinds — used in the vt_tui_form_item type

ConstantValueDescription
VT_FORM_INPUT0Single-line text input field.
VT_FORM_BUTTON1Push button.

Form return codes — returned by vt_tui_form_handle

ConstantValueDescription
VT_FORM_PENDING-2Form is still active — call again next frame.
VT_FORM_CANCEL-1Escape was pressed — discard changes.
button .ret value (≥ 0)An activated button's .ret value, e.g. VT_RET_OK.

Menu return value extraction macros

vt_tui_menubar_handle returns group * 1000 + item_index (both 1-based) when an item is activated, or 0 while idle or after Escape closes a dropdown.

MacroDescription
VT_TUI_MENU_GROUP(r)Extract the 1-based group index from a menu return value.
VT_TUI_MENU_ITEM(r)Extract the 1-based item index within that group.
' File=group 1, Edit=group 2
' File/Open = 1002,  Edit/Copy = 2002
Select Case VT_TUI_MENU_GROUP(r)
    Case 1  ' File
        Select Case VT_TUI_MENU_ITEM(r)
            Case 1 : ' New
            Case 2 : ' Open
        End Select
    Case 2  ' Edit
        Select Case VT_TUI_MENU_ITEM(r)
            Case 1 : ' Cut
            Case 2 : ' Copy
        End Select
End Select

Initialization

vt_screen

Open or reopen the virtual text screen. Calling it again closes and reinitializes with the new settings.

Function vt_screen(mode As Long = VT_SCREEN_0, flags As Long = VT_WINDOWED, pages As Long = 1) As Long
ParameterTypeDefaultDescription
modeLongVT_SCREEN_0Screen mode. One of the VT_SCREEN_* constants.
flagsLongVT_WINDOWEDWindow behaviour flags. One or more VT_WINDOWED / VT_FULLSCREEN_* constants combined with Or.
pagesLong1Number of cell buffers to allocate (1–8). Page 0 is always VT_VIDEO. Extra pages are used with vt_page / vt_pcopy.

Return value

SDL2 backend:
0 = success
-1 = SDL_Init failed
-2 = SDL window creation failed
-3 = SDL renderer creation failed
-4 = font texture creation failed
-5 = page buffer memory allocation failed

VT_TTY backend:
0 = success
-1 = Windows < 10: ENABLE_VIRTUAL_TERMINAL_PROCESSING not supported. All subsequent calls silently no-op.
-6 = page buffer memory allocation failed
Call vt_title before vt_screen to set the window title before the window appears (SDL2 only).
Call vt_scrollback after vt_screen to enable the scrollback buffer (SDL2 only).
Call vt_copypaste after vt_screen to enable selection/clipboard support (SDL2 only).
SDL2 backend and raw Linux TTY: the SDL2 backend requires a graphical session (X11 or Wayland). Running from a raw Linux console — the TTYs reachable via Ctrl+Alt+F1F6 outside of a desktop session — is not supported. VT detects this via the TERM=linux environment variable, prints a clear error message, and terminates the program immediately rather than hanging.

Workaround: launch from a terminal emulator inside a graphical session, or use the VT_TTY backend which is designed specifically for raw terminal output.

vt_scrollback

Allocate (or resize, or disable) the scrollback history buffer. Must be called after vt_screen. Calling it again clears any existing history.

Sub vt_scrollback(lines As Long)
ParameterTypeDefaultDescription
linesLongnoneNumber of lines of history to keep. Pass 0 to disable scrollback and free the buffer.
Scrollback captures lines scrolled off the top of the scroll region. The keyboard bindings used to navigate history depend on the active backend and environment — see Internal Keyboard Bindings for the full per-environment breakdown. Scrollback is automatically exited when any content key is pressed.

vt_shutdown

Explicitly tear down the VT screen and free all SDL resources. This is optional: on normal program exit, SDL cleans itself up. Use it when you need to close the window mid-program.

Sub vt_shutdown()

vt_title

Set the window title. Safe to call before or after vt_screen. Before: the title is stored and applied when the window is created. After: applied immediately to the live window.

Sub vt_title(txt As String)
ParameterTypeDefaultDescription
txtStringnoneThe title bar text.

vt_on_close

Register a callback function that is invoked when the user clicks the window [X] button. Without a callback the default behaviour is to shut down immediately and call End — the program exits with no chance to intervene. With a callback registered the library calls your function first and acts on the return value.

Sub vt_on_close(cb As Function() As Byte)
ParameterTypeDescription
cbFunction() As Byte Pointer to a Function() As Byte. Pass 0 to remove the callback and restore the default auto-close behaviour.

Callback return value

0 — allow the close: library calls vt_shutdown() then End, identical to the default.
1 — veto the close: window stays open, the event is discarded, program continues normally.
VT functions are safe inside the callback. The SDL event-pump reentrancy guard is released before your callback is invoked and re-acquired afterwards, so vt_inkey, vt_getkey, vt_sleep, vt_present, and all drawing functions work correctly. This means you can draw a confirmation dialog and wait for a keypress directly inside the callback.

If your callback calls vt_shutdown() and End explicitly, the re-acquire never runs — that is fine.
' Declare the callback with the exact signature:
Function on_close() As Byte
    ' Ask the user before closing:
    vt_color VT_BLACK, VT_LIGHT_GREY
    vt_locate 25, 1
    vt_print "  Unsaved changes!  Quit? (Y/N)" & String(48, " ")
    vt_present()

    Dim ans As String = vt_getchar("YyNn")
    If LCase(ans) = "y" Then Return 0   ' allow close

    ' Restore UI and veto:
    draw_statusbar()
    vt_present()
    Return 1
End Function

' Register after vt_screen:
vt_screen VT_SCREEN_0
vt_on_close(@on_close)

' Remove callback (restore default auto-close):
vt_on_close(0)

Display

vt_border_color

Set the colour of the letterbox bars that appear outside the logical viewport in windowed or fullscreen-aspect modes. Default is black (0, 0, 0).

Sub vt_border_color(r As Long, g As Long, b As Long)
ParameterTypeDefaultDescription
rLongnoneRed channel (0–255).
gLongnoneGreen channel (0–255).
bLongnoneBlue channel (0–255).

vt_present

Composite the visible page and flip it to the screen. Always renders the visible page (vis_page), which may differ from the active drawing page when using multi-page mode. You are responsible for calling this; drawing functions such as vt_print and vt_cls do not call it automatically.

Sub vt_present()
vt_inkey and vt_sleep call vt_present automatically (throttled) to keep the display alive during wait loops.

Printing & Cursor

vt_cls

Clear the active scroll region to spaces using the current foreground and background colours. Resets the cursor to row 1, column 1 of the scroll region. Does not call vt_present.

Sub vt_cls(bg As Long = -1)
ParameterTypeDefaultDescription
bgLong-1Background colour index (0–15). -1 keeps the current background colour.

vt_color

Set the active foreground and/or background colour used by vt_print, vt_cls, and vt_input.

Sub vt_color(fg As Long = -1, bg As Long = -1)
ParameterTypeDefaultDescription
fgLong-1Foreground colour index (0–15), optionally OR'd with VT_BLINK (16). -1 keeps the current foreground.
bgLong-1Background colour index (0–15). -1 keeps the current background.
' Set only foreground, leave background unchanged:
vt_color VT_BRIGHT_GREEN

' Set both:
vt_color VT_WHITE, VT_BLUE

' Blinking foreground:
vt_color VT_RED Or VT_BLINK, VT_BLACK

vt_locate

Move the text cursor and optionally change its visibility and glyph. Coordinates are 1-based. Parameters with value -1 are left unchanged.

Sub vt_locate(row As Long = -1, col As Long = -1, vis As Long = -1, cursor_ch As Long = -1)
ParameterTypeDefaultDescription
rowLong-1Target row, 1-based. Out-of-range values are ignored. -1 keeps current row.
colLong-1Target column, 1-based. Out-of-range values are ignored. -1 keeps current column.
visLong-1Cursor visibility: 1 = visible, 0 = hidden, -1 = keep current.
cursor_chLong-1CP437 glyph index (0–255) used to draw the cursor. Default is 219 (solid block). -1 keeps current.

vt_print

Print a string at the current cursor position using the active colour. The cursor advances after each character; the display is not automatically flipped. There is no implicit newline — use VT_NEWLINE (Chr(10)) within the string for line breaks. CRLF pairs are collapsed to a single line feed.

Sub vt_print(txt As String)
ParameterTypeDefaultDescription
txtStringnoneThe string to print. May contain Chr(10) for newlines and Chr(13)+Chr(10) for CRLF (collapsed to one newline).
' Position, colour, print, flip:
vt_locate 5, 1
vt_color VT_CYAN, VT_BLACK
vt_print "Line one" & VT_NEWLINE & "Line two"
vt_present()

vt_print_center

Print a string horizontally centered on the given screen row. Uses the current colour and does not call vt_present.

Sub vt_print_center(row As Long, txt As String)
ParameterTypeDefaultDescription
rowLongnoneTarget row, 1-based.
txtStringnoneThe string to center and print.

vt_scroll_enable

Enable or disable automatic scrolling of the scroll region. When disabled, text printed past the bottom of the scroll region stays on the last row. Scroll is enabled by default.

Sub vt_scroll_enable(state As Byte)
ParameterTypeDefaultDescription
stateBytenone1 = enable scrolling, 0 = disable.

Cell Access

vt_get_cell

Read the character, foreground colour, and background colour of a single cell on the active work page. Coordinates are 1-based. Out-of-range coordinates are ignored.

Sub vt_get_cell(col As Long, row As Long, ByRef ch As UByte, ByRef fg As UByte, ByRef bg As UByte)
ParameterTypeDefaultDescription
colLongnoneColumn, 1-based.
rowLongnoneRow, 1-based.
chUByte (ByRef)noneReceives the CP437 character code.
fgUByte (ByRef)noneReceives the foreground colour index (may include blink bit).
bgUByte (ByRef)noneReceives the background colour index.

vt_set_cell

Write a character cell directly on the active work page without moving the cursor. Does not call vt_present. Out-of-range coordinates are silently ignored.

Sub vt_set_cell(col As Long, row As Long, ch As UByte, fg As UByte, bg As UByte)
ParameterTypeDefaultDescription
colLongnoneColumn, 1-based.
rowLongnoneRow, 1-based.
chUBytenoneCP437 character code (0–255).
fgUBytenoneForeground colour index (0–15), optionally OR'd with 16 for blink.
bgUBytenoneBackground colour index (0–15).

Scroll Region & Scrollback

vt_scroll

Move the scrollback view by a number of lines. Positive values scroll toward older history; negative values scroll toward the live view. Scrollback must be enabled with vt_scrollback first. Does not call vt_present.

Function vt_scroll(amount As Long) As Byte
ParameterTypeDefaultDescription
amountLongnoneLines to scroll. Positive = back in history, negative = toward live view. Clamped to valid range automatically.

Return value

1 = offset changed
0 = already at limit, amount is zero, or scrollback is disabled
vt_scroll is called automatically by the library when the user presses the appropriate scrollback keys. You only need to call it directly if you want to drive scrollback from your own input (e.g. a custom mouse wheel handler).

The keys that trigger it differ by backend and environment — see Internal Keyboard Bindings for the full table.

vt_screen_to_live_row

Translate a screen row number to the corresponding live buffer row while the scrollback view is offset. Useful when implementing click-to-locate functionality: call this before snapping back to the live view with vt_scroll(-99999).

Function vt_screen_to_live_row(screen_row As Long) As Long
ParameterTypeDefaultDescription
screen_rowLongnone1-based screen row as seen during the current scrollback offset.

Return value

1-based live buffer row that corresponds to screen_row.
0 if the screen row is showing pure scrollback history with no live equivalent.
Pass-through (returns screen_row) when scrollback is not active or the row is a fixed row outside the scroll region.
' Click-to-locate pattern during scrollback:
Dim lr As Long = vt_screen_to_live_row(clicked_row)
If lr > 0 Then
    vt_scroll -99999   ' snap back to live
    vt_locate lr, clicked_col
End If

vt_view_print

Restrict the scroll region and print region to a range of rows (like QBasic's VIEW PRINT). Rows outside this range are unaffected by vt_cls, vt_print, and scrolling. Call with no arguments (or both -1) to reset to the full screen.

Sub vt_view_print(top_row As Long = -1, bot_row As Long = -1)
ParameterTypeDefaultDescription
top_rowLong-1First row of the scroll region, 1-based. -1 resets both bounds to the full screen.
bot_rowLong-1Last row of the scroll region, 1-based. Must be ≥ top_row.

Internal Keyboard Bindings

VT intercepts certain key combinations internally before they reach your program. These bindings are hardwired into the event pump and require no setup. Which keys are active depends on the active backend and, in TTY mode, on the host environment.

Scrollback navigation

Scrollback must be enabled with vt_scrollback for these to have any effect. Any key not listed below exits scrollback and returns to the live view.

Key SDL2
(windowed / fullscreen)
VT_TTY
terminal emulator
(XFCE, xterm…)
VT_TTY
raw Linux console
(Ctrl+Alt+F1–F6)
VT_TTY
Windows 10+
console
Shift+PgUp ✓ scroll back 1 line ✗ intercepted by terminal ✗ intercepted by kernel ✓ scroll back 1 line
Shift+PgDn ✓ scroll forward 1 line ✗ intercepted by terminal ✗ intercepted by kernel ✓ scroll forward 1 line
Ctrl+Shift+PgUp ✓ scroll back ½ page ✗ intercepted by terminal ✗ intercepted by kernel ✓ scroll back ½ page
Ctrl+Shift+PgDn ✓ scroll forward ½ page ✗ intercepted by terminal ✗ intercepted by kernel ✓ scroll forward ½ page
Alt+PgUp ✓ scroll back 1 line ✗ unbound in default keymap
Alt+PgDn ✓ scroll forward 1 line ✗ unbound in default keymap
Alt+– (minus) ✓ scroll back 1 line ✓ scroll back 1 line
Alt+. (period) ✓ scroll forward 1 line ✓ scroll forward 1 line
Why different bindings?
On Linux the SDL2 backend owns its own window and input queue, so Shift+PgUp reaches VT directly. In a terminal emulator, the terminal itself intercepts Shift+PgUp for its own scrollback, so VT uses Alt+PgUp instead (sent as ESC[5;3~). On a raw Linux virtual console (Ctrl+Alt+F1F6), both Shift+PgUp and Alt+PgUp are consumed by the kernel VT driver before reaching the program — but Alt+– and Alt+. are present in the default Linux keymap (Meta_minus / Meta_period) and pass through cleanly, making them the universal raw-TTY scrollback binding that works everywhere.

Copy / Paste

Available only in SDL2 mode when vt_copypaste has been called to enable it. These keys are never intercepted in TTY mode.

KeyEffect
Ctrl+InsCopy current selection to clipboard.
Shift+InsPaste clipboard text (inside vt_input only).
Shift+Arrow / Home / EndExtend keyboard selection.
Left mouse dragSelect a region.
Right mouse buttonCopy current selection.
Middle mouse buttonPaste (inside vt_input only).

Alt+letter (TUI menubar)

Used by vt_tui_menubar_draw to open a menu group by its first letter. VT delivers these as a keyrec with VT_ALT(k) = 1 and VT_CHAR(k) set to the letter. The detection method is backend-dependent but transparent to your code:

BackendHow Alt+letter is detected
SDL2 SDL suppresses its text-input event while Alt is held. VT recovers the letter from the raw SDL scancode in the key-down event.
VT_TTY Linux Terminal sends ESC + letter byte. VT's escape-sequence parser intercepts this and sets the Alt bit.
VT_TTY Windows LEFT_ALT_PRESSED / RIGHT_ALT_PRESSED flags in the Win32 KEY_EVENT_RECORD. Folded into bit 31 of the keyrec.
Both parameters must be -1 together to reset. Passing one as -1 while the other has a valid value only applies the valid value.

Pages

Pages are separate full-screen cell buffers. Multiple pages are allocated at vt_screen time via the pages argument. Page 0 (VT_VIDEO) is always the startup visible page. Up to 8 pages are supported (indices 0–7).

vt_page

Set the active drawing page and the visible display page independently. All drawing commands write to the work page; vt_present reads from the vis page. Invalid indices are silently ignored.

Sub vt_page(work As Long, vis As Long)
ParameterTypeDefaultDescription
workLongnoneDrawing page index (0 ≤ work < pages). All drawing commands go here.
visLongnoneDisplay page index (0 ≤ vis < pages). This page is shown by vt_present.
' Classic double-buffer setup: draw on page 1, display page 0.
vt_page 1, VT_VIDEO
' ... draw freely on page 1 ...
vt_pcopy 1, VT_VIDEO   ' push finished frame to page 0
vt_present()
' Swap back when done:
vt_page VT_VIDEO, VT_VIDEO

vt_pcopy

Copy one page buffer to another (equivalent to QBasic PCOPY). Marks the display dirty but does not call vt_present. src = dst is a no-op. Invalid indices are silently ignored.

Sub vt_pcopy(src As Long, dst As Long)
ParameterTypeDefaultDescription
srcLongnoneSource page index.
dstLongnoneDestination page index.

Palette

The palette is a flat array of 48 bytes: 16 colours × 3 channels (R, G, B). Entry idx starts at byte idx * 3. The default is the CGA palette.

vt_palette

Set a single palette entry, or reset all 16 colours to CGA defaults.

Sub vt_palette(idx As Long = -1, r As Long = -1, g As Long = -1, b As Long = -1)
ParameterTypeDefaultDescription
idxLong-1Colour index (0–15). Pass -1 to reset all 16 entries to the CGA defaults (r, g, b are ignored in this case).
rLong-1Red channel (0–255). When idx ≥ 0, always pass all three channels explicitly.
gLong-1Green channel (0–255).
bLong-1Blue channel (0–255).
When idx ≥ 0, always supply all three colour channels. The defaults (-1) are only meaningful for the reset case (idx = -1). Omitting channels when setting a specific entry produces 255 (white) because -1 And 255 = 255.
' Replace colour index 4 (Red) with a custom orange:
vt_palette 4, 220, 120, 0

' Reset all 16 colours back to CGA defaults:
vt_palette

vt_palette_get

Bulk-read all 16 palette entries into a caller-supplied UByte array of at least 48 elements.

Sub vt_palette_get(pal() As UByte)
ParameterTypeDefaultDescription
pal()UByte arraynoneArray to receive 48 bytes of palette data (16 colours × R, G, B).

vt_palette_set

Bulk-write all 16 palette entries from a caller-supplied UByte array of 48 elements.

Sub vt_palette_set(pal() As UByte)
ParameterTypeDefaultDescription
pal()UByte arraynoneArray containing 48 bytes of palette data (16 colours × R, G, B).

Screen Query

vt_cols

Returns the total number of columns in the current screen mode.

Function vt_cols() As Long

vt_csrlin

Returns the current cursor row (1-based).

Function vt_csrlin() As Long

vt_pos

Returns the current cursor column (1-based).

Function vt_pos() As Long

vt_rows

Returns the total number of rows in the current screen mode.

Function vt_rows() As Long

Keyboard

vt_getchar

Block until a printable character (ASCII 32–255) is pressed. If allowed is non-empty, only characters within that string are accepted; all others are discarded silently. Returns the accepted character as a one-character String.

Function vt_getchar(allowed As String = "") As String
ParameterTypeDefaultDescription
allowedString""Whitelist of accepted characters. Empty string = accept any printable character.

Return value

Single-character String containing the accepted character.
' Accept only Y or N:
vt_print "Continue? (Y/N) "
vt_present()
Dim ans As String = UCase(vt_getchar("YyNn"))

vt_getkey

Block until any key (including non-printable and special keys) is pressed. Returns the same packed ULong format as vt_inkey; use the VT_SCAN / VT_CHAR / VT_SHIFT / VT_CTRL / VT_ALT macros to extract fields.

Function vt_getkey() As ULong

Return value

Packed key event value. Use VT_CHAR, VT_SCAN, VT_SHIFT, VT_CTRL, VT_ALT, VT_REPEAT macros to decode.

vt_inkey

Non-blocking key read. Returns the next key from the buffer, or 0 if no key is pending. Also pumps events, updates blink, and calls vt_present if the display is dirty (throttled to 2 ms).

Function vt_inkey() As ULong

Return value

Packed key event (non-zero) if a key was waiting.
0 if the buffer is empty or the library is not ready.
' Game loop pattern:
Do
    Dim k As ULong = vt_inkey()
    If VT_SCAN(k) = VT_KEY_ESC Then Exit Do
    ' ... update and draw ...
    vt_present()
    Sleep 16, 1
Loop

vt_input

Full-featured blocking single-line text editor at the current cursor position. Supports navigation (Home, End, Left, Right), insertion, deletion (Backspace, Del), and clipboard paste when copy/paste is enabled. Returns the entered string on Enter, or an empty string on Escape.

Function vt_input(max_len As Long = -1, initial As String = "", allowed As String = "", cancelled As Byte Ptr = 0) As String
ParameterTypeDefaultDescription
max_lenLong-1Maximum input length. -1 fills all remaining columns on the current row.
initialString""Pre-filled text shown at startup. Pressing Escape restores this text, clears the screen, and returns "".
allowedString""Whitelist of accepted characters. Empty = accept any printable ASCII.
cancelledByte Ptr0 (null)Optional pointer to a Byte. Set to 1 on Escape, 0 on Enter. Pass 0 to ignore.

Return value

The entered string on Enter.
"" (empty string) on Escape.
Paste (Shift+INS or MMB) is only functional inside vt_input. The editor uses the current vt_color settings throughout. It does not call vt_present directly; the internal vt_inkey calls handle presentation.
' Prompt with pre-filled default, numeric only, detect cancel:
Dim esc As Byte
vt_locate 10, 1
vt_print "Enter amount: "
vt_present()
Dim amount As String = vt_input(8, "0", "0123456789.", @esc)
If esc Then
    vt_print "Cancelled" & VT_NEWLINE
Else
    vt_print "Got: " & amount & VT_NEWLINE
End If
vt_present()

vt_key_flush

Discard all keys currently waiting in the internal key buffer.

Sub vt_key_flush()

vt_key_held

Poll the real-time state of a key. Returns 1 if the key is physically held down at the time of the call. Useful for smooth movement in game loops where event-based repeat is not fast enough. Accepts the same VT_KEY_* scancode constants used with VT_SCAN.

Function vt_key_held(vtscan As Long) As Byte
ParameterTypeDefaultDescription
vtscanLongnoneA VT_KEY_* scancode constant.

Return value

1 if the key is currently pressed.
0 if not pressed, scancode not recognized, or library not ready.
' Smooth movement example:
If vt_key_held(VT_KEY_LEFT)  Then x -= 1
If vt_key_held(VT_KEY_RIGHT) Then x += 1

vt_key_repeat

Configure the timing of auto-repeat events generated when a key is held down. Default: 400 ms initial delay, 30 ms repeat rate. Set either value to 0 to disable the corresponding phase of repeat.

Sub vt_key_repeat(initial_ms As Long, rate_ms As Long)
ParameterTypeDefaultDescription
initial_msLong400Delay in milliseconds before the first repeat event fires.
rate_msLong30Interval in milliseconds between subsequent repeat events.

vt_pump

Manually drain the SDL event queue. Normally called automatically by vt_inkey, vt_present, and vt_sleep. Rarely needed directly — useful in tight loops that never call any of those. Re-entrant safe (a second call while inside returns immediately).

Sub vt_pump()

vt_sleep

Delay for a fixed number of milliseconds, or wait indefinitely for any keypress. Keeps the display alive (blink, present) during the wait.

Sub vt_sleep(ms As Long = 0)
ParameterTypeDefaultDescription
msLong0Delay in milliseconds. 0 = wait for any keypress.
When ms = 0, the keypress is consumed from the buffer. When ms > 0, no key is consumed; the delay simply elapses.

Mouse

vt_getmouse

Non-blocking read of the current mouse state. All parameters are optional pointers; pass 0 to skip any field. Must enable the mouse first with vt_mouse(1). The wheel accumulator is reset to zero each time it is read. Button state uses the VT_MOUSE_BTN_* bitmask constants.

Function vt_getmouse(col As Long Ptr = 0, row As Long Ptr = 0, btns As Long Ptr = 0, whl As Long Ptr = 0) As Byte
ParameterTypeDefaultDescription
colLong Ptr0Receives 1-based column. Pass 0 to skip.
rowLong Ptr0Receives 1-based row. Pass 0 to skip.
btnsLong Ptr0Receives button bitmask. Pass 0 to skip.
whlLong Ptr0Receives accumulated wheel delta (+up / −down) since last read. Reset to 0 on read. Pass 0 to skip.

Return value

0 = ok
-1 = mouse not enabled, or library not ready
' Read position and buttons:
Dim mx As Long, my As Long, mb As Long
vt_getmouse @mx, @my, @mb

' Read wheel only:
Dim mw As Long
vt_getmouse 0, 0, 0, @mw

' Read buttons only (FreeBASIC skips defaulted pointer params):
vt_getmouse(,,@mb)

vt_mouse

Enable or disable mouse tracking entirely. Enabling hides the OS cursor, starts tracking, and snaps the character cursor to the current physical mouse position. Disabling restores the OS cursor and clears button/wheel state.

Sub vt_mouse(enabled As Byte)
ParameterTypeDefaultDescription
enabledBytenone1 = enable mouse tracking, 0 = disable.

vt_mouselock

Enable or disable SDL window grab, which confines the mouse cursor to the window. Fullscreen modes enable this automatically at vt_screen time. Safe to call before or after vt_mouse(1).

Sub vt_mouselock(onoff As Byte)
ParameterTypeDefaultDescription
onoffBytenone1 = lock cursor inside window, 0 = unlock.

vt_setmouse

Set the character cursor position and/or visibility. Coordinates are 1-based. Pass -1 to leave any field unchanged. Safe to call when the mouse is disabled; settings take effect when re-enabled.

Sub vt_setmouse(col As Long = -1, row As Long = -1, vis As Byte = -1)
ParameterTypeDefaultDescription
colLong-1Column, 1-based. Clamped to screen bounds. -1 = keep current.
rowLong-1Row, 1-based. Clamped to screen bounds. -1 = keep current.
visByte-11 = show cursor, 0 = hide cursor, -1 = keep current.

Copy/Paste

vt_copypaste

Configure the copy/paste and text selection system. Call after vt_screen. Clears any active selection.

Sub vt_copypaste(flags As Long)
ParameterTypeDefaultDescription
flagsLongnoneOne or more VT_CP_* constants combined with Or. VT_CP_DISABLED (0) disables all interception.
Keyboard mode (VT_CP_KBD): Shift+Arrow / Shift+Home / Shift+End extends a selection from the cursor. Ctrl+INS copies the selection to the clipboard. Shift+INS pastes from the clipboard (inside vt_input only).

Mouse mode (VT_CP_MOUSE): LMB drag creates a selection. RMB copies the current selection. MMB pastes from the clipboard (inside vt_input only).

Copy works during scrollback. Keyboard selection extension and paste are gated on scrollback being at the live view (sb_offset = 0).

Selected text is stored in the system clipboard as plain text, with trailing spaces per row trimmed and rows joined with CRLF.
' Enable both keyboard and mouse copy/paste:
vt_screen VT_SCREEN_0
vt_copypaste VT_CP_KBD Or VT_CP_MOUSE

' Disable entirely:
vt_copypaste VT_CP_DISABLED

File I/O (.vts format)

The .vts format is a compact binary screen dump: 4-byte magic "VTBS", 4-byte cols, 4-byte rows (all little-endian Long), followed by cols × rows × 3 bytes of cell data (ch, fg, bg per cell, left-to-right, top-to-bottom). Both functions operate on the active work page.

vt_bload

Load a .vts file into the active work page. Validates the magic and screen dimensions before touching the cell buffer. Sets the display dirty on success so the next vt_present reflects the loaded content.

Function vt_bload(fname As String) As Long
ParameterTypeDefaultDescription
fnameStringnonePath to the .vts file to load.

Return value

0 = success
-1 = library not ready (call vt_screen first)
-2 = file could not be opened
-3 = bad magic — not a valid .vts file
-4 = dimension mismatch — file was saved in a different screen mode

vt_bsave

Save the active work page to a .vts file.

Function vt_bsave(fname As String) As Long
ParameterTypeDefaultDescription
fnameStringnoneDestination file path. Created or overwritten.

Return value

0 = success
-1 = library not ready (call vt_screen first)
-2 = file could not be opened for writing

Font

vt_font_reset

Restore the built-in embedded CP437 font selected by the current screen mode. Destroys any custom font loaded with vt_loadfont. Must be called after vt_screen.

Function vt_font_reset() As Long

Return value

0 = success
-1 = library not ready
-2 = surface or texture creation failed

vt_loadfont

Load a BMP font sheet and replace the active font texture at runtime. Uses SDL2 core only (no SDL_image). The loaded font stays active until vt_loadfont is called again, vt_font_reset is called, or vt_screen reinitializes the window.

Function vt_loadfont(fname As String, sheet_w As Long = -1, sheet_h As Long = -1, mask_r As UByte = 0, mask_g As UByte = 0, mask_b As UByte = 0) As Long
ParameterTypeDefaultDescription
fnameStringnonePath to a BMP file. Any bit depth is accepted (converted internally).
sheet_wLong-1Expected BMP pixel width for validation. -1 = accept any width.
sheet_hLong-1Expected BMP pixel height for validation. -1 = accept any height.
mask_rUByte0Red component of the background (transparent) colour in the BMP.
mask_gUByte0Green component of the background colour.
mask_bUByte0Blue component of the background colour.

Return value

0 = success
-1 = library not ready
-2 = BMP load or surface creation failed
-3 = image dimensions do not match the current screen mode glyph size (or explicit sheet_w / sheet_h mismatch)
-4 = SDL texture upload failed

Sheet layout detection

The layout is detected automatically from the BMP pixel dimensions relative to the current screen mode's glyph size (gw × gh):

LayoutBMP sizeExample (8×8 glyphs)
16×16 grid16⋅gw × 16⋅gh128 × 128
256×1 strip256⋅gw × gh2048 × 8

Common mask colours:

Colourmask_rmask_gmask_b
Black (default)000
Magenta2550255
White255255255
All non-mask pixels are normalized to white in the internal texture so that SDL color-mod tinting in vt_present applies the correct foreground color, exactly as with the built-in embedded fonts.
' Load a magenta-keyed grid font sheet:
Dim r As Long = vt_loadfont("myfont.bmp", -1, -1, 255, 0, 255)
If r <> 0 Then
    vt_print "Font load failed: " & r & VT_NEWLINE
End If

Utilities

vt_rnd

Return a random Long in the inclusive range [lo .. hi]. Negative bounds are fully supported. If lo > hi the two values are swapped silently. Seeding the RNG with Randomize is the caller's responsibility — vt_rnd does not call it.

Function vt_rnd(lo As Long, hi As Long) As Long
ParameterTypeDefaultDescription
loLongnoneLower bound (inclusive).
hiLongnoneUpper bound (inclusive).
' Seed once at startup, then use freely:
Randomize Timer

Dim n As Long = vt_rnd(-15, 15)   ' -15 to 15
Dim d As Long = vt_rnd(1, 6)      ' die roll
Dim x As Long = vt_rnd(1, vt_cols())

Sort opt-in

Generic in-place array sorting, permutation application, and Fisher-Yates shuffle. No SDL2 required — usable with or without a VT screen open.

Opt-in: define VT_USE_SORT before the include. Without it the entire extension is absent at compile time — zero overhead.
' Enable sort in your project:
#define VT_USE_SORT
#include once "vt/vt.bi"
The two direction constants (VT_ASCENDING, VT_DESCENDING) are also part of this extension and documented in the Constants section.

vt_sort

Sort a 1D array in-place using Shellsort. Overloaded for all numeric types (Byte, UByte, Short, UShort, Long, ULong, LongInt, ULongInt, Single, Double) and String. The compiler selects the correct overload automatically — a single call works for every supported type. Arrays with any LBound are handled correctly.

Two call forms are available: a direction constant, or a comparator callback for custom ordering.

ZString not supported. ZString * N cannot be covered by a generic overload because the fixed length N is part of the type in FreeBASIC — every distinct size would require its own overload, which is not feasible. ZString is a C-interop type; data intended for sorting should be stored as String. If you do have a ZString * N array to sort, use the index-sort pattern: sort a Long index array with a comparator that reads the zstring array via the index value, then read results in index order — no physical rearrangement of the zstring array is needed.

Form 1 — direction constant

Sub vt_sort(arr() As <T>, order As Long)
ParameterTypeDescription
arr()any supported 1D arrayArray to sort in-place.
orderLongVT_ASCENDING (0) or VT_DESCENDING (1).

Form 2 — comparator callback

Sub vt_sort(arr() As <T>, cmp As Function(As <T>, As <T>) As Long)
ParameterTypeDescription
arr()any supported 1D arrayArray to sort in-place.
cmpFunction(As <T>, As <T>) As LongReturns negative if a comes first, zero if equal, positive if b comes first. Pass with @.
The comparator defines the sort order entirely. To sort descending via a comparator, flip the return sign in your function.
' Direction sort:
Dim nums(4) As Long = {5, 1, 4, 2, 3}
vt_sort nums(), VT_ASCENDING    ' 1 2 3 4 5
vt_sort nums(), VT_DESCENDING   ' 5 4 3 2 1

' String array, alphabetical:
Dim words(2) As String = {"cherry", "apple", "banana"}
vt_sort words(), VT_ASCENDING   ' apple banana cherry

' Custom comparator: sort strings by character count (shortest first):
Function cmp_by_length(a As String, b As String) As Long
    Return Len(a) - Len(b)
End Function

Dim tags(2) As String = {"hi", "hello", "hey"}
vt_sort tags(), @cmp_by_length  ' hi hey hello

vt_sort_apply

Apply a permutation index to a 1D array, physically rearranging its elements in-place. This is the companion function to the index-sort pattern — the standard technique for sorting tables stored as parallel arrays.

pidx() values are 0-based offsets from LBound(arr), exactly matching the index arrays built and sorted by vt_sort. The permutation index is consumed by the apply step — build a fresh index array if you need to apply the same order to additional arrays.

Overloaded for the same types as vt_sort. The pidx parameter is always Long.

Sub vt_sort_apply(arr() As <T>, pidx() As Long)
ParameterTypeDescription
arr()any supported 1D arrayArray to rearrange in-place.
pidx()Long arrayPermutation index. Values are 0-based offsets into arr. Produced by sorting an index array with vt_sort.

The index-sort pattern (sorting a table of parallel arrays)

FreeBASIC tables are often stored as several parallel arrays of equal length — one per column. Because vt_sort operates on a single array, you cannot sort all columns at once directly. The index-sort pattern solves this in three steps without duplicating the data. It also applies naturally to ZString arrays, which vt_sort cannot overload directly.

  1. Build a Long index array {0, 1, 2, …} — one entry per row.
  2. Sort the index array using vt_sort with a comparator that reads the real data via the index values.
  3. Either read in index order (non-destructive view) or call vt_sort_apply on every parallel array to physically rearrange them.
Because the comparator uses module-level Shared variables, FreeBASIC's lack of closures is not a problem — the comparator simply accesses the shared arrays directly. This technique works for any array type including ZString * N.
' Table stored as parallel arrays:
Dim Shared g_item(3) As String
Dim Shared g_qty (3) As Long
g_item(0) = "eggs"   : g_qty(0) = 5
g_item(1) = "butter" : g_qty(1) = 2
g_item(2) = "milk"   : g_qty(2) = 10
g_item(3) = "bread"  : g_qty(3) = 3

' Index comparators peek into shared arrays via the index value:
Function cmp_by_qty(a As Long, b As Long) As Long
    Return g_qty(a) - g_qty(b)
End Function

' Step 1: build index array.
Dim idx(3) As Long
Dim i As Long
For i = 0 To 3 : idx(i) = i : Next i

' Step 2: sort the index, not the data.
vt_sort idx(), @cmp_by_qty   ' idx = {1,3,0,2} = butter,bread,eggs,milk

' Step 3a: read in index order (original arrays untouched):
For i = 0 To 3
    Print g_item(idx(i)); " "; g_qty(idx(i))
Next i

' Step 3b: physically rearrange both arrays using the same index:
vt_sort_apply g_item(), idx()
vt_sort_apply g_qty(),  idx()
' g_item() and g_qty() are now in qty-ascending order.

vt_sort_shuffle

Shuffle a 1D array in-place using the Fisher-Yates algorithm, producing an unbiased uniform random permutation. Seeding the RNG with Randomize is the caller's responsibility — vt_sort_shuffle does not call it. Overloaded for the same types as vt_sort.

Sub vt_sort_shuffle(arr() As <T>)
ParameterTypeDescription
arr()any supported 1D arrayArray to shuffle in-place.
' Shuffle a deck of cards:
Randomize Timer
Dim deck(51) As Long
Dim i As Long
For i = 0 To 51 : deck(i) = i : Next i
vt_sort_shuffle deck()

' Shuffle a string list:
Dim names(3) As String = {"Alice", "Bob", "Carol", "Dave"}
vt_sort_shuffle names()

Math opt-in

Pure FreeBASIC math helpers. No SDL2 required — usable with or without a VT screen open. Especially useful for game logic: grid distance, line of sight, smooth movement, matrix rotation.

Opt-in: define VT_USE_MATH before the include. Without it the entire extension is absent at compile time — zero overhead.
' Enable math in your project:
#define VT_USE_MATH
#include once "vt/vt.bi"
The four convenience macros (VT_MIN, VT_MAX, VT_CLAMP, VT_SIGN) are also part of this extension and documented in the Constants section.

vt_wrap

Wraps v into the inclusive range [lo .. hi] with roll-over. Unlike VT_CLAMP, hitting an edge rolls around to the opposite side. If hi − lo + 1 ≤ 0 the function returns lo.

Function vt_wrap(v As Long, lo As Long, hi As Long) As Long
ParameterTypeDescription
vLongThe value to wrap.
loLongLower bound (inclusive).
hiLongUpper bound (inclusive).
' Menu cursor that wraps at the edges:
cursor_row = vt_wrap(cursor_row + delta, 1, num_items)

' Toroidal map: player walks off the right edge, appears on the left:
px = vt_wrap(px + 1, 0, map_width - 1)

vt_lerp

Linear interpolation between a and b. At t = 0 the result is a; at t = 1 the result is b. t is not clamped — values outside [0..1] extrapolate beyond the endpoints.

Function vt_lerp(a As Double, b As Double, t As Double) As Double
ParameterTypeDescription
aDoubleStart value (returned when t = 0).
bDoubleEnd value (returned when t = 1).
tDoubleInterpolation factor. Not clamped.
' Animated value moving from 0 to 100 over time:
Dim brightness As Double = vt_lerp(0, 100, elapsed / total)

' Blend two palette entries at the midpoint:
Dim r As Double = vt_lerp(col_a.r, col_b.r, 0.5)

vt_approach

Moves cur toward tgt by amt without overshooting. If cur = tgt it returns unchanged. amt should be positive.

Function vt_approach(cur As Long, tgt As Long, amt As Long) As Long
ParameterTypeDescription
curLongCurrent value.
tgtLongTarget value to approach.
amtLongStep size per call. Should be positive.
' Smooth enemy movement: move one step per frame toward the player:
enemy_x = vt_approach(enemy_x, player_x, 1)
enemy_y = vt_approach(enemy_y, player_y, 1)

' Animated score counter that catches up 10 points per frame:
display_score = vt_approach(display_score, real_score, 10)

vt_manhattan

Manhattan (taxicab) distance between two grid points. Counts only orthogonal steps — no diagonals. Equivalent to Abs(x2 − x1) + Abs(y2 − y1).

Function vt_manhattan(x1 As Long, y1 As Long, x2 As Long, y2 As Long) As Long
' Only attack if the player is within 4 orthogonal steps:
If vt_manhattan(enemy_x, enemy_y, player_x, player_y) <= 4 Then
    attack()
End If

vt_chebyshev

Chebyshev (king-moves) distance between two grid points. Diagonal steps cost 1, the same as orthogonal — matches 8-direction movement. Equivalent to VT_MAX(Abs(x2 − x1), Abs(y2 − y1)).

Function vt_chebyshev(x1 As Long, y1 As Long, x2 As Long, y2 As Long) As Long
' Check if a target is within melee range (1 king-move):
If vt_chebyshev(ax, ay, bx, by) = 1 Then
    melee_attack()
End If

vt_in_rect

Returns 1 if point (px, py) lies within the rectangle. The rect is half-open: [rx, rx+rw) × [ry, ry+rh) — the right and bottom edges are excluded. Returns 0 otherwise.

Function vt_in_rect(px As Long, py As Long, rx As Long, ry As Long, rw As Long, rh As Long) As Byte
ParameterTypeDescription
pxLongPoint X coordinate.
pyLongPoint Y coordinate.
rxLongRectangle left edge.
ryLongRectangle top edge.
rwLongRectangle width in cells.
rhLongRectangle height in cells.
' Hit-test a mouse click against a button region:
Dim mcol As Long, mrow As Long, mbtns As Long
vt_getmouse @mcol, @mrow, @mbtns, 0
If (mbtns And VT_MOUSE_BTN_LEFT) AndAlso vt_in_rect(mcol, mrow, 10, 5, 20, 3) Then
    button_clicked()
End If

vt_digits

Returns the number of characters that Str(n) or Print n would produce, including the minus sign for negative numbers. Zero returns 1. Uses LongInt internally to handle the full Long range without overflow.

Function vt_digits(n As Long) As Long
nReturns
01
12344
-993 (minus + 2 digits)
-214748364811
' Right-align a score in a fixed-width column:
Dim score As Long = 9870
Dim col As Long = 75 - vt_digits(score)  ' compute start column
vt_locate 1, col
vt_print Str(score)

vt_bresenham_walk

Walks every cell on the straight line from (x1, y1) to (x2, y2) using Bresenham's line algorithm and calls cb(x, y) for each cell in order. Both endpoints are included.

The callback is a Sub(x As Long, y As Long) — pass its address with @. Because FreeBASIC has no closures, use module-level Shared variables to give the callback access to map data or other state.

Sub vt_bresenham_walk(x1 As Long, y1 As Long, x2 As Long, y2 As Long, cb As Sub(As Long, As Long))
ParameterTypeDescription
x1, y1LongStart cell (included in walk).
x2, y2LongEnd cell (included in walk).
cbSub(As Long, As Long)Called once per cell on the line. Pass with @.
' Draw a line of ASCII dashes on the text screen:
Sub draw_dash(x As Long, y As Long)
    vt_set_cell x, y, Asc("-"), VT_WHITE, VT_BLACK
End Sub

vt_bresenham_walk 1, 1, 40, 15, @draw_dash
vt_present()

vt_los

Line-of-sight check from (x1, y1) to (x2, y2) using Bresenham's algorithm. The blocker function is called for each cell on the line and must return 1 if that cell blocks sight, 0 if clear.

The start cell is never tested — the observer is standing there. The end cell is tested — a wall blocks LOS to itself. Returns 1 if the path is clear, 0 if blocked.

Because FreeBASIC has no closures, use module-level Shared variables to give the blocker function access to your map array.

Function vt_los(x1 As Long, y1 As Long, x2 As Long, y2 As Long, blocker As Function(As Long, As Long) As Byte) As Byte
ParameterTypeDescription
x1, y1LongObserver position. Never passed to blocker.
x2, y2LongTarget position. Passed to blocker.
blockerFunction(As Long, As Long) As ByteReturns 1 if the cell blocks sight. Pass with @.

Return value

1 = line of sight is clear
0 = blocked by at least one cell on the path
' Roguelike enemy vision check:
Dim Shared g_map(79, 49) As Byte   ' 1 = wall

Function is_wall(x As Long, y As Long) As Byte
    Return g_map(x, y)
End Function

If vt_los(enemy_x, enemy_y, player_x, player_y, @is_wall) Then
    ' enemy can see the player -- enter alert state
    alert_enemy()
End If

vt_mat_rotate_cw

Rotates a 2D Long array 90° clockwise in-place. The array must be square. LBound and UBound are used internally to detect the size automatically — no size argument is needed. Non-square arrays are silently ignored (no-op). Works with any LBound (0-based or 1-based declarations).

Sub vt_mat_rotate_cw(m() As Long)
ParameterTypeDescription
m()Long arraySquare 2D array to rotate in-place.
' Rotate a Tetris piece clockwise:
Dim piece(0 To 3, 0 To 3) As Long
' ... fill piece with 0/1 values ...
vt_mat_rotate_cw piece()   ' one 90-degree CW step

vt_mat_rotate_ccw

Rotates a 2D Long array 90° counter-clockwise in-place. Identical behaviour to vt_mat_rotate_cw but in the opposite direction.

Sub vt_mat_rotate_ccw(m() As Long)
ParameterTypeDescription
m()Long arraySquare 2D array to rotate in-place.
' Four CCW rotations return the matrix to its original orientation:
vt_mat_rotate_ccw piece()
vt_mat_rotate_ccw piece()
vt_mat_rotate_ccw piece()
vt_mat_rotate_ccw piece()   ' back to start

Sound opt-in

The sound extension generates waveform samples and plays them via SDL_QueueAudio. No SDL2_mixer required — SDL2 core audio only. The audio subsystem initializes automatically on the first vt_sound call and does not require vt_screen to be open.

Opt-in: define VT_USE_SOUND before the include. Without it the entire extension is absent at compile time — zero overhead.
' Enable sound in your project:
#define VT_USE_SOUND
#include once "vt/vt.bi"
Shutdown is handled automatically by vt_shutdown. If vt_screen is called again after audio was active, the next vt_sound call transparently reopens the audio device.

vt_sound

Generate samples for a tone of the given frequency and duration and add them to the SDL audio queue. The waveform and blocking behaviour are selectable. Pass freq = 0 to stop playback and clear the queue immediately.

Function vt_sound(freq As Long, dur_ms As Long = 200, wave As Long = VT_WAVE_SQUARE, blocking As Long = VT_SOUND_BLOCKING) As Long
ParameterTypeDefaultDescription
freqLongnoneFrequency in Hz. 0 = stop and clear the queue immediately (all other parameters are ignored).
dur_msLong200Duration in milliseconds.
waveLongVT_WAVE_SQUAREWaveform. One of the VT_WAVE_* constants.
blockingLongVT_SOUND_BLOCKINGVT_SOUND_BLOCKING — waits for the note to finish while keeping the window fully alive (pump, blink, present). VT_SOUND_BACKGROUND — queues samples and returns immediately.

Return value

0 = success
-1 = SDL audio init or memory allocation failed
-2 = queue cap exceeded (VT_SND_QUEUE_CAP), call was skipped
Blocking wait and window responsiveness: when VT_SOUND_BLOCKING is used the wait loop calls vt_pump, blink update, and present on every tick, so the window redraws, blinks, and responds to the close button normally during the wait. This mirrors the behaviour of vt_sleep. When no window is open the loop simply sleeps and audio drains on its own SDL thread.
' Single blocking note (default):
vt_sound 440, 500

' Simple melody -- blocking, plays note-by-note:
vt_sound 262, 200
vt_sound 330, 200
vt_sound 392, 400

' Same melody queued in the background -- screen updates while it plays:
vt_sound 262, 200, VT_WAVE_SQUARE, VT_SOUND_BACKGROUND
vt_sound 330, 200, VT_WAVE_SQUARE, VT_SOUND_BACKGROUND
vt_sound 392, 400, VT_WAVE_SQUARE, VT_SOUND_BACKGROUND
vt_print "Playing..." : vt_present()
vt_sound_wait()

' Stop playback immediately:
vt_sound 0

vt_sound_wait

Block until the audio queue fully drains. This is the sync barrier for sequences queued with VT_SOUND_BACKGROUND — it lets you run code (update the screen, compute the next frame) while a melody plays, then wait for it to finish without having to calculate the total duration yourself. The window stays alive during the wait, identical to the blocking wait in vt_sound. No-op if the sound subsystem was never initialized.

Sub vt_sound_wait()

VT_BEEP

Convenience macro. Plays a short 800 Hz square-wave beep, blocking. Equivalent to vt_sound(800, 200).

#Define VT_BEEP vt_sound(800, 200)
' Simple error beep:
VT_BEEP

Strings opt-in

Pure FreeBASIC string utilities. No SDL2 or open VT screen required — all helpers work in any FreeBASIC project. Fills gaps that FreeBASIC's built-in string handling does not cover (InStr, Mid, LTrim etc. are already available and are not duplicated here).

Opt-in: define VT_USE_STRINGS before the include. Without it the entire extension is absent at compile time — zero overhead.
' Enable string helpers in your project:
#define VT_USE_STRINGS
#include once "vt/vt.bi"

vt_str_replace

Replace every non-overlapping occurrence of find in s with repl. Scanning always advances past the replacement so overlapping matches are not produced. Returns s unchanged if find is empty.

Function vt_str_replace(s As String, find As String, repl As String) As String
ParameterTypeDescription
sStringSource string.
findStringSubstring to search for. Empty string is a no-op.
replStringReplacement string. May be empty to delete all occurrences.
' Replace spaces with underscores:
Dim fname As String = vt_str_replace("hello world foo", " ", "_")
' fname = "hello_world_foo"

' Delete all occurrences:
Dim stripped As String = vt_str_replace("a--b--c", "--", "")
' stripped = "abc"

vt_str_split

Split s by delim and fill arr() with the resulting segments. The array is ReDim'd by this function; the caller only needs to declare Dim arr() As String. Returns the number of elements produced.

If delim is empty, each character of s becomes its own element. A trailing delimiter produces a final empty element (e.g. "a,b," yields three elements, the last being ""). An empty s always returns one element containing "".

Function vt_str_split(s As String, delim As String, arr() As String) As Long
ParameterTypeDescription
sStringSource string to split.
delimStringDelimiter string. Empty = split into individual characters.
arr()String arrayReceives the segments. ReDim'd to 0 To count-1.

Return value

Number of elements placed in arr().
' Parse a comma-separated list:
Dim parts() As String
Dim cnt As Long = vt_str_split("one,two,three", ",", parts())
' cnt = 3,  parts(0)="one"  parts(1)="two"  parts(2)="three"

For i As Long = 0 To cnt - 1
    vt_print parts(i) + !"\n"
Next i

vt_str_pad_left

Right-align s in a field of exactly length characters by padding with ch on the left. If s is already longer than length, the rightmost length characters are returned (truncation keeps the least-significant end, useful for numbers). Only the first character of ch is used; if ch is empty a space is substituted. Returns "" if length ≤ 0.

Function vt_str_pad_left(s As String, length As Long, ch As String) As String
ParameterTypeDescription
sStringSource string.
lengthLongDesired output width in characters.
chStringFill character. Only the first character is used.
' Zero-pad a score value to 6 digits:
vt_print vt_str_pad_left(Str(score), 6, "0")   ' "001450"

' Right-align a label in a 10-char column:
vt_print "[" + vt_str_pad_left("hi", 10, " ") + "]"   ' "[        hi]"

vt_str_pad_right

Left-align s in a field of exactly length characters by padding with ch on the right. If s is already longer than length, the leftmost length characters are returned. Only the first character of ch is used; if ch is empty a space is substituted. Returns "" if length ≤ 0.

Function vt_str_pad_right(s As String, length As Long, ch As String) As String
ParameterTypeDescription
sStringSource string.
lengthLongDesired output width in characters.
chStringFill character. Only the first character is used.
' Fixed-width label next to a zero-padded value:
vt_print vt_str_pad_right("SCORE", 10, " ") + vt_str_pad_left(Str(score), 6, "0")
' "SCORE      001450"

vt_str_repeat

Return s concatenated n times. Returns "" for n ≤ 0 or an empty s. Single-character strings use FreeBASIC's String() internally for efficiency.

Function vt_str_repeat(s As String, n As Long) As String
ParameterTypeDescription
sStringString to repeat.
nLongNumber of repetitions. 0 or negative returns "".
' Draw a separator line:
vt_print vt_str_repeat("-=", 20) + !"\n"   ' "-=-=-=-=-= ..." (40 chars)

' Build a progress bar:
Dim bar As String = "[" + vt_str_repeat("#", filled) + vt_str_repeat(".", empty_slots) + "]"

vt_str_count

Count non-overlapping occurrences of find in s. Returns 0 if either string is empty.

Function vt_str_count(s As String, find As String) As Long
ParameterTypeDescription
sStringString to search in.
findStringSubstring to count. Empty string always returns 0.

Return value

Number of non-overlapping occurrences found. 0 if none or if either argument is empty.
' Count vowels (as single chars):
Dim n As Long = vt_str_count("banana", "a")   ' 3

' Count two-char patterns (non-overlapping):
n = vt_str_count("banana", "an")   ' 2  ("b[an][an]a")

vt_str_starts_with

Returns 1 if s begins with pfx, 0 otherwise. An empty pfx always returns 1.

Function vt_str_starts_with(s As String, pfx As String) As Byte
ParameterTypeDescription
sStringString to test.
pfxStringExpected prefix.
' Simple command dispatch:
If vt_str_starts_with(input_line, "/quit") Then end_game = 1
If vt_str_starts_with(input_line, "/say ")  Then broadcast Mid(input_line, 6)

vt_str_ends_with

Returns 1 if s ends with sfx, 0 otherwise. An empty sfx always returns 1.

Function vt_str_ends_with(s As String, sfx As String) As Byte
ParameterTypeDescription
sStringString to test.
sfxStringExpected suffix.
' Check file extension:
If vt_str_ends_with(filename, ".vts") Then vt_bload filename

vt_str_trim_chars

Remove any character found in the set chars from both ends of s and return the result. FreeBASIC's built-in Trim only removes spaces; this function accepts any set of characters. Returns "" if s consists entirely of characters in the set. Returns s unchanged if chars is empty.

Function vt_str_trim_chars(s As String, chars As String) As String
ParameterTypeDescription
sStringSource string.
charsStringSet of characters to strip. Each character in this string is treated as an individual trim candidate.
' Strip decorative border characters:
Dim clean As String = vt_str_trim_chars("***hello***", "*")
' clean = "hello"

' Strip spaces and dots from both ends:
clean = vt_str_trim_chars("  ..hi.. ", " .")
' clean = "hi"

vt_str_wordwrap

Insert Chr(10) line breaks into s so that no line exceeds col_width characters. Words are never split — if a word is longer than col_width it occupies its own line unbroken. Existing Chr(10) and Chr(13) characters in s are preserved and reset the line-length counter; CRLF pairs are collapsed to a single Chr(10).

The return value is a plain string with embedded newlines. Pass it directly to vt_print, which handles Chr(10) natively.

Function vt_str_wordwrap(s As String, col_width As Long = -1, first_offset As Long = 0) As String
ParameterTypeDefaultDescription
sStringnoneSource text to wrap.
col_widthLong-1Maximum line width in characters. -1 uses scr_cols if a VT screen is open, otherwise falls back to 80.
first_offsetLong0Characters already consumed on the first line before the text begins (e.g. the width of an inline label). Subsequent lines always start at column 0.
No screen required: col_width = -1 falls back to 80 when no VT screen is open, so vt_str_wordwrap is safe to call before vt_screen or in headless contexts.
' Wrap a long description inside a 30-char dialog window interior:
Dim msg As String = "This item restores your health fully and also cures poison."
vt_print vt_str_wordwrap(msg, 30)

' Label already printed on the same line -- tell the wrapper to account for it:
vt_print "Description: "
vt_print vt_str_wordwrap(msg, 40, 13)

' Use screen width automatically (requires open screen):
vt_print vt_str_wordwrap(msg)

File I/O Helpers opt-in

Fills gaps in FreeBASIC's built-in file and directory handling. Wraps Dir() from dir.bi cleanly, hiding the fbDirectory constant and the multi-call scan idiom from caller code. Functions that duplicate existing FreeBASIC built-ins without adding behaviour (MkDir, RmDir, Kill, Name) are intentionally not wrapped — use them directly.

Opt-in: define VT_USE_FILE before the include. Without it the entire extension is absent at compile time — zero overhead.
' Enable file helpers in your project:
#define VT_USE_FILE
#include once "vt/vt.bi"
No SDL2 or open VT screen required — all helpers are pure FreeBASIC and usable in any project.
Windows Explorer interference: if Explorer (or any shell tool) has the target directory open, RmDir may fail or vt_file_isdir may briefly return 1 immediately after deletion due to Explorer holding a filesystem watch handle. This is OS behaviour, not a library bug. Close Explorer or any open file manager window pointing at the target before recursive deletion if reliable return codes are required.

File Flags

Bit-flags passed to vt_file_copy, vt_file_rmdir, and vt_file_list. Combine with Or.

ConstantValueApplies toDescription
VT_FILE_SHOW_HIDDEN1vt_file_listInclude hidden items in the listing.
VT_FILE_SHOW_DIRS2vt_file_listInclude subdirectory names alongside files.
VT_FILE_DIRS_ONLY4vt_file_listReturn only subdirectory names. Implies VT_FILE_SHOW_DIRS.
VT_FILE_OVERWRITE8vt_file_copyAllow the destination file to be overwritten if it already exists.
VT_FILE_RECURSIVE16vt_file_rmdirDelete all contents and subdirectories before removing the directory. Destructive — no undo.

vt_file_exists

Returns 1 if path names an existing file, 0 if the path does not exist or is a directory. Uses Dir() directly — no vbcompat.bi required.

Function vt_file_exists(path As String) As Byte
ParameterTypeDescription
pathStringPath to test. May be relative or absolute.

Return value

1 = file exists
0 = not found, or path is a directory
' Guard a bload call:
If vt_file_exists("savegame.vts") Then
    vt_bload "savegame.vts"
End If

vt_file_isdir

Returns 1 if path is an existing directory, 0 otherwise. Hides the fbDirectory constant and dir.bi dependency from caller code entirely.

Function vt_file_isdir(path As String) As Byte
ParameterTypeDescription
pathStringPath to test. May be relative or absolute.

Return value

1 = path is an existing directory
0 = not found, or path is a file
' Create output folder only if it does not already exist:
If vt_file_isdir("output") = 0 Then MkDir "output"

vt_file_copy

Copy a file or a full directory tree. The function auto-detects whether src is a file or a directory — no separate call needed.

File mode: the source file is read into memory and written to the destination in a single binary I/O operation. Fails if the destination already exists unless VT_FILE_OVERWRITE is passed.

Directory mode: the full directory tree is recreated under dst. If dst does not exist it is created; if it already exists the trees are merged. Individual files that already exist in dst are silently skipped unless VT_FILE_OVERWRITE is set. All entries are collected in a single Dir() pass before anything is written, so the global scan state is never corrupted by recursion.

Recursion trap: copying a directory into itself or into one of its own subdirectories is detected and refused with -4. For example vt_file_copy("data", "data/backup") returns -4 before any file is touched.
Function vt_file_copy(src As String, dst As String, flags As Long = 0) As Long
ParameterTypeDefaultDescription
srcStringnoneSource file or directory path.
dstStringnoneDestination file or directory path.
flagsLong0VT_FILE_OVERWRITE — overwrite existing destination files.

Return value

0 = success
-1 = source not found (neither file nor directory)
-2 = destination file exists without VT_FILE_OVERWRITE, or dst is an existing file while src is a directory (type mismatch)
-3 = I/O error (open, read/write, or MkDir failure)
-4 = recursion trap: dst is src itself or lives inside src
' Simple backup before overwriting:
vt_file_copy "config.ini", "config.bak"

' Overwrite an existing destination file:
vt_file_copy "save_new.vts", "save_slot1.vts", VT_FILE_OVERWRITE

' Copy a full directory tree:
vt_file_copy "data", "data_backup"

' Copy tree, overwrite any files that already exist in dst:
vt_file_copy "data", "data_backup", VT_FILE_OVERWRITE

' Check result:
Dim ret As Long = vt_file_copy("src.dat", "dst.dat")
If ret <> 0 Then
    vt_print "copy failed: " & ret & VT_NEWLINE
End If

vt_file_rmdir

Remove a directory. Without VT_FILE_RECURSIVE the directory must be empty — for that common case FreeBASIC's built-in RmDir is sufficient and preferred. Use vt_file_rmdir when recursive deletion of a populated directory tree is needed.

When VT_FILE_RECURSIVE is set, all files and subdirectories are collected in a single Dir() pass before anything is deleted, so the global Dir() scan state is never corrupted by recursive calls. Files are removed with Kill, subdirectories by recursing into vt_file_rmdir, then the now-empty root is removed with RmDir.

Warning: VT_FILE_RECURSIVE is permanently destructive. There is no undo. The flag must be passed explicitly — the default is always safe (empty-dir only).
Function vt_file_rmdir(path As String, flags As Long = 0) As Long
ParameterTypeDefaultDescription
pathStringnonePath to the directory to remove.
flagsLong0Optional flags. VT_FILE_RECURSIVE deletes all contents first.

Return value

0 = success
-1 = path is not an existing directory
-2 = directory is not empty and VT_FILE_RECURSIVE was not set
-3 = failed (permissions or partial recursive failure)
' Remove an empty temp directory (prefer plain RmDir for this):
vt_file_rmdir "tmp_empty"

' Remove a populated directory tree -- explicit opt-in required:
vt_file_rmdir "build_output", VT_FILE_RECURSIVE

vt_file_list

Scan a directory for items matching pattern and fill arr() with their bare names (no path prefix). The . and .. entries are always silently skipped. The array is ReDim'd by this function; the caller only needs to declare Dim arr() As String.

By default only files are returned. Pass VT_FILE_SHOW_DIRS to include subdirectory names alongside files, or VT_FILE_DIRS_ONLY to return only subdirectory names.

Function vt_file_list(path As String, pattern As String, arr() As String, flags As Long = 0) As Long
ParameterTypeDefaultDescription
pathStringnoneDirectory to scan. Pass "" to scan the current working directory.
patternStringnoneFilename pattern, e.g. "*.txt" or "*". Supports * and ? wildcards.
arr()String arraynoneReceives the item names. ReDim'd to 0 To count-1.
flagsLong0Optional combination of VT_FILE_SHOW_HIDDEN, VT_FILE_SHOW_DIRS, VT_FILE_DIRS_ONLY.

Return value

Number of items placed in arr(). 0 if nothing matched.
' List all .txt files in a folder:
Dim files() As String
Dim cnt As Long = vt_file_list("notes", "*.txt", files())
For i As Long = 0 To cnt - 1
    vt_print files(i) & VT_NEWLINE
Next i

' List files and subdirectories together:
cnt = vt_file_list("data", "*", files(), VT_FILE_SHOW_DIRS)

' List only subdirectories:
cnt = vt_file_list("data", "*", files(), VT_FILE_DIRS_ONLY)

' Scan current directory, include hidden files:
cnt = vt_file_list("", "*", files(), VT_FILE_SHOW_HIDDEN)

TUI Widgets opt-in

DOS-style TUI widgets: windows, dialogs, menus, forms, file picker, editor. All coordinates are 1-based column / row, matching the rest of the VT API. Enable with:

#Define VT_USE_TUI
#include once "vt/vt.bi"

VT_USE_TUI automatically pulls in VT_USE_STRINGS and VT_USE_FILE — you do not need to define them separately. All TUI functions work identically on both the SDL2 and the TTY backends.

Theme auto-init: if neither vt_tui_theme nor vt_tui_theme_default is called before the first draw call, the default DOS/BIOS theme is applied automatically. You will never see all-black-on-black by accident.

vt_tui_theme_default / vt_tui_theme

Set the colour theme used by all TUI widgets. All colours are CGA palette indices (0–15, see Color Constants). The theme is a single global struct — there is no per-widget override.

vt_tui_theme_default restores the built-in DOS/BIOS defaults. vt_tui_theme sets all fields in one call. Neither function requires vt_screen to have been called first.

Sub vt_tui_theme_default() Sub vt_tui_theme(win_fg As UByte, win_bg As UByte, title_fg As UByte, title_bg As UByte, bar_fg As UByte, bar_bg As UByte, btn_fg As UByte, btn_bg As UByte, dlg_fg As UByte, dlg_bg As UByte, inp_fg As UByte, inp_bg As UByte, border_style As UByte)
ParameterTypeDescription
win_fg / win_bgUByteWindow body text and background.
title_fg / title_bgUByteTitle bar text and background. Also used as the filled portion colour of a progress bar.
bar_fg / bar_bgUByteStatus bar and menu bar text and background.
btn_fg / btn_bgUByteButton text and background (unfocused state).
dlg_fg / dlg_bgUByteDialog body text and background. Also used for dropdown menu item rows.
inp_fg / inp_bgUByteInput field and editor text and background. Also used as the empty portion colour of a progress bar.
border_styleUByte0 = single-line CP437 box-drawing (default). 1 = double-line CP437 box-drawing.

Default values

Rolefgbg
window bodyVT_BLACKVT_LIGHT_GREY
title barVT_WHITEVT_BLUE
status / menu barVT_BLACKVT_CYAN
buttonVT_BLACKVT_LIGHT_GREY
dialog bodyVT_BLACKVT_LIGHT_GREY
input field / editorVT_WHITEVT_DARK_GREY
Highlight / selection: focused buttons, selected list items, and open menu group headers are rendered with foreground and background XOR’d with 15 (full colour inversion). This is not configurable — it always contrasts correctly regardless of the active theme.
' Restore DOS blue-scheme defaults:
vt_tui_theme_default()

' Custom dark theme:
vt_tui_theme VT_LIGHT_GREY, VT_BLACK,    _
             VT_WHITE,      VT_DARK_GREY, _
             VT_BLACK,      VT_CYAN,      _
             VT_LIGHT_GREY, VT_DARK_GREY, _
             VT_LIGHT_GREY, VT_BLACK,     _
             VT_WHITE,      VT_DARK_GREY, _
             0

vt_tui_rect_fill

Fill a 1-based rectangle with a single character and colour. This is the base drawing primitive — all other widgets use it internally to clear their area.

Sub vt_tui_rect_fill(x As Long, y As Long, wid As Long, hei As Long, ch As UByte, fg As UByte, bg As UByte)
ParameterTypeDescription
x, yLong1-based column and row of the top-left corner.
wid, heiLongWidth and height in cells.
chUByteCP437 character code to fill with. Use 32 (space) for a solid colour block.
fg, bgUByteForeground and background colour indices.
' Clear a region to dark grey:
vt_tui_rect_fill 1, 1, 40, 10, 32, VT_LIGHT_GREY, VT_DARK_GREY

' Solid red banner row:
vt_tui_rect_fill 1, 1, 80, 1, 32, VT_BLACK, VT_RED

vt_tui_hline / vt_tui_vline

Draw a horizontal or vertical separator line using CP437 box-drawing characters. The character is chosen automatically from the active theme's border_style: single-line style uses characters 196 (horizontal) and 179 (vertical); double-line style uses 205 and 186.

Sub vt_tui_hline(x As Long, y As Long, wid As Long, fg As UByte, bg As UByte) Sub vt_tui_vline(x As Long, y As Long, hei As Long, fg As UByte, bg As UByte)
ParameterTypeDescription
x, yLong1-based start column and row.
wid / heiLongLength of the line in cells.
fg, bgUByteForeground and background colour indices.
' Horizontal divider across a full 80-column screen:
vt_tui_hline 1, 13, 80, VT_DARK_GREY, VT_BLACK

' Vertical divider splitting the screen at column 40:
vt_tui_vline 40, 1, 25, VT_DARK_GREY, VT_BLACK

vt_tui_window

Draw window chrome: a border, a title bar, and optionally a close button and drop shadow. Does not save or restore the background and does not block. The window interior is filled with the theme's win_fg / win_bg colours.

Sub vt_tui_window(x As Long, y As Long, wid As Long, hei As Long, title As String, flags As Long = 0)
ParameterTypeDefaultDescription
x, yLongnone1-based column and row of the top-left corner.
wid, heiLongnoneOuter width and height including the border.
titleStringnoneText centred in the title bar. Truncated if too long.
flagsLong0Combination of VT_TUI_WIN_CLOSEBTN and / or VT_TUI_WIN_SHADOW.
VT_TUI_WIN_CLOSEBTN renders [x] visually only. The caller is responsible for detecting clicks on it using vt_tui_mouse_in_rect(x + wid - 4, y, 3, 1).
' Centred window, 40 wide × 12 tall, with shadow:
Dim wx As Long = (80 - 40) \ 2 + 1
Dim wy As Long = (25 - 12) \ 2 + 1
vt_tui_window wx, wy, 40, 12, "Settings", VT_TUI_WIN_SHADOW

' With close button — detect click separately:
vt_tui_window wx, wy, 40, 12, "Settings", VT_TUI_WIN_CLOSEBTN Or VT_TUI_WIN_SHADOW
If vt_tui_mouse_in_rect(wx + 40 - 4, wy, 3, 1) Then
    ' [x] was clicked
End If

vt_tui_statusbar

Fill an entire screen row with the theme's bar_bg colour and print caption left-aligned in bar_fg. Typical use: the last row for a keyboard hint bar, or row 1 below a menu bar.

Sub vt_tui_statusbar(row As Long, caption As String)
ParameterTypeDescription
rowLong1-based screen row to fill (full width).
captionStringText printed left-aligned. Clipped to screen width.
' Hint bar on the last row:
vt_tui_statusbar 25, " F1=Help  F10=Menu  Esc=Quit"

vt_tui_progress

Draw a horizontal progress bar at an arbitrary screen position. The filled portion uses the theme's title_bg colour as a solid block; the empty portion uses inp_bg. Both halves use space (32) as the character so colour alone carries the bar.

Sub vt_tui_progress(x As Long, y As Long, wid As Long, value As Long, max_val As Long, flags As Long = 0)
ParameterTypeDefaultDescription
x, yLongnone1-based column and row.
widLongnoneWidth of the bar in cells.
valueLongnoneCurrent progress value. Clamped to [0 .. max_val].
max_valLongnoneMaximum value. Must be > 0.
flagsLong0VT_TUI_PROG_LABEL — print a centred nn% label. Label fg colour inverts per-cell so it reads correctly across both halves.
' Progress bar updating inside a loop:
For i As Long = 0 To 100
    vt_tui_progress 5, 10, 40, i, 100, VT_TUI_PROG_LABEL
    vt_present()
    vt_sleep 20
Next i

vt_tui_mouse_in_rect

Hit-test helper. Returns 1 if the current mouse position is inside the given 1-based rectangle, 0 otherwise. Returns 0 immediately if the library is not ready or the mouse is disabled. Reads the internal mouse state updated by vt_inkey — call this after polling input, not before.

Function vt_tui_mouse_in_rect(x As Long, y As Long, wid As Long, hei As Long) As Byte
ParameterTypeDescription
x, yLong1-based column and row of the top-left corner of the hit area.
wid, heiLongWidth and height in cells.

Return value

1 = mouse cursor is inside the rectangle
0 = outside, mouse disabled, or library not ready
' Hover detection over a custom 10-cell-wide button at row 5:
If vt_tui_mouse_in_rect(10, 5, 10, 1) Then
    ' highlight the button
End If

' Close-button detection for a window at (wx, wy) of outer width ww:
If vt_tui_mouse_in_rect(wx + ww - 4, wy, 3, 1) Then
    ' [x] hovered or clicked
End If

vt_tui_input_field

Single-line text input at an arbitrary screen position. Blocking. Draws a field of wid cells in the theme's inp_fg / inp_bg colours and lets the user edit the text. Supports buffers longer than the visible width via horizontal scrolling. The text cursor is shown during editing and its previous visibility state is restored on exit.

Function vt_tui_input_field(x As Long, y As Long, wid As Long, initial As String, max_len As Long) As String
ParameterTypeDescription
x, yLong1-based column and row of the left edge of the field.
widLongVisible field width in cells.
initialStringPre-filled text. Cursor starts at the end of the text.
max_lenLongMaximum characters the buffer may hold. Pass 0 to use wid as the limit.

Return value

Edited string on Enter.
initial unchanged on Escape.

Key bindings

KeyAction
Left / RightMove cursor one character.
Home / EndCursor to start / end of text.
Backspace / DelDelete left / right of cursor.
Printable charInsert at cursor (respects max_len).
EnterConfirm — return edited text.
EscapeCancel — return initial unchanged.
' Prompt for a filename:
Dim fname As String = vt_tui_input_field(10, 12, 30, "", 64)
If Len(fname) > 0 Then
    ' use fname
End If

' Pre-filled edit (e.g. rename dialog):
Dim newname As String = vt_tui_input_field(10, 12, 30, "oldname.txt", 64)

vt_tui_listbox

Scrollable item list. Blocking. Draws the list at the given position, handles keyboard and mouse input, and returns when the user confirms or cancels. A scrollbar is drawn automatically on the right edge when the item count exceeds the visible height.

Function vt_tui_listbox(x As Long, y As Long, wid As Long, hei As Long, items() As String, flags As Long = 0) As Long
ParameterTypeDefaultDescription
x, yLongnone1-based column and row of the top-left corner.
wid, heiLongnoneWidth and visible height in cells. The scrollbar occupies the rightmost cell when active.
items()String arraynoneItems to display. Any array lower bound is accepted.
flagsLong0Reserved for future use.

Return value

0-based index of the confirmed item (Enter or double-click).
-1 on Escape (cancel).

Key and mouse bindings

InputAction
Up / DownMove selection one item.
PgUp / PgDnMove selection by hei items.
Home / EndJump to first / last item.
EnterConfirm selection.
EscapeCancel, return -1.
ClickHighlight the clicked item.
Double-clickConfirm the clicked item immediately.
Scroll wheelScroll the view without moving the selection.
' Difficulty picker:
Dim opts(2) As String
opts(0) = "Easy" : opts(1) = "Normal" : opts(2) = "Hard"

Dim picked As Long = vt_tui_listbox(20, 8, 20, 3, opts())
If picked >= 0 Then
    vt_print "Chose: " & opts(picked) & VT_NEWLINE
End If

vt_tui_dialog

Self-contained blocking modal dialog. Saves the background before drawing and restores it on close. The text is word-wrapped automatically to fit; the window grows to accommodate both the wrapped content and the button row. The dialog is centred on screen (default 60 columns wide, clamped to screen width minus 4).

Function vt_tui_dialog(caption As String, txt As String, flags As Long = VT_DLG_OK) As Long
ParameterTypeDefaultDescription
captionStringnoneTitle bar text.
txtStringnoneBody text. Word-wrapped to fit the dialog width.
flagsLongVT_DLG_OKButton set constant, optionally combined with VT_DLG_NO_ESC.

Return value

One of VT_RET_OK, VT_RET_CANCEL, VT_RET_YES, VT_RET_NO.

Key and mouse bindings

InputAction
TabAdvance focus to the next button (wraps).
Left / RightMove focus between buttons.
EnterActivate the focused button.
EscapeReturn VT_RET_CANCEL (unless VT_DLG_NO_ESC is set).
ClickActivate the clicked button immediately.
' Quit confirmation:
If vt_tui_dialog("Quit", "Are you sure you want to quit?", VT_DLG_YESNO) = VT_RET_YES Then
    End
End If

' Mandatory choice (Escape disabled):
Dim r As Long = vt_tui_dialog("Disk Full", "Retry or abort the operation?", _
                               VT_DLG_OKCANCEL Or VT_DLG_NO_ESC)

' Simple info box:
vt_tui_dialog "Done", "File saved successfully."

vt_tui_file_dialog

Self-contained blocking file / directory picker. Saves the background before drawing and restores it on close. Centred on screen, automatically sized to fit the available area (maximum 70 columns wide).

The listing always includes subdirectories (shown with a trailing /) and a .. entry at the top whenever a parent exists. Files are matched against pattern. All returned paths use forward slashes on both Windows and Linux.

Function vt_tui_file_dialog(title As String, start_path As String, pattern As String, flags As Long = 0) As String
ParameterTypeDefaultDescription
titleStringnoneTitle bar text and confirm button label (e.g. "Open", "Save").
start_pathStringnoneInitial directory. Backslashes are normalized to forward slashes automatically. Pass "" or "./" for the current working directory.
patternStringnoneFilename wildcard, e.g. "*.txt" or "*". Ignored when VT_TUI_FD_DIRSONLY is set.
flagsLong0VT_TUI_FD_DIRSONLY — show only subdirectories; Enter or OK on a directory confirms it.

Return value

Full selected path as a String. Directory paths end with /.
"" on cancel (Escape or Cancel button).

Key and mouse bindings

InputAction
Up / Down / PgUp / PgDn / Home / EndNavigate the file list. The name field updates to match the selected entry.
Enter on a fileConfirm that file.
Enter on a directoryNavigate into it (or confirm it in VT_TUI_FD_DIRSONLY mode).
Double-click a directoryNavigate into it (or confirm in VT_TUI_FD_DIRSONLY mode).
Double-click a fileConfirm that file.
BackspaceDelete the last character from the name field, or navigate up one directory if the name field is empty.
TabActivate the name field for direct text entry.
Printable charAppend to the name field.
F2Keyboard equivalent of the OK button.
Click on name fieldOpen the name field for editing.
Scroll wheelScroll the file list.
EscapeCancel, return "".
' Open file picker for FreeBASIC sources:
Dim path As String = vt_tui_file_dialog("Open", "./", "*.bas")
If Len(path) > 0 Then
    vt_print "Selected: " & path & VT_NEWLINE
End If

' Save-as dialog starting in a saves subfolder:
path = vt_tui_file_dialog("Save", "./saves/", "*.sav")

' Directory picker only:
path = vt_tui_file_dialog("Choose Folder", "./", "*", VT_TUI_FD_DIRSONLY)

vt_tui_editor

Multi-line text editor / viewer widget. Blocking. Text is stored and returned as a single String with Chr(10) as the line separator. The widget renders inside the given rectangle using the theme's inp_fg / inp_bg colours. Lines exceeding wid are truncated in display; no horizontal scrolling.

Function vt_tui_editor(x As Long, y As Long, wid As Long, hei As Long, txt As String, flags As Long = 0) As String
ParameterTypeDefaultDescription
x, yLongnone1-based column and row of the top-left corner of the editing area.
wid, heiLongnoneWidth and height in cells. Place directly inside a vt_tui_window border.
txtStringnoneInitial text. Use Chr(10) as the line separator.
flagsLong0VT_TUI_ED_READONLY — view and scroll only, no editing. Text cursor is hidden.

Return value

Edited text on F10 or Ctrl+Enter.
Original txt unchanged on Escape.

Key bindings

KeyAction
Up / DownMove cursor one line.
PgUp / PgDnMove cursor by hei lines.
Left / RightMove cursor one character (byte offset).
Home / EndCursor to start / end of the current line.
EnterInsert a newline (edit mode only).
Backspace / DelDelete left / right of cursor (edit mode only).
Printable charInsert at cursor (edit mode only).
F10 or Ctrl+EnterConfirm — return the edited text.
EscapeCancel — return original text unchanged.
No undo / redo in v1. No word-wrap in edit mode — lines exceeding wid are displayed truncated but stored intact.
' Note editor inside a window:
Dim note As String = "First line" & Chr(10) & "Second line"
vt_tui_window 5, 3, 70, 20, "Notes  [F10=Save  Esc=Cancel]"
note = vt_tui_editor(6, 4, 68, 18, note)

' Read-only file viewer:
Dim content As String
Open "readme.txt" For Binary As #1
content = Space(LOF(1))
Get #1, , content
Close #1
vt_tui_editor 1, 1, 80, 24, content, VT_TUI_ED_READONLY

vt_tui_menubar_draw / vt_tui_menubar_handle

Horizontal menu bar. Two-function design: vt_tui_menubar_draw renders the bar passively (non-blocking, call once per frame); vt_tui_menubar_handle is non-blocking while no menu is open and blocks with its own inner event loop while a dropdown is open.

Sub vt_tui_menubar_draw(row As Long, groups() As String) Function vt_tui_menubar_handle(row As Long, groups() As String, items() As String, counts() As Long, k As ULong) As Long
ParameterTypeDescription
rowLong1-based screen row for the menu bar (typically row 1).
groups()String arrayGroup header labels, e.g. "File", "Edit", "Help".
items()String arrayFlat array of all items across all groups, in group order.
counts()Long arrayItem count per group, parallel to groups().
kULongKey value from vt_inkey() for the current frame.

Return value (vt_tui_menubar_handle)

group * 1000 + item_index (both 1-based) when an item is activated — use VT_TUI_MENU_GROUP(r) and VT_TUI_MENU_ITEM(r) to unpack.
0 while idle or after Escape closes a dropdown.

Opening and navigating dropdowns

InputAction
Alt + first letter of group labelOpen that group's dropdown.
Click on a group headerOpen that group's dropdown.
Up / Down (dropdown open)Move selection. Wraps around.
Left / Right (dropdown open)Switch to the adjacent group.
Alt + first letter (dropdown open)Switch to that group's dropdown.
Enter (dropdown open)Activate the selected item.
Click on a dropdown itemActivate that item.
Click on a different group headerSwitch to that group's dropdown.
Click anywhere elseClose the dropdown, return 0.
EscapeClose the dropdown, return 0.
Alt+letter detection works identically on SDL2 and both TTY backends via VT_ALT(k) and VT_CHAR(k) — no backend-specific code needed in the caller.
No nested submenus in v1. No item-level hotkeys in v1.
' Setup arrays:
Dim groups(2) As String
groups(0) = "File" : groups(1) = "Edit" : groups(2) = "Help"

Dim items(6) As String
items(0) = "New"   : items(1) = "Open" : items(2) = "Save" : items(3) = "Quit"
items(4) = "Cut"   : items(5) = "Copy"
items(6) = "About"

Dim counts(2) As Long
counts(0) = 4 : counts(1) = 2 : counts(2) = 1

' Main loop:
Dim k As ULong
Do
    k = vt_inkey()

    vt_tui_menubar_draw 1, groups()
    Dim r As Long = vt_tui_menubar_handle(1, groups(), items(), counts(), k)
    If r <> 0 Then
        Select Case VT_TUI_MENU_GROUP(r)
            Case 1  ' File
                Select Case VT_TUI_MENU_ITEM(r)
                    Case 1 : ' New
                    Case 2 : ' Open
                    Case 3 : ' Save
                    Case 4 : End  ' Quit
                End Select
        End Select
    End If

    vt_present()
    vt_sleep 10
Loop

vt_tui_form_draw / vt_tui_form_handle

Non-blocking form widget. Two-function design: vt_tui_form_draw renders all items passively each frame; vt_tui_form_handle processes one key or mouse event and returns immediately. The caller owns the vt_tui_form_item array and loops until vt_tui_form_handle returns a value other than VT_FORM_PENDING.

vt_tui_form_item type

Declare an array of this type to define the form layout. Set .cpos and .view_off to 0 when initialising — vt_tui_form_handle manages them internally afterward.

Type vt_tui_form_item kind As UByte ' VT_FORM_INPUT or VT_FORM_BUTTON x As Long ' 1-based column y As Long ' 1-based row wid As Long ' input: visible field width; ignored for buttons val As String ' input: current text; button: label e.g. " OK " ret As Long ' button: return value on activation; input: unused max_len As Long ' input: max chars (0 = use wid); button: unused cpos As Long ' internal -- init to 0, do not modify view_off As Long ' internal -- init to 0, do not modify End Type
Sub vt_tui_form_draw(items() As vt_tui_form_item, ByRef focused As Long) Function vt_tui_form_handle(items() As vt_tui_form_item, ByRef focused As Long, k As ULong) As Long
ParameterTypeDescription
items()vt_tui_form_item arrayForm items. Any array lower bound is accepted.
focusedLong (ByRef)0-based index of the currently focused item. Updated in place by form_handle on focus changes so the next form_draw call reflects the new state.
kULongKey value from vt_inkey() for the current frame. Pass 0 if no key was pressed this frame.

Return value (vt_tui_form_handle)

VT_FORM_PENDING (−2) — still editing, call again next frame.
VT_FORM_CANCEL (−1) — Escape was pressed.
≥ 0 — the .ret value of an activated button (e.g. VT_RET_OK = 1, VT_RET_CANCEL = 0).
VT_FORM_PENDING is −2, not 0, so that VT_RET_CANCEL = 0 can be a valid button return value without ambiguity. Test for done with result <> VT_FORM_PENDING.

Key and mouse bindings

InputAction
Tab / Shift+TabAdvance / retreat focus through all items (wraps).
Enter on an inputAdvance focus to the next item (same as Tab).
Enter on a buttonActivate that button, return its .ret.
Left / Right on a buttonMove focus to the adjacent item.
Left / Right on an inputMove the cursor within the field.
Home / End on an inputCursor to start / end of the text.
Backspace / Del on an inputDelete left / right of cursor.
Printable char on an inputInsert at cursor (respects max_len).
Click on an inputFocus it; cursor snaps to the click column.
Click on a buttonActivate immediately, return its .ret.
Escape (any item)Return VT_FORM_CANCEL.
' Login form: two text inputs and two buttons.
Dim fitems(3) As vt_tui_form_item

fitems(0).kind    = VT_FORM_INPUT : fitems(0).x = 16 : fitems(0).y = 8
fitems(0).wid     = 20            : fitems(0).max_len = 32

fitems(1).kind    = VT_FORM_INPUT : fitems(1).x = 16 : fitems(1).y = 10
fitems(1).wid     = 20            : fitems(1).max_len = 32

fitems(2).kind    = VT_FORM_BUTTON : fitems(2).x = 16 : fitems(2).y = 12
fitems(2).val     = " OK "         : fitems(2).ret = VT_RET_OK

fitems(3).kind    = VT_FORM_BUTTON : fitems(3).x = 23 : fitems(3).y = 12
fitems(3).val     = " Cancel "     : fitems(3).ret = VT_RET_CANCEL

Dim focused As Long = 0
Dim result  As Long = VT_FORM_PENDING
Dim k       As ULong

Do
    k = vt_inkey()
    vt_tui_window 10, 5, 42, 11, "Login"
    vt_locate 8,  5 : vt_print "Username:"
    vt_locate 10, 5 : vt_print "Password:"
    vt_tui_form_draw   fitems(), focused
    result = vt_tui_form_handle(fitems(), focused, k)
    vt_present()
    vt_sleep 10
Loop While result = VT_FORM_PENDING

If result = VT_RET_OK Then
    vt_print "User: " & fitems(0).val & VT_NEWLINE
End If

VT Virtual Text Screen Library — API Reference — v1.3.4
FreeBASIC 1.10.1 / SDL2 + TTY / Windows + Linux