diff --git a/README.md b/README.md index 73a2b7a..d5a77b5 100644 --- a/README.md +++ b/README.md @@ -1,226 +1,110 @@ # DVX -- DOS Visual eXecutive -A windowed GUI compositor and widget toolkit targeting DJGPP/DPMI on -486+ hardware. VESA VBE 2.0+ LFB only. Motif-inspired visual style -with 2px bevels, fixed bitmap fonts, and dirty-rect compositing. +DVX is a windowed GUI environment and application platform for DOS. +It provides a Motif-inspired desktop, a cooperative multitasking kernel, +a widget toolkit, a compiled BASIC language with a visual form designer, +and a small set of bundled applications. -The system runs on 86Box (primary target) and real DOS hardware. +The system is a compositor built on VESA VBE 2.0+ linear framebuffer +video modes. A dirty-rectangle compositor keeps redraw cost proportional +to what actually changes on screen, so the desktop is responsive even at +1024x768 on modest hardware. -## Architecture +## What's Included -DVX is built as a set of DXE3 dynamic modules loaded by a bootstrap -executable. The loader resolves dependencies and loads modules in -topological order, then hands control to the shell. - -``` -dvx.exe (loader) - | - +-- libs/libtasks.lib cooperative task switcher - +-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra) - +-- libs/texthelp.lib shared text editing helpers - +-- libs/listhelp.lib shared list/dropdown helpers - +-- libs/dvxshell.lib shell (app lifecycle, desktop) - +-- libs/taskmgr.lib task manager (Ctrl+Esc, separate DXE) - | - +-- libs/basrt.lib BASIC runtime (VM + values) - +-- libs/serial.lib serial communications (rs232 + packet + seclink) - | - +-- widgets/*.wgt 26 widget type plugins - | - +-- apps/*/*.app DXE applications -``` - - -## Directory Structure - -| Directory | Output | Description | -|--------------|-------------------------|--------------------------------------------| -| `loader/` | `bin/dvx.exe` | Bootstrap loader, platform layer, stb_ds | -| `core/` | `bin/libs/libdvx.lib` | Core GUI library (5 layers + widget infra) | -| `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching | -| `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers | -| `listhelp/` | `bin/libs/listhelp.lib` | Shared list/dropdown helpers | -| `shell/` | `bin/libs/dvxshell.lib` | DVX Shell (app lifecycle, desktop) | -| `taskmgr/` | `bin/libs/taskmgr.lib` | Task Manager (separate DXE, Ctrl+Esc) | -| `widgets/` | `bin/widgets/*.wgt` | 26 individual widget DXE modules | -| `apps/` | `bin/apps/*/*.app` | Application DXE modules | -| `tools/` | `bin/dvxres`, `bin/mkicon`, `bin/mktbicon` | Resource and icon tools (host native) | -| `config/` | `bin/config/`, `bin/libs/`, `bin/widgets/` | INI config, themes, wallpapers, dep files | -| `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver | -| `packet/` | `lib/libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ | -| `security/` | `lib/libsecurity.a` | DH key exchange, XTEA-CTR cipher, RNG | -| `seclink/` | `lib/libseclink.a` | Secure serial link (channels, encryption) | -| `proxy/` | `bin/secproxy` | Linux proxy: 86Box <-> telnet BBS | -| `termdemo/` | `bin/termdemo.exe` | Standalone encrypted terminal demo | - - -## Build - -Requires the DJGPP cross-compiler at `~/djgpp/djgpp`. - -``` -make # build everything -make clean # remove all build artifacts -./mkcd.sh # build + create ISO for 86Box -``` - -The top-level Makefile builds in dependency order: - -``` -core -> tasks -> loader -> texthelp -> listhelp -> widgets -> shell -> taskmgr -> apps - tools (host native, parallel) -``` - -Build output goes to `bin/` (executables, DXE modules, config) and -`obj/` (intermediate object files). - - -## Runtime Directory Layout (bin/) - -``` -bin/ - dvx.exe bootstrap loader (entry point) - libs/ - libtasks.lib task switching library - libdvx.lib core GUI library - texthelp.lib text editing helpers - listhelp.lib list/dropdown helpers - dvxshell.lib DVX shell - taskmgr.lib task manager (Ctrl+Esc) - basrt.lib BASIC runtime (VM + values) - serial.lib serial communications stack - *.dep dependency files for load ordering - widgets/ - box.wgt VBox/HBox/Frame containers - button.wgt push button - ... (26 widget modules total) - *.dep dependency files - apps/ - progman/progman.app Program Manager (desktop) - notepad/notepad.app text editor - clock/clock.app clock display - dvxdemo/dvxdemo.app widget showcase - cpanel/cpanel.app control panel - imgview/imgview.app image viewer - dvxbasic/dvxbasic.app DVX BASIC IDE + compiler - config/ - dvx.ini system configuration - themes/ color theme files (.thm) - wpaper/ wallpaper images -``` - - -## Core Architecture (5 Layers) - -1. **dvxVideo** -- VESA VBE init, LFB mapping, backbuffer, pixel format, color packing -2. **dvxDraw** -- Rectangle fills, bevels, text rendering, cursor/icon drawing -3. **dvxComp** -- Dirty rectangle tracking, merge, compositor, LFB flush -4. **dvxWm** -- Window stack, chrome, drag/resize, menus, scrollbars, hit testing -5. **dvxApp** -- Event loop, input polling, public API, color schemes, wallpaper - - -## Widget System - -Widgets are isolated DXE modules. Core knows nothing about individual -widget types -- no compile-time enum, no union, no per-widget structs in -dvxWidget.h. - -* **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time -* **void *data**: Each widget allocates its own private data struct -* **ABI-stable dispatch**: `WidgetClassT.handlers[]` is an array of function - pointers indexed by `WGT_METHOD_*` constants (0-20, room for 32). Core - dispatches via `w->wclass->handlers[WGT_METHOD_PAINT]` etc., so adding - new methods does not break existing widget DXE binaries -* **Generic drag**: `WGT_METHOD_ON_DRAG_UPDATE` and `WGT_METHOD_ON_DRAG_END` - provide widget-level drag support without per-widget hacks in core -* **Per-widget API registry**: `wgtRegisterApi("name", &api)` replaces the monolithic API -* **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros -* **Shared helpers**: texthelp.lib (text editing) and listhelp.lib (dropdown/list) -* **All limits dynamic**: widget child arrays, app slots, and desktop callbacks - are stb_ds dynamic arrays with no fixed maximums - - -## DXE Module System - -All modules are DXE3 dynamic libraries loaded by dvx.exe at startup. -The loader recursively scans `libs/` for `.lib` files and `widgets/` for `.wgt` -files, reads `.dep` files for dependencies, and loads in topological -order. Widget modules that export `wgtRegister()` have it called after -loading. - -Each `.dep` file lists base names of modules that must load first, one -per line. Comments start with `#`. - - -## App Types - -**Callback-only** (`hasMainLoop = false`): `appMain` creates windows, -registers callbacks, and returns. The app lives through event callbacks -in the shell's main loop. No dedicated task or stack needed. - -**Main-loop** (`hasMainLoop = true`): A cooperative task is created for -the app. `appMain` runs its own loop calling `tsYield()` to share CPU. -Used for apps that need continuous processing (clocks, terminal -emulators, games). - - -## Crash Recovery - -The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL -before loading any apps, so even crashes during app initialization are -caught. If an app crashes, the handler `longjmp`s back to the shell's -main loop, the crashed app is force-killed, and the shell continues -running. Diagnostic information (registers, faulting EIP) is logged to -`dvx.log`. - - -## Bundled Applications - -| App | File | Type | Description | -|-----------------|----------------|-----------|------------------------------------------------------------| -| Program Manager | `progman.app` | Callback | App launcher grid, system info dialog | -| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save, clipboard | -| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable | -| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating 31 widget types | -| Control Panel | `cpanel.app` | Callback | Themes, wallpaper, video mode, mouse configuration | -| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, GIF images with aspect-ratio zoom | -| DVX BASIC | `dvxbasic.app` | Callback | Visual Basic 3 clone: form designer, compiler, VM | - - -## Serial / Networking Stack - -A layered encrypted serial communications stack for connecting DVX to -remote systems (BBS, etc.) through 86Box's emulated UART: - -| Layer | Library | Description | -|----------|------------------|---------------------------------------------------| -| rs232 | `librs232.a` | ISR-driven UART driver, ring buffers, flow control | -| packet | `libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ | -| security | `libsecurity.a` | 1024-bit DH, XTEA-CTR cipher, DRBG RNG | -| seclink | `libseclink.a` | Channel multiplexing, per-packet encryption | -| proxy | `secproxy` | Linux bridge: 86Box serial <-> telnet BBS | - - -## Third-Party Dependencies - -All third-party code is vendored as single-header libraries: - -| Library | Location | Purpose | -|-------------------|--------------------|---------------------------------| -| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) | -| stb_image_write.h | `core/thirdparty/` | PNG export for screenshots | -| stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps | - -stb_ds implementation is compiled into dvx.exe (the loader) with -`STBDS_REALLOC`/`STBDS_FREE` overridden to use `dvxRealloc`/`dvxFree`, -so all `arrput`/`arrfree` calls in DXE code are tracked per-app. The -functions are exported via `dlregsym` to all DXE modules. +* **DVX Shell** -- desktop, app lifecycle manager, Task Manager, crash + recovery. The shell is the program the user interacts with and the + host for all running applications. +* **Widget toolkit** -- 31 widget types as plug-in modules: buttons, + labels, text inputs, list boxes, tree views, tab controls, sliders, + spinners, progress bars, canvases, scroll panes, splitters, an ANSI + terminal, and more. +* **DVX BASIC** -- a Visual Basic 3 compatible language with a visual + form designer, per-procedure code editor, project management, and a + bytecode compiler and VM. BASIC programs compile to standalone DVX + applications. +* **Help system** -- a full hypertext help viewer with table of contents, + keyword index, full-text search, and inline images. Help files are + compiled from a plain-text source format. +* **Serial / networking stack** -- an ISR-driven UART driver, HDLC + packet framing with CRC-16 and Go-Back-N reliable delivery, 1024-bit + Diffie-Hellman key exchange, and XTEA-CTR encryption, plus a Linux + bridge that connects a virtual serial port to a telnet BBS. +* **Control Panel** -- themes, wallpapers, video mode, mouse + configuration, all with live preview. +* **Bundled applications** -- Program Manager (desktop launcher), + Notepad, Clock, Image Viewer, DVX Demo, Control Panel, DVX BASIC + IDE, Help Viewer, Resource Editor, Icon Editor, Help Editor, BASIC + Demo. ## Target Hardware -* CPU: 486 baseline, Pentium-optimized paths where significant -* Video: VESA VBE 2.0+ with LFB (no bank switching) -* Platform: 86Box emulator (trusted reference), real DOS hardware -* Resolutions: 640x480, 800x600, 1024x768 at 8/15/16/32 bpp +* DOS with a 386-class CPU or newer (486 recommended, Pentium for best + performance) +* VESA BIOS Extensions 2.0 or newer with linear framebuffer support +* 4 MB of extended memory minimum; 8 MB or more recommended +* Any resolution the video card reports at 8, 15, 16, 24, or 32 bits + per pixel. 640x480, 800x600, and 1024x768 are the common cases. +* Two-button mouse; scroll wheel supported if a compatible driver is + loaded + + +## Building and Deploying + +``` +make # build everything +make clean # remove all build artifacts +./mkcd.sh # build and produce a CD-ROM ISO image +``` + +`make` builds in dependency order: core, tasks, loader, helpers, +host tools, widgets, shell, task manager, apps. Build output goes to +`bin/` and intermediate objects to `obj/`. + +`./mkcd.sh` builds everything, then wraps `bin/` into an ISO 9660 +image for use with an emulator or for burning to physical media. + + +## Directory Layout + +``` +dvxgui/ + assets/ logo and splash artwork + bin/ build output (runtime tree; mirrors what ships) + config/ shipped INI, themes, wallpapers + docs/ HTML exports of the built-in help content + LICENSE.txt MIT license + Makefile top-level build + mkcd.sh build-and-package script + src/ + apps/ bundled applications + include/ shared include files (BASIC library declarations) + libs/ core libraries (GUI, tasks, shell, helpers, serial, SQL) + loader/ bootstrap executable + tools/ host-native development tools and the BASIC compiler + widgets/ widget plug-in modules +``` + +See `src/tools/README.md` for the host-side development tools +(resource tool, icon generators, help compiler, proxy), and +`src/apps/kpunch/README.md` for the SDK contract applications must +follow. + + +## Documentation + +Full reference documentation ships with the system as browsable help: + +* `docs/dvx_system_reference.html` -- system API, widget catalog +* `docs/dvx_basic_reference.html` -- DVX BASIC language reference +* `docs/dvx_help_viewer.html` -- help viewer guide + +The same content is available from inside DVX via the Help Viewer and +Program Manager. + + +## License + +MIT. See `LICENSE.txt`. diff --git a/docs/dvx_basic_reference.html b/docs/dvx_basic_reference.html index 20b16e8..81a6f59 100644 --- a/docs/dvx_basic_reference.html +++ b/docs/dvx_basic_reference.html @@ -79,6 +79,7 @@ img { max-width: 100%; }
  • File I/O
  • +
  • File System Statements
  • Built-in Functions
  • Common Properties, Events, and Methods @@ -222,9 +224,6 @@ img { max-width: 100%; }
  • Breakpoints
  • Stepping
  • Debug Mode
  • -
  • DBG_IDLE
  • -
  • DBG_RUNNING
  • -
  • DBG_PAUSED
  • Locals Window
  • Variable Inspection
  • Call Stack
  • @@ -316,7 +315,6 @@ img { max-width: 100%; }
  • END FUNCTION
  • DEF FN
  • BYVAL
  • -
  • BYREF
  • EXIT SUB
  • EXIT FUNCTION
  • EXIT
  • @@ -358,6 +356,16 @@ img { max-width: 100%; }
  • FOR APPEND
  • FOR RANDOM
  • FOR BINARY
  • +
  • KILL
  • +
  • NAME
  • +
  • FILECOPY
  • +
  • MKDIR
  • +
  • RMDIR
  • +
  • CHDIR
  • +
  • CHDRIVE
  • +
  • GETATTR
  • +
  • SETATTR
  • +
  • File Attributes
  • ASC
  • CHR$
  • FORMAT$
  • @@ -409,12 +417,18 @@ img { max-width: 100%; }
  • Show
  • Hide
  • Me
  • +
  • Nothing
  • DoEvents
  • DOEVENTS
  • MsgBox
  • MSGBOX
  • InputBox$
  • INPUTBOX$
  • +
  • CreateForm
  • +
  • CreateControl
  • +
  • SetEvent
  • +
  • RemoveControl
  • +
  • With
  • vbModal
  • Control Arrays
  • Event Handlers
  • @@ -453,9 +467,22 @@ img { max-width: 100%; }
  • vbYes
  • vbNo
  • vbRetry
  • +
  • vbNormal
  • +
  • vbReadOnly
  • +
  • vbHidden
  • +
  • vbSystem
  • +
  • vbDirectory
  • +
  • vbArchive
  • True
  • False
  • Predefined Constants
  • +
  • Include Libraries
  • +
  • commdlg.bas
  • +
  • sql.bas
  • +
  • comm.bas
  • +
  • resource.bas
  • +
  • help.bas
  • +
  • DECLARE LIBRARY
  • Common Properties
  • Common Events
  • Common Methods
  • @@ -487,8 +514,6 @@ img { max-width: 100%; }
  • Terminal
  • ANSI Terminal
  • VT100
  • -
  • CommAttach
  • -
  • CommOpen
  • Frame
  • Container
  • HBox
  • @@ -584,7 +609,7 @@ img { max-width: 100%; }

    DVX BASIC IDE Guide

    -

    DVX BASIC is a Visual BASIC development environment for the DVX GUI System. It provides a VB3-style integrated development environment with a code editor, form designer, project system, and a full interactive debugger -- all running natively on DOS under the DVX windowing system.

    +

    DVX BASIC is a Visual BASIC development environment for DVX. It provides a VB3-style integrated development environment with a code editor, form designer, project system, and a full interactive debugger -- all integrated into DVX itself.

    This guide covers every feature of the IDE: menus, toolbar, editor, form designer, project management, debugger, and auxiliary windows.

    IDE Windows

    The DVX BASIC IDE is modeled after Visual Basic 3.0. It consists of several floating windows arranged on the DVX desktop:

    @@ -599,7 +624,7 @@ img { max-width: 100%; }
  • Immediate Window -- an interactive REPL for evaluating expressions and modifying variables at runtime.
  • Debug Windows -- Locals, Call Stack, Watch, and Breakpoints windows that appear automatically when debugging.

    -

    The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine (VM). Programs execute in cooperative slices (10,000 VM steps per slice), yielding to the DVX event loop between slices so the GUI remains responsive.

    +

    The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine. Programs execute in cooperative slices, yielding to DVX between slices so the GUI remains responsive. Call DoEvents in long-running loops to keep the event loop flowing.

    File Menu

    Edit Menu

    Run Menu

    @@ -639,6 +664,8 @@ img { max-width: 100%; } Save File Ctrl+S Save the active file to disk. Save All Save all modified files in the project. --- + Make Executable... Compile the project and save it as a standalone .app file. Prompts for a debug or release build. + --- Remove File Remove the selected file from the project (prompts to save if modified). --- Exit Close the IDE (prompts to save unsaved changes). @@ -677,6 +704,8 @@ img { max-width: 100%; } Step Out Ctrl+Shift+F8 Run until the current SUB/FUNCTION returns. Run to Cursor Ctrl+F8 Run until execution reaches the line where the cursor is positioned. --- + Output Window to Log Checkbox: when enabled, PRINT output is also mirrored to the DVX log. Persisted in preferences. + --- Toggle Breakpoint F9 Toggle a breakpoint on the current editor line. --- Clear Output Clear the Output window. @@ -729,9 +758,12 @@ img { max-width: 100%; }

    Help Menu

    -
      Menu Item            Description
    -  ---------            -----------
    -  About DVX BASIC...   Show the About dialog with version and copyright information.
    +
      Menu Item            Shortcut   Description
    +  ---------            --------   -----------
    +  DVX BASIC Help       F1         Open the DVX BASIC help (language reference, IDE guide, and control reference).
    +  DVX API Reference               Open the DVX developer API reference.
    +  ---
    +  About DVX BASIC...              Show the About dialog with version and copyright information.

    Back to Overview

    @@ -773,14 +805,15 @@ img { max-width: 100%; }

    Function -- lists all event handlers (procedures) for the selected object. Implemented handlers are listed first (plain text); unimplemented handlers follow in brackets (e.g., [Click]). Selecting an unimplemented event creates a new event handler stub.

    The editor shows one procedure at a time. Each procedure has its own buffer, and switching between them is instantaneous. The (General) section contains module-level declarations and code.

    Syntax Highlighting

    -

    The editor applies real-time syntax coloring as you type. The following categories are highlighted in distinct colors:

    +

    The editor applies real-time syntax coloring as you type. Seven categories are highlighted in distinct colors; the colors themselves are editable in Tools > Preferences > Colors.

      Category          Examples
       --------          --------
    +  Default Text      Identifiers, whitespace.
       Keywords          IF, THEN, FOR, NEXT, SUB, FUNCTION, DIM, PRINT, SELECT, CASE, DO, LOOP, WHILE, WEND, END, EXIT, CALL, GOSUB, GOTO, RETURN, DECLARE, CONST, TYPE, AND, OR, NOT, XOR, MOD, etc.
    -  Type names        INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN, BYTE, TRUE, FALSE
    -  String literals   "Hello, World!"
    +  Types             INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN, TRUE, FALSE
    +  Strings           "Hello, World!"
       Comments          ' This is a comment, REM This is a comment
    -  Numbers           42, 3.14
    +  Numbers           42, 3.14, &HFF
       Operators         =, <, >, +, -, *, /, \, &

    Editor Features

    Toolbox

    -

    The Toolbox (Window > Toolbox) is a floating palette of buttons, one for each available control type. Controls are loaded dynamically from the widget plugin registry -- any widget DXE that provides a basName appears in the toolbox.

    +

    The Toolbox (Window > Toolbox) is a floating palette of buttons, one for each available control type. Every widget registered with DVX that has a BASIC name appears in the toolbox.

    Click a tool to select it (the active tool name is stored in the designer state), then click on the form to place a new instance. Click the same tool again to deselect it and return to pointer mode.

    Available Controls

      VB Name         Description
    @@ -892,12 +925,12 @@ img { max-width: 100%; }
     

    Debugger

    -

    The DVX BASIC IDE includes a full interactive debugger. The debugger operates as a state machine with three states:

    -
      State          Description
    -  -----          -----------
    -  DBG_IDLE       No program loaded or running.
    -  DBG_RUNNING    Program is executing (VM running in slices).
    -  DBG_PAUSED     Execution is paused at a breakpoint or step point. The IDE GUI is fully interactive.
    +

    The DVX BASIC IDE includes a full interactive debugger. The debugger has three states:

    +
      State       Description
    +  -----       -----------
    +  Idle        No program loaded or running.
    +  Running     The program is executing.
    +  Paused      Execution is paused at a breakpoint or step point. The IDE is fully interactive -- you can inspect and change variables, set or clear breakpoints, step, continue, or stop.

    Starting a Debug Session

    END SUB and END FUNCTION lines

    Breakpoint Storage

    -

    Each breakpoint records the project file index, the code line number within the file, the procedure index, and the procedure name (as Object.Event). When the project is compiled, breakpoints are converted to concatenated source line numbers that match the VM's OP_LINE opcodes.

    +

    Each breakpoint records the file it belongs to, the line number within that file, and the procedure name (Object.Event). Breakpoints are stored in the project and survive compilation.

    Visual Indicators

    Combined -- items(0).price = 9.99

    -

    The new value is written directly into the VM's live variable slot (local, global, or form scope). A confirmation message is displayed, and the Locals and Watch windows update automatically to reflect the change.

    +

    The new value is written directly into the running program's variable (local, global, or form scope). A confirmation message is displayed, and the Locals and Watch windows update automatically to reflect the change.

    If the assignment target cannot be resolved (unknown variable, out-of-bounds index, wrong type), an error message is displayed.

    Back to Overview

    @@ -1087,23 +1120,26 @@ img { max-width: 100%; }

    Preferences

    -

    Open via Tools > Preferences. Settings are saved to dvxbasic.ini in the app's config directory.

    -

    Editor Section

    -
      Setting                                      Description                                                                               Default
    -  -------                                      -----------                                                                               -------
    -  Skip comments/strings when renaming          When renaming a control or form, skip occurrences inside comments and string literals.     On
    -  Require variable declaration (OPTION EXPLICIT)   When enabled, variables must be declared with DIM before use.                          Off
    -  Tab width                                    Number of spaces per tab stop (1-8).                                                      3
    -  Insert spaces instead of tabs                When enabled, pressing Tab inserts spaces. When disabled, inserts a real tab character.     On
    -

    New Project Defaults Section

    +

    Open via Tools > Preferences. Settings are saved to dvxbasic.ini in the app's config directory. The dialog has two tabs.

    +

    General Tab

    +

    Editor Section

    +
      Setting                                          Description                                                                           Default
    +  -------                                          -----------                                                                           -------
    +  Skip comments/strings when renaming              When renaming a control or form, skip occurrences inside comments and string literals.  On
    +  Require variable declaration (OPTION EXPLICIT)   When enabled, newly created projects have OPTION EXPLICIT on by default.                Off
    +  Tab width                                        Number of spaces per tab stop.                                                         3
    +  Insert spaces instead of tabs                    When enabled, pressing Tab inserts spaces. When disabled, inserts a real tab character. On
    +

    New Project Defaults Section

    These fields set the default values for new project metadata:

    -
      Field         Description                              Default
    -  -----         -----------                              -------
    -  Author        Default author name.                     (empty)
    -  Company       Default company name.                    (empty)
    -  Version       Default version string.                  1.0
    -  Copyright     Default copyright notice.                (empty)
    -  Description   Default project description (multi-line).   (empty)
    +
      Field         Description                                   Default
    +  -----         -----------                                   -------
    +  Author        Default author name.                          (empty)
    +  Publisher     Default publisher/company name.               (empty)
    +  Version       Default version string.                       1.0
    +  Copyright     Default copyright notice.                     (empty)
    +  Description   Default project description (multi-line).     (empty)
    +

    Colors Tab

    +

    Customizes the syntax-highlighting colors used in the code editor. The left side is a list of seven color entries (Default Text, Keywords, Strings, Comments, Numbers, Operators, Types). Select an entry and adjust its red, green, and blue components using the three sliders on the right. The Preview swatch shows the current color. Changes are applied to the open editor when you click OK.

    Back to Overview

    @@ -1119,6 +1155,7 @@ img { max-width: 100%; } Ctrl+F Find Ctrl+H Replace Ctrl+E Menu Editor + F1 DVX BASIC Help F3 Find Next F5 Run Shift+F5 Debug @@ -1163,55 +1200,101 @@ rate! = 3.14 ' Single pi# = 3.14159265 ' Double name$ = "Hello" ' String

    Numeric Literals

    -
      Form               Example          Description
    -  ----               -------          -----------
    -  Decimal integer    42               Values -32768..32767 are Integer; larger are Long
    -  Hex integer        &HFF             Hexadecimal literal
    -  Long suffix        42&, &HFF&       Force Long type
    -  Floating-point     3.14, 1.5E10     Double by default
    -  Single suffix      3.14!            Force Single type
    -  Double suffix      3.14#            Force Double type
    +
      Form               Example                Description
    +  ----               -------                -----------
    +  Decimal integer    42                     Values -32768..32767 are Integer; larger are Long
    +  Hex integer        &HFF, &H1234           Hexadecimal literal
    +  Integer suffix     42%                    Force Integer type
    +  Long suffix        42&, &HFF&             Force Long type
    +  Floating-point     3.14, 1.5E10, 2.5D3    Any number containing a decimal point or exponent is Double by default
    +  Single suffix      3.14!                  Force Single type
    +  Double suffix      3.14#                  Force Double type
    +
    Note: Both E and D can introduce an exponent (e.g. 1.5E10 and 2.5D3 are equivalent forms of a scientific-notation double).

    Type Promotion

    When mixing types in expressions, values are automatically promoted to a common type: Integer -> Long -> Single -> Double. Strings are not automatically converted to numbers (use VAL and STR$).

    +

    Boolean Values

    +

    Boolean values use -1 for True and 0 for False. Any non-zero numeric value is treated as True in a conditional context. The keywords True and False are reserved and may be used anywhere a Boolean value is expected.

    See also: Conversion Functions

    Operators

    -

    Operators listed from highest precedence (evaluated first) to lowest precedence (evaluated last).

    +

    Operators listed from highest precedence (evaluated first) to lowest precedence (evaluated last). Parentheses override precedence and may be nested freely.

      Precedence     Operator                  Description
       ----------     --------                  -----------
       1 (highest)    ^                         Exponentiation
    -  2              - (unary)                 Negation
    -  3              *  /  \  MOD              Multiply, float div, integer div, modulus
    -  4              +  -                      Addition, subtraction
    +  2              - (unary), + (unary)      Negation (unary plus is a no-op)
    +  3              *  /  \  MOD              Multiply, float divide, integer divide, modulus
    +  4              +  -                      Addition, subtraction (+ also concatenates strings)
       5              &                         String concatenation
       6              =  <>  <  >  <=  >=       Comparison (returns Boolean)
       7              NOT                       Logical/bitwise NOT
       8              AND                       Logical/bitwise AND
    -  9              XOR                       Logical/bitwise XOR
    +  9              XOR                       Logical/bitwise exclusive-or
       10             OR                        Logical/bitwise OR
       11             EQV                       Logical/bitwise equivalence
       12 (lowest)    IMP                       Logical/bitwise implication
    +

    Arithmetic Operators

    +
      Operator   Description
    +  --------   -----------
    +  +          Addition (or string concatenation when both operands are strings)
    +  -          Subtraction (or negation when used as a unary prefix)
    +  *          Multiplication
    +  /          Floating-point division (result is always Single or Double)
    +  \          Integer division (result is Long, fractional part discarded)
    +  MOD        Integer modulus (remainder)
    +  ^          Exponentiation (result is Double)
    +

    Comparison Operators

    +

    Comparison operators return True (-1) or False (0).

    +
      Operator   Description
    +  --------   -----------
    +  =          Equal to
    +  <>         Not equal to
    +  <          Less than
    +  >          Greater than
    +  <=         Less than or equal to
    +  >=         Greater than or equal to
    +

    String comparisons are controlled by OPTION COMPARE. With OPTION COMPARE BINARY (the default) strings are compared by byte value. With OPTION COMPARE TEXT comparisons are case-insensitive.

    +

    Logical / Bitwise Operators

    +

    The logical operators work on Boolean values and also perform bitwise operations on integer values.

    +
      Operator   Description
    +  --------   -----------
    +  NOT        Logical/bitwise complement
    +  AND        Logical/bitwise AND
    +  OR         Logical/bitwise OR
    +  XOR        Logical/bitwise exclusive-or
    +  EQV        Logical/bitwise equivalence (same as NOT (a XOR b))
    +  IMP        Logical/bitwise implication (same as (NOT a) OR b)

    String Concatenation

    -

    Use & to concatenate strings. The + operator also concatenates when both operands are strings.

    +

    Use & to concatenate strings. The + operator also concatenates when both operands are strings. Prefer & because it does not depend on operand types.

    result$ = "Hello" & " " & "World"
    -result$ = firstName$ & " " & lastName$
    +result$ = firstName$ & " " & lastName$ + +' + also concatenates when both operands are strings +greeting$ = "Hi " + name$

    Statements

    -

    Multiple statements can appear on one line separated by :. Lines can be continued with _ at the end. Comments start with ' or REM.

    -

    Declaration Statements (DIM, REDIM, CONST, TYPE)

    +

    Multiple statements can appear on one line separated by : (colon). A line can be continued onto the next physical line by ending it with an underscore (_) preceded by whitespace. Comments start with ' (apostrophe) or the REM keyword and run to the end of the line. The character ? is a shorthand for PRINT.

    +
    a = 10 : b = 20 : c = a + b        ' three statements on one line
    +
    +total = price * quantity _
    +      + shipping + tax              ' one statement continued across lines
    +
    +REM This is a comment
    +' So is this
    +? "Hello"                           ' same as PRINT "Hello"
    +

    Declaration Statements (DIM, REDIM, CONST, TYPE, DECLARE)

    Conditional Statements (IF, SELECT CASE)

    Loop Statements (FOR, DO, WHILE)

    Procedures (SUB, FUNCTION, DEF FN)

    Flow Control (EXIT, CALL, GOTO, GOSUB, ON)

    Input/Output (PRINT, INPUT, DATA/READ)

    -

    Miscellaneous Statements

    +

    Miscellaneous Statements (ON ERROR, SHELL, SLEEP, END)

    Declaration Statements

    DIM

    -

    Declares variables and arrays with an explicit type.

    +

    Declares variables and arrays with an explicit type. If no type is given, the type defaults to whatever DEFtype (if any) is in effect for the variable's first letter; without a DEFtype statement the default is Single.

    DIM variable AS type
     DIM variable(upperBound) AS type
     DIM variable(lower TO upper) AS type
    @@ -1219,6 +1302,8 @@ DIM variable(dim1, dim2, ...) AS type
     DIM variable AS UdtName
     DIM variable AS STRING * n
     DIM SHARED variable AS type
    +

    Multiple variables can be declared at module level so their values persist between procedure calls. Arrays can have up to 8 dimensions. A variable declared with type suffix does not need the AS clause:

    +
    Dim name$, count%, total&       ' suffixes infer the types

    Examples:

    Dim name As String
     Dim count As Integer
    @@ -1227,7 +1312,8 @@ Dim matrix(1 To 10, 1 To 10) As Single
     Dim Shared globalFlag As Boolean
     Dim record As PersonType
     Dim fixedStr As String * 20
    -
    Note: DIM SHARED makes a variable accessible from all procedures without passing it as a parameter.
    +
    Note: DIM SHARED at module level makes a variable accessible from every procedure without passing it as a parameter. Inside a SUB or FUNCTION, DIM declares a local variable that is recreated on each call (use STATIC to retain its value between calls).
    +

    Fixed-length strings (STRING * n) are padded with spaces and truncated when assigned so their length is always exactly n.

    REDIM

    Reallocates a dynamic array, optionally preserving existing data.

    REDIM array(newBounds) AS type
    @@ -1235,12 +1321,15 @@ REDIM PRESERVE array(newBounds) AS type
    ReDim items(newSize) As String
     ReDim Preserve scores(1 To newCount) As Integer

    CONST

    -

    Declares a named constant. The value must be a literal (integer, float, string, or boolean).

    -
    CONST name = value
    +

    Declares a named constant. The value must be a literal (integer, long, float, string, or boolean). Constants are not variables -- you cannot assign a new value to them or pass them by reference.

    +
    CONST name = value
    +CONST name AS type = value
    Const MAX_SIZE = 100
     Const PI = 3.14159265
     Const APP_NAME = "DVX App"
    -Const DEBUG_MODE = True
    +Const DEBUG_MODE = True +Const HEADER As String = "=== Report ===" +

    Constants are scoped to the file or procedure in which they are declared. Constants declared at module level are visible to all procedures in the same module.

    TYPE...END TYPE

    Defines a user-defined type (record/structure).

    TYPE TypeName
    @@ -1258,20 +1347,20 @@ p.firstName = "Scott"
     p.age = 30

    UDT fields can themselves be UDTs (nested types).

    DECLARE

    -

    Forward-declares a SUB or FUNCTION. Required when a procedure is called before it is defined.

    -
    DECLARE SUB name ([BYVAL] param AS type, ...)
    -DECLARE FUNCTION name ([BYVAL] param AS type, ...) AS returnType
    +

    Forward-declares a SUB or FUNCTION. This is rarely required because the compiler supports forward references within a module, but it is still accepted for compatibility:

    +
    DECLARE SUB name ([BYVAL] [OPTIONAL] param AS type, ...)
    +DECLARE FUNCTION name ([BYVAL] [OPTIONAL] param AS type, ...) AS returnType

    DECLARE LIBRARY

    -

    Declares external native functions from a dynamically loaded library. This allows BASIC programs to call functions exported by DXE libraries.

    +

    Declares external functions from a native library bundled with DVX. This is how BASIC programs reach operating-system services, serial ports, databases, and other native facilities. Rather than writing DECLARE LIBRARY blocks yourself, add one of the shipped include files (such as commdlg.bas or sql.bas) to your project; the include file contains the proper declarations.

    DECLARE LIBRARY "libraryName"
         DECLARE SUB name ([BYVAL] param AS type, ...)
         DECLARE FUNCTION name ([BYVAL] param AS type, ...) AS returnType
     END DECLARE
    -
    Declare Library "rs232"
    -    Declare Function ComOpen(ByVal port As Integer) As Integer
    -    Declare Sub ComClose(ByVal port As Integer)
    -    Declare Sub ComSend(ByVal port As Integer, ByVal data$ As String)
    +
    Declare Library "basrt"
    +    Declare Function SQLOpen(ByVal path As String) As Integer
    +    Declare Sub SQLClose(ByVal db As Integer)
     End Declare
    +

    See also: BASIC Runtime Libraries

    STATIC

    Declares a local variable that retains its value between calls.

    STATIC variable AS type
    @@ -1304,11 +1393,18 @@ udt.field = expression LET variable = expression

    The LET keyword is optional and supported for compatibility.

    SWAP

    -

    Exchanges the values of two variables.

    +

    Exchanges the values of two variables. The variables must be the same type.

    SWAP variable1, variable2
    Swap a, b
    +

    SET

    +

    Assigns an object reference (form or control) to a variable. Required when creating a form or control with CreateForm / CreateControl so that the returned reference is stored in the variable:

    +
    SET variable = objectExpression
    +
    Dim frm As Long
    +Set frm = CreateForm("MyForm", 320, 240)
    +frm.Caption = "Built in code"
    +

    For ordinary numeric or string assignment, SET is not used.

    ERASE

    -

    Frees the memory of an array and resets it.

    +

    Frees the memory of a dynamic array and resets it to undimensioned state. Fixed-size arrays (declared with constant bounds) reset their elements but keep their shape.

    ERASE arrayName
    @@ -1412,16 +1508,31 @@ Wend

    Procedures

    SUB...END SUB

    Defines a subroutine (no return value).

    -
    SUB name ([BYVAL] param AS type, ...)
    +
    SUB name ([BYVAL] [OPTIONAL] param AS type, ...)
         statements
     END SUB
    Sub Greet(ByVal name As String)
         Print "Hello, " & name
     End Sub
    -

    Parameters are passed ByRef by default. Use ByVal for value semantics. Use EXIT SUB to return early.

    +

    Parameters are passed by reference by default. Use ByVal for value semantics; there is no separate ByRef keyword (omitting ByVal is the by-reference form). Use EXIT SUB to return early. A SUB is called either with or without parentheses; when used as a statement, parentheses are optional:

    +
    Greet "World"
    +Greet("World")
    +Call Greet("World")
    +

    Optional Parameters

    +

    Mark a parameter OPTIONAL to allow callers to omit it. An optional parameter must be positioned after all required parameters and receives an empty/zero default when not supplied.

    +
    Sub Announce(ByVal msg As String, Optional ByVal loud As Integer)
    +    If loud Then
    +        Print UCase$(msg)
    +    Else
    +        Print msg
    +    End If
    +End Sub
    +
    +Announce "hello"            ' loud defaults to 0
    +Announce "hello", True      ' loud is -1

    FUNCTION...END FUNCTION

    Defines a function with a return value.

    -
    FUNCTION name ([BYVAL] param AS type, ...) AS returnType
    +
    FUNCTION name ([BYVAL] [OPTIONAL] param AS type, ...) AS returnType
         statements
         name = returnValue
     END FUNCTION
    @@ -1430,10 +1541,13 @@ END FUNCTION
    End Function

    Assign to the function name to set the return value. Use EXIT FUNCTION to return early.

    DEF FN

    -

    Defines a single-expression function.

    +

    Defines a single-expression function. The function name begins with FN and is invoked just like a normal function. DEF FN functions cannot span multiple statements.

    DEF FNname(params) = expression
    Def FnSquare(x) = x * x
    -Print FnSquare(5)     ' prints 25
    +Print FnSquare(5) ' prints 25 + +Def FnCelsius(f As Double) = (f - 32) * 5 / 9 +Print FnCelsius(212) ' prints 100

    Flow Control

    @@ -1470,24 +1584,45 @@ ON expression GOSUB label1, label2, ...

    Input/Output Statements

    PRINT

    -

    Outputs text to the console or to a file channel.

    -
    PRINT [expression] [{; | ,} expression] ...
    -PRINT #channel, expression
    +

    Writes expressions to the output window (or to a file channel).

    +
    PRINT [expression [{; | ,} expression] ...]
    +PRINT #channel, expression [; expression] ...
     PRINT USING format$; expression [; expression] ...
      -
    • ; between items -- no separator (items are concatenated)
    • +
    • ; between items -- no separator (items appear next to each other, or separated by a space for numbers)
    • , between items -- advance to the next 14-column tab zone
    • -
    • Trailing ; or , suppresses the newline
    • +
    • A trailing ; or , suppresses the newline normally written at the end of the statement

    ? is an alias for PRINT

    Special functions inside PRINT:

    • SPC(n) -- print n spaces
    -

    TAB(n) -- advance to column n

    +

    TAB(n) -- advance to column n (first column is 1)

    Print "Name:"; Tab(20); name$
    -Print Using "###.##"; total
    +Print "X="; x, "Y="; y        ' comma = next 14-column zone
     Print #1, "Written to file"
    +

    PRINT USING

    +

    PRINT USING formats each following expression according to a format string and prints the result. If more expressions are supplied than the format string consumes, the format string is reused from the start for each.

    +
      Format character   Meaning
    +  ----------------   -------
    +  #                  Digit (replaced with a digit or space to pad on the left)
    +  .                  Decimal-point position
    +  ,                  Insert thousands separator
    +  +                  At start or end: always show a sign character
    +  -                  At end: show a trailing minus sign only when negative
    +  **                 At start: fill leading spaces with asterisks
    +  $$                 At start: floating dollar sign
    +  ^^^^               Anywhere: format the value in scientific notation
    +  !                  First character of a string only
    +  &                  Entire string (variable length)
    +  \ ... \            Fixed-length string field (width = 2 + number of spaces between backslashes)
    +
    Print Using "###.##"; 3.14159             ' "  3.14"
    +Print Using "$$#,##0.00"; 1234567.89      ' "$1,234,567.89"
    +Print Using "**#,##0.00"; 42.5            ' "*****42.50"
    +Print Using "####.####^^^^"; 0.000123     ' scientific notation
    +Print Using "\  \"; "Hello"               ' fixed-width 4-char: "Hell"
    +Print Using "& likes &"; name$; food$

    INPUT

    Reads a line of text from the user or from a file channel.

    INPUT variable
    @@ -1510,11 +1645,11 @@ Restore

    Miscellaneous Statements

    Error Handling

    ON ERROR GOTO label     ' Enable error handler
    -ON ERROR GOTO 0          ' Disable error handler
    -RESUME                   ' Retry the statement that caused the error
    -RESUME NEXT              ' Continue at the next statement after the error
    -ERROR n                  ' Raise a runtime error with error number n
    -

    The ERR keyword returns the current error number in expressions.

    +ON ERROR GOTO 0 ' Disable error handler +RESUME ' Retry the statement that caused the error +RESUME NEXT ' Continue at the next statement after the error +ERROR n ' Raise a runtime error with error number n
    +

    The ERR keyword returns the current error number in expressions (it is 0 when no error is active).

    On Error GoTo ErrorHandler
     Open "missing.txt" For Input As #1
     Exit Sub
    @@ -1522,21 +1657,39 @@ Exit Sub
     ErrorHandler:
         Print "Error number:"; Err
         Resume Next
    +

    Common Error Numbers

    +
      Number   Meaning
    +  ------   -------
    +  1        FOR loop error (NEXT without FOR, NEXT variable mismatch, FOR stack underflow)
    +  4        Out of DATA
    +  7        Out of memory
    +  9        Subscript out of range / invalid variable or field index
    +  11       Division by zero
    +  13       Type mismatch / not an array / not a TYPE instance
    +  26       FOR loop nesting too deep
    +  51       Internal error (bad opcode)
    +  52       Bad file number or file not open
    +  53       File not found
    +  54       Bad file mode
    +  58       File already exists or rename failed
    +  67       Too many files open
    +  75       Path/file access error
    +  76       Path not found

    SHELL

    -

    Executes an operating system command.

    +

    Executes an operating-system command.

    SHELL "command"
    -

    When used as a function, returns the exit code of the command.

    +

    When used as a function, SHELL returns the exit code of the command.

    Shell "DIR /B"
     exitCode = Shell("COPY A.TXT B.TXT")

    SLEEP

    -

    Pauses execution for a specified number of seconds.

    -
    SLEEP seconds
    +

    Pauses execution for the given number of seconds. With no argument, sleeps for 1 second.

    +
    SLEEP [seconds]

    RANDOMIZE

    -

    Seeds the random number generator.

    +

    Seeds the random number generator. Without an argument (not shown here) and without a prior RANDOMIZE, the sequence is the same on every run. Use RANDOMIZE TIMER to get a different sequence each run.

    RANDOMIZE seed
     RANDOMIZE TIMER          ' Seed from system clock

    END

    -

    Terminates program execution immediately.

    +

    Terminates program execution immediately. Any form that is loaded is unloaded before the program exits.

    END
    @@ -1578,61 +1731,143 @@ OPEN filename$ FOR BINARY AS #channel PUT #channel, [recordNum], variable

    SEEK

    Sets the file position. As a function, returns the current position.

    -
    SEEK #channel, position      ' Statement: set position
    +
    SEEK #channel, position       ' Statement: set position
     pos = SEEK(channel)           ' Function: get current position
    +
    +

    File System Statements

    +

    DVX BASIC provides statements for working with the file system -- creating, deleting, renaming, and copying files and directories, and reading or setting file attributes.

    +

    KILL

    +

    Deletes a file.

    +
    KILL filename$
    +
    Kill "temp.bak"
    +

    NAME ... AS

    +

    Renames (or moves) a file.

    +
    NAME oldName$ AS newName$
    +
    Name "draft.txt" As "final.txt"
    +

    FILECOPY

    +

    Copies a file. If the destination already exists it is overwritten.

    +
    FILECOPY source$, destination$
    +
    FileCopy "data.db", "data.bak"
    +

    MKDIR / RMDIR

    +

    Creates or removes a directory.

    +
    MKDIR path$
    +RMDIR path$
    +
    MkDir App.Data & "\backups"
    +RmDir "C:\OLD"
    +

    CHDIR / CHDRIVE

    +

    Changes the current working directory or drive.

    +
    CHDIR path$
    +CHDRIVE drive$
    +
    ChDir "C:\DVX\PROJECTS"
    +ChDrive "D"
    +

    GETATTR / SETATTR

    +

    GETATTR returns the attributes of a file (as a function). SETATTR sets them (as a statement). Attributes are a bitmask of the vb file-attribute constants.

    +
    attrs = GETATTR(filename$)
    +SETATTR filename$, attrs
    +
      Constant       Value    Attribute
    +  --------       -----    ---------
    +  vbNormal       0        Normal file (no attributes set)
    +  vbReadOnly     1        Read-only
    +  vbHidden       2        Hidden
    +  vbSystem       4        System file
    +  vbDirectory    16       Entry is a directory
    +  vbArchive      32       File has been modified since last backup
    +
    If (GetAttr("readme.txt") And vbReadOnly) <> 0 Then
    +    Print "File is read-only."
    +End If
    +
    +SetAttr "secret.dat", vbHidden + vbReadOnly
    +

    CURDIR$ / DIR$

    +

    CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done).

    +
    cwd$ = CURDIR$
    +first$ = DIR$("*.txt")
    +WHILE first$ <> ""
    +    Print first$
    +    first$ = DIR$        ' next match, no argument
    +WEND
    +

    FILELEN

    +

    Returns the length of a file in bytes.

    +
    bytes = FILELEN(filename$)
    +

    String Functions

      Function                          Returns   Description
       --------                          -------   -----------
    -  ASC(s$)                           Integer   ASCII code of first character of s$
    +  ASC(s$)                           Integer   ASCII code of the first character of s$
       CHR$(n)                           String    Character with ASCII code n
    -  FORMAT$(value, fmt$)              String    Formats a numeric value using format string
    -  HEX$(n)                           String    Hexadecimal representation of n
    +  FORMAT$(value, fmt$)              String    Formats a numeric value using a format string
    +  HEX$(n)                           String    Hexadecimal representation of n (uppercase, no leading &H)
       INSTR(s$, find$)                  Integer   Position of find$ in s$ (1-based), 0 if not found
    -  INSTR(start, s$, find$)          Integer   Search starting at position start
    +  INSTR(start, s$, find$)           Integer   Search starting at position start (1-based)
       LCASE$(s$)                        String    Converts s$ to lowercase
       LEFT$(s$, n)                      String    Leftmost n characters of s$
    -  LEN(s$)                           Integer   Length of s$
    +  LEN(s$)                           Integer   Length of s$ in characters
       LTRIM$(s$)                        String    Removes leading spaces from s$
    -  MID$(s$, start [, length])        String    Substring from position start (1-based)
    +  MID$(s$, start)                   String    Substring from start (1-based) to end of string
    +  MID$(s$, start, length)           String    Substring of length characters starting at start
       RIGHT$(s$, n)                     String    Rightmost n characters of s$
       RTRIM$(s$)                        String    Removes trailing spaces from s$
       SPACE$(n)                         String    String of n spaces
    -  STR$(n)                           String    Converts number n to string
    -  STRING$(n, char)                  String    String of n copies of char
    +  STR$(n)                           String    Converts number n to string (leading space for non-negative)
    +  STRING$(n, char)                  String    String of n copies of char (char can be an ASCII code or single-character string)
       TRIM$(s$)                         String    Removes leading and trailing spaces from s$
       UCASE$(s$)                        String    Converts s$ to uppercase
    -  VAL(s$)                           Double    Converts string s$ to a numeric value
    + VAL(s$) Double Converts string s$ to a numeric value; stops at first non-numeric character +

    FORMAT$

    +

    FORMAT$ formats a numeric value using a BASIC-style format string. The format characters are the same as the ones used by PRINT USING.

    +
    s$ = FORMAT$(value, fmt$)
    +
    Print Format$(3.14159, "###.##")      ' "  3.14"
    +Print Format$(42, "00000")            ' "00042"
    +Print Format$(1234.5, "#,##0.00")     ' "1,234.50"
    +Print Format$(0.5, "PERCENT")         ' "50%"
    +

    The accepted format characters are # (digit or pad space), 0 (digit or pad zero), . (decimal point), , (thousands separator), + and - (sign placement), $$ (floating dollar sign), ** (asterisk fill), and the literal word PERCENT (multiplies by 100 and appends %). See PRINT USING for details on each.

    MID$ Assignment

    -

    MID$ can also be used on the left side of an assignment to replace a portion of a string:

    -
    Mid$(s$, 3, 2) = "XY"    ' Replace 2 characters starting at position 3
    +

    MID$ can also be used on the left side of an assignment to replace a portion of a string without changing its length:

    +
    Mid$(s$, start [, length]) = replacement$
    +
    Dim s$
    +s$ = "Hello, World!"
    +Mid$(s$, 8, 5) = "DVX--"
    +Print s$                   ' Hello, DVX--!
    +

    If length is omitted, MID$ replaces up to LEN(replacement$) characters. The target string's total length never changes -- extra characters in replacement$ are truncated and shorter replacements leave trailing characters in place.

    Math Functions

      Function   Returns   Description
       --------   -------   -----------
       ABS(n)     Double    Absolute value of n
    -  ATN(n)     Double    Arctangent of n (in radians)
    -  COS(n)     Double    Cosine of n (radians)
    +  ATN(n)     Double    Arctangent of n (result in radians)
    +  COS(n)     Double    Cosine of n (n in radians)
       EXP(n)     Double    e raised to the power n
    -  FIX(n)     Integer   Truncates n toward zero (removes fractional part)
    +  FIX(n)     Integer   Truncates n toward zero (drops the fractional part)
       INT(n)     Integer   Largest integer less than or equal to n (floor)
       LOG(n)     Double    Natural logarithm (base e) of n
    -  RND[(n)]   Double    Random number between 0 (inclusive) and 1 (exclusive)
    -  SGN(n)     Integer   Sign of n: -1, 0, or 1
    -  SIN(n)     Double    Sine of n (radians)
    +  RND[(n)]   Double    Random number in the range 0 <= RND < 1
    +  SGN(n)     Integer   Sign of n: -1 if negative, 0 if zero, 1 if positive
    +  SIN(n)     Double    Sine of n (n in radians)
       SQR(n)     Double    Square root of n
    -  TAN(n)     Double    Tangent of n (radians)
    -  TIMER      Double    Number of seconds since midnight
    -
    Note: RND with a negative argument seeds and returns. RND(0) returns the previous value. Use RANDOMIZE to seed the generator.
    + TAN(n) Double Tangent of n (n in radians) + TIMER Double Seconds since midnight +
    Note: RND with no argument returns the next number in the sequence. Use RANDOMIZE or RANDOMIZE TIMER to seed the generator so you don't get the same sequence each run.
    +

    Color Functions

    +

    The color functions work with 24-bit RGB colors packed into a Long.

    +
      Function           Returns   Description
    +  --------           -------   -----------
    +  RGB(r, g, b)       Long      Combine red, green, and blue components (0-255) into a 24-bit color
    +  GETRED(color)      Integer   Red component (0-255) of a color built with RGB
    +  GETGREEN(color)    Integer   Green component (0-255) of a color built with RGB
    +  GETBLUE(color)     Integer   Blue component (0-255) of a color built with RGB
    +
    Dim c As Long
    +c = RGB(255, 128, 0)           ' bright orange
    +Print GetRed(c); GetGreen(c); GetBlue(c)    ' 255 128 0
    +Me.BackColor = RGB(0, 0, 128)  ' dark blue background

    Conversion Functions

      Function   Returns   Description
       --------   -------   -----------
       CDBL(n)    Double    Converts n to Double
    -  CINT(n)    Integer   Converts n to Integer (with banker's rounding)
    +  CINT(n)    Integer   Converts n to Integer (rounds half away from zero)
       CLNG(n)    Long      Converts n to Long
       CSNG(n)    Single    Converts n to Single
       CSTR(n)    String    Converts n to its String representation
    @@ -1641,37 +1876,44 @@ pos = SEEK(channel) ' Function: get current position

    File I/O Functions

      Function                    Returns   Description
       --------                    -------   -----------
    -  EOF(channel)                Boolean   True if file pointer is at end of file
    -  FREEFILE                    Integer   Next available file channel number
    -  INPUT$(n, #channel)         String    Reads n characters from the file
    +  EOF(channel)                Boolean   True if the file pointer is at end of file
    +  FREEFILE                    Integer   Next available file channel number (1..16)
    +  INPUT$(n, #channel)         String    Reads exactly n characters from the file
       LOC(channel)                Long      Current read/write position in the file
       LOF(channel)                Long      Length of the file in bytes
       SEEK(channel)               Long      Current file position (function form)
    -  LBOUND(array [, dim])       Integer   Lower bound of an array dimension
    -  UBOUND(array [, dim])       Integer   Upper bound of an array dimension
    + FILELEN(path$) Long Length of the named file in bytes (no OPEN needed) + GETATTR(path$) Integer File attribute bits (see vbReadOnly, vbHidden, etc.) + CURDIR$ String Current working directory + DIR$(pattern$) String First filename matching pattern, or "" + DIR$ String Next match from the last DIR$(pattern$), or "" when done + LBOUND(array [, dim]) Integer Lower bound of an array dimension (default dim = 1) + UBOUND(array [, dim]) Integer Upper bound of an array dimension (default dim = 1)

    Miscellaneous Functions

      Function          Returns   Description
       --------          -------   -----------
       DATE$             String    Current date as "MM-DD-YYYY"
    -  TIME$             String    Current time as "HH:MM:SS"
    -  ENVIRON$(name$)   String    Value of environment variable name$
    -  ERR               Integer   Current runtime error number (0 if no error)
    + TIME$ String Current time as "HH:MM:SS" (24-hour) + TIMER Double Seconds since midnight + ENVIRON$(name$) String Value of the environment variable; empty string if not set + ERR Integer Current runtime error number (0 if no error) + SHELL(cmd$) Integer Exit code of the command (also usable as a statement, discarding the code)

    Form and Control Statements

    -

    DVX BASIC supports Visual Basic-style forms and controls for building graphical user interfaces. Forms are defined in .frm files and loaded at runtime.

    +

    DVX BASIC supports Visual Basic-style forms and controls for building graphical user interfaces. A form is normally designed visually in the IDE and saved as a .frm file, but forms and their controls can also be built entirely in code.

    Loading and Unloading Forms

    LOAD FormName
     UNLOAD FormName
    -

    LOAD creates the form and its controls in memory. UNLOAD destroys the form and frees its resources.

    +

    LOAD creates the form and its controls in memory. It fires Form_Load when the form is first loaded. UNLOAD destroys the form, firing Form_QueryUnload (which can cancel the close) and then Form_Unload. The form name here is the literal name of the form as it appears in its .frm file.

    Showing and Hiding Forms

    -
    FormName.Show [modal]
    +
    FormName.Show [mode]
     FormName.Hide
    -Me.Show [modal]
    +Me.Show [mode]
     Me.Hide
    -

    Pass vbModal (1) to Show for a modal dialog.

    +

    Pass vbModal (1) to Show for a modal dialog that blocks until the user closes it. Omit the mode for a modeless (non-blocking) window.

    Form2.Show vbModal
     Me.Hide

    Property Access

    @@ -1679,42 +1921,94 @@ Me.Hide
    ControlName.Property = value
     value = ControlName.Property
    Text1.Text = "Hello"
    -label1.Caption = "Name: " & name$
    +Label1.Caption = "Name: " & name$
     x = Text1.Left

    Method Calls

    ControlName.Method [args]
    List1.AddItem "New entry"
     List1.Clear

    Me Keyword

    -

    Me refers to the current form. Use it to access the form's own properties, controls, and methods from within event handlers.

    +

    Me refers to the current form -- that is, the form whose event handler is running. Use it to access the form's own properties, controls, and methods from within event handlers, especially when the form's name may change or when the same code runs from multiple forms.

    Me.Caption = "Updated Title"
     Me.Text1.Text = ""
     Me.Hide
    +

    Nothing

    +

    Nothing is a null object reference. Assign it to an object-typed variable to indicate "no object".

    +
    Set myCtrl = Nothing

    Control Arrays

    Multiple controls can share a name with unique indices. Access individual controls with parenthesized indices:

    Option1(0).Value = True
     Label1(idx).Caption = "Item " & Str$(idx)
     Me.Label1(i).Visible = True
    +

    WITH

    +

    The WITH block provides a shorthand for setting several properties on the same control or for making several method calls. Inside the block, begin each property or method with a dot and omit the control name.

    +
    WITH ControlName
    +    .Property1 = value1
    +    .Property2 = value2
    +    .Method arg1, arg2
    +END WITH
    +
    With Label1
    +    .Caption = "Status: Ready"
    +    .ForeColor = RGB(0, 128, 0)
    +    .Visible   = True
    +End With
    +

    Building Forms in Code

    +

    In addition to loading forms from .frm files, you can build a form entirely from code with CreateForm, CreateControl, SetEvent, and RemoveControl. The returned reference should be stored with SET.

    +

    CreateForm

    +
    SET formVar = CreateForm(name$, width%, height%)
    +

    Creates a new form with the given name and initial size. Returns a form reference.

    +

    CreateControl

    +
    SET ctrlVar = CreateControl(formRef, typeName$, ctrlName$ [, parentCtrlRef])
    +

    Creates a control on the given form. typeName$ is the control type (e.g. "CommandButton", "Label", "TextBox"). ctrlName$ is the unique name for the new control. If a parentCtrlRef is supplied, the new control is nested inside it (useful for placing controls inside containers like HBox or Frame).

    +

    SetEvent

    +
    SETEVENT ctrlRef, eventName$, handlerName$
    +

    Wires an arbitrary SUB as the handler for a control's event. The default event-handler convention (ControlName_EventName) is used automatically for controls defined in a .frm file; SetEvent is for controls you create dynamically or for reusing a single handler across multiple controls.

    +

    RemoveControl

    +
    REMOVECONTROL formRef, ctrlName$
    +

    Removes a control from a form.

    +
    Dim frm As Long
    +Dim lbl As Long
    +
    +Set frm = CreateForm("MyDlg", 300, 200)
    +frm.Caption = "Built in code"
    +
    +Set lbl = CreateControl(frm, "Label", "StatusLabel")
    +StatusLabel.Caption = "Hello, world!"
    +
    +Dim btn As Long
    +Set btn = CreateControl(frm, "CommandButton", "BtnClose")
    +BtnClose.Caption = "Close"
    +SetEvent btn, "Click", "OnCloseClick"
    +
    +frm.Show
    +
    +Sub OnCloseClick
    +    Unload Me
    +End Sub

    DoEvents

    -

    Yields control to the DVX event loop, allowing the GUI to process pending events. Call this in long-running loops to keep the UI responsive.

    +

    Yields control to DVX so the GUI can process pending events. Call this in long-running loops to keep the UI responsive (otherwise the window will appear frozen).

    DOEVENTS
    For i = 1 To 10000
         ' process data
         If i Mod 100 = 0 Then DoEvents
     Next

    MsgBox

    -

    Displays a message box dialog. Can be used as a statement (discards result) or as a function (returns the button clicked).

    -
    MSGBOX message$ [, flags]
    -result = MSGBOX(message$ [, flags])
    +

    Displays a message-box dialog. Can be used as a statement (discards the result) or as a function (returns the button clicked).

    +
    MSGBOX message$ [, flags [, title$]]
    +result = MSGBOX(message$ [, flags [, title$]])
    +

    flags is the sum of a button-style constant and an icon constant. See Predefined Constants for the complete list.

    MsgBox "Operation complete"
    -answer = MsgBox("Continue?", vbYesNo + vbQuestion)
    +answer = MsgBox("Continue?", vbYesNo + vbQuestion, "Confirm")
     If answer = vbYes Then
         ' proceed
     End If

    InputBox$

    -

    Displays an input dialog and returns the user's text entry.

    +

    Displays an input dialog and returns the user's text entry. Returns an empty string if the user cancels.

    result$ = INPUTBOX$(prompt$ [, title$ [, default$]])
    -
    name$ = InputBox$("Enter your name:", "Name Entry", "")
    +
    name$ = InputBox$("Enter your name:", "Name Entry", "")
    +If name$ <> "" Then
    +    Print "Hello, "; name$
    +End If

    Event Handler Convention

    Event handlers are named ControlName_EventName and defined as SUBs:

    Sub Command1_Click()
    @@ -1729,65 +2023,70 @@ Sub Text1_Change()
         Label1.Caption = "You typed: " & Text1.Text
     End Sub

    Common Events

    -
      Event          Description
    -  -----          -----------
    -  Click          Control was clicked
    -  DblClick       Control was double-clicked
    -  Change         Control value/text changed
    -  KeyPress       Key was pressed (receives key code)
    -  KeyDown        Key went down (receives key code and shift state)
    -  KeyUp          Key was released
    -  MouseDown      Mouse button pressed
    -  MouseUp        Mouse button released
    -  MouseMove      Mouse moved over control
    -  GotFocus       Control received input focus
    -  LostFocus      Control lost input focus
    -  Form_Load      Form is being loaded
    -  Form_Unload    Form is being unloaded
    -  Form_Resize    Form was resized
    -  Timer          Timer interval elapsed
    +
      Event                Description
    +  -----                -----------
    +  Click                Control was clicked
    +  DblClick             Control was double-clicked
    +  Change               Control value or text changed
    +  KeyPress             Printable key was pressed (receives ASCII code)
    +  KeyDown              Any key was pressed (receives key code and shift state)
    +  KeyUp                Key was released
    +  MouseDown            Mouse button pressed (receives button, X, Y)
    +  MouseUp              Mouse button released
    +  MouseMove            Mouse moved over the control
    +  Scroll               Control was scrolled (mouse wheel or scrollbar)
    +  GotFocus             Control received input focus
    +  LostFocus            Control lost input focus
    +  Form_Load            Form is being loaded (fires once, after controls are created)
    +  Form_QueryUnload     Form is about to close; set Cancel = 1 to abort
    +  Form_Unload          Form is being unloaded
    +  Form_Resize          Form was resized
    +  Form_Activate        Form gained focus
    +  Form_Deactivate      Form lost focus
    +  Timer1_Timer         Timer control interval elapsed (Timer control's default event)

    SQL Functions

    -

    DVX BASIC includes built-in SQLite database support through a set of SQL functions. All functions use database handles and result set handles (integers) returned by SQLOpen and SQLQuery.

    +

    DVX BASIC includes SQLite database support through the sql.bas include library. Add sql.bas to your project to gain access to these functions. All functions use database handles and result-set handles (integers) returned by SQLOpen and SQLQuery.

    Opening and Closing Databases

    SQLOpen

    -

    Opens a SQLite database file and returns a database handle.

    -
    db = SQLOPEN(path$)
    +

    Opens a SQLite database file and returns a database handle (> 0) or 0 on failure. The file is created if it does not exist.

    +
    db = SQLOpen(path$)
    db = SQLOpen(App.Data & "\mydata.db")

    SQLClose

    Closes an open database.

    -
    SQLCLOSE db
    +
    SQLClose db

    Executing SQL

    SQLExec

    -

    Executes a SQL statement that does not return data (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Can be used as a statement or as a function returning a Boolean success flag.

    -
    SQLEXEC db, sql$
    -ok = SQLEXEC(db, sql$)
    +

    Executes a SQL statement that does not return data (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Returns True on success.

    +
    ok = SQLExec(db, sql$)
    SQLExec db, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"
     SQLExec db, "INSERT INTO users (name) VALUES ('Scott')"
     ok = SQLExec(db, "DELETE FROM users WHERE id = 5")

    SQLAffected

    Returns the number of rows affected by the last INSERT, UPDATE, or DELETE.

    -
    count = SQLAFFECTED(db)
    +
    count = SQLAffected(db)

    Querying Data

    SQLQuery

    -

    Executes a SELECT query and returns a result set handle.

    -
    rs = SQLQUERY(db, sql$)
    +

    Executes a SELECT query and returns a result-set cursor.

    +
    rs = SQLQuery(db, sql$)
    rs = SQLQuery(db, "SELECT id, name FROM users ORDER BY name")

    SQLNext

    -

    Advances to the next row in the result set. Can be used as a statement or as a function returning True if a row is available.

    -
    SQLNEXT rs
    -hasRow = SQLNEXT(rs)
    +

    Advances the cursor to the next row. Returns True if a row is available, False when there are no more rows.

    +
    hasRow = SQLNext(rs)

    SQLEof

    -

    Returns True if there are no more rows in the result set.

    -
    done = SQLEOF(rs)
    +

    Returns True if the cursor is past the last row.

    +
    done = SQLEof(rs)

    Reading Fields

    -

    SQLField$

    -

    Returns a field value as a string. The field can be specified by column index (0-based) or by column name.

    -
    value$ = SQLFIELD$(rs, columnIndex)
    -value$ = SQLFIELD$(rs, "columnName")
    -
    name$ = SQLField$(rs, "name")
    -first$ = SQLField$(rs, 0)
    +

    SQLField$ / SQLFieldText$

    +

    Returns a field value as a string. Use SQLField$ to look up by column name; use SQLFieldText$ to look up by 0-based column index.

    +
    value$ = SQLField$(rs, columnName$)
    +value$ = SQLFieldText$(rs, columnIndex)
    +
    name$  = SQLField$(rs, "name")
    +first$ = SQLFieldText$(rs, 0)
    +

    SQLFieldName$

    +

    Returns the column name for a given 0-based column index.

    +
    colName$ = SQLFieldName$(rs, columnIndex)

    SQLFieldInt

    Returns a field value as an integer.

    value = SQLFIELDINT(rs, columnIndex)
    @@ -1796,26 +2095,25 @@ first$ = SQLField$(rs, 0)
    value# = SQLFIELDDBL(rs, columnIndex)

    SQLFieldCount

    Returns the number of columns in the result set.

    -
    count = SQLFIELDCOUNT(rs)
    +
    count = SQLFieldCount(rs)

    Result Set Cleanup

    SQLFreeResult

    -

    Frees a result set. Always call this when done iterating a query.

    -
    SQLFREERESULT rs
    +

    Frees a result-set cursor. Always call this when finished iterating a query.

    +
    SQLFreeResult rs

    Error Information

    SQLError$

    Returns the last error message for the database.

    -
    msg$ = SQLERROR$(db)
    +
    msg$ = SQLError$(db)

    Complete SQL Example

    -
    Dim db As Long
    -Dim rs As Long
    +
    Dim db As Integer
    +Dim rs As Integer
     
     db = SQLOpen(App.Data & "\contacts.db")
     SQLExec db, "CREATE TABLE IF NOT EXISTS contacts (name TEXT, phone TEXT)"
     SQLExec db, "INSERT INTO contacts VALUES ('Alice', '555-1234')"
     
     rs = SQLQuery(db, "SELECT name, phone FROM contacts")
    -Do While Not SQLEof(rs)
    -    SQLNext rs
    +Do While SQLNext(rs)
         Print SQLField$(rs, "name"); Tab(20); SQLField$(rs, "phone")
     Loop
     SQLFreeResult rs
    @@ -1835,10 +2133,10 @@ Print "Running from: " & App.Path

    INI Functions

    -

    DVX BASIC provides built-in functions for reading and writing standard INI configuration files.

    +

    DVX BASIC provides built-in functions for reading and writing standard INI configuration files (the same text format used by DVX system configuration).

    IniRead

    -

    Reads a value from an INI file. Returns the default value if the key is not found.

    -
    value$ = INIREAD(file$, section$, key$, default$)
    +

    Reads a value from an INI file. Returns the default value if the file, section, or key is not found. All four arguments are required.

    +
    value$ = IniRead(file$, section$, key$, default$)
    name$ = IniRead(App.Config & "\app.ini", "User", "Name", "Unknown")
     fontSize = Val(IniRead(App.Config & "\app.ini", "Display", "FontSize", "12"))

    IniWrite

    @@ -1849,7 +2147,7 @@ IniWrite App.Config & "\app.ini", "Display", "FontS

    Predefined Constants

    -

    The following constants are predefined by the compiler and available in all programs.

    +

    The following constants are predefined by the compiler and available in all programs. No DIM or CONST declaration is required to use them.

    MsgBox Button Style Flags

      Constant        Value   Description
       --------        -----   -----------
    @@ -1859,13 +2157,13 @@ IniWrite App.Config & "\app.ini", "Display", "FontS
       vbYesNoCancel   3       Yes, No, and Cancel buttons
       vbRetryCancel   4       Retry and Cancel buttons

    MsgBox Icon Flags

    -

    Add an icon flag to the button style to display an icon in the message box.

    +

    Add an icon flag to the button style (with + or OR) to display an icon in the message box.

      Constant        Value   Description
       --------        -----   -----------
    -  vbInformation   &H10    Information icon
    -  vbExclamation   &H20    Warning icon
    -  vbCritical      &H30    Error/critical icon
    -  vbQuestion      &H40    Question mark icon
    + vbInformation &H10 Information icon (i) + vbExclamation &H20 Warning icon (!) + vbCritical &H30 Error/critical icon (X) + vbQuestion &H40 Question icon (?)

    MsgBox Return Values

      Constant   Value   Description
       --------   -----   -----------
    @@ -1878,12 +2176,43 @@ IniWrite App.Config & "\app.ini", "Display", "FontS
     
      Constant   Value   Description
       --------   -----   -----------
       vbModal    1       Show form as modal dialog
    +

    File Attribute Flags

    +

    Bit values used with GETATTR and SETATTR.

    +
      Constant      Value   Description
    +  --------      -----   -----------
    +  vbNormal      0       Normal file (no attributes)
    +  vbReadOnly    1       Read-only
    +  vbHidden      2       Hidden
    +  vbSystem      4       System file
    +  vbDirectory   16      Entry is a directory
    +  vbArchive     32      File has been modified since last backup

    Boolean Constants

      Constant   Value   Description
       --------   -----   -----------
       True       -1      Boolean true
       False      0       Boolean false
    +
    +

    Include Libraries

    +

    DVX ships a set of BASIC include files (.bas) in the sdk/include/basic directory. Each file is a small library of DECLARE LIBRARY wrappers around native DVX runtime functions. Add an include file to your project (the IDE's Add File command, or editing the .dbp with another tool) and the declared functions become available to your code.

    +
      File           Provides
    +  ----           --------
    +  commdlg.bas    File Open/Save dialogs, input dialogs, choice dialogs, save-prompt dialogs
    +  sql.bas        SQLite database access (SQLOpen, SQLQuery, SQLNext, SQLField$, etc.)
    +  comm.bas       Serial-port I/O (raw and packet) and optional encrypted links
    +  resource.bas   Read and write DVX resource blocks (icons, text, binary data) in bundled files
    +  help.bas       Compile help source files and open the DVX help viewer
    +

    Using an Include Library

    +
    ' With commdlg.bas added to the project
    +Dim path As String
    +path = basFileOpen("Open a file", "Text Files (*.txt)|All Files (*.*)")
    +
    +If path <> "" Then
    +    Print "Selected: " & path
    +End If
    +
    Note: DVX BASIC does not have an $INCLUDE directive. Libraries are added to the project so that the compiler sees their DECLARE LIBRARY blocks.
    +

    See also: BASIC Runtime Library

    +

    Common Properties, Events, and Methods

    Every control in DVX BASIC inherits a set of common properties, events, and methods. These are handled by the form runtime before dispatching to widget-specific interface descriptors.

    @@ -1902,8 +2231,8 @@ IniWrite App.Config & "\app.ini", "Display", "FontS Weight Integer R/W Layout weight. 0 = fixed size, >0 = share extra space proportionally. Visible Boolean R/W Whether the control is visible. Enabled Boolean R/W Whether the control accepts user input. - BackColor Long R/W Background color as a 32-bit ARGB value. - ForeColor Long R/W Foreground (text) color as a 32-bit ARGB value. + BackColor Long R/W Background color as a 24-bit RGB value packed in a Long (use the RGB function to construct). + ForeColor Long R/W Foreground (text) color as a 24-bit RGB value packed in a Long (use the RGB function to construct). TabIndex Integer R Accepted for VB compatibility but ignored. DVX has no tab order.

    Common Events

    These events are wired on every control loaded from a .frm file. Controls created dynamically at runtime via code only receive Click, DblClick, Change, GotFocus, and LostFocus; the keyboard, mouse, and scroll events below require the control to be defined in the .frm file.

    @@ -2175,7 +2504,7 @@ End Sub
    Left Integer 0 Initial X position. Used when Centered is False. Top Integer 0 Initial Y position. Used when Centered is False. Layout String "VBox" Content box layout: "VBox" (vertical) or "HBox" (horizontal). - AutoSize Boolean True When True, the window shrink-wraps to fit its content. + AutoSize Boolean False When True, the window shrink-wraps to fit its content. Resizable Boolean True Whether the user can resize the window at runtime. Centered Boolean True When True, the window is centered on screen. When False, Left/Top are used.

    Form Events

    @@ -2215,21 +2544,21 @@ End Sub

    Terminal

    -

    DVX Extension -- DVX Widget: ansiterm (ANSI terminal emulator)

    -

    A VT100/ANSI terminal emulator widget. Supports ANSI escape sequences, scrollback buffer, and serial communication. Default size is 80 columns by 25 rows.

    +

    DVX Extension -- DVX Widget: ansiterm

    +

    A VT100/ANSI terminal emulator with a text-mode cell buffer, 16-color CGA palette, and scrollback history. Processes standard ANSI escape sequences (cursor control, erase, scroll, SGR colors, DEC private modes) typically used by DOS BBS software. Default size is 80 columns by 25 rows. Supports text selection with the mouse, Ctrl+C to copy, and Ctrl+V to send clipboard text as input.

    Type-Specific Properties

      Property     Type      Description
       ----------   -------   -------------------------------------------
       Cols         Integer   Number of character columns (read-only).
       Rows         Integer   Number of character rows (read-only).
    -  Scrollback   Integer   Number of scrollback lines (write-only).
    + Scrollback Integer Maximum number of scrollback lines. Older lines are dropped (write-only; default 500).

    Type-Specific Methods

      Method             Description
       ------             -----------
    -  Clear              Clear the terminal screen.
    -  Poll               Process pending comm data and update the display.
    -  Write text$        Write text (with ANSI escape processing) to the terminal.
    -

    No default event.

    + Clear Clear the terminal screen and reset the cursor to the home position. + Poll Poll the attached communication channel for incoming bytes and feed them into the ANSI parser. + Write text$ Write a string to the terminal with full ANSI escape processing. +

    No default event. Uses common events only.

    Serial Communication

    Use the comm.bas library to connect a Terminal to a serial link:

    Dim link As Integer
    @@ -2237,16 +2566,26 @@ link = CommOpen(1, 115200)
     CommHandshake link
     CommAttach link, "Terminal1"

    See comm.bas for the full communications API.

    +

    Example

    +
    Begin Terminal Terminal1
    +    MinWidth  = 640
    +    MinHeight = 400
    +End
    +
    +Sub Form_Load ()
    +    Terminal1.Write "Welcome!" & Chr$(13) & Chr$(10)
    +End Sub

    Common Properties, Events, and Methods

    Frame

    -

    VB Equivalent: Frame -- DVX Widget: frame (titled VBox container)

    +

    VB Equivalent: Frame -- DVX Widget: frame (titled container)

    A container with a titled border. Child controls are placed inside the frame using VBox layout. In the .frm file, nest Begin/End blocks inside the Frame block.

    Type-Specific Properties

      Property   Type     Description
       --------   ------   -------------------------------------------
       Caption    String   The title displayed in the frame border.
    +

    No type-specific methods or events.

    Container: Yes

    Default Event: Click

    Example

    @@ -2264,32 +2603,51 @@ End

    HBox

    DVX Extension -- DVX Widget: hbox (horizontal layout container)

    -

    A container that arranges its children horizontally, left to right. Use Weight on children to distribute extra space.

    +

    A container that arranges its children horizontally, left to right. No visible border. Use Weight on children to distribute extra space proportionally.

    +

    No type-specific properties, methods, or events.

    Container: Yes

    Default Event: Click

    -

    No type-specific properties.

    +

    Example

    +
    Begin HBox ButtonRow
    +    Spacing = 4
    +    Begin CommandButton Command1
    +        Caption = "OK"
    +    End
    +    Begin CommandButton Command2
    +        Caption = "Cancel"
    +    End
    +End

    Common Properties, Events, and Methods

    VBox

    VBox

    DVX Extension -- DVX Widget: vbox (vertical layout container)

    -

    A container that arranges its children vertically, top to bottom. No title or border. Use Weight on children to distribute extra space.

    +

    A container that arranges its children vertically, top to bottom. No title or border. Use Weight on children to distribute extra space proportionally.

    +

    No type-specific properties, methods, or events.

    Container: Yes

    Default Event: Click

    -

    No type-specific properties.

    +

    Example

    +
    Begin VBox Column1
    +    Spacing = 4
    +    Begin Label Label1
    +        Caption = "Name:"
    +    End
    +    Begin TextBox Text1
    +    End
    +End

    Common Properties, Events, and Methods

    HBox

    CommandButton

    VB Equivalent: CommandButton -- DVX Widget: button | Name Prefix: Command

    -

    A push button that triggers an action when clicked. Created with wgtButton(parent, text).

    +

    A push button that triggers an action when clicked. The Caption may include a '&' before a character to mark an accelerator key (e.g. "&OK" underlines 'O', and Alt+O activates the button).

    Type-Specific Properties

      Property   Type     Description
       --------   ------   -------------------------------------------
    -  Caption    String   The text displayed on the button. Use & for accelerator keys (e.g. "&OK").
    -

    No additional type-specific properties beyond common properties and Caption.

    + Caption String The text displayed on the button. Use '&' for an accelerator key. +

    No type-specific methods or events.

    Default Event: Click

    Example

    Begin Form Form1
    @@ -2339,12 +2697,13 @@ Picture1.Save "output.bmp"

    CheckBox

    VB Equivalent: CheckBox -- DVX Widget: checkbox

    -

    A toggle control with a label. Checked state is exposed as a Boolean.

    +

    A toggle control with a label. The checked state is exposed as a Boolean. Supports accelerator keys (put '&' before a character in the Caption). Press Space or Enter to toggle when focused.

    Type-Specific Properties

      Property   Type      Description
       --------   -------   -------------------------------------------
       Caption    String    The text displayed next to the checkbox.
       Value      Boolean   True if checked, False if unchecked.
    +

    No type-specific methods or events.

    Default Event: Click

    Example

    Begin CheckBox Check1
    @@ -2362,37 +2721,55 @@ End Sub

    ComboBox

    -

    VB Equivalent: ComboBox -- DVX Widget: combobox (editable text field + drop-down list, max 256 chars)

    -

    A combination of a text input and a drop-down list. The user can type text or select from the list. Supports the same AddItem/RemoveItem/Clear/List methods as ListBox.

    +

    VB Equivalent: ComboBox -- DVX Widget: combobox (editable text field + drop-down list, default maxLen 256)

    +

    A combination of a single-line text input and a drop-down list. The user can either type a value or pick one from the list. Selecting a list item copies its text into the edit field. Supports the same AddItem / RemoveItem / Clear / List methods as ListBox.

    Type-Specific Properties

      Property    Type      Description
       ---------   -------   -------------------------------------------
       Text        String    The text in the editable field.
    -  ListIndex   Integer   Index of the currently selected list item (-1 = none).
    -  ListCount   Integer   Number of items in the drop-down list (read-only).
    + ListIndex Integer Index of the currently selected list item (-1 = user typed something not in the list).

    Type-Specific Methods

    -

    Same as ListBox: AddItem, RemoveItem, Clear, List.

    -

    See ListBox for details

    +
      Method              Description
    +  ------              -----------
    +  AddItem text$       Append an item to the drop-down list.
    +  Clear               Remove all items.
    +  List(index%)        Return the text of the item at the given index.
    +  ListCount()         Return the number of items in the list.
    +  RemoveItem index%   Remove the item at the given index.

    Default Event: Click

    +

    Example

    +
    Begin ComboBox Combo1
    +End
    +
    +Sub Form_Load ()
    +    Combo1.AddItem "Small"
    +    Combo1.AddItem "Medium"
    +    Combo1.AddItem "Large"
    +End Sub
    +
    +Sub Combo1_Change ()
    +    Label1.Caption = "Selected: " & Combo1.Text
    +End Sub

    Common Properties, Events, and Methods

    +

    See ListBox for details

    Data

    -

    VB Equivalent: Data -- DVX Widget: data (database record navigator)

    -

    A data access control that connects to a SQLite database and provides record navigation. Other controls can bind to a Data control via their DataSource and DataField properties.

    +

    VB Equivalent: Data -- DVX Widget: data | Name Prefix: Data

    +

    A data access control that connects to a SQLite database and provides record navigation. Other controls bind to a Data control via their DataSource and DataField properties. The Data control cache is populated by calling Refresh. Move operations auto-save the current row if dirty.

    See Data Binding for details

    Type-Specific Properties

    -
      Property       Type      R/W   Description
    -  ------------   -------   ---   -------------------------------------------
    -  DatabaseName   String    R/W   Path to the SQLite database file.
    -  RecordSource   String    R/W   Table name or SQL SELECT query for the recordset.
    -  KeyColumn      String    R/W   Primary key column name (used for UPDATE/DELETE operations).
    -  Caption        String    R/W   Text displayed on the navigator bar.
    -  BOF            Boolean   R     True if the current position is before the first record (read-only).
    -  EOF            Boolean   R     True if the current position is past the last record (read-only).
    -  MasterSource   String    R/W   Name of a master Data control (for master-detail binding).
    -  MasterField    String    R/W   Column in the master recordset to filter by.
    -  DetailField    String    R/W   Column in this recordset that matches the master field.
    +
      Property       Type      Description
    +  ------------   -------   -------------------------------------------
    +  DatabaseName   String    Path to the SQLite database file.
    +  RecordSource   String    Table name or SQL SELECT query for the recordset.
    +  KeyColumn      String    Primary key column name (used for UPDATE/DELETE operations).
    +  Caption        String    Text displayed on the navigator bar.
    +  BOF            Boolean   True if the current position is the first record or there are no records (read-only).
    +  EOF            Boolean   True if the current position is past the last record or there are no records (read-only).
    +  MasterSource   String    Name of a master Data control (for master-detail binding).
    +  MasterField    String    Column in the master recordset to read for the filter value.
    +  DetailField    String    Column in this recordset that matches the master field.

    Type-Specific Methods

      Method         Parameters   Description
       ------------   ----------   -------------------------------------------
    @@ -2400,28 +2777,44 @@ End Sub
    MoveLast (none) Navigate to the last record. MoveNext (none) Navigate to the next record. MovePrevious (none) Navigate to the previous record. - AddNew (none) Add a new blank record. + AddNew (none) Append a blank record and move to it. Delete (none) Delete the current record. Refresh (none) Re-query the database and reload records. Update (none) Write pending changes to the database.

    Type-Specific Events

      Event        Parameters          Description
       ----------   -----------------   -------------------------------------------
    -  Reposition   (none)              Fires after the current record changes (navigation). This is the default event.
    -  Validate     Cancel As Integer   Fires before writing a record. Set Cancel = 1 to abort.
    + Reposition (none) Fires after the current record changes. Default event. + Validate Cancel As Integer Fires before writing. Set Cancel = 1 to abort. +

    Default Event: Reposition

    +

    Example

    +
    Begin Data Data1
    +    DatabaseName = "books.db"
    +    RecordSource = "titles"
    +    KeyColumn    = "id"
    +End
    +
    +Begin TextBox Text1
    +    DataSource = "Data1"
    +    DataField  = "title"
    +End
    +
    +Sub Form_Load ()
    +    Data1.Refresh
    +End Sub

    Common Properties, Events, and Methods

    Data Binding

    DBGrid

    DBGrid

    -

    VB Equivalent: DBGrid -- DVX Widget: dbgrid

    -

    A data-bound grid that displays records from a Data control in a tabular format. Columns are auto-generated from the query results. Bind it using the DataSource property.

    +

    VB Equivalent: DBGrid -- DVX Widget: dbgrid | Name Prefix: DBGrid

    +

    A data-bound grid that displays records from a Data control in a tabular format. Columns auto-populate from the query results. Bind the grid using the DataSource property. Click a column header to sort; drag a column border to resize. Selecting a row moves the bound Data control's cursor to that row.

    Type-Specific Properties

      Property     Type      Description
       ----------   -------   -------------------------------------------
       DataSource   String    Name of the Data control that supplies records.
    -  GridLines    Boolean   Show or hide grid lines between cells.
    + GridLines Boolean Show or hide grid lines between cells (default: True).

    Type-Specific Methods

      Method    Parameters   Description
       -------   ----------   -------------------------------------------
    @@ -2429,8 +2822,27 @@ End Sub

    Type-Specific Events

      Event      Parameters   Description
       --------   ----------   -------------------------------------------
    -  Click      (none)       Fires when a cell is clicked.
    -  DblClick   (none)       Fires when a cell is double-clicked. This is the default event.
    + Click (none) Fires when a row is clicked. + DblClick (none) Fires when a row is double-clicked. Default event. +

    Default Event: DblClick

    +

    Example

    +
    Begin Data Data1
    +    DatabaseName = "books.db"
    +    RecordSource = "titles"
    +End
    +
    +Begin DBGrid DBGrid1
    +    DataSource = "Data1"
    +    GridLines  = True
    +End
    +
    +Sub Form_Load ()
    +    Data1.Refresh
    +End Sub
    +
    +Sub DBGrid1_DblClick ()
    +    MsgBox "Opening row " & Str$(Data1.BOF)
    +End Sub

    Common Properties, Events, and Methods

    Data

    Data Binding

    @@ -2438,53 +2850,98 @@ End Sub

    DropDown

    DVX Extension -- DVX Widget: dropdown (non-editable drop-down list)

    -

    A read-only drop-down list. Unlike ComboBox, the user cannot type free text; they can only select from the provided items. Supports AddItem/RemoveItem/Clear/List.

    +

    A read-only drop-down list. Unlike ComboBox, the user cannot type free text; they can only select from the provided items. Supports AddItem / RemoveItem / Clear / List. Use the arrow keys to navigate and Enter to commit when the list is open.

    Type-Specific Properties

      Property    Type      Description
       ---------   -------   -------------------------------------------
    -  ListIndex   Integer   Index of the currently selected item.
    -  ListCount   Integer   Number of items (read-only).
    + ListIndex Integer Index of the currently selected item (-1 = none).

    Type-Specific Methods

    -

    Same as ListBox: AddItem, RemoveItem, Clear, List.

    -

    See ListBox for details

    +
      Method              Description
    +  ------              -----------
    +  AddItem text$       Append an item to the drop-down list.
    +  Clear               Remove all items.
    +  List(index%)        Return the text of the item at the given index.
    +  ListCount()         Return the number of items in the list.
    +  RemoveItem index%   Remove the item at the given index.

    Default Event: Click

    +

    Example

    +
    Begin DropDown DropDown1
    +End
    +
    +Sub Form_Load ()
    +    DropDown1.AddItem "Small"
    +    DropDown1.AddItem "Medium"
    +    DropDown1.AddItem "Large"
    +    DropDown1.ListIndex = 1
    +End Sub
    +
    +Sub DropDown1_Click ()
    +    Label1.Caption = "Picked: " & DropDown1.List(DropDown1.ListIndex)
    +End Sub

    Common Properties, Events, and Methods

    +

    See ListBox for details

    ImageButton

    DVX Extension -- DVX Widget: imagebutton

    -

    A button that displays an image instead of text. Like Image, it requires pixel data at creation time and is typically loaded via the Picture property.

    +

    A button that displays an image instead of text. Has the same press/release behavior as CommandButton, but shows a bitmap. Typically used inside a Toolbar.

    Type-Specific Properties

      Property      Type      Description
       -----------   -------   -------------------------------------------
    -  Picture       String    Path to a BMP file to load (write-only).
    +  Picture       String    Path to an image file to load.
       ImageWidth    Integer   Width of the loaded image in pixels (read-only).
       ImageHeight   Integer   Height of the loaded image in pixels (read-only).
    +

    No type-specific methods or events.

    Default Event: Click

    +

    Example

    +
    Begin Toolbar Toolbar1
    +    Begin ImageButton ImageButton1
    +        Picture = "icons/save.bmp"
    +    End
    +    Begin ImageButton ImageButton2
    +        Picture = "icons/open.bmp"
    +    End
    +End
    +
    +Sub ImageButton1_Click ()
    +    MsgBox "Save clicked"
    +End Sub

    Common Properties, Events, and Methods

    Image

    VB Equivalent: Image -- DVX Widget: image

    -

    A static image display control. Loads BMP images from file. Cannot be placed via the designer toolbox (requires pixel data at creation time); typically created in code or loaded via the Picture property.

    +

    A static image display control. Loads bitmap images from file. Set the Picture property at design-time (in the .frm file) or at runtime to load a file.

    Type-Specific Properties

      Property      Type      Description
       -----------   -------   -------------------------------------------
    -  Picture       String    Path to a BMP file to load (write-only).
    +  Picture       String    Path to an image file to load.
    +  Stretch       Boolean   When True, the image stretches to fill the widget; when False, shown at natural size.
       ImageWidth    Integer   Width of the loaded image in pixels (read-only).
       ImageHeight   Integer   Height of the loaded image in pixels (read-only).
    +

    No type-specific methods or events.

    Default Event: Click

    +

    Example

    +
    Begin Image Image1
    +    Picture = "assets/logo.bmp"
    +    Stretch = True
    +End
    +
    +Sub Image1_Click ()
    +    MsgBox "Size: " & Str$(Image1.ImageWidth) & "x" & Str$(Image1.ImageHeight)
    +End Sub

    Common Properties, Events, and Methods

    Label

    VB Equivalent: Label -- DVX Widget: label

    -

    A static text label. Supports left, center, and right alignment.

    +

    A static text label. Supports left, center, and right alignment. Put '&' before a character in the Caption to define an accelerator that moves focus to the next focusable control.

    Type-Specific Properties

      Property    Type   Description
       ---------   ----   -------------------------------------------
       Caption     String The text displayed by the label.
       Alignment   Enum   Text alignment: Left (default), Center, or Right.
    +

    No type-specific methods or events.

    Default Event: Click

    Example

    Begin Label Label1
    @@ -2496,26 +2953,26 @@ End

    ListBox

    VB Equivalent: ListBox -- DVX Widget: listbox

    -

    A scrollable list of selectable items. Items are managed via methods (AddItem, RemoveItem, Clear). Supports single and multi-select modes.

    +

    A scrollable list of selectable items. Items are managed via methods (AddItem, RemoveItem, Clear). Supports single and multi-select modes, drag-to-reorder, and keyboard navigation with type-ahead search.

    Type-Specific Properties

      Property    Type      Description
       ---------   -------   -------------------------------------------
    -  ListIndex   Integer   Index of the currently selected item (-1 = no selection).
    -  ListCount   Integer   Number of items in the list (read-only).
    + ListIndex Integer Index of the currently selected item (-1 = no selection).

    Type-Specific Methods

      Method            Parameters                                Description
       ---------------   ---------------------------------------   -------------------------------------------
       AddItem           Text As String                            Add an item to the end of the list.
    -  RemoveItem        Index As Integer                          Remove the item at the given index.
       Clear             (none)                                    Remove all items from the list.
    -  List              Index As Integer                          Return the text of the item at the given index.
    -  SelectAll         (none)                                    Select all items (multi-select mode).
       ClearSelection    (none)                                    Deselect all items.
    -  SetMultiSelect    Multi As Boolean                          Enable or disable multi-select mode.
    -  SetReorderable    Reorderable As Boolean                    Enable or disable drag-to-reorder.
       IsItemSelected    Index As Integer                          Returns True if the item at Index is selected.
    +  List              Index As Integer                          Return the text of the item at the given index.
    +  ListCount         (none)                                    Return the number of items in the list.
    +  RemoveItem        Index As Integer                          Remove the item at the given index.
    +  SelectAll         (none)                                    Select all items (multi-select mode).
       SetItemSelected   Index As Integer, Selected As Boolean     Select or deselect a specific item.
    -  SetItems          Items As String                           Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue").
    + SetItems Items As String Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue"). Replaces existing items. + SetMultiSelect Multi As Boolean Enable or disable multi-select mode. + SetReorderable Reorderable As Boolean Enable or disable drag-to-reorder.

    Default Event: Click

    Example

    Sub Form_Load ()
    @@ -2662,7 +3119,7 @@ End Sub

    Splitter

    DVX Extension -- DVX Widget: splitter

    -

    A resizable split pane. Holds exactly two child widgets separated by a draggable divider. Default orientation is vertical (top/bottom split).

    +

    A resizable split pane. Holds exactly two child widgets separated by a draggable divider. Default orientation is vertical (vertical divider, panes side-by-side).

    Type-Specific Properties

      Property   Type      Description
       --------   -------   -------------------------------------------
    @@ -2674,7 +3131,7 @@ End Sub

    StatusBar

    VB Equivalent: StatusBar -- DVX Widget: statusbar

    -

    A horizontal container styled as a status bar, typically placed at the bottom of a form. At the C API level it accepts child widgets, but it is not registered as a container in the form runtime, so child controls cannot be nested inside it in .frm files. Set its Caption property to display status text.

    +

    A horizontal container styled as a status bar, typically placed at the bottom of a form. At the C API level it holds child widgets (such as labels), each drawn with a sunken-panel border for the classic segmented status-bar look. It is not registered as a container in the form runtime, so child controls cannot be nested inside it in .frm files.

    No type-specific properties, events, or methods.

    Common Properties, Events, and Methods

    @@ -2721,7 +3178,7 @@ End Sub

    TextArea

    -

    VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, max 4096 chars)

    +

    VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, default max 65536 chars)

    A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports line numbers, auto-indent, find/replace, and tab configuration.

    Type-Specific Properties

      Property     Type      R/W   Description
    @@ -2767,7 +3224,7 @@ Print "Replaced"; count; "occurrences"
      Property   Type      Description
       --------   -------   -------------------------------------------
       Enabled    Boolean   True to start the timer, False to stop it.
    -  Interval   Integer   Timer interval in milliseconds (write-only from BASIC).
    + Interval Integer Timer interval in milliseconds.

    Type-Specific Methods

      Method   Parameters   Description
       ------   ----------   -------------------------------------------
    @@ -2831,7 +3288,7 @@ Print "Items:"; TreeView1.ItemCount()

    WrapBox

    -

    DVX Extension -- DVX Widget: wrapbox

    +

    DVX Extension -- DVX Widget: wrapbox | Name Prefix: WrapBox

    A container that arranges children in a flowing layout, wrapping to the next row when the available width is exceeded. Similar to CSS flexbox with flex-wrap: wrap.

    Type-Specific Properties

      Property    Type   Description
    diff --git a/docs/dvx_system_reference.html b/docs/dvx_system_reference.html
    index 6f452cc..e669bd9 100644
    --- a/docs/dvx_system_reference.html
    +++ b/docs/dvx_system_reference.html
    @@ -38,6 +38,10 @@ img { max-width: 100%; }
     
  • Display Pipeline
  • Window System
  • Widget System
  • +
  • Modal Dialogs
  • +
  • Resource System
  • +
  • Preferences System
  • +
  • Per-App Memory Tracking
  • DXE Module System
  • Event Model
  • Font System
  • @@ -49,13 +53,21 @@ img { max-width: 100%; }
  • DVX GUI API Reference
  • @@ -88,6 +100,9 @@ img { max-width: 100%; }
  • isWordChar
  • wordEnd
  • wordStart
  • +
  • wordBoundaryLeft
  • +
  • wordBoundaryRight
  • +
  • textEditSaveUndo
  • widgetTextEditDragUpdateLine
  • widgetTextEditMouseClick
  • widgetTextEditOnKey
  • @@ -102,6 +117,7 @@ img { max-width: 100%; }
  • Packet Transport
  • Security (DH + XTEA)
  • Secure Link
  • +
  • End-to-End Example
  • BASIC Runtime Library @@ -193,6 +209,19 @@ img { max-width: 100%; }
  • Layout
  • Flexbox
  • WgtIfaceT
  • +
  • Dialogs
  • +
  • Message Box
  • +
  • File Dialog
  • +
  • Input Box
  • +
  • Resources
  • +
  • DXE Resources
  • +
  • DvxResHandleT
  • +
  • Preferences
  • +
  • INI files
  • +
  • PrefsHandleT
  • +
  • Memory Tracking
  • +
  • dvxMalloc
  • +
  • dvxFree
  • DXE
  • DXE3
  • Modules
  • @@ -222,7 +251,7 @@ img { max-width: 100%; }
  • Theming
  • Bevel
  • Platform Layer
  • -
  • dvxPlatform
  • +
  • dvxPlat.h
  • VESA
  • VBE
  • INT 33h
  • @@ -258,10 +287,17 @@ img { max-width: 100%; }
  • AccelEntryT
  • VideoModeInfoT
  • CursorT
  • -
  • dvxCursor.h
  • +
  • dvxCur.h
  • Cursor Shapes
  • CURSOR_ARROW
  • CURSOR_BUSY
  • +
  • dvxFont.h
  • +
  • Font
  • +
  • CP437
  • +
  • BitmapFontT
  • +
  • dvxPal.h
  • +
  • Palette
  • +
  • 8-bit mode
  • dvxVideo.h
  • videoInit
  • videoShutdown
  • @@ -329,6 +365,8 @@ img { max-width: 100%; }
  • dvxDestroyWindow
  • dvxRaiseWindow
  • dvxFitWindow
  • +
  • dvxFitWindowH
  • +
  • dvxFitWindowW
  • dvxResizeWindow
  • dvxMinimizeWindow
  • dvxMaximizeWindow
  • @@ -360,7 +398,11 @@ img { max-width: 100%; }
  • dvxAddAccel
  • dvxCascadeWindows
  • dvxTileWindows
  • +
  • dvxTileWindowsH
  • +
  • dvxTileWindowsV
  • dvxLoadImage
  • +
  • dvxLoadImageAlpha
  • +
  • dvxLoadImageFromMemory
  • dvxFreeImage
  • dvxImageInfo
  • dvxSaveImage
  • @@ -373,7 +415,7 @@ img { max-width: 100%; }
  • dvxResLoadData
  • dvxTextHash
  • dvxChangeVideoMode
  • -
  • dvxWidget.h
  • +
  • dvxWgt.h
  • WidgetT
  • WidgetClassT
  • wgtInitWindow
  • @@ -404,6 +446,62 @@ img { max-width: 100%; }
  • wgtPixels
  • wgtChars
  • wgtPercent
  • +
  • dvxDlg.h
  • +
  • dvxMessageBox
  • +
  • dvxFileDialog
  • +
  • dvxInputBox
  • +
  • dvxIntInputBox
  • +
  • dvxChoiceDialog
  • +
  • dvxPromptSave
  • +
  • dvxErrorBox
  • +
  • dvxInfoBox
  • +
  • dvxRes.h
  • +
  • Resources
  • +
  • DvxResHandleT
  • +
  • dvxResOpen
  • +
  • dvxResRead
  • +
  • dvxResFind
  • +
  • dvxResClose
  • +
  • dvxResAppend
  • +
  • dvxResRemove
  • +
  • dvxPrefs.h
  • +
  • Preferences
  • +
  • PrefsHandleT
  • +
  • prefsLoad
  • +
  • prefsSave
  • +
  • prefsGetString
  • +
  • prefsGetInt
  • +
  • prefsGetBool
  • +
  • prefsSetString
  • +
  • prefsSetInt
  • +
  • prefsSetBool
  • +
  • dvxMem.h
  • +
  • dvxMalloc
  • +
  • dvxFree
  • +
  • dvxCalloc
  • +
  • dvxRealloc
  • +
  • dvxStrdup
  • +
  • dvxMemGetAppUsage
  • +
  • dvxMemResetApp
  • +
  • dvxMemSnapshotLoad
  • +
  • dvxWgtP.h
  • +
  • widgetAlloc
  • +
  • widgetAddChild
  • +
  • widgetDestroyChildren
  • +
  • widgetHitTest
  • +
  • widgetFindNextFocusable
  • +
  • widgetFindPrevFocusable
  • +
  • widgetFindByAccel
  • +
  • widgetDrawScrollbarH
  • +
  • widgetDrawScrollbarV
  • +
  • dvxPlat.h
  • +
  • Platform Layer
  • +
  • PlatformKeyEventT
  • +
  • platformVideoInit
  • +
  • platformFlushRect
  • +
  • platformMousePoll
  • +
  • platformKeyboardRead
  • +
  • dvxLog
  • libtasks
  • Task Switching
  • Cooperative Multitasking
  • @@ -467,6 +565,12 @@ img { max-width: 100%; }
  • isWordChar
  • wordEnd
  • wordStart
  • +
  • wordBoundaryLeft
  • +
  • Word Boundary
  • +
  • wordBoundaryRight
  • +
  • Word Boundary
  • +
  • textEditSaveUndo
  • +
  • Undo
  • widgetTextEditDragUpdateLine
  • Drag Select
  • widgetTextEditMouseClick
  • @@ -525,6 +629,8 @@ img { max-width: 100%; }
  • secLinkHandshake
  • secLinkSend
  • secLinkPoll
  • +
  • Serial Example
  • +
  • BBS Client
  • BASIC Runtime
  • BasVmT
  • BasValueT
  • @@ -600,6 +706,7 @@ img { max-width: 100%; }
  • wgtCanvasDrawText
  • wgtCanvasSave
  • wgtCanvasLoad
  • +
  • wgtCanvasResize
  • wgtCanvasSetPenColor
  • wgtCanvasSetPenSize
  • wgtCanvasSetMouseCallback
  • @@ -610,32 +717,37 @@ img { max-width: 100%; }
  • ComboBox
  • wgtComboBox
  • wgtComboBoxSetItems
  • +
  • wgtComboBoxAddItem
  • +
  • wgtComboBoxRemoveItem
  • +
  • wgtComboBoxClear
  • wgtComboBoxGetSelected
  • wgtComboBoxSetSelected
  • DataCtrl
  • wgtDataCtrl
  • Database Binding
  • Master-Detail
  • -
  • wgtDataCtrlRefresh
  • -
  • wgtDataCtrlMoveFirst
  • -
  • wgtDataCtrlMoveNext
  • -
  • wgtDataCtrlGetField
  • -
  • wgtDataCtrlSetField
  • -
  • wgtDataCtrlUpdate
  • -
  • wgtDataCtrlAddNew
  • -
  • wgtDataCtrlDelete
  • +
  • dataCtrlRefresh
  • +
  • dataCtrlMoveFirst
  • +
  • dataCtrlMoveNext
  • +
  • dataCtrlGetField
  • +
  • dataCtrlSetField
  • +
  • dataCtrlUpdate
  • +
  • dataCtrlAddNew
  • +
  • dataCtrlDelete
  • DbGrid
  • wgtDbGrid
  • -
  • wgtDbGridSetDataWidget
  • Database Grid
  • -
  • wgtDbGridRefresh
  • -
  • wgtDbGridSetColumnVisible
  • -
  • wgtDbGridSetColumnHeader
  • -
  • wgtDbGridSetColumnWidth
  • -
  • wgtDbGridGetSelectedRow
  • +
  • dbGridRefresh
  • +
  • dbGridSetColumnVisible
  • +
  • dbGridSetColumnHeader
  • +
  • dbGridSetColumnWidth
  • +
  • dbGridGetSelectedRow
  • Dropdown
  • wgtDropdown
  • wgtDropdownSetItems
  • +
  • wgtDropdownAddItem
  • +
  • wgtDropdownRemoveItem
  • +
  • wgtDropdownClear
  • wgtDropdownGetSelected
  • wgtDropdownSetSelected
  • ImageButton
  • @@ -648,12 +760,16 @@ img { max-width: 100%; }
  • wgtImageFromFile
  • wgtImageSetData
  • wgtImageLoadFile
  • +
  • wgtImageSetTransparent
  • Label
  • wgtLabel
  • wgtLabelSetAlign
  • +
  • wgtLabelGetAlign
  • ListBox
  • wgtListBox
  • -
  • wgtListBoxSetItems
  • +
  • wgtListBoxAddItem
  • +
  • wgtListBoxRemoveItem
  • +
  • wgtListBoxClear
  • wgtListBoxGetSelected
  • wgtListBoxSetSelected
  • wgtListBoxSetMultiSelect
  • @@ -764,7 +880,53 @@ img { max-width: 100%; }

    Welcome!

    help.png

    -

    DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, core API, libraries, and widget toolkit.

    +

    DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, the core libdvx API, the widget toolkit, and the platform abstraction layer.

    +

    What Is libdvx?

    +

    libdvx is the heart of DVX -- a DXE3 library that provides:

    +
      +
    • A VESA VBE 2.0+ video backend (LFB only, no bank switching).
    • +
    • Optimised 2D drawing primitives (rectangle fill, bevels, text, bitmap blits, cursors).
    • +
    • A dirty-rectangle compositor that minimises LFB traffic.
    • +
    • A window manager with Motif-style chrome, drag/resize, menus, and scrollbars.
    • +
    • A retained-mode widget toolkit with automatic layout.
    • +
    +

    Modal dialog helpers, an INI preferences system, a DXE resource system, and per-app memory tracking.

    +

    Applications written in C link against libdvx (and the widget DXEs they need) to build native DVX programs. Applications written in DVX BASIC compile to bytecode that runs on top of libdvx via the form runtime.

    +

    Target Audience

    +

    This document is aimed at developers writing native C code for DVX:

    +
      +
    • System-level contributors maintaining libdvx itself.
    • +
    • Widget authors writing new .wgt DXE modules.
    • +
    • Application authors writing .app DXE modules.
    • +
    +

    Tool authors (e.g. the BASIC compiler/runtime) that sit on top of libdvx.

    +

    All examples and signatures assume the DJGPP cross-compiler toolchain.

    +

    What's Covered

    +
      +
    • Architecture -- Five-layer model, display pipeline, event model, build system.
    • +
    +

    API Reference -- Every public function, struct, enum, and constant documented with parameters and return values.

    +

    Use the table of contents on the left to navigate. The API reference is organised by header file; each function has a one-line summary, parameter table, and (where useful) a working example.

    +

    Conventions

    +
      +
    • Types end in a capital T (e.g. WindowT, DisplayT, BlitOpsT).
    • +
    • Enum types end in a capital E (e.g. ColorIdE, WallpaperModeE, ScrollbarOrientE).
    • +
    • Public functions use camelCase and prefixes that identify the subsystem: dvx* (application), wm* (window manager), wgt* (widget), prefs* (preferences), draw* / rect* (drawing), dirtyList* / flush* (compositor), video* / packColor / setClipRect (video), platform* (platform layer).
    • +
    • Constants use SCREAMING_SNAKE_CASE (e.g. HIT_CONTENT, MB_OK, CURSOR_ARROW).
    • +
    +

    Every header uses stdint.h types (int32_t, uint8_t) and stdbool.h types (bool).

    +

    Getting Started

    +

    If you're new to DVX, read these topics in order:

    +
      +
    • Architecture Overview -- The big picture, five-layer model, and the design philosophy.
    • +
    • Display Pipeline -- How the backbuffer, dirty list, and compositor work together.
    • +
    • Event Model -- How input becomes window/widget callbacks.
    • +
    • Widget System -- The retained-mode toolkit layered on the window manager.
    • +
    +

    DXE Module System -- How apps and widgets are loaded dynamically.

    +

    Then dip into the API Reference to find specific functions. The public entry point for any application is dvxInit / dvxRun in dvxApp.h.

    +

    License

    +

    DVX is distributed under the MIT License (see the copyright notice at the top of every source file). Third-party code (stb_image, stb_ds, stb_image_write in thirdparty/) is used under its own permissive license.

    DVX Architecture Overview

    @@ -775,7 +937,7 @@ img { max-width: 100%; }
  • VESA VBE 2.0+ LFB only -- no bank switching. If the hardware cannot provide a linear framebuffer, initialization fails.
  • 486 baseline -- all hot paths are written to be fast on a 486, with Pentium-specific paths where the gain is significant.
  • Single-tasking cooperative model -- applications yield the CPU via tsYield(); there is no preemptive scheduler.
  • -
  • 86Box is the trusted reference platform for testing. DOSBox-X is not used; any bugs observed are treated as DVX bugs.
  • +
  • A single emulator is the trusted reference platform for testing; any bugs observed there are treated as DVX bugs.
  • No external font or cursor files -- all bitmaps are compiled in as static const data.

    The runtime environment consists of a bootstrap loader (dvx.exe) that loads core DXE libraries, widget plugins, and the shell, which in turn loads and manages DXE application modules.

    @@ -784,6 +946,10 @@ img { max-width: 100%; }

    Display Pipeline

    Window System

    Widget System

    +

    Modal Dialogs

    +

    Resource System

    +

    Preferences System

    +

    Per-App Memory Tracking

    DXE Module System

    Event Model

    Font System

    @@ -815,7 +981,7 @@ img { max-width: 100%; } | +------------------------------------------+ | | | | +------------------------------------------+ | - | | Platform Layer (dvxPlatform.h) | | dvxPlatformDos.c + | | Platform Layer (dvxPlat.h) | | dvxPlatformDos.c | | OS-specific: video, input, asm spans | | | +------------------------------------------+ | | | @@ -921,13 +1087,13 @@ img { max-width: 100%; }

    Hit Test Regions

    wmHitTest() iterates the stack front-to-back and returns a hit-part identifier: HIT_CONTENT, HIT_TITLE, HIT_CLOSE, HIT_RESIZE, HIT_MENU, HIT_VSCROLL, HIT_HSCROLL, HIT_MINIMIZE, HIT_MAXIMIZE. Resize edge detection returns a bitmask of RESIZE_LEFT, RESIZE_RIGHT, RESIZE_TOP, RESIZE_BOTTOM (corners combine two edges).

    Menu System

    -

    Menus use fixed-size arrays with inline char buffers (no heap strings). Up to 8 menus per bar, items dynamically allocated. Supports cascading submenus via MenuItemT.subMenu pointer. Item types: normal, checkbox, radio. Separators are non-interactive items. The popup state (PopupStateT) tracks a stack of parent frames for cascading submenu nesting.

    +

    Menus use fixed-size inline char buffers for labels (MAX_MENU_LABEL = 32, no heap strings). Both menus per bar and items per menu are stored in dynamic arrays that grow on demand. Supports cascading submenus via MenuItemT.subMenu pointer. Item types: normal, checkbox, radio. Separators are non-interactive items. The popup state (PopupStateT) tracks a stack of parent frames for cascading submenu nesting.

    Minimized Windows

    Minimized windows display as 64x64 icons at the bottom of the screen with beveled borders, similar to a classic desktop icon bar. Icons show a scaled-down preview of the window's content buffer, refreshed one per frame in a round-robin fashion to amortize the scaling cost.

    Widget System

    -

    The widget system (dvxWidget.h) is a retained-mode toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container.

    +

    The widget system (dvxWgt.h) is a retained-mode toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container.

    WidgetT Base Structure

    Every widget shares the same WidgetT struct. The type field is a runtime-assigned integer ID. The wclass pointer references the widget's WidgetClassT vtable. Widget-specific private data is stored in w->data (opaque void*).

    Tree linkage: parent, firstChild, lastChild, nextSibling. No prevSibling -- this halves pointer overhead and removal is still O(n) for typical tree depths of 5-10.

    @@ -990,6 +1156,78 @@ img { max-width: 100%; }

    Widget Interface Descriptors (WgtIfaceT)

    Each widget can register an interface descriptor that describes its BASIC-facing properties, methods, and events. These descriptors are used by the form runtime and IDE for generic dispatch and property panel enumeration. Properties have typed getters/setters (WGT_IFACE_STRING, WGT_IFACE_INT, WGT_IFACE_BOOL, WGT_IFACE_ENUM).

    +
    +

    Modal Dialogs (dvxDlg.h)

    +

    Pre-built modal dialog boxes block the caller and run their own event loop via dvxUpdate() until the user dismisses them. Each dialog sets ctx->modalWindow to prevent input from reaching other windows while open.

    +

    Dialog Types

    +
      Function              Purpose
    +  --------              -------
    +  dvxMessageBox         Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.)
    +  dvxErrorBox           Shortcut for MB_OK + MB_ICONERROR
    +  dvxInfoBox            Shortcut for MB_OK + MB_ICONINFO
    +  dvxFileDialog         File open/save dialog with directory navigation and filter dropdown
    +  dvxInputBox           Single-line text input with OK/Cancel
    +  dvxIntInputBox        Integer input with spinner, clamp range, and step
    +  dvxChoiceDialog       List selection with OK/Cancel
    +  dvxPromptSave         Canonical "Save changes?" Yes/No/Cancel dialog
    +

    Flag Encoding

    +

    dvxMessageBox flags split into two nibbles: button configuration (low) and icon type (high). This matches the Win16 MessageBox() convention so OR'd flags read naturally:

    +
    dvxMessageBox(ctx, "Confirm", "Delete file?", MB_YESNO | MB_ICONQUESTION);
    +

    Return values are ID_OK / ID_CANCEL / ID_YES / ID_NO / ID_RETRY.

    +
    +
    +

    Resource System (dvxRes.h)

    +

    Resources are appended to DXE3 files (.app, .wgt, .lib) after the normal DXE content. The DXE loader never reads past the DXE header, so the appended data is invisible to dlopen. Every DXE can carry its own icons, text strings, and binary data without separate resource files.

    +

    On-Disk Layout

    +
    [DXE3 content]           -- untouched, loaded by dlopen
    +[resource data entries]  -- sequential, variable length
    +[resource directory]     -- fixed-size entries (48 bytes each)
    +[footer]                 -- magic + directory offset + count (16 bytes)
    +

    Readers start from the end: seek to EOF - sizeof(footer), verify the magic (DVX_RES_MAGIC = "DVXR"), then seek to the directory. Entries are sorted at open time so lookups are O(log n) via bsearch.

    +

    Types

    +
      Type ID           Value   Payload
    +  -------           -----   -------
    +  DVX_RES_ICON      1       Image data (BMP or PNG, typically 16x16 or 32x32 icons)
    +  DVX_RES_TEXT      2       Null-terminated string (localisable UI text, author, copyright)
    +  DVX_RES_BINARY    3       Arbitrary data (app-specific)
    +

    Runtime API

    +
      Function           Description
    +  --------           -----------
    +  dvxResOpen         Open a handle by reading the footer and directory
    +  dvxResRead         Look up a resource by name and read its data into a malloc'd buffer
    +  dvxResFind         Look up a resource's directory entry (no data copy)
    +  dvxResClose        Release the handle
    +  dvxResAppend       Append a new resource to a DXE file
    +  dvxResRemove       Remove a resource by name
    +

    The dvxApp.h wrappers (dvxResLoadIcon, dvxResLoadText, dvxResLoadData) combine open + read + close for the common case.

    +
    +
    +

    Preferences System (dvxPrefs.h)

    +

    Handle-based API over classic INI files. Multiple files can be open at once. Each prefsLoad / prefsCreate returns a PrefsHandleT opaque pointer passed to subsequent calls and freed with prefsClose. The system INI lives at DVX_INI_PATH = "CONFIG\\DVX.INI".

    +

    Usage Pattern

    +
    PrefsHandleT *h = prefsLoad(DVX_INI_PATH);
    +int32_t  w      = prefsGetInt(h, "video", "width", 640);
    +bool     sound  = prefsGetBool(h, "audio", "enable", true);
    +const char *bg  = prefsGetString(h, "desktop", "wallpaper", "");
    +
    +prefsSetInt(h, "video", "width", 800);
    +prefsSave(h);
    +prefsClose(h);
    +

    Boolean getters recognise "true"/"yes"/"1" and "false"/"no"/"0" (case-insensitive). If the file does not exist, prefsLoad still returns a valid empty handle with the path captured so prefsSave can write a new file.

    +
    +
    +

    Per-App Memory Tracking (dvxMem.h)

    +

    All allocations route through dvxMalloc/dvxFree wrappers that prepend a 16-byte header recording the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected at app termination.

    +

    Transparent Interception

    +

    DXE code does NOT need to include dvxMem.h. The DXE export table maps malloc / free / calloc / realloc / strdup to the dvxXxx wrappers, so standard C code is tracked automatically.

    +

    Explicit use (e.g. in the Task Manager) can include dvxMem.h to call:

    +
      Function               Purpose
    +  --------               -------
    +  dvxMemSnapshotLoad     Baseline a newly-loaded app's memory state
    +  dvxMemGetAppUsage      Query current bytes allocated for an app
    +  dvxMemResetApp         Free every tracked allocation charged to an app
    +

    The dvxMemAppIdPtr pointer is set by the shell to &ctx->currentAppId so the allocator always knows which app to charge.

    +

    DXE Module System

    DVX uses DJGPP's DXE3 (Dynamic eXtension) format for all loadable modules. DXE3 supports RTLD_GLOBAL symbol sharing -- symbols exported by one module are visible to all subsequently loaded modules. This is critical: widget DXEs call core API functions (e.g. rectFill, wgtInvalidate) that are exported by the core library DXE.

    @@ -1127,7 +1365,7 @@ img { max-width: 100%; }

    Platform Layer

    -

    All OS-specific and CPU-specific code is isolated behind dvxPlatform.h. To port DVX, implement a new dvxPlatformXxx.c against this header.

    +

    All OS-specific and CPU-specific code is isolated behind platform/dvxPlat.h. To port DVX, implement a new dvxPlatformXxx.c against this header.

    Implementations

      File                Target        Details
       ----                ------        -------
    @@ -1153,7 +1391,7 @@ img { max-width: 100%; }
     

    Cross-Compilation

    DVX is cross-compiled from Linux using a DJGPP cross-compiler (i586-pc-msdosdjgpp-gcc). The top-level Makefile orchestrates building all subsystems in dependency order.

      make               -- build everything
    -  ./mkcd.sh          -- build + create ISO for 86Box
    + ./mkcd.sh -- build + create ISO for the target emulator

    Build Targets

      all: core tasks loader texthelp listhelp tools widgets shell taskmgr serial sql apps
      Target     Output                    Description
    @@ -1188,7 +1426,7 @@ img { max-width: 100%; }
     
  • Counts widget modules.
  • Creates an ISO 9660 image from bin/ using mkisofs: -iso-level 1 (strict 8.3 filenames for DOS), -J (Joliet extensions for long names), -V DVX (volume label).
  • -

    Places the ISO at ~/.var/app/net._86box._86Box/data/86Box/dvx.iso for 86Box to mount as CD-ROM.

    +

    Places the ISO at the target emulator's CD-ROM mount path.

    Compiler Flags

      -O2                  Optimization level 2
       -march=i486          486 instruction set baseline
    @@ -1197,7 +1435,7 @@ img { max-width: 100%; }
     

    Directory Layout

      dvxgui/
       +-- core/              Core library sources (dvxVideo, dvxDraw, dvxComp, dvxWm, dvxApp, widget infra)
    -  |   +-- platform/      Platform abstraction (dvxPlatform.h, dvxPlatformDos.c)
    +  |   +-- platform/      Platform abstraction (dvxPlat.h, dvxPlatformDos.c, dvxPlatformUtil.c)
       |   +-- thirdparty/    stb_image, stb_ds, stb_image_write
       +-- loader/            Bootstrap loader (dvx.exe)
       +-- tasks/             Cooperative task switcher (libtasks.lib)
    @@ -1224,7 +1462,7 @@ img { max-width: 100%; }
       +-- security/          DH key exchange, XTEA cipher, DRBG RNG
       +-- seclink/           Encrypted channel wrapper
       +-- serial/            Combined serial stack DXE
    -  +-- proxy/             Linux proxy (86Box <-> secLink <-> telnet)
    +  +-- proxy/             Linux proxy (emulator <-> secLink <-> telnet)
       +-- sql/               SQLite integration
       +-- bin/               Build output (dvx.exe, libs/, widgets/, apps/, config/)
       +-- obj/               Intermediate object files
    @@ -1232,27 +1470,43 @@ img { max-width: 100%; }
     

    DVX GUI API Reference

    -

    DOS Visual eXecutive -- Complete public API documentation generated from source headers.

    -

    The DVX GUI is built as a five-layer architecture. Each layer is defined in its own header file. This reference covers every public function, type, and constant.

    -

    Layers

    +

    DOS Visual eXecutive -- Complete public API documentation covering every public function, type, and constant in the libdvx headers.

    +

    The DVX GUI is built as a five-layer architecture. Each layer is defined in its own header file. Additional headers cover dialogs, resources, preferences, memory tracking, the platform abstraction, and the widget system.

    +

    Headers

      -
    • dvxTypes.h -- Shared type definitions
    • -
    • dvxCursor.h -- Cursor definitions
    • -
    • dvxVideo.h -- Layer 1: VESA VBE Video Backend
    • -
    • dvxDraw.h -- Layer 2: Drawing Primitives
    • -
    • dvxComp.h -- Layer 3: Dirty Rectangle Compositor
    • -
    • dvxWm.h -- Layer 4: Window Manager
    • +
    • dvxTypes.h -- Shared type definitions and constants
    • +
    • dvxCur.h -- Embedded mouse cursor bitmaps
    • +
    • dvxFont.h -- Embedded 8x16 CP437 bitmap font
    • +
    • dvxPal.h -- 8-bit mode 256-color palette
    • +
    • dvxVideo.h -- Layer 1: VESA VBE video backend
    • +
    • dvxDraw.h -- Layer 2: 2D drawing primitives
    • +
    • dvxComp.h -- Layer 3: Dirty rectangle compositor
    • +
    • dvxWm.h -- Layer 4: Window manager
    • dvxApp.h -- Layer 5: Application API
    • +
    • dvxDlg.h -- Modal dialog boxes
    • +
    • dvxRes.h -- DXE-embedded resource system
    • +
    • dvxPrefs.h -- INI preferences system
    • +
    • dvxMem.h -- Per-app memory tracking
    • +
    • dvxWgt.h -- Widget system public API
    • +
    • dvxWgtP.h -- Widget plugin API
    -

    dvxWidget.h -- Widget System

    +

    platform/dvxPlat.h -- Platform abstraction layer

    dvxTypes.h -- Shared Type Definitions

    -

    dvxCursor.h -- Cursor Definitions

    -

    dvxVideo.h -- Layer 1: VESA VBE Video Backend

    +

    dvxCur.h -- Cursor Definitions

    +

    dvxFont.h -- Bitmap Font Data

    +

    dvxPal.h -- 8-bit Palette

    +

    dvxVideo.h -- Layer 1: Video Backend

    dvxDraw.h -- Layer 2: Drawing Primitives

    -

    dvxComp.h -- Layer 3: Dirty Rectangle Compositor

    +

    dvxComp.h -- Layer 3: Compositor

    dvxWm.h -- Layer 4: Window Manager

    dvxApp.h -- Layer 5: Application API

    -

    dvxWidget.h -- Widget System

    +

    dvxDlg.h -- Modal Dialogs

    +

    dvxRes.h -- Resource System

    +

    dvxPrefs.h -- Preferences

    +

    dvxMem.h -- Memory Tracking

    +

    dvxWgt.h -- Widget System

    +

    dvxWgtP.h -- Widget Plugin API

    +

    platform/dvxPlat.h -- Platform Layer

    dvxTypes.h -- Shared Type Definitions

    @@ -1305,7 +1559,7 @@ img { max-width: 100%; }
      Field                          Description
       -----                          -----------
       int32_t charWidth              Fixed width per glyph (always 8)
    -  int32_t charHeight             Glyph height (14 or 16)
    +  int32_t charHeight             Glyph height (16 in the bundled font)
       int32_t firstChar              ASCII code of first glyph (typically 0)
       int32_t numChars               Number of glyphs (typically 256)
       const uint8_t *glyphData       Packed 1bpp data, charHeight bytes per glyph
    @@ -1346,7 +1600,7 @@ img { max-width: 100%; } char title[MAX_TITLE_LEN] Window title text (max 128 chars) bool visible, focused, minimized, maximized, resizable, modal Window state flags bool iconNeedsRefresh true when contentBuf changed (for minimized icon refresh) - bool needsPaint true until first onPaint call + uint8_t paintNeeded PAINT_NONE / PAINT_PARTIAL / PAINT_FULL int32_t maxW, maxH Maximum dimensions int32_t preMaxX, preMaxY, preMaxW, preMaxH Saved geometry before maximize uint8_t *contentBuf Per-window content backbuffer @@ -1362,38 +1616,49 @@ img { max-width: 100%; }

    Callbacks:

      Callback                                                     Description
       --------                                                     -----------
    -  onPaint(WindowT *win, RectT *dirtyArea)                      Content repaint requested
    -  onKey(WindowT *win, int32_t key, int32_t mod)                Key press
    -  onKeyUp(WindowT *win, int32_t scancode, int32_t mod)         Key release
    -  onMouse(WindowT *win, int32_t x, int32_t y, int32_t btn)    Mouse event (content-relative)
    -  onResize(WindowT *win, int32_t newW, int32_t newH)           Window resized
    -  onClose(WindowT *win)                                        Close requested
    -  onMenu(WindowT *win, int32_t menuId)                         Menu item or accelerator activated
    -  onScroll(WindowT *win, ScrollbarOrientE orient, int32_t val) Scrollbar value changed
    -  onCursorQuery(WindowT *win, int32_t x, int32_t y)            Return CURSOR_* for hit position
    -  onFocus(WindowT *win)                                        Window gained focus
    -  onBlur(WindowT *win)                                         Window lost focus
    + onPaint(WindowT *win, RectT *dirtyArea) Content repaint requested + onKey(WindowT *win, int32_t key, int32_t mod) Key press + onKeyUp(WindowT *win, int32_t scancode, int32_t mod) Key release + onMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) Mouse event (content-relative) + onResize(WindowT *win, int32_t newW, int32_t newH) Window resized + onClose(WindowT *win) Close requested + onMenu(WindowT *win, int32_t menuId) Menu item or accelerator activated + onScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) Scrollbar value changed + int32_t onCursorQuery(WindowT *win, int32_t x, int32_t y) Return CURSOR_* for hit position + onFocus(WindowT *win) Window gained focus + onBlur(WindowT *win) Window lost focus

    WindowStackT

    Z-ordered window stack (front-to-back: index count-1 is topmost). Owns system-wide drag/resize/scroll interaction state.

    -
      Field                                        Description
    -  -----                                        -----------
    -  WindowT **windows                            Dynamic array of window pointers
    -  int32_t count, cap                           Current count and allocated capacity
    -  int32_t focusedIdx                           Stack index of focused window
    -  int32_t dragWindow, dragOffX, dragOffY       Active drag state
    -  int32_t resizeWindow, resizeEdge             Active resize state
    +
      Field                                              Description
    +  -----                                              -----------
    +  WindowT **windows                                  Dynamic array of window pointers
    +  int32_t count, cap                                 Current count and allocated capacity
    +  int32_t focusedIdx                                 Stack index of focused window
    +  int32_t dragWindow, dragOffX, dragOffY             Active drag state
    +  int32_t dragStartX, dragStartY                     Mouse position at drag begin (for deadzone)
    +  bool dragActive                                    True once mouse moved past deadzone
    +  int32_t resizeWindow, resizeEdge                   Active resize state
       int32_t scrollWindow, scrollOrient, scrollDragOff  Active scroll drag state

    MenuT / MenuItemT / MenuBarT

    Menu system types. Fixed-size label buffers (MAX_MENU_LABEL = 32). Cascading submenus supported via MenuItemT.subMenu pointer.

    -
      Field                        Description
    -  -----                        -----------
    -  MenuItemT.label              Item text (supports & accelerator markers)
    -  MenuItemT.id                 Application-defined command ID
    -  MenuItemT.type               MenuItemNormalE, MenuItemCheckE, or MenuItemRadioE
    -  MenuItemT.separator          true = horizontal divider line
    -  MenuItemT.enabled, checked   Item state
    -  MenuItemT.subMenu            Child menu for cascading (NULL if leaf)
    -  MenuBarT.activeIdx           Open popup index (-1 = none)
    +
      Field                            Description
    +  -----                            -----------
    +  MenuItemT.label                  Item text (supports & accelerator markers)
    +  MenuItemT.id                     Application-defined command ID
    +  MenuItemT.type                   MenuItemNormalE, MenuItemCheckE, or MenuItemRadioE
    +  MenuItemT.separator              true = horizontal divider line
    +  MenuItemT.enabled, checked       Item state
    +  MenuItemT.accelKey               Lowercase accelerator character (0 = none)
    +  MenuItemT.subMenu                Child menu for cascading (NULL if leaf)
    +  MenuT.label                      Menu bar label (e.g. "File")
    +  MenuT.items                      Dynamic array of MenuItemT
    +  MenuT.itemCount, itemCap         Current count and allocated capacity
    +  MenuT.barX, barW                 Computed position/width on the menu bar
    +  MenuT.accelKey                   Lowercase accelerator character (0 = none)
    +  MenuBarT.menus                   Dynamic array of MenuT
    +  MenuBarT.menuCount, menuCap      Current count and allocated capacity
    +  MenuBarT.activeIdx               Open popup index (-1 = none)
    +  MenuBarT.positionsDirty          True when barX/barW need recomputation

    ScrollbarT

    Window-level scrollbar state. Managed by the WM layer, drawn after content.

      Field                        Description
    @@ -1409,7 +1674,11 @@ img { max-width: 100%; }
       -----                      -----------
       AccelEntryT.key            ASCII character or KEY_Fxx constant
       AccelEntryT.modifiers      Bitmask of ACCEL_CTRL, ACCEL_SHIFT, ACCEL_ALT
    -  AccelEntryT.cmdId          Command ID passed to onMenu
    + AccelEntryT.cmdId Command ID passed to onMenu + AccelEntryT.normKey Pre-normalized key (uppercased) for fast matching + AccelEntryT.normMods Pre-masked modifiers (CTRL|ALT only) for fast matching + AccelTableT.entries Dynamic array of AccelEntryT + AccelTableT.count, cap Current count and allocated capacity

    VideoModeInfoT

    Describes an available video mode (enumerated at init).

      Field            Description
    @@ -1493,8 +1762,8 @@ img { max-width: 100%; }
       DVX_MAX(a, b)   Return the larger of two values
    -

    dvxCursor.h -- Cursor Definitions

    -

    Embedded 16x16 mouse cursor bitmaps compiled as static const data. No external cursor files. Uses the standard AND/XOR mask encoding from the IBM VGA hardware cursor spec.

    +

    dvxCur.h -- Cursor Definitions

    +

    Embedded 16x16 mouse cursor bitmaps compiled as static const data. No external cursor files. Uses the standard AND/XOR mask encoding from the IBM VGA hardware cursor spec. The AND mask selects transparency (1 = transparent, 0 = opaque) and the XOR data selects black vs. white for opaque pixels.

    Cursor Shape IDs

      Define                      Value   Description
       ------                      -----   -----------
    @@ -1508,7 +1777,70 @@ img { max-width: 100%; }
       CURSOR_COUNT                7       Total number of cursor shapes

    Data

    dvxCursors[CURSOR_COUNT]

    -

    Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each entry includes the AND mask, XOR data, dimensions, and hot spot coordinates.

    +

    Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each entry includes the AND mask, XOR data (both as 16-element uint16_t arrays), dimensions (16x16), and hot spot coordinates.

    +

    dvxCursor

    +

    Legacy alias for backward compatibility with code that predates multi-cursor support. Equivalent to dvxCursors[CURSOR_ARROW].

    +
    +
    +

    dvxFont.h -- Embedded Bitmap Font Data

    +

    Raw glyph bitmaps for the standard 8x16 VGA ROM font covering the full IBM Code Page 437 character set (256 glyphs). Compiled in as a static const array, avoiding a real-mode INT 10h call at startup.

    +

    Glyph format: 1 bit per pixel, 8 pixels wide, MSB = leftmost pixel. Each glyph occupies 16 consecutive bytes (one byte per scanline). The 8-pixel width means per-scanline rendering never needs bit shifting across byte boundaries.

    +

    CP437 covers:

    +
      +
    • 0-31 -- Smiley, card suits, arrows, notes (classic extended ASCII glyphs)
    • +
    • 32-126 -- Printable ASCII
    • +
    • 127 -- House glyph
    • +
    • 128-175 -- Accented Latin letters, currency, fractions
    • +
    • 176-223 -- Box-drawing characters (essential for chrome gadgets)
    • +
    • 224-254 -- Greek letters, math symbols, super/subscripts
    • +
    +

    255 -- Non-breaking space

    +

    Data

    +

    font8x16

    +

    Static const uint8_t array of 256 * 16 = 4096 bytes containing the packed 1bpp glyph bitmaps, in ASCII code order.

    +

    dvxFont8x16

    +

    Static const BitmapFontT struct describing the 8x16 font, ready to pass to drawText/drawTextN/drawChar. Use this as the default font for all text rendering.

    +

    Constants

    +
      Define              Value   Description
    +  ------              -----   -----------
    +  FONT_CHAR_WIDTH     8       Fixed glyph width in pixels (defined in dvxTypes.h)
    +
    +
    +

    dvxPal.h -- 256-Color Palette for 8-bit Mode

    +

    Defines the 256-color VGA/VESA palette used in 8-bit video modes. Layout:

    +
      Range     Purpose
    +  -----     -------
    +  0-215     6x6x6 color cube -- uniformly distributed RGB (6 levels per channel: 0, 51, 102, 153, 204, 255). Index = r*36 + g*6 + b.
    +  216-231   16-step grey ramp for smooth gradients
    +  232-239   Dedicated UI chrome colors (highlight, shadow, title bar, desktop)
    +  240-255   Reserved (black)
    +

    The cube layout enables O(1) color lookup: snapping an RGB triplet to the nearest cube vertex is integer division by 51. The grey ramp and chrome slots hold colors that need exact values not available in the cube.

    +

    Chrome Color Indices

    +
      Define                     Value   Description
    +  ------                     -----   -----------
    +  PAL_CHROME_HIGHLIGHT       232     White (bevel highlight)
    +  PAL_CHROME_SHADOW          233     Dark grey (bevel shadow)
    +  PAL_CHROME_ACTIVE_BG       234     Navy (focused title bar)
    +  PAL_CHROME_INACTIVE_BG     235     Grey (unfocused title bar)
    +  PAL_CHROME_DESKTOP         236     Steel blue (desktop)
    +  PAL_CHROME_SELECTION       237     Navy (selection highlight)
    +  PAL_CHROME_TEXT            238     Black (text)
    +  PAL_CHROME_WHITE           239     Bright white
    +

    Inline Functions

    +

    dvxGeneratePalette

    +
    static inline void dvxGeneratePalette(uint8_t *pal);
    +

    Populate a 768-byte (256 * 3) RGB buffer with the default DVX 8-bit palette. Called once at init in 8-bit mode to program the DAC.

    +
      Parameter   Description
    +  ---------   -----------
    +  pal         Output RGB buffer (768 bytes)
    +

    dvxNearestPalEntry

    +
    static inline uint8_t dvxNearestPalEntry(const uint8_t *pal, uint8_t r, uint8_t g, uint8_t b);
    +

    Find the nearest palette entry for an RGB color using minimum Euclidean distance. Two-phase: snap to color cube vertex (O(1)), then scan grey ramp and chrome entries (216-239) for a closer match. Called by packColor() in 8-bit mode.

    +
      Parameter   Description
    +  ---------   -----------
    +  pal         Active 768-byte RGB palette
    +  r, g, b     Color components (0-255)
    +

    Returns: Palette index (0-239) of the nearest match.

    dvxVideo.h -- Layer 1: VESA VBE Video Backend

    @@ -1536,6 +1868,14 @@ img { max-width: 100%; } d Display context (provides pixel format) r, g, b Color components (0-255)

    Returns: Native pixel value suitable for direct framebuffer write.

    +

    unpackColor

    +
    void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b);
    +

    Reverse of packColor -- decode a native pixel value to 8-bit RGB components. For direct-color modes, reverses the shift/mask arithmetic. For 8-bit mode, looks up the palette entry at the given index.

    +
      Parameter   Description
    +  ---------   -----------
    +  d           Display context
    +  color       Packed native pixel value
    +  r, g, b     Output: color components (0-255)

    setClipRect

    void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h);

    Set the clip rectangle on the display. All subsequent draw operations clip to this rectangle. The caller must save and restore the clip rect around scoped operations.

    @@ -1592,6 +1932,18 @@ img { max-width: 100%; } srcBuf, srcPitch Source buffer and pitch srcX, srcY Source origin w, h Rectangle dimensions +

    rectCopyTransparent

    +
    void rectCopyTransparent(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h, uint32_t keyColor);
    +

    Copy with color-key transparency. Pixels matching keyColor are skipped, letting the destination show through. Used to draw image buttons and icons with transparent backgrounds.

    +
      Parameter        Description
    +  ---------        -----------
    +  d                Display context
    +  ops              Blit operations vtable
    +  dstX, dstY       Destination position
    +  srcBuf, srcPitch Source buffer and pitch
    +  srcX, srcY       Source origin
    +  w, h             Rectangle dimensions
    +  keyColor         Packed color treated as transparent

    drawBevel

    void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style);

    Draw a beveled frame. Top/left edges in highlight color, bottom/right in shadow. Interior filled with face color if non-zero.

    @@ -1904,6 +2256,20 @@ img { max-width: 100%; }
      Parameter   Description
       ---------   -----------
       menu        Menu to append separator to
    +

    wmRemoveMenuItem

    +
    bool wmRemoveMenuItem(MenuT *menu, int32_t id);
    +

    Remove a menu item by command ID. Searches the menu's items array and shifts remaining items down to close the gap.

    +
      Parameter   Description
    +  ---------   -----------
    +  menu        Menu to remove item from
    +  id          Command ID to remove
    +

    Returns: true if a matching item was found and removed.

    +

    wmClearMenuItems

    +
    void wmClearMenuItems(MenuT *menu);
    +

    Remove all items from a menu while preserving the menu itself on the menu bar. Use when rebuilding a menu's contents dynamically (e.g. a recent-files submenu).

    +
      Parameter   Description
    +  ---------   -----------
    +  menu        Menu to empty

    wmMenuItemIsChecked

    bool wmMenuItemIsChecked(MenuBarT *bar, int32_t id);

    Query the checked state of a menu item by command ID. Searches all menus in the bar.

    @@ -2006,6 +2372,19 @@ img { max-width: 100%; } colors Color scheme stack Window stack clipTo Dirty rectangle +

    wmDrawVScrollbarAt

    +
    void wmDrawVScrollbarAt(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t h, int32_t scrollPos, int32_t visibleItems, int32_t totalItems);
    +

    Draw a standalone vertical scrollbar at the given screen coordinates, without a ScrollbarT struct. Used by popup lists (dropdown, combobox) that need a scrollbar inline within a popup overlay. Thumb size and position are computed from scrollPos / visibleItems / totalItems.

    +
      Parameter       Description
    +  ---------       -----------
    +  d               Display context
    +  ops             Blit operations vtable
    +  colors          Color scheme
    +  x, y            Screen position (top of scrollbar)
    +  h               Track height in pixels
    +  scrollPos       Current top visible item index
    +  visibleItems    Visible item count (for thumb size)
    +  totalItems      Total item count

    Hit Testing

    wmHitTest

    int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hitPart);
    @@ -2476,14 +2855,16 @@ img { max-width: 100%; } mode WallpaperStretchE, WallpaperTileE, or WallpaperCenterE

    Mouse Configuration

    dvxSetMouseConfig

    -
    void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold);
    -

    Configure mouse behavior.

    +
    void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio, int32_t wheelStep);
    +

    Configure mouse behavior. Updates the in-memory AppContextT fields and programs any INT 33h settings that need driver-side adjustment.

      Parameter        Description
       ---------        -----------
       ctx              Application context
       wheelDir         1 = normal, -1 = reversed
       dblClickMs       Double-click speed in milliseconds (e.g. 500)
    -  accelThreshold   Double-speed threshold in mickeys/sec (0 = don't change)
    + accelThreshold Double-speed threshold in mickeys/sec (0 = don't change) + mickeyRatio Mickeys per 8 pixels (0 = don't change, 8 = default speed) + wheelStep Lines to scroll per wheel notch (1-10, default 3)

    Accelerators

    dvxCreateAccelTable

    AccelTableT *dvxCreateAccelTable(void);
    @@ -2542,7 +2923,7 @@ img { max-width: 100%; }

    Returns: Pixel buffer, or NULL on failure.

    dvxLoadImageFromMemory

    uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch);
    -

    Load an image from a memory buffer. Same output format as dvxLoadImage(). Caller must free with dvxFreeImage().

    +

    Load an image from a memory buffer (typically resource data). Same output format as dvxLoadImage(). Caller must free with dvxFreeImage().

      Parameter     Description
       ---------     -----------
       ctx           Application context
    @@ -2551,6 +2932,19 @@ img { max-width: 100%; }
       outW, outH    Output: image dimensions
       outPitch      Output: row pitch in bytes

    Returns: Pixel buffer, or NULL on failure.

    +

    dvxLoadImageAlpha

    +
    uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor);
    +

    Load an image from memory with alpha transparency. Pixels with alpha < 128 are replaced with a packed magenta key color. If any transparent pixels are found, *outHasAlpha is set to true and *outKeyColor receives the packed key color for use with rectCopyTransparent. Caller must free with dvxFreeImage.

    +
      Parameter     Description
    +  ---------     -----------
    +  ctx           Application context
    +  data          Image data buffer
    +  dataLen       Buffer size in bytes
    +  outW, outH    Output: image dimensions
    +  outPitch      Output: row pitch
    +  outHasAlpha   Output: true if any transparent pixels found
    +  outKeyColor   Output: packed key color (valid when outHasAlpha is true)
    +

    Returns: Pixel buffer, or NULL on failure.

    dvxFreeImage

    void dvxFreeImage(uint8_t *data);

    Free a pixel buffer returned by dvxLoadImage() or dvxLoadImageFromMemory().

    @@ -2650,7 +3044,7 @@ img { max-width: 100%; }

    Returns: 32-bit hash value.

    -

    dvxWidget.h -- Widget System

    +

    dvxWgt.h -- Widget System

    Retained-mode widget toolkit layered on the DVX window manager. Widgets form a tree (parent-child) rooted at a per-window VBox container. Layout is automatic: measure minimum sizes bottom-up, then allocate space top-down with flexbox-like weighted distribution. Widget types are registered dynamically at runtime via DXE plugins.

    WidgetT Structure

    Core widget structure. Generic across all widget types; type-specific data lives in the void *data pointer managed by each widget's DXE.

    @@ -2670,7 +3064,11 @@ img { max-width: 100%; } uint32_t fgColor, bgColor Custom colors (0 = use scheme defaults) bool visible, enabled, readOnly State flags bool swallowTab Tab key goes to widget, not focus navigation + bool paintDirty Needs repaint (set by wgtInvalidatePaint) + bool childDirty A descendant needs repaint (for WCLASS_PAINTS_CHILDREN) + bool pressed WCLASS_PRESS_RELEASE: set while button is pressed char accelKey Accelerator character (0 = none) + int32_t contentOffX, contentOffY Content offset for mouse event coordinates void *userData, *data Application data and widget-private data const char *tooltip Tooltip text (NULL = none) MenuT *contextMenu Right-click menu (NULL = none) @@ -2685,9 +3083,9 @@ img { max-width: 100%; } onKeyPress(WidgetT *w, int32_t keyAscii) ASCII key press onKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) Key down onKeyUp(WidgetT *w, int32_t keyCode, int32_t shift) Key up - onMouseDown(WidgetT *w, int32_t btn, int32_t x, int32_t y) Mouse button pressed - onMouseUp(WidgetT *w, int32_t btn, int32_t x, int32_t y) Mouse button released - onMouseMove(WidgetT *w, int32_t btn, int32_t x, int32_t y) Mouse moved + onMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y) Mouse button pressed + onMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y) Mouse button released + onMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y) Mouse moved onScroll(WidgetT *w, int32_t delta) Mouse wheel onValidate(WidgetT *w) Return false to cancel a write

    Size Specification Macros

    @@ -2840,15 +3238,16 @@ img { max-width: 100%; } availW/H Available space font Bitmap font (for character-based sizing)

    wgtPaint

    -
    void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
    +
    void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint);

    Paint the entire widget tree by depth-first traversal. Each widget's clip rect is set to its bounds. Overlays (popups, tooltips) are painted in a second pass on top.

    -
      Parameter   Description
    -  ---------   -----------
    -  root        Root widget
    -  d           Display context
    -  ops         Blit operations vtable
    -  font        Bitmap font
    -  colors      Color scheme
    +
      Parameter     Description
    +  ---------     -----------
    +  root          Root widget
    +  d             Display context
    +  ops           Blit operations vtable
    +  font          Bitmap font
    +  colors        Color scheme
    +  fullRepaint   true = repaint every widget; false = only paintDirty widgets

    Debug

    wgtSetDebugLayout

    void wgtSetDebugLayout(AppContextT *ctx, bool enabled);
    @@ -2960,13 +3359,833 @@ img { max-width: 100%; } wclsPoll(w, win) WGT_METHOD_POLL Periodic polling wclsQuickRepaint(w, outY, outH) WGT_METHOD_QUICK_REPAINT Fast partial repaint wclsScrollChildIntoView(parent, child) WGT_METHOD_SCROLL_CHILD_INTO_VIEW Scroll child visible +

    Method IDs

    +

    Fixed constants used to index into WidgetClassT.handlers[]. Method IDs are stable ABI -- new methods are appended at the next sequential ID, never reordered or reused.

    +
      ID   Symbol                              Signature
    +  --   ------                              ---------
    +  0    WGT_METHOD_PAINT                    void (w, d, ops, font, colors)
    +  1    WGT_METHOD_PAINT_OVERLAY            void (w, d, ops, font, colors)
    +  2    WGT_METHOD_CALC_MIN_SIZE            void (w, font)
    +  3    WGT_METHOD_LAYOUT                   void (w, font)
    +  4    WGT_METHOD_GET_LAYOUT_METRICS       void (w, font, pad, gap, extraTop, borderW)
    +  5    WGT_METHOD_ON_MOUSE                 void (w, root, vx, vy)
    +  6    WGT_METHOD_ON_KEY                   void (w, key, mod)
    +  7    WGT_METHOD_ON_ACCEL_ACTIVATE        void (w, root)
    +  8    WGT_METHOD_DESTROY                  void (w)
    +  9    WGT_METHOD_ON_CHILD_CHANGED         void (parent, child)
    +  10   WGT_METHOD_GET_TEXT                 const char *(w)
    +  11   WGT_METHOD_SET_TEXT                 void (w, text)
    +  12   WGT_METHOD_CLEAR_SELECTION          bool (w)
    +  13   WGT_METHOD_CLOSE_POPUP              void (w)
    +  14   WGT_METHOD_GET_POPUP_RECT           void (w, font, contentH, popX, popY, popW, popH)
    +  15   WGT_METHOD_ON_DRAG_UPDATE           void (w, root, x, y)
    +  16   WGT_METHOD_ON_DRAG_END              void (w, root, x, y)
    +  17   WGT_METHOD_GET_CURSOR_SHAPE         int32_t (w, vx, vy)
    +  18   WGT_METHOD_POLL                     void (w, win)
    +  19   WGT_METHOD_QUICK_REPAINT            int32_t (w, outY, outH)
    +  20   WGT_METHOD_SCROLL_CHILD_INTO_VIEW   void (parent, child)
    +  --   WGT_METHOD_COUNT = 21, WGT_METHOD_MAX = 32
    +

    WGT_CLASS_VERSION is bumped on breaking ABI changes. The framework rejects WidgetClassT structs whose version does not match.

    +

    Widget Class Dispatch Table

    +

    WidgetClassT

    +
    typedef struct WidgetClassT {
    +    uint32_t version;
    +    uint32_t flags;
    +    void    *handlers[WGT_METHOD_MAX];
    +} WidgetClassT;
    +
      Field       Description
    +  -----       -----------
    +  version     Must equal WGT_CLASS_VERSION at registration
    +  flags       Bitmask of WCLASS_xxx flags
    +  handlers    Array of 32 function pointers indexed by WGT_METHOD_xxx
    +

    Size Encoding Constants

    +
      Define              Value        Description
    +  ------              -----        -----------
    +  WGT_SIZE_TYPE_MASK  0xC0000000   Top 2 bits: unit type selector
    +  WGT_SIZE_VAL_MASK   0x3FFFFFFF   Bottom 30 bits: numeric value
    +  WGT_SIZE_PIXELS     0x00000000   Unit tag: pixels
    +  WGT_SIZE_CHARS      0x40000000   Unit tag: character widths
    +  WGT_SIZE_PERCENT    0x80000000   Unit tag: percentage of parent
    +  WGT_WEIGHT_FILL     100          Default "flexible" weight value
    +

    Widget Interface Descriptors

    +

    WgtPropDescT

    +

    Describes a BASIC-exposed property on a widget type. Used by the form runtime and IDE for generic get/set dispatch.

    +
      Field       Description
    +  -----       -----------
    +  name        BASIC property name (e.g. "Caption", "Value")
    +  type        Data type: WGT_IFACE_STRING, WGT_IFACE_INT, WGT_IFACE_BOOL, WGT_IFACE_FLOAT, or WGT_IFACE_ENUM
    +  getFn       Getter function pointer (NULL if write-only)
    +  setFn       Setter function pointer (NULL if read-only)
    +  enumNames   For WGT_IFACE_ENUM: NULL-terminated array of value names
    +

    WgtMethodDescT

    +

    Describes an invokable method on a widget type.

    +
      Field       Description
    +  -----       -----------
    +  name        BASIC method name (e.g. "Clear", "SetFocus")
    +  sig         Calling convention ID (WGT_SIG_xxx)
    +  fn          Function pointer
    +

    WgtEventDescT

    +

    Describes an event beyond the standard common set (Click, DblClick, Change, GotFocus, LostFocus).

    +
      Field       Description
    +  -----       -----------
    +  name        Event name (e.g. "Change", "Timer")
    +

    WgtIfaceT

    +

    Top-level interface descriptor registered by each widget DXE.

    +
      Field         Description
    +  -----         -----------
    +  basName       VB-style name (e.g. "CommandButton"), or NULL
    +  props         Array of WgtPropDescT
    +  propCount     Length of props array
    +  methods       Array of WgtMethodDescT
    +  methodCount   Length of methods array
    +  events        Array of WgtEventDescT (extras beyond common set)
    +  eventCount    Length of events array
    +  createSig     How to call the widget's create() function (WgtCreateSigE)
    +  createArgs    Default numeric args for design-time instantiation
    +  isContainer   true = widget can hold child widgets
    +  defaultEvent  Default event name for form designer
    +  namePrefix    Auto-name prefix (NULL = derive from basName)
    +

    Property Data Type Constants

    +
      Define              Value   Description
    +  ------              -----   -----------
    +  WGT_IFACE_STRING    0       const char *
    +  WGT_IFACE_INT       1       int32_t
    +  WGT_IFACE_BOOL      2       bool
    +  WGT_IFACE_FLOAT     3       float
    +  WGT_IFACE_ENUM      4       int32_t with named values
    +

    Create Function Signature Constants

    +
      Define                              Value   Form
    +  ------                              -----   ----
    +  WGT_CREATE_PARENT                   0       fn(parent)
    +  WGT_CREATE_PARENT_TEXT              1       fn(parent, const char *text)
    +  WGT_CREATE_PARENT_INT               2       fn(parent, int32_t)
    +  WGT_CREATE_PARENT_INT_INT           3       fn(parent, int32_t, int32_t)
    +  WGT_CREATE_PARENT_INT_INT_INT       4       fn(parent, int32_t, int32_t, int32_t)
    +  WGT_CREATE_PARENT_INT_BOOL          5       fn(parent, int32_t, bool)
    +  WGT_CREATE_PARENT_BOOL              6       fn(parent, bool)
    +  WGT_CREATE_PARENT_DATA              7       fn(parent, data, w, h, pitch) -- not auto-creatable
    +

    WGT_MAX_CREATE_ARGS is 3.

    +
    +
    +

    dvxDlg.h -- Modal Dialogs

    +

    Pre-built modal dialog boxes that block the caller and run their own event loop via dvxUpdate() until the user dismisses them. Modal dialogs set ctx->modalWindow to prevent input from reaching other windows for the dialog's lifetime.

    +

    Message Box Flags

    +

    Button configurations (low nibble):

    +
      Define             Value    Description
    +  ------             -----    -----------
    +  MB_OK              0x0000   OK button only (default)
    +  MB_OKCANCEL        0x0001   OK and Cancel
    +  MB_YESNO           0x0002   Yes and No
    +  MB_YESNOCANCEL     0x0003   Yes, No, and Cancel
    +  MB_RETRYCANCEL     0x0004   Retry and Cancel
    +

    Icon types (high nibble, OR with button flags):

    +
      Define             Value    Description
    +  ------             -----    -----------
    +  MB_ICONINFO        0x0010   Information icon
    +  MB_ICONWARNING     0x0020   Warning icon
    +  MB_ICONERROR       0x0030   Error icon
    +  MB_ICONQUESTION    0x0040   Question mark icon
    +

    Return values:

    +
      Define             Value   Button pressed
    +  ------             -----   --------------
    +  ID_OK              1       OK
    +  ID_CANCEL          2       Cancel or dialog closed
    +  ID_YES             3       Yes
    +  ID_NO              4       No
    +  ID_RETRY           5       Retry
    +

    dvxMessageBox

    +
    int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags);
    +

    Display a modal message box. Blocks the caller by running dvxUpdate() in a loop until a button is pressed or the dialog is closed. The window is automatically destroyed on return.

    +
      Parameter   Description
    +  ---------   -----------
    +  ctx         Application context
    +  title       Window title (NULL falls back to a generic default)
    +  message     Body text
    +  flags       MB_xxx button + icon flags, OR'd together
    +

    Returns: ID_xxx of the button that was pressed.

    +
    // Example: yes/no confirmation
    +int32_t r = dvxMessageBox(ctx, "Save", "Save changes before closing?", MB_YESNOCANCEL | MB_ICONQUESTION);
    +if (r == ID_YES) { saveFile(); }
    +

    dvxErrorBox

    +
    int32_t dvxErrorBox(AppContextT *ctx, const char *title, const char *message);
    +

    Convenience wrapper for MB_OK | MB_ICONERROR. Title defaults to "Error" when NULL.

    +
      Parameter   Description
    +  ---------   -----------
    +  ctx         Application context
    +  title       Dialog title, or NULL for "Error"
    +  message     Error message
    +

    Returns: ID_OK.

    +

    dvxInfoBox

    +
    int32_t dvxInfoBox(AppContextT *ctx, const char *title, const char *message);
    +

    Convenience wrapper for MB_OK | MB_ICONINFO.

    +
      Parameter   Description
    +  ---------   -----------
    +  ctx         Application context
    +  title       Dialog title
    +  message     Informational message
    +

    Returns: ID_OK.

    +

    File Dialog

    +

    FileFilterT

    +

    Describes a filter entry in the file dialog's format dropdown. The glob pattern is extracted from inside the parentheses.

    +
      Field   Description
    +  -----   -----------
    +  label   e.g. "Text Files (*.txt)" or "Images (*.bmp;*.png;*.jpg;*.gif)"
    +

    File Dialog Flags

    +
      Define      Value    Description
    +  ------      -----    -----------
    +  FD_OPEN     0x0000   File open dialog (default)
    +  FD_SAVE     0x0001   File save dialog (with overwrite prompt)
    +

    dvxFileDialog

    +
    bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);
    +

    Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a filename input, and an optional filter dropdown.

    +
      Parameter     Description
    +  ---------     -----------
    +  ctx           Application context
    +  title         Window title
    +  flags         FD_OPEN or FD_SAVE
    +  initialDir    Starting directory (NULL = current working directory)
    +  filters       Array of FileFilterT, or NULL
    +  filterCount   Number of filter entries
    +  outPath       Output buffer for selected path
    +  outPathSize   Capacity of outPath buffer
    +

    Returns: true if the user selected a file, false if cancelled or closed.

    +

    Input Boxes

    +

    dvxInputBox

    +
    bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize);
    +

    Display a modal input box with a prompt label, text field, and OK/Cancel buttons.

    +
      Parameter     Description
    +  ---------     -----------
    +  ctx           Application context
    +  title         Window title
    +  prompt        Prompt label above the input field
    +  defaultText   Initial text (NULL = empty)
    +  outBuf        Output buffer
    +  outBufSize    Capacity of outBuf
    +

    Returns: true if OK was pressed, false if cancelled.

    +

    dvxIntInputBox

    +
    bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal);
    +

    Display a modal integer input box with a spinner (up/down arrows) for clamped numeric input.

    +
      Parameter     Description
    +  ---------     -----------
    +  ctx           Application context
    +  title         Window title
    +  prompt        Prompt label
    +  defaultVal    Initial value
    +  minVal        Minimum allowed value
    +  maxVal        Maximum allowed value
    +  step          Increment per arrow click
    +  outVal        Output: entered value
    +

    Returns: true if OK pressed, false if cancelled.

    +

    Choice Dialog

    +

    dvxChoiceDialog

    +
    bool dvxChoiceDialog(AppContextT *ctx, const char *title, const char *prompt, const char **items, int32_t itemCount, int32_t defaultIdx, int32_t *outIdx);
    +

    Display a modal dialog with a listbox of choices. The user picks one item and clicks OK, or cancels.

    +
      Parameter     Description
    +  ---------     -----------
    +  ctx           Application context
    +  title         Window title
    +  prompt        Prompt label above the list
    +  items         Array of choice strings
    +  itemCount     Number of items
    +  defaultIdx    Initially highlighted item (-1 = none)
    +  outIdx        Output: selected index
    +

    Returns: true if a choice was made, false if cancelled.

    +

    Save Prompt

    +

    Return codes for dvxPromptSave:

    +
      Define            Value   Action
    +  ------            -----   ------
    +  DVX_SAVE_YES      1       Save, then proceed
    +  DVX_SAVE_NO       2       Discard, then proceed
    +  DVX_SAVE_CANCEL   3       Abort the operation
    +

    dvxPromptSave

    +
    int32_t dvxPromptSave(AppContextT *ctx, const char *title);
    +

    Display a canonical "Save changes?" dialog with Yes/No/Cancel buttons and an appropriate question icon.

    +
      Parameter   Description
    +  ---------   -----------
    +  ctx         Application context
    +  title       Document name for message context
    +

    Returns: DVX_SAVE_YES, DVX_SAVE_NO, or DVX_SAVE_CANCEL.

    +
    +
    +

    dvxRes.h -- DXE-Embedded Resource System

    +

    Resources are appended to DXE3 files (.app, .wgt, .lib) after the normal DXE content. The DXE loader never reads past its own header-specified sections, so the appended resource block is invisible to dlopen. This lets each DXE carry its own icons, localized text, help content, and binary data without separate resource files.

    +

    File Layout

    +
    [DXE3 content]           -- untouched, loaded by dlopen
    +[resource data entries]  -- sequential, variable length
    +[resource directory]     -- fixed-size entries (48 bytes each)
    +[footer]                 -- magic + directory offset + entry count (16 bytes)
    +

    Reading starts from the end: seek to EOF - sizeof(footer), verify the magic, then seek to the directory.

    +

    Constants

    +
      Define              Value          Description
    +  ------              -----          -----------
    +  DVX_RES_ICON        1              Image data (BMP, PNG, etc.)
    +  DVX_RES_TEXT        2              Null-terminated string
    +  DVX_RES_BINARY      3              Arbitrary binary data (app-specific)
    +  DVX_RES_MAGIC       0x52585644     Footer signature ("DVXR" little-endian)
    +  DVX_RES_NAME_MAX    32             Max resource name length including null
    +

    Structures

    +

    DvxResDirEntryT

    +

    Directory entry describing one resource (48 bytes on disk).

    +
      Field        Description
    +  -----        -----------
    +  name         Resource name (up to 32 chars, null-terminated)
    +  type         DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY
    +  offset       Absolute file offset of the data
    +  size         Data size in bytes
    +  reserved     Padding (must be zero)
    +

    DvxResFooterT

    +

    Footer at the very end of a DXE file with appended resources (16 bytes).

    +
      Field         Description
    +  -----         -----------
    +  magic         DVX_RES_MAGIC
    +  dirOffset     Absolute file offset of the resource directory
    +  entryCount    Number of directory entries
    +  reserved      Padding (must be zero)
    +

    DvxResHandleT

    +

    Opaque runtime handle representing an open resource block.

    +
      Field         Description
    +  -----         -----------
    +  path          Source file path
    +  entries       Directory entries (sorted by name for bsearch)
    +  entryCount    Number of entries
    +

    Runtime API

    +

    dvxResOpen

    +
    DvxResHandleT *dvxResOpen(const char *path);
    +

    Open a resource handle by reading the footer and directory. Returns NULL if the file has no resource block or cannot be read. Entries are sorted on open for O(log n) name lookup via bsearch.

    +
      Parameter   Description
    +  ---------   -----------
    +  path        Path to the DXE (or any file) with appended resources
    +

    Returns: Handle pointer, or NULL.

    +

    dvxResRead

    +
    void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize);
    +

    Find a resource by name and read its data into a freshly malloc'd buffer. Caller must free the returned buffer.

    +
      Parameter   Description
    +  ---------   -----------
    +  h           Resource handle
    +  name        Resource name
    +  outSize     Output: data size in bytes
    +

    Returns: Data buffer, or NULL if not found or I/O failed.

    +

    dvxResFind

    +
    const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name);
    +

    Look up a resource's directory entry by name. The returned pointer is valid until dvxResClose.

    +
      Parameter   Description
    +  ---------   -----------
    +  h           Resource handle
    +  name        Resource name
    +

    Returns: Pointer to directory entry, or NULL if not found.

    +

    dvxResClose

    +
    void dvxResClose(DvxResHandleT *h);
    +

    Close a resource handle and free its memory.

    +
      Parameter   Description
    +  ---------   -----------
    +  h           Handle to close
    +

    dvxResAppend

    +
    int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize);
    +

    Append a resource to an existing DXE file. Preserves existing resources and adds the new one. Thin wrapper over the resource writer in tools/dvxResWrite.h.

    +
      Parameter   Description
    +  ---------   -----------
    +  path        Target DXE path
    +  name        Resource name (up to DVX_RES_NAME_MAX - 1 chars)
    +  type        DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY
    +  data        Resource payload
    +  dataSize    Payload size in bytes
    +

    Returns: 0 on success, -1 on failure.

    +

    dvxResRemove

    +
    int32_t dvxResRemove(const char *path, const char *name);
    +

    Remove a resource from a DXE file by name. If this was the last resource, the file is truncated back to the original DXE content.

    +
      Parameter   Description
    +  ---------   -----------
    +  path        Target DXE path
    +  name        Resource name to remove
    +

    Returns: 0 on success, -1 if not found or I/O failed.

    +

    See Also

    +

    dvxResLoadIcon / dvxResLoadText / dvxResLoadData -- higher-level helpers on top of this API

    +
    +
    +

    dvxPrefs.h -- INI Preferences System

    +

    Handle-based API over classic INI files. Each prefsLoad/prefsCreate returns a PrefsHandleT that must be passed to all subsequent calls and freed with prefsClose when done. Multiple INI files can be open simultaneously.

    +

    Constants

    +
      Define          Value                                Description
    +  ------          -----                                -----------
    +  DVX_INI_PATH    "CONFIG" DVX_PATH_SEP "DVX.INI"      Canonical system INI path
    +

    Types

    +

    PrefsHandleT

    +

    Opaque handle representing an in-memory INI file. Fields are not exposed.

    +

    Lifecycle

    +

    prefsCreate

    +
    PrefsHandleT *prefsCreate(void);
    +

    Create an empty preferences handle with no associated file. Use this when building an INI from scratch before calling prefsSaveAs.

    +

    Returns: New handle, or NULL on allocation failure.

    +

    prefsLoad

    +
    PrefsHandleT *prefsLoad(const char *filename);
    +

    Load an INI file into a new handle. If the file does not exist, returns a valid empty handle with the path stored so prefsSave can write a new file later.

    +
      Parameter   Description
    +  ---------   -----------
    +  filename    Path to INI file
    +

    Returns: Handle, or NULL on allocation failure.

    +

    prefsSave

    +
    bool prefsSave(PrefsHandleT *h);
    +

    Save the in-memory state back to the file that was loaded (or captured by prefsLoad for a non-existent file).

    +
      Parameter   Description
    +  ---------   -----------
    +  h           Preferences handle
    +

    Returns: true on success.

    +

    prefsSaveAs

    +
    bool prefsSaveAs(PrefsHandleT *h, const char *filename);
    +

    Save to a specific file, overriding the handle's stored path.

    +
      Parameter   Description
    +  ---------   -----------
    +  h           Preferences handle
    +  filename    Output file path
    +

    Returns: true on success.

    +

    prefsClose

    +
    void prefsClose(PrefsHandleT *h);
    +

    Release all memory held by the handle. Does not save -- call prefsSave first if needed.

    +
      Parameter   Description
    +  ---------   -----------
    +  h           Handle to close
    +

    Getters

    +

    prefsGetString

    +
    const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal);
    +

    Retrieve a string value from [section]/key. The returned pointer is valid until the key is modified or the handle is closed.

    +
      Parameter    Description
    +  ---------    -----------
    +  h            Preferences handle
    +  section      Section name (without brackets)
    +  key          Key name
    +  defaultVal   Returned if the key is not present
    +

    Returns: Key value or defaultVal.

    +

    prefsGetInt

    +
    int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal);
    +

    Retrieve an integer value. Supports decimal and negative numbers.

    +
      Parameter    Description
    +  ---------    -----------
    +  h            Preferences handle
    +  section      Section name
    +  key          Key name
    +  defaultVal   Returned if the key is absent or non-numeric
    +

    Returns: Parsed int, or defaultVal.

    +

    prefsGetBool

    +
    bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal);
    +

    Retrieve a boolean value. Recognises "true"/"yes"/"1" as true and "false"/"no"/"0" as false (case-insensitive).

    +
      Parameter    Description
    +  ---------    -----------
    +  h            Preferences handle
    +  section      Section name
    +  key          Key name
    +  defaultVal   Returned if the key is absent or unrecognised
    +

    Returns: Parsed bool, or defaultVal.

    +

    Setters

    +

    prefsSetString

    +
    void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value);
    +

    Set a string value. Creates the section and key if they do not already exist.

    +

    prefsSetInt

    +
    void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value);
    +

    Set an integer value (stored in decimal).

    +

    prefsSetBool

    +
    void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value);
    +

    Set a boolean value (stored as "true"/"false").

    +

    prefsRemove

    +
    void prefsRemove(PrefsHandleT *h, const char *section, const char *key);
    +

    Remove a key from a section. No-op if the key does not exist.

    +
    +
    +

    dvxMem.h -- Per-App Memory Tracking

    +

    Wraps malloc/free/calloc/realloc/strdup with a 16-byte header that records the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected when an app is terminated.

    +

    DXE code does NOT need to include this header for tracking to work. The DXE export table maps malloc/free/calloc/realloc/strdup to these wrappers transparently. Call sites that need the explicit dvxXxx names (e.g. dvxMemGetAppUsage) or the dvxMemAppIdPtr declaration should include dvxMem.h directly.

    +

    Globals

    +

    dvxMemAppIdPtr

    +
    extern int32_t *dvxMemAppIdPtr;
    +

    Pointer to the current owning app ID. The shell points this at its currentAppId field during init so every allocation is charged to the running app. When dvxMemAppIdPtr is NULL or points to zero, allocations are charged to the shell.

    +

    Allocation Wrappers

    +

    dvxMalloc

    +
    void *dvxMalloc(size_t size);
    +

    Tracked malloc. Returns a pointer to user memory (header is hidden in front of it).

    +

    dvxCalloc

    +
    void *dvxCalloc(size_t nmemb, size_t size);
    +

    Tracked calloc.

    +

    dvxRealloc

    +
    void *dvxRealloc(void *ptr, size_t size);
    +

    Tracked realloc. Works correctly with tracked pointers only -- the magic header identifies them.

    +

    dvxFree

    +
    void dvxFree(void *ptr);
    +

    Tracked free. Falls through to the real free() if the pointer's header magic does not match, so mixing tracked and untracked pointers is safe.

    +

    dvxStrdup

    +
    char *dvxStrdup(const char *s);
    +

    Tracked strdup.

    +

    Accounting

    +

    dvxMemSnapshotLoad

    +
    void dvxMemSnapshotLoad(int32_t appId);
    +

    Record a baseline memory snapshot for the given app. Called right before app code starts so later calls to dvxMemGetAppUsage can report net growth.

    +
      Parameter   Description
    +  ---------   -----------
    +  appId       App ID to snapshot
    +

    dvxMemGetAppUsage

    +
    uint32_t dvxMemGetAppUsage(int32_t appId);
    +

    Return the total bytes currently charged to the given app ID.

    +
      Parameter   Description
    +  ---------   -----------
    +  appId       App ID to query
    +

    Returns: Bytes allocated for this app.

    +

    dvxMemResetApp

    +
    void dvxMemResetApp(int32_t appId);
    +

    Free every tracked allocation charged to the given app. Called by the shell after an app exits or crashes to reclaim its memory.

    +
      Parameter   Description
    +  ---------   -----------
    +  appId       App ID to flush
    +
    +
    +

    dvxWgtP.h -- Widget Plugin API

    +

    Included by widget .c files (DXE modules) to access core infrastructure for tree manipulation, focus management, scrollbar drawing, layout helpers, and shared interaction state. This header is NOT intended for application code -- use dvxWgt.h for the public widget API.

    +

    Validation Macros

    +

    VALIDATE_WIDGET / VALIDATE_WIDGET_VOID

    +
    VALIDATE_WIDGET(w, wtype, retval);     // returns retval on mismatch
    +VALIDATE_WIDGET_VOID(w, wtype);        // returns void on mismatch
    +

    Bail out of a widget method if the widget pointer is NULL or its type does not match the expected wtype. Use at the top of every widget-specific API function.

    +

    Core Constants

    +
      Define             Value   Description
    +  ------             -----   -----------
    +  DEFAULT_SPACING    4       Default pixel spacing between VBox/HBox children
    +  DEFAULT_PADDING    4       Default internal padding for containers
    +  SB_MIN_THUMB       14      Minimum scrollbar thumb size in pixels
    +  WGT_SB_W           14      Widget-internal scrollbar width
    +  KEY_MOD_SHIFT      0x03    Shift modifier bits
    +  KEY_MOD_CTRL       0x04    Ctrl modifier bit
    +  KEY_MOD_ALT        0x08    Alt modifier bit
    +

    Inline Helpers

    +

    clampInt

    +
    static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi);
    +

    Clamp val into the [lo, hi] range.

    +

    drawTextEmbossed

    +
    static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors);
    +

    Draw text twice -- once offset by (1,1) in highlight color, then overlaid in shadow color at (x,y) -- for a classic Motif-style embossed look used on disabled labels.

    +

    drawTextAccelEmbossed

    +
    static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors);
    +

    Embossed text with & accelerator markers. Same offset logic as drawTextEmbossed, but processes & markers.

    +

    Shared Interaction State

    +

    Extern variables defined in widgetCore.c. These hold system-wide widget state: only one focused widget, one open popup, one pressed button at a time.

    +
      Variable              Type       Purpose
    +  --------              ----       -------
    +  sCursorBlinkOn        bool       Current state of the text cursor blink
    +  sDblClickTicks        clock_t    Double-click interval in clock() ticks
    +  sDebugLayout          bool       When true, draw colored debug borders around containers
    +  sClosedPopup          WidgetT*   Widget whose popup just closed (suppresses re-open)
    +  sFocusedWidget        WidgetT*   Currently focused widget
    +  sKeyPressedBtn        WidgetT*   Button being pressed via keyboard (for release tracking)
    +  sOpenPopup            WidgetT*   Currently open popup (dropdown/combo list)
    +  sDragWidget           WidgetT*   Widget being dragged (any drag type)
    +  sPollWidgetCount      int32_t    Count of widgets with WCLASS_NEEDS_POLL flag set
    +  sPollWidgets          WidgetT**  Dynamic array of poll widgets (stb_ds)
    +  sCursorBlinkFn        fn ptr     Callback invoked when the blink flips (repaints focused text)
    +

    Core Widget Functions

    +

    widgetAlloc

    +
    WidgetT *widgetAlloc(WidgetT *parent, int32_t type);
    +

    Allocate a new WidgetT, initialize its fields to defaults, link it as the last child of parent (if non-NULL), and assign its class pointer from widgetClassTable[type].

    +

    widgetAddChild

    +
    void widgetAddChild(WidgetT *parent, WidgetT *child);
    +

    Append child to parent's child list. Updates parent's firstChild/lastChild and child's nextSibling pointers.

    +

    widgetRemoveChild

    +
    void widgetRemoveChild(WidgetT *parent, WidgetT *child);
    +

    Unlink child from parent's child list. Does not destroy the child.

    +

    widgetDestroyChildren

    +
    void widgetDestroyChildren(WidgetT *w);
    +

    Recursively destroy all descendants of w, calling each one's wclsDestroy handler.

    +

    Focus Management

    +

    widgetFindNextFocusable

    +
    WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
    +

    Find the next focusable widget in tab order after the given widget. Wraps to the beginning at the end of the tree.

    +

    widgetFindPrevFocusable

    +
    WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
    +

    Find the previous focusable widget in tab order. Wraps to the end at the beginning of the tree.

    +

    widgetFindByAccel

    +
    WidgetT *widgetFindByAccel(WidgetT *root, char key);
    +

    Find a focusable widget whose accelKey (lowercase) matches the given character. Used for Alt+letter hotkeys on buttons and labels.

    +

    Hit Testing

    +

    widgetHitTest

    +
    WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
    +

    Find the deepest widget at (x, y) within w's subtree. Respects WCLASS_NO_HIT_RECURSE so certain widget types can prevent children from being hit-tested.

    +

    Utility Queries

    +

    widgetCountVisibleChildren

    +
    int32_t widgetCountVisibleChildren(const WidgetT *w);
    +

    Count children whose visible flag is true.

    +

    widgetFrameBorderWidth

    +
    int32_t widgetFrameBorderWidth(const WidgetT *w);
    +

    Return the effective border width for a container widget (used to inset child layout).

    +

    widgetIsFocusable

    +
    bool widgetIsFocusable(int32_t type);
    +

    True if the widget class with this type ID has the WCLASS_FOCUSABLE flag.

    +

    widgetIsHorizContainer

    +
    bool widgetIsHorizContainer(int32_t type);
    +

    True if the widget class lays out children horizontally (WCLASS_HORIZ_CONTAINER).

    +

    multiClickDetect

    +
    int32_t multiClickDetect(int32_t vx, int32_t vy);
    +

    Track consecutive clicks at roughly the same position within sDblClickTicks. Returns the current click count (1 for single, 2 for double, etc.).

    +

    Clipboard

    +

    clipboardCopy

    +
    void clipboardCopy(const char *text, int32_t len);
    +

    Copy text to the process-wide clipboard buffer. Same underlying storage as dvxClipboardCopy.

    +

    clipboardGet

    +
    const char *clipboardGet(int32_t *outLen);
    +

    Retrieve clipboard text.

    +

    clipboardMaxLen

    +
    int32_t clipboardMaxLen(void);
    +

    Return the fixed capacity of the clipboard buffer in bytes.

    +

    Scrollbar Helpers

    +

    ScrollHitE

    +

    Scrollbar hit-test result.

    +
      Value                Description
    +  -----                -----------
    +  ScrollHitNoneE       Click fell outside the scrollbar
    +  ScrollHitArrowDecE   Up/left arrow
    +  ScrollHitArrowIncE   Down/right arrow
    +  ScrollHitPageDecE    Trough above/before the thumb
    +  ScrollHitPageIncE    Trough below/after the thumb
    +  ScrollHitThumbE      The draggable thumb
    +

    widgetScrollbarThumb

    +
    void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);
    +

    Compute thumb position and size for a scrollbar with the given geometry. Enforces SB_MIN_THUMB.

    +

    widgetDrawScrollbarV / widgetDrawScrollbarVEx

    +
    void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
    +void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW);
    +

    Draw a vertical scrollbar inside a widget. The Ex variant lets the caller specify bar width for non-standard scrollbars (e.g. combo popups). Standard variant uses WGT_SB_W.

    +

    widgetDrawScrollbarH / widgetDrawScrollbarHEx

    +
    void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
    +void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW);
    +

    Draw a horizontal scrollbar inside a widget. Standard variant uses WGT_SB_W.

    +

    widgetScrollbarHitTest

    +
    ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
    +

    Classify a click position along a scrollbar into ScrollHitE. sbLen is the scrollbar length, relPos is the click position along the bar.

    +

    Layout Functions

    +

    widgetCalcMinSizeBox / widgetCalcMinSizeTree

    +
    void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font);
    +void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font);
    +

    Generic box-container min-size calculation (bottom-up layout pass). widgetCalcMinSizeBox handles a single VBox/HBox; widgetCalcMinSizeTree recurses through the whole subtree.

    +

    widgetLayoutBox / widgetLayoutChildren

    +
    void widgetLayoutBox(WidgetT *w, const BitmapFontT *font);
    +void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
    +

    Generic box-container layout (top-down pass). widgetLayoutBox lays out a single container; widgetLayoutChildren processes each child's layout handler.

    +

    Event Dispatch

    +

    widgetOnPaint / widgetOnMouse / widgetOnKey / etc.

    +
    void widgetOnPaint(WindowT *win, RectT *dirtyArea);
    +void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
    +void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
    +void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod);
    +void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
    +void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
    +void widgetOnFocus(WindowT *win);
    +void widgetOnBlur(WindowT *win);
    +

    Window-level event handlers that dispatch to individual widgets in the tree. wgtInitWindow installs these as the window's onPaint/onMouse/onKey/etc. callbacks.

    +

    widgetManageScrollbars

    +
    void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
    +

    Add, remove, or resize window scrollbars based on the widget tree's current min-size vs. the window content area.

    +

    Paint Helpers

    +

    widgetPaintOne

    +
    void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
    +

    Paint a single widget (sets clip rect, calls the widget's PAINT handler). Does not recurse.

    +

    widgetPaintOverlays

    +
    void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
    +

    Paint the overlay pass: dropdown popups and tooltips drawn on top of the main tree.

    +

    Pressable Button Helpers

    +

    Shared state machine for WCLASS_PRESS_RELEASE widgets (Button, ImageButton). The w->pressed flag tracks the visual state; these helpers fire the widget's onClick callback on release-within-bounds.

    +

    widgetPressableOnMouse / widgetPressableOnKey / widgetPressableOnDragUpdate / widgetPressableOnDragEnd / widgetPressableOnAccelActivate

    +
    void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
    +void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod);
    +void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
    +void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
    +void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root);
    +

    Register these directly as the widget class's handlers for WGT_METHOD_ON_MOUSE, WGT_METHOD_ON_KEY, WGT_METHOD_ON_DRAG_UPDATE, WGT_METHOD_ON_DRAG_END, and WGT_METHOD_ON_ACCEL_ACTIVATE.

    +

    Generic Text Handlers

    +

    For widgets whose data struct has a (const char *) as its first field and the WCLASS_HAS_TEXT flag set, these implement GET_TEXT / SET_TEXT directly.

    +

    widgetTextGet / widgetTextSet

    +
    const char *widgetTextGet(const WidgetT *w);
    +void        widgetTextSet(WidgetT *w, const char *text);
    +

    Register these directly as the widget class's handlers for WGT_METHOD_GET_TEXT and WGT_METHOD_SET_TEXT. The text is strdup'd on set and freed automatically on destroy.

    +

    Class Table

    +

    widgetClassTable

    +
    extern const WidgetClassT **widgetClassTable;
    +

    stb_ds dynamic array. Each widget DXE appends its class pointer via wgtRegisterClass() during wgtRegister(). Indexed by the runtime type ID assigned at registration.

    +
    +
    +

    platform/dvxPlat.h -- Platform Abstraction Layer

    +

    All OS-specific and CPU-specific code is isolated behind this interface. Port DVX to a new platform by implementing a new dvxPlatformXxx.c against this header. Currently the only implementation is dvxPlatformDos.c for DJGPP/DPMI.

    +

    Platform functions must be stateless (or manage their own internal state) and must not reference AppContextT or any layer above dvxTypes.h.

    +

    Constants

    +
      Define                   Value   Description
    +  ------                   -----   -----------
    +  DVX_MAX_PATH             260     Maximum file path length
    +  PLATFORM_SYSINFO_MAX     4096    Maximum size of formatted system info text
    +  DVX_PATH_SEP             "\\"    Native directory separator on DOS
    +

    Types

    +

    PlatformKeyEventT

    +

    Keyboard event with ASCII and scancode separated. Extended keys (arrows, F-keys) have ascii == 0 and a non-zero scancode.

    +
      Field       Description
    +  -----       -----------
    +  ascii       ASCII value, 0 for extended/function keys
    +  scancode    PC scancode (e.g. 0x48 = Up, 0x50 = Down)
    +

    PlatformLogFnT

    +
    typedef void (*PlatformLogFnT)(const char *fmt, ...);
    +

    Log function signature used by the crash handler.

    +

    PlatformSymOverrideT

    +

    Name/function-pointer pair for registering symbol overrides with the DXE loader.

    +
      Field   Description
    +  -----   -----------
    +  name    Symbol name as seen by DXEs
    +  func    Function pointer to use instead of the default
    +

    Lifecycle

    +

    dvxLog

    +
    void dvxLog(const char *fmt, ...);
    +

    Append a formatted line to dvx.log. Implemented in dvx.exe and exported to every DXE via the loader.

    +

    platformInit

    +
    void platformInit(void);
    +

    One-time platform initialization. On DOS this installs signal handlers for clean shutdown on Ctrl+C/Ctrl+Break.

    +

    platformYield

    +
    void platformYield(void);
    +

    Cooperative CPU yield, used between frames when the event loop has nothing to do. On DOS this calls __dpmi_yield() to be friendly to multitaskers.

    +

    Video

    +

    platformVideoInit / platformVideoShutdown

    +
    int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
    +void    platformVideoShutdown(DisplayT *d);
    +

    Low-level video mode setup and teardown. Called by videoInit / videoShutdown.

    +

    platformVideoEnumModes

    +
    void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
    +

    Enumerate LFB-capable graphics modes. Invokes cb once per available mode.

    +

    platformVideoSetPalette

    +
    void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
    +

    Program the VGA/VESA DAC palette registers (8-bit mode only).

    +

    platformVideoFreeBuffers

    +
    void platformVideoFreeBuffers(DisplayT *d);
    +

    Free the backbuffer and palette without restoring text mode. Used when switching between graphics modes live.

    +

    Framebuffer Flush

    +

    platformFlushRect

    +
    void platformFlushRect(const DisplayT *d, const RectT *r);
    +

    Copy a rectangle from the system RAM backbuffer to the LFB. The single critical path where PCI bus write speed matters. On DOS, each scanline uses rep movsd for aligned 32-bit writes.

    +

    Span Operations

    +

    platformSpanFill8/16/32

    +
    void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count);
    +void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count);
    +void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count);
    +

    Fill count pixels at dst with color. Three depth variants because the fill instruction differs by size. On DOS these use inline rep stosl / rep stosw / rep stosb asm.

    +

    platformSpanCopy8/16/32

    +
    void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
    +void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
    +void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
    +

    Copy count pixels from src to dst. On DOS these use rep movsd.

    +

    Mouse Input

    +

    platformMouseInit

    +
    void platformMouseInit(int32_t screenW, int32_t screenH);
    +

    Initialize the mouse driver and constrain movement to the screen bounds.

    +

    platformMousePoll

    +
    void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);
    +

    Poll current mouse state. Buttons is a bitmask: bit 0 = left, bit 1 = right, bit 2 = middle.

    +

    platformMouseWheelInit / platformMouseWheelPoll

    +
    bool    platformMouseWheelInit(void);
    +int32_t platformMouseWheelPoll(void);
    +

    Detect and activate the CuteMouse Wheel API. platformMouseWheelPoll returns the accumulated delta (positive = scroll down) since the last poll.

    +

    platformMouseSetAccel / platformMouseSetMickeys

    +
    void platformMouseSetAccel(int32_t threshold);
    +void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys);
    +

    Set the double-speed threshold and mickey-to-pixel ratio. See dvxSetMouseConfig for a higher-level interface.

    +

    platformMouseWarp

    +
    void platformMouseWarp(int32_t x, int32_t y);
    +

    Move the mouse cursor to an absolute screen position. Used to clamp the cursor to window edges during resize.

    +

    Keyboard Input

    +

    platformKeyboardGetModifiers

    +
    int32_t platformKeyboardGetModifiers(void);
    +

    Return the current modifier key state in BIOS shift-state format.

    +

    platformKeyboardRead

    +
    bool platformKeyboardRead(PlatformKeyEventT *evt);
    +

    Non-blocking read of the next key from the keyboard buffer. Returns true if a key was available.

    +

    platformKeyUpInit / platformKeyUpShutdown / platformKeyUpRead

    +
    void platformKeyUpInit(void);
    +void platformKeyUpShutdown(void);
    +bool platformKeyUpRead(PlatformKeyEventT *evt);
    +

    Key-up event detection. Install/uninstall the INT 9 hook around process lifetime. platformKeyUpRead returns true if a key release was detected since the last call.

    +

    platformAltScanToChar

    +
    char platformAltScanToChar(int32_t scancode);
    +

    Translate an Alt+key scancode to its corresponding ASCII character. Returns 0 for non-printable scancodes.

    +

    System Information

    +

    platformGetSystemInfo

    +
    const char *platformGetSystemInfo(const DisplayT *display);
    +

    Return a pre-formatted text string with hardware/OS information (CPU, clock, memory, video, mouse, disks). Valid for the lifetime of the process.

    +

    platformGetMemoryInfo

    +
    bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
    +

    Query total and free physical memory.

    +

    File System

    +

    platformValidateFilename

    +
    const char *platformValidateFilename(const char *name);
    +

    Validate a filename against platform-specific rules (8.3 on DOS). Returns NULL if valid, or a human-readable error string.

    +

    platformMkdirRecursive

    +
    int32_t platformMkdirRecursive(const char *path);
    +

    Create a directory and all parent directories (like mkdir -p). Existing directories are not an error.

    +

    platformChdir

    +
    int32_t platformChdir(const char *path);
    +

    Change the working directory, including drive letter on DOS (calls setdisk() first when the path starts with "X:").

    +

    platformPathDirEnd

    +
    char *platformPathDirEnd(const char *path);
    +

    Return a pointer to the last directory separator in path, or NULL. On DOS accepts both "/" and "\\".

    +

    platformPathBaseName

    +
    const char *platformPathBaseName(const char *path);
    +

    Return a pointer to the leaf (basename) portion of path.

    +

    platformReadFile

    +
    char *platformReadFile(const char *path, int32_t *outLen);
    +

    Slurp a whole file into a freshly malloc'd, NUL-terminated buffer. Caller frees. Binary-safe: the NUL is past the end of the reported length.

    +

    platformGlobMatch

    +
    bool platformGlobMatch(const char *pattern, const char *name);
    +

    Simple glob pattern match. Case-insensitive. Supports * (zero or more chars) and ? (single char).

    +

    platformLineEnding / platformStripLineEndings

    +
    const char *platformLineEnding(void);
    +int32_t     platformStripLineEndings(char *buf, int32_t len);
    +

    Return the native line ending string, or strip line-ending characters from a buffer in place.

    +

    String Helpers

    +

    dvxSkipWs

    +
    const char *dvxSkipWs(const char *s);
    +

    Advance past leading spaces and tabs. Returns s unchanged if NULL or already non-whitespace.

    +

    dvxTrimRight

    +
    int32_t dvxTrimRight(char *buf);
    +

    Strip trailing spaces, tabs, carriage returns, and newlines from buf in place. Returns the new length.

    +

    DXE Module Support

    +

    platformRegisterDxeExports

    +
    void platformRegisterDxeExports(void);
    +

    Register platform and C runtime symbols with the dynamic module loader so DXE modules can resolve them at load time.

    +

    platformRegisterSymOverrides

    +
    void platformRegisterSymOverrides(const PlatformSymOverrideT *entries);
    +

    Register function pointer overrides for subsequently loaded modules. entries is terminated by {NULL, NULL}.

    +

    Crash Recovery

    +

    platformInstallCrashHandler

    +
    void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn);
    +

    Install signal handlers for SIGSEGV, SIGFPE, SIGILL. On crash, logs platform-specific diagnostics via logFn, stores the signal in *crashSignal, then longjmps to recoveryBuf.

    +

    platformLogCrashDetail

    +
    void platformLogCrashDetail(int sig, PlatformLogFnT logFn);
    +

    Log platform-specific crash diagnostics. Called internally from the crash handler and exposed for manual invocation.

    +

    VGA Splash Screen

    +

    platformSplashInit / platformSplashShutdown

    +
    void platformSplashInit(void);
    +void platformSplashShutdown(void);
    +

    Enter/leave VGA mode 13h (320x200x256) used for the boot splash.

    +

    platformSplashLoadRaw

    +
    bool platformSplashLoadRaw(const char *path);
    +

    Load and display a raw splash file (768-byte palette + 64000-byte pixel data).

    +

    platformSplashFillRect

    +
    void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color);
    +

    Fill a rectangle on the mode 13h screen with a palette index. Used to draw the progress bar during module loading.

    libtasks -- Cooperative Task Switching

    Credit-based cooperative (non-preemptive) multitasking library for DOS protected mode (DJGPP/DPMI). Each task receives (priority + 1) credits per scheduling round. Tasks run round-robin, consuming one credit per turn. When all credits are exhausted, every ready task is refilled. Higher-priority tasks run proportionally more often but never starve lower ones.

    -

    Header: tasks/taskswitch.h

    +

    Header: libtasks/taskSwch.h

    +

    Loaded as: bin/libs/libtasks.lib

    Why Cooperative

    DOS is single-threaded with no kernel scheduler. DPMI provides no preemption primitives. The DVX GUI event model is inherently single-threaded: one compositor, one input queue, one window stack. Cooperative switching lets each task yield at safe points, avoiding synchronization primitives entirely.

    +

    Scheduling Semantics

    +

    Task storage is a stb_ds dynamic array. Terminated slots are recycled by tsCreate so the array does not grow unboundedly. The scheduler performs a linear scan for the next Ready task whose credit count is positive; when no task has credits left, every Ready task is refilled with (priority + 1) credits and the scan restarts.

    +
      Priority            Credits per round  Approximate share
    +  --------            -----------------  -----------------
    +  TS_PRIORITY_LOW     1                  ~4% (1 of 25 possible credits)
    +  TS_PRIORITY_NORMAL  6                  ~22%
    +  TS_PRIORITY_HIGH    11                 ~41%
    +

    Actual share depends on the mix of priorities currently running. What the algorithm guarantees is that no ready task ever goes unscheduled -- even a priority-0 task always earns at least one turn per round.

    +

    Stack Allocation

    +

    Task 0 uses the caller's stack (no separate allocation). Every other task gets a heap-allocated stack of the size passed to tsCreate (or TS_DEFAULT_STACK_SIZE if 0 is passed). Stacks are freed at task termination, either via tsExit or tsKill.

    Error Codes

      Constant         Value   Description
       --------         -----   -----------
    @@ -2999,7 +4218,7 @@ img { max-width: 100%; }
     

    tsInit

    int32_t tsInit(void);

    Initialize the task system. The calling context becomes task 0 (the main task). Task 0 is special: it cannot be killed or paused, and tsRecoverToMain() always returns control here. No separate stack is allocated for task 0; it uses the process stack directly.

    -

    Returns: TS_OK on success, TS_ERR_INIT if already initialized.

    +

    Returns: TS_OK on success, TS_ERR_PARAM if already initialized.

    tsShutdown

    void tsShutdown(void);

    Shut down the task system and free all task stacks and internal storage. Safe to call even if tsInit() was never called.

    @@ -3013,7 +4232,7 @@ img { max-width: 100%; } arg Opaque argument passed to entry stackSize Stack size in bytes (pass 0 for TS_DEFAULT_STACK_SIZE) priority Scheduling priority (0..10; use TS_PRIORITY_* constants)
    -

    Returns: Task ID (>= 0) on success, or a negative error code (TS_ERR_INIT, TS_ERR_PARAM, TS_ERR_NOMEM).

    +

    Returns: Task ID (>= 0) on success, or a negative error code (TS_ERR_PARAM if not initialized or entry is NULL, TS_ERR_NOMEM if stack allocation failed).

    tsYield

    void tsYield(void);

    Yield CPU to the next eligible ready task using credit-based round-robin. This is the sole mechanism for task switching. Every task must call this (or a GUI function that calls it) periodically, or it will monopolize the CPU.

    @@ -3023,7 +4242,7 @@ img { max-width: 100%; }
      Parameter   Description
       ---------   -----------
       taskId      ID of the task to pause
    -

    Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, TS_ERR_STATE if the task is not in a pausable state.

    +

    Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE if the task is the main task or not in a pausable state (not Running or Ready).

    tsResume

    int32_t tsResume(uint32_t taskId);

    Resume a paused task. Credits are refilled so the task gets a fair share of CPU time immediately rather than waiting for the next scheduling round.

    @@ -3038,7 +4257,7 @@ img { max-width: 100%; } --------- ----------- taskId ID of the task to modify priority New priority level (0..10) -

    Returns: TS_OK on success, TS_ERR_PARAM on invalid ID or out-of-range priority.

    +

    Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE if the task is terminated.

    tsGetPriority

    int32_t tsGetPriority(uint32_t taskId);

    Get a task's current scheduling priority.

    @@ -3073,7 +4292,7 @@ img { max-width: 100%; }
      Parameter   Description
       ---------   -----------
       taskId      ID of the task to terminate
    -

    Returns: TS_OK on success, TS_ERR_PARAM on invalid ID or illegal target (main task, self).

    +

    Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE on illegal target (main task, self, or already terminated).

    tsRecoverToMain

    void tsRecoverToMain(void);

    Crash recovery: force the scheduler back to the main task (ID 0). Call after longjmp from a signal handler that fired in a non-main task. The crashed task is NOT cleaned up by this call; call tsKill() afterward to free its resources. This exists because longjmp unwinds the crashed task's stack but the scheduler state still points to it.

    @@ -3085,7 +4304,8 @@ img { max-width: 100%; }

    DVX Shell Library

    The DVX shell library manages the lifecycle of DXE applications: loading, launching, tracking, and reaping. It provides the bridge between the DVX GUI compositor and dynamically loaded DXE3 application modules.

    -

    Header: shell/shellApp.h

    +

    Header: dvxshell/shellApp.h

    +

    Loaded as: bin/libs/dvxshell.lib

    App Model

    The shell supports two kinds of DXE apps:

      @@ -3100,6 +4320,21 @@ img { max-width: 100%; } _appDescriptor AppDescriptorT Yes _appMain int32_t (*)(DxeAppContextT *) Yes _appShutdown void (*)(void) No +

      The shell resolves appDescriptor at dlopen time; if missing, the load fails with an error dialog. appMain is called immediately for callback-only apps or from the new task for main-loop apps. appShutdown, if present, is called during graceful reap (but not during force kill from crashed apps).

      +

      Callback-Only vs Main-Loop Apps

      +

      The app descriptor's hasMainLoop flag selects between two very different lifecycles:

      +
        +
      • hasMainLoop = false (callback-only). The shell calls appMain directly on task 0 at dlopen time. The function creates windows, registers event callbacks, and returns. After that the app has no executing thread of its own -- it exists purely through GUI callbacks dispatched by dvxUpdate. The shell reaps the app automatically when its last window closes. Best for modal tools, dialogs, and event-driven utilities.
      • +
      +

      hasMainLoop = true (main-loop). The shell creates a dedicated cooperative task (via tsCreate) with the descriptor's stackSize (or TS_DEFAULT_STACK_SIZE) and priority. appMain runs in that task and can do its own work loop, calling tsYield or any GUI function that yields. The app terminates when appMain returns (the wrapper sets AppStateTerminatingE) or when forced via shellForceKillApp. Best for terminal emulators, games, and any app with continuous background work.

      +

      Both app types use the same export interface; only the descriptor's flags differ. Apps cannot switch modes at runtime.

      +

      Icon Conventions

      +

      Shell-level UI (Program Manager, Task Manager) displays app icons at 16x16 and 32x32. Icons are not handled by the shell itself; each app embeds its own icons via the DVX resource system (DVX_RES_ICON):

      +
        +
      • 16x16 BMP for toolbar entries and list rows
      • +
      +

      32x32 BMP for desktop shortcuts and Program Manager tiles

      +

      The Program Manager reads the app's 32x32 icon resource when building shortcut tiles. Apps without an icon resource fall back to a default shell-provided bitmap.

      State Machine

      App slots progress through four states:

      Free -> Loaded -> Running -> Terminating -> Free
      @@ -3130,18 +4365,20 @@ img { max-width: 100%; } stackSize int32_t SHELL_STACK_DEFAULT or explicit byte count for the task stack. priority int32_t TS_PRIORITY_* value or custom priority for the task.

      DxeAppContextT

      -

      Passed as the sole argument to appMain(). Gives the app access to the shell's GUI context and its own identity.

      -
        Field                          Type           Description
      -  -----                          ----           -----------
      -  shellCtx                       AppContextT *  The shell's GUI context (for creating windows, drawing, etc.).
      -  appId                          int32_t        This app's unique ID (slot index, 1-based).
      -  appPath[DVX_MAX_PATH]          char[]         Full path to the .app DXE file.
      -  appDir[DVX_MAX_PATH]           char[]         Directory containing the .app file (for loading resources).
      -  configDir[DVX_MAX_PATH]        char[]         Writable config directory (e.g. CONFIG/APPS/KPUNCH/DVXBASIC/).
      -  args[1024]                     char[]         Launch arguments (empty string if none).
      -  helpFile[DVX_MAX_PATH]         char[]         Help file path (for F1 context help).
      -  helpTopic[128]                 char[]         Current help topic ID (updated by the app at runtime).
      -

      The appDir field is derived from the .app file path at load time so apps can find their own resources via relative paths. This is necessary because the working directory is shared by all apps in DOS.

      +

      Passed as the sole argument to appMain(). Gives the app access to the shell's GUI context and its own identity. Heap-allocated by shellLoadApp so its address is stable across reallocations of the internal app slot table (apps may safely cache the pointer in their static globals).

      +
        Field                          Type                    Description
      +  -----                          ----                    -----------
      +  shellCtx                       AppContextT *           The shell's GUI context (for creating windows, drawing, etc.).
      +  appId                          int32_t                 This app's unique ID (slot index, 1-based).
      +  appPath[DVX_MAX_PATH]          char[]                  Full path to the .app DXE file.
      +  appDir[DVX_MAX_PATH]           char[]                  Directory containing the .app file (for loading resources).
      +  configDir[DVX_MAX_PATH]        char[]                  Writable config directory (e.g. CONFIG/APPS/KPUNCH/DVXBASIC/).
      +  args[1024]                     char[]                  Launch arguments (empty string if none).
      +  helpFile[DVX_MAX_PATH]         char[]                  Help file path (for F1 context help).
      +  helpTopic[128]                 char[]                  Current help topic ID (updated by the app at runtime).
      +  onHelpQuery                    void (*)(void *ctx)     Optional callback the shell fires on F1 so the app can refresh helpTopic from context.
      +  helpQueryCtx                   void *                  Opaque context pointer passed to onHelpQuery.
      +

      The appDir field is derived from the .app file path at load time so apps can find their own resources via relative paths. This is necessary because the working directory is shared by all apps in DOS. The config directory is mirrored from the app path: an app at APPS/KPUNCH/DVXBASIC/dvxbasic.app gets CONFIG/APPS/KPUNCH/DVXBASIC/.

      AppStateE

        Value                 Description
         -----                 -----------
      @@ -3187,8 +4424,8 @@ img { max-width: 100%; }
       

      Gracefully shut down a single app. Calls the app's shutdownFn (if present), destroys all windows belonging to the app, kills its task (if any), closes the DXE handle, and frees the context. The slot returns to AppStateFreeE.

      shellForceKillApp

      void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
      -

      Forcibly kill an app without calling its shutdown hook. Used by the Task Manager "End Task" or when an app has crashed and cannot be trusted to run cleanup code.

      -

      Cleanup order: windows first (removes from compositor), then task (frees stack), then DXE handle (unmaps code). Closing the DXE before destroying windows would cause callbacks into unmapped code.

      +

      Forcibly kill an app. Used by the Task Manager "End Task" or as part of crash recovery. Calls the app's shutdownFn (if present) so it can unregister callbacks before its DXE is unmapped, then destroys all of the app's windows, kills its task (for main-loop apps), and finally closes the DXE handle.

      +

      Cleanup order: shutdownFn first (so the app can unregister callbacks while still mapped), then windows (removes from compositor), then task (frees stack), then DXE handle (unmaps code). Closing the DXE before destroying windows would cause callbacks into unmapped code.

      shellTerminateAllApps

      void shellTerminateAllApps(AppContextT *ctx);

      Force-kill all running apps. Called during shell shutdown. Iterates all slots and calls shellForceKillApp() on each active app.

      @@ -3222,7 +4459,14 @@ shellConfigPath(ctx, "settings.ini", path, sizeof(path));

    Desktop Callbacks

    -

    The shell provides a notification mechanism for app state changes (load, reap, crash). Desktop managers register a callback to refresh their display when apps come and go.

    +

    The shell wires several AppContextT event hooks (onCtrlEsc, onF1, onTitleChange, idleCallback) during startup and exposes them to other DXEs via function-pointer extern and registration API. Desktop managers register a callback to refresh their display when apps come and go. The Task Manager DXE registers its Ctrl+Esc handler via the shellCtrlEscFn extern pointer (see Task Manager documentation).

    +

    Shell Event Hooks

    +
      AppContextT field    Handler                 Purpose
    +  -----------------    -------                 -------
    +  idleCallback         idleYield               Yields to cooperative tasks when dvxUpdate has no input events and no dirty rects.
    +  onCtrlEsc            ctrlEscHandler          Invokes shellCtrlEscFn if the Task Manager DXE is loaded.
    +  onF1                 f1HelpHandler           Launches the help viewer, optionally passing the focused app's helpFile and helpTopic.
    +  onTitleChange        titleChangeHandler      Runs shellDesktopUpdate so Program Manager window lists refresh.

    shellRegisterDesktopUpdate

    void shellRegisterDesktopUpdate(void (*updateFn)(void));

    Register a callback for app state change notifications. Multiple callbacks are supported. Apps typically call this during appMain() to receive notifications.

    @@ -3238,7 +4482,7 @@ shellConfigPath(ctx, "settings.ini", path, sizeof(path));

    System Information

    -

    Header: shell/shellInfo.h

    +

    Header: dvxshell/shellInf.h

    Thin wrapper around the platform layer's hardware detection. Gathers system information at startup, logs it, and caches the result for display in dialogs.

    shellInfoInit

    void shellInfoInit(AppContextT *ctx);
    @@ -3251,8 +4495,28 @@ shellConfigPath(ctx, "settings.ini", path, sizeof(path));

    dvxSql -- SQL Database Interface

    High-level wrapper around SQLite3 for DVX applications. Manages database connections and result set cursors via integer handles so BASIC code never touches raw pointers. All handles are 1-based; 0 indicates an error or invalid handle.

    Header: sql/dvxSql.h

    +

    SQLite Wrapping

    +

    dvxSql wraps the bundled SQLite3 amalgamation (sql/thirdparty/sqlite/examples/sqlite3.h). It manages two internal dynamic tables keyed by 1-based handles:

    +
      +
    • Database table: each slot holds a sqlite3 * plus a per-database error string and affected-row count.
    • +
    +

    Cursor table: each slot holds a sqlite3_stmt *, the owning database handle, and EOF tracking.

    +

    Growing either table doubles its capacity. Closed slots are recycled by subsequent dvxSqlOpen or dvxSqlQuery calls, keeping handle values stable for the caller.

    Handle Model

    -

    Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it.

    +

    Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it, so callers do not need to track cursor lifetimes per-database.

    +

    Connection Lifecycle

    +
      +
    • dvxSqlOpen allocates a handle and calls sqlite3_open. The database file is created if it does not exist.
    • +
    • Use dvxSqlExec for DDL and non-query DML. On success, dvxSqlAffectedRows returns the row count for the last call on this handle.
    • +
    • Use dvxSqlQuery to obtain a cursor over a SELECT. Iterate with dvxSqlNext and read columns with dvxSqlFieldText, dvxSqlFieldInt, dvxSqlFieldDbl, or dvxSqlFieldByName.
    • +
    +

    Call dvxSqlFreeResult when the cursor is no longer needed. Call dvxSqlClose when the database is no longer needed; any cursors still open on that database are finalized automatically.

    +

    Common Patterns

    +
      +
    • Parameterize user input with dvxSqlEscape before interpolating into SQL strings.
    • +
    • Check dvxSqlError(db) for the last error on a handle; its message is stable until the next operation on the same database.
    • +
    +

    dvxSqlFieldByName matches column names case-insensitively, suitable for most real-world schemas.

    Database Operations

    Cursor Operations

    Utility Functions

    @@ -3432,6 +4696,16 @@ dvxSqlExec(db, sql);

    Text Help Library

    Shared text editing infrastructure library for DVX widget DXEs. Provides cursor blink management, cross-widget selection clearing, word boundary logic, and a complete single-line text editing engine. Used by TextInput, Spinner, ComboBox, AnsiTerm, and other widgets that need text editing behavior.

    Header: texthelp/textHelp.h

    +

    Loaded as: bin/libs/texthelp.lib

    +

    How Widgets Use This Library

    +

    Widget DXEs that implement text editing delegate to textHelp for the four high-cost behaviors:

    +
      +
    • Cursor blink. The library tracks a 250 ms blink timer in a static global and the focused widget reads sCursorBlinkOn (exposed via libdvx) when repainting.
    • +
    • Selection clearing. When a widget gains focus it calls clearOtherSelections(self) so only one widget ever has an active text selection.
    • +
    • Word boundaries. isWordChar, wordStart/wordEnd, and wordBoundaryLeft/wordBoundaryRight implement the logic for double-click word selection and Ctrl+Left/Right navigation in a uniform way.
    • +
    +

    Single-line editing engine. widgetTextEditOnKey, widgetTextEditMouseClick, widgetTextEditDragUpdateLine, and widgetTextEditPaintLine form a pointer-parameterized implementation of keyboard, mouse, drag, and paint behaviors. Widgets (TextInput, Spinner, ComboBox, AnsiTerm) hand the library pointers to their internal buffer, cursor, scroll offset, and selection state.

    +

    The engine is intentionally pointer-parameterized rather than struct-based so widgets can reuse it without adopting a shared state struct. Each widget owns its own buffer and state and passes pointers in on every call.

    Constants

      Constant             Value    Description
       --------             -----    -----------
    @@ -3481,6 +4755,44 @@ dvxSqlExec(db, sql);
    pos int32_t Starting position (character index).

    Returns the index of the first character of the word containing pos.

    +
    +

    wordBoundaryLeft

    +

    Finds the left word boundary for Ctrl+Left navigation. Skips non-word characters first, then skips word characters, leaving the cursor at the start of the previous word.

    +
    int32_t wordBoundaryLeft(const char *buf, int32_t pos);
    +
      Parameter    Type           Description
    +  ---------    ----           -----------
    +  buf          const char *   The text buffer to scan.
    +  pos          int32_t        Starting position (character index).
    +

    Returns the index of the previous word boundary. Returns 0 if pos is already at or before the start of the buffer.

    +
    +
    +

    wordBoundaryRight

    +

    Finds the right word boundary for Ctrl+Right navigation. Skips word characters first, then skips non-word characters, leaving the cursor at the start of the next word.

    +
    int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
    +
      Parameter    Type           Description
    +  ---------    ----           -----------
    +  buf          const char *   The text buffer to scan.
    +  len          int32_t        Length of the text buffer.
    +  pos          int32_t        Starting position (character index).
    +

    Returns the index of the next word boundary. Returns len if pos is already at or past the end of the buffer.

    +
    +
    +

    textEditSaveUndo

    +

    Capture a snapshot of the current buffer contents and cursor position into an undo buffer. Called by widgetTextEditOnKey before every destructive operation (insert, delete, paste, cut). Truncates to bufSize - 1 characters so the null terminator always fits.

    +
    void textEditSaveUndo(char *buf, int32_t len,
    +    int32_t cursor, char *undoBuf,
    +    int32_t *pUndoLen, int32_t *pUndoCursor,
    +    int32_t bufSize);
    +
      Parameter       Type         Description
    +  ---------       ----         -----------
    +  buf             char *       Source buffer to snapshot.
    +  len             int32_t      Current length of source contents.
    +  cursor          int32_t      Current cursor position to save.
    +  undoBuf         char *       Destination undo buffer. If NULL the call is a no-op.
    +  pUndoLen        int32_t *    [out] Length of saved undo contents.
    +  pUndoCursor     int32_t *    [out] Saved cursor position.
    +  bufSize         int32_t      Size of the destination undo buffer including the null terminator.
    +

    widgetTextEditDragUpdateLine

    Called during mouse drag to extend the selection in a single-line text field. Converts a viewport-relative pixel x coordinate to a character position and updates the cursor and selection end accordingly. Auto-scrolls the text when the mouse moves past the left or right visible edge.

    @@ -3613,13 +4925,15 @@ dvxSqlExec(db, sql);

    List Helper Library

    -

    Shared helper routines for dropdown and list-based widget DXEs (ListBox, Dropdown, ComboBox, ListView, TreeView). Provides dropdown arrow rendering, item measurement, keyboard navigation, popup geometry calculation, and popup list painting.

    +

    Shared helper routines for dropdown and list-based widget DXEs (ListBox, Dropdown, ComboBox, ListView). Provides dropdown arrow rendering, item measurement, keyboard navigation, popup geometry calculation, popup list painting, type-ahead search, and popup scrollbar hit testing.

    Header: listhelp/listHelp.h

    +

    Loaded as: bin/libs/listhelp.lib

    Constants

    -
      Constant               Value   Description
    -  --------               -----   -----------
    -  DROPDOWN_BTN_WIDTH     16      Width of the dropdown arrow button in pixels.
    -  DROPDOWN_MAX_VISIBLE   8       Maximum number of items visible in a popup list.
    +
      Constant               Value               Description
    +  --------               -----               -----------
    +  DROPDOWN_BTN_WIDTH     16                  Width of the dropdown arrow button in pixels.
    +  DROPDOWN_MAX_VISIBLE   8                   Maximum number of items visible in a popup list before scrolling.
    +  POPUP_SCROLLBAR_W      SCROLLBAR_WIDTH     Popup vertical scrollbar width (mirrors the core window-manager scrollbar width).

    widgetDrawDropdownArrow

    Draw the triangular dropdown arrow glyph centered at a given position.

    void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops,
    @@ -3649,7 +4963,7 @@ dvxSqlExec(db, sql);
    current Currently selected index. count Total number of items in the list. pageSize Number of visible items (used for PgUp/PgDn step size). -

    Returns the new index, clamped to [0, count-1].

    +

    Returns the new index, clamped to [0, count-1]. Returns -1 for unrecognized keys so callers can detect whether the key was consumed.

    widgetDropdownPopupRect

    Calculate the screen rectangle for a dropdown popup list, positioning it below the owning widget and clamping to screen bounds.

    void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font,
    @@ -3667,7 +4981,7 @@ dvxSqlExec(db, sql);
    popH Output: popup height in pixels.

    The popup is sized to show up to DROPDOWN_MAX_VISIBLE items.

    widgetPaintPopupList

    -

    Render a popup item list with highlight, scrolling, and beveled border.

    +

    Render a popup item list with highlight, scrolling, and beveled border. When itemCount exceeds DROPDOWN_MAX_VISIBLE, draws a vertical scrollbar on the right edge sized according to scrollPos.

    void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops,
         const BitmapFontT *font, const ColorSchemeT *colors,
         int32_t popX, int32_t popY, int32_t popW, int32_t popH,
    @@ -3687,21 +5001,64 @@ dvxSqlExec(db, sql);
    itemCount Total number of items. hoverIdx Index of the highlighted (hovered/selected) item, or -1 for none. scrollPos Index of the first visible item (scroll offset). +

    widgetTypeAheadSearch

    +

    Case-insensitive type-ahead search. Scans items forward from currentIdx + 1, wrapping around, for the next item whose first character matches ch. Used by list and dropdown widgets to jump to an item based on a single keystroke.

    +
    int32_t widgetTypeAheadSearch(char ch, const char **items,
    +    int32_t itemCount, int32_t currentIdx);
    +
      Parameter     Description
    +  ---------     -----------
    +  ch            Character to match (case-insensitive).
    +  items         Array of null-terminated item strings.
    +  itemCount     Number of items in the array.
    +  currentIdx    Currently selected index (search starts at currentIdx + 1 and wraps).
    +

    Returns the index of the next matching item, or -1 if no match is found or itemCount is not positive.

    +

    widgetPopupScrollbarClick

    +

    Hit-test a popup scrollbar click. Tests whether the (x, y) coordinate falls on the popup's vertical scrollbar track; if so, updates scrollPos for up/down arrow clicks or page-up/page-down clicks in the trough. No-op on the thumb itself (callers handle drag separately).

    +
    bool widgetPopupScrollbarClick(int32_t x, int32_t y,
    +    int32_t popX, int32_t popY, int32_t popW, int32_t popH,
    +    int32_t itemCount, int32_t visibleItems,
    +    int32_t *scrollPos);
    +
      Parameter       Description
    +  ---------       -----------
    +  x               Click X in the popup's coordinate space.
    +  y               Click Y in the popup's coordinate space.
    +  popX            Popup X origin.
    +  popY            Popup Y origin.
    +  popW            Popup width.
    +  popH            Popup height.
    +  itemCount       Total number of items.
    +  visibleItems    Number of visible rows in the popup.
    +  scrollPos       [in/out] Current scroll offset; updated in place when the click hits the scrollbar.
    +

    Returns true if the click landed on the scrollbar (even if scrollPos was not changed, for example clicking the thumb), false if the click was in the item list area. When itemCount is less than or equal to DROPDOWN_MAX_VISIBLE, always returns false (there is no scrollbar).

    Task Manager

    -

    System-level task manager for DVX. Displays running applications and per-app memory usage. Always accessible via Ctrl+Esc regardless of which application is focused. Persists independently of the desktop app (Program Manager).

    +

    System-level task manager for DVX. Displays all running applications with a six-column ListView (Name, Title, File, Type, Memory, Status) and exposes Switch To, End Task, and Run... operations. Always accessible via Ctrl+Esc regardless of which application is focused. Persists independently of the desktop app (Program Manager).

    Header: taskmgr/shellTaskMgr.h

    Loaded as: bin/libs/taskmgr.lib

    +

    Integration

    +

    The task manager is a shell-level library, not a regular DXE app. At DXE load time a GCC constructor function registers the Task Manager with the shell by setting the shellCtrlEscFn function pointer (declared extern in dvxshell/shellApp.h). The shell invokes shellCtrlEscFn on every Ctrl+Esc keystroke; if taskmgr.lib is not loaded the pointer stays NULL and Ctrl+Esc does nothing.

    +

    The Task Manager owns its window on behalf of the shell itself (appId = 0), not any running app, which is why it survives when the desktop app terminates. It registers shellTaskMgrRefresh with shellRegisterDesktopUpdate so the list refreshes live as apps load, reap, or crash.

    +

    Integration with libtasks

    +

    The Task Manager reads from the shell's app slot table (shellGetApp, shellAppSlotCount, shellRunningAppCount) rather than from the libtasks scheduler directly. Each ShellAppT slot carries its cooperative task ID when hasMainLoop is true; End Task calls shellForceKillApp, which in turn calls tsKill on that task ID (for main-loop apps) after destroying the app's windows.

    +

    Features

    +
      +
    • Six-column ListView showing Name, Title, File, Type (Task or Callback), Memory (from dvxMemGetAppUsage), and Status for every running app.
    • +
    • Switch To button (Alt+S): raises and focuses the selected app's topmost window, restoring it if minimized. Also triggered by double-clicking a row.
    • +
    • End Task button (Alt+E): force-kills the selected app via shellForceKillApp.
    • +
    • Run... button (Alt+R): opens a file dialog to browse for and launch a .app file.
    • +
    • Status bar showing running app count plus total and used system memory.
    • +
    +

    Single-instance: reopening the Task Manager while it is already visible raises and focuses the existing window rather than creating a new one.

    shellTaskMgrOpen

    Open the Task Manager window, or raise it to the front if already open.

    void shellTaskMgrOpen(AppContextT *ctx);
      Parameter   Description
       ---------   -----------
    -  ctx         Application context (from dvxInit). Required for window creation and event registration.
    -

    Called by the shell's global Ctrl+Esc handler.

    + ctx Application context (from dvxInit). Required for window creation, stack manipulation, and registering the desktop-update callback. +

    Called by the shell's global Ctrl+Esc handler via the shellCtrlEscFn hook.

    shellTaskMgrRefresh

    -

    Refresh the task list display. Call this when applications are launched or terminated so the Task Manager reflects the current state.

    +

    Rebuild the task list display from the current shell app slot table and refresh the status bar. Registered with shellRegisterDesktopUpdate at open time so it is invoked whenever the shell detects app state changes.

    void shellTaskMgrRefresh(void);

    Takes no parameters. Safe to call even if the Task Manager window is not currently open (the call is a no-op in that case).

    @@ -3715,22 +5072,43 @@ dvxSqlExec(db, sql);

    secLink -- Convenience wrapper: channel multiplexing, per-packet encryption

    Loaded as: bin/libs/kpunch/serial/serial.lib

    +

    Layered Architecture

    +
    +--------------------------------------------------+
    +|  Application                                     |
    +|                                                  |
    +|  secLinkSend()  -- send on channel, opt. encrypt |
    +|  secLinkPoll()  -- receive + dispatch            |
    ++--------------------------------------------------+
    +|  secLink  (channel multiplex, TX/RX ciphers)     |
    ++--------------------------------------------------+
    +|  packet   (HDLC framing, CRC-16, Go-Back-N ARQ)  |
    ++--------------------------------------------------+
    +|  security -- DH key exchange, XTEA-CTR, DRBG     |
    +|            (used by secLink during handshake)    |
    ++--------------------------------------------------+
    +|  rs232    (ISR-driven UART, 2048-byte rings)     |
    ++--------------------------------------------------+
    +|  UART hardware (8250 / 16450 / 16550 / 16550A)   |
    ++--------------------------------------------------+
    +

    The security layer is invoked by secLink during the Diffie-Hellman handshake and for per-packet cipher operations. It has no runtime coupling to rs232 or packet, so it can be used standalone for cryptographic operations independent of the serial stack.

    Layer 1: RS-232 UART Driver

    Layer 2: Packet Transport

    Layer 3: Security (DH + XTEA)

    Layer 4: Secure Link

    +

    End-to-End Example

    RS-232 UART Driver

    ISR-driven serial port driver supporting up to 4 simultaneous COM ports. A shared ISR drains UART FIFOs into per-port ring buffers (2048 bytes, power-of-2 for fast index wrapping). Flow control (XON/XOFF, RTS/CTS, DTR/DSR) operates within the ISR using watermark thresholds. All ISR data structures are DPMI-locked to prevent page faults.

    Header: rs232/rs232.h

    Port Constants

    -
      Constant      Value   Description
    -  --------      -----   -----------
    -  RS232_COM1    0       First serial port.
    -  RS232_COM2    1       Second serial port.
    -  RS232_COM3    2       Third serial port.
    -  RS232_COM4    3       Fourth serial port.
    +
      Constant          Value   Description
    +  --------          -----   -----------
    +  RS232_COM1        0       First serial port.
    +  RS232_COM2        1       Second serial port.
    +  RS232_COM3        2       Third serial port.
    +  RS232_COM4        3       Fourth serial port.
    +  RS232_NUM_PORTS   4       Total number of supported COM ports.

    Handshake Modes

      Constant                  Value   Description
       --------                  -----   -----------
    @@ -3793,28 +5171,29 @@ dvxSqlExec(db, sql);
    len Maximum bytes to read.

    Returns the number of bytes actually read (may be less than len).

    rs232Write

    -

    Blocking polled write directly to UART transmit holding register. Bypasses the TX ring buffer. Use for small, immediate writes.

    +

    Blocking polled write directly to UART transmit holding register. Bypasses the TX ring buffer. Polls LSR_TX_HOLD_EMPTY before writing each byte. Use for small, immediate writes where order matters (the packet layer uses this for frame transmission).

    int rs232Write(int com, const char *data, int len);
    -

    Returns RS232_SUCCESS or a negative error code.

    +

    Returns the number of bytes actually written (may be less than len if software flow control is asserted), or a negative error code (RS232_ERR_INVALID_PORT, RS232_ERR_NOT_OPEN, RS232_ERR_NULL_PTR).

    rs232WriteBuf

    Non-blocking write to the transmit ring buffer. The ISR drains buffered data to the UART asynchronously.

    int rs232WriteBuf(int com, const char *data, int len);

    Returns the number of bytes actually queued (may be less than len if the buffer is full).

    rs232Set

    -

    Change all line parameters on an open port.

    +

    Change all line parameters on an open port. Internally calls rs232SetBps, rs232SetData, rs232SetParity, rs232SetStop, and rs232SetHandshake in sequence. Returns RS232_SUCCESS on success or the first negative error code encountered.

    int rs232Set(int com, int32_t bps, int dataBits,
         char parity, int stopBits, int handshake);

    Configuration Getters

      Function                   Returns
       --------                   -------
       rs232GetBase(com)          I/O base address.
    -  rs232GetBps(com)           Current baud rate.
    -  rs232GetData(com)          Data bits setting.
    -  rs232GetParity(com)        Parity character ('N', 'O', 'E', 'M', 'S').
    -  rs232GetStop(com)          Stop bits setting.
    -  rs232GetHandshake(com)     Handshake mode.
    +  rs232GetBps(com)           Current baud rate (int32_t).
    +  rs232GetData(com)          Data bits setting (5-8).
    +  rs232GetParity(com)        Parity character: 'n', 'o', 'e', 'm', or 's' (lowercase).
    +  rs232GetStop(com)          Stop bits setting (1 or 2).
    +  rs232GetHandshake(com)     Handshake mode (RS232_HANDSHAKE_*).
       rs232GetIrq(com)           IRQ number.
       rs232GetUartType(com)      Detected UART type (RS232_UART_*).
    +

    rs232GetParity returns a lowercase character on success. Callers comparing against uppercase constants must use either both cases or tolower().

    Status Getters

      Function                   Returns
       --------                   -------
    @@ -3865,10 +5244,10 @@ dvxSqlExec(db, sql);
    PKT_ERR_NOT_OPEN -2 Connection is not open. PKT_ERR_WOULD_BLOCK -4 Operation would block. PKT_ERR_OVERFLOW -5 Buffer overflow. - PKT_ERR_INVALID_PARAM -6 Invalid parameter. - PKT_ERR_TX_FULL -7 Transmit window is full. + PKT_ERR_INVALID_PARAM -6 Invalid parameter (NULL handle, bad data pointer, length out of range). + PKT_ERR_TX_FULL -7 Transmit window is full (non-blocking send only). PKT_ERR_NO_DATA -8 No data available. - PKT_ERR_DISCONNECTED -9 Remote side disconnected. + PKT_ERR_DISCONNECTED -9 Serial port returned a negative read (disconnect/error).

    PktRecvCallbackT

    Callback type for received packets.

    typedef void (*PktRecvCallbackT)(void *ctx,
    @@ -3879,43 +5258,45 @@ dvxSqlExec(db, sql);
    data Payload bytes (valid only during the callback). len Payload length.

    pktOpen

    -

    Open a packetized connection over an already-open COM port.

    +

    Open a packetized connection over an already-open COM port. Only one packet connection may exist on a given COM port at a time; a second pktOpen call on the same port returns NULL to avoid silent framing corruption.

    PktConnT *pktOpen(int com, int windowSize,
         PktRecvCallbackT callback, void *callbackCtx);
      Parameter     Description
       ---------     -----------
       com           COM port index (RS232_COM1..RS232_COM4). Must already be open via rs232Open.
    -  windowSize    Sliding window size (1..PKT_MAX_WINDOW). Pass 0 for PKT_DEFAULT_WINDOW.
    -  callback      Called when a complete, CRC-verified packet is received.
    +  windowSize    Sliding window size (1..PKT_MAX_WINDOW). Pass 0 for PKT_DEFAULT_WINDOW. Values greater than PKT_MAX_WINDOW are clamped.
    +  callback      Called when a complete, CRC-verified, in-order packet is received. The data pointer is valid only during the callback.
       callbackCtx   User pointer passed to the callback.
    -

    Returns an opaque PktConnT handle, or NULL on failure.

    +

    Returns an opaque PktConnT handle, or NULL on failure (invalid port, duplicate open, or allocation failure).

    pktClose

    -

    Close a packetized connection. Does not close the underlying COM port.

    +

    Close a packetized connection and release its registry slot. Does not close the underlying COM port. The caller is responsible for calling rs232Close separately when no longer needed.

    void pktClose(PktConnT *conn);

    pktSend

    -

    Send a data packet.

    +

    Send a data packet. The payload is copied into an internal retransmit slot before transmission, so the caller may reuse the supplied buffer immediately.

    int pktSend(PktConnT *conn, const uint8_t *data,
         int len, bool block);
      Parameter   Description
       ---------   -----------
       conn        Connection handle from pktOpen.
    -  data        Payload bytes (up to PKT_MAX_PAYLOAD).
    +  data        Payload bytes (1..PKT_MAX_PAYLOAD).
       len         Payload length.
    -  block       If true, block until transmit window has space. If false, return PKT_ERR_TX_FULL when the window is full.
    -

    Returns PKT_SUCCESS or a negative error code.

    + block If true, poll pktPoll internally until transmit window has space. If false, return PKT_ERR_TX_FULL immediately when the window is full. +

    Returns PKT_SUCCESS on success, PKT_ERR_INVALID_PARAM for NULL/out-of-range input, PKT_ERR_TX_FULL for a non-blocking call with a full window, or PKT_ERR_DISCONNECTED if the serial port drops during a blocking wait.

    pktPoll

    -

    Poll for incoming data, process received frames, and handle retransmits. Must be called frequently in the main loop.

    +

    Poll for incoming data, process received frames, and handle retransmits. Must be called frequently in the main loop. The callback is invoked synchronously for each complete, CRC-verified, in-order data frame, so the caller should be prepared for reentrant pktSend calls from within the callback.

    int pktPoll(PktConnT *conn);
    -

    Returns the number of valid data packets delivered to the callback.

    +

    Returns the number of valid data packets delivered to the callback this call, or PKT_ERR_INVALID_PARAM if conn is NULL, or PKT_ERR_DISCONNECTED if the serial layer returned a negative read.

    pktReset

    -

    Reset the connection state (sequence numbers, buffers) and send a RST frame to the remote side.

    +

    Reset the local connection state (sequence numbers, transmit slots, RX state machine) and send a RST frame to the remote side. The remote's RST handler clears its own state and echoes a RST back. Useful for recovering from a desynchronized connection.

    int pktReset(PktConnT *conn);
    +

    Returns PKT_SUCCESS on success or PKT_ERR_INVALID_PARAM if conn is NULL.

    pktCanSend

    -

    Check whether there is room in the transmit window for another packet.

    +

    Check whether there is room in the transmit window for another packet. Returns false when conn is NULL or the transmit window is full.

    bool pktCanSend(PktConnT *conn);

    pktGetPending

    -

    Get the number of unacknowledged packets currently in the transmit window.

    +

    Get the number of unacknowledged packets currently in the transmit window. Ranges from 0 (all sent packets acknowledged) to windowSize (window full).

    int pktGetPending(PktConnT *conn);
    +

    Returns the pending count on success, or PKT_ERR_INVALID_PARAM if conn is NULL.

    Security (DH + XTEA)

    @@ -3974,10 +5355,11 @@ void secCipherCrypt(SecCipherT *c, void secCipherDestroy(SecCipherT *c);
      Function             Description
       --------             -----------
    -  secCipherCreate      Allocate a cipher context with a 128-bit key.
    -  secCipherSetNonce    Set the initial counter value (nonce). Must be unique per session.
    -  secCipherCrypt       Encrypt or decrypt data in place. The counter increments automatically.
    -  secCipherDestroy     Free the cipher context.
    + secCipherCreate Allocate a cipher context with a 128-bit key. Returns NULL on allocation failure or NULL key. Initial counter is zero. + secCipherSetNonce Set the initial counter value (nonce). Must be unique per session. Both the stored nonce and the running counter are set to (nonceLo, nonceHi). + secCipherCrypt Encrypt or decrypt data in place. The counter increments by one every 8 bytes (one XTEA block). + secCipherDestroy Securely zero the cipher context (key and counter state) and free its memory. +

    secCipherCreate returns NULL if the key pointer is NULL or allocation fails. secCipherCrypt is a no-op when any of c/data is NULL or len <= 0.

    +
    +

    End-to-End Example

    +

    Below is a complete secure BBS client that opens COM1, exchanges DH keys, and then shuttles terminal traffic over a single encrypted channel. It demonstrates the typical serial stack usage: open the link, seed the RNG, run the handshake, then enter a poll+send loop.

    +
    #include "rs232/rs232.h"
    +#include "packet/packet.h"
    +#include "security/security.h"
    +#include "seclink/secLink.h"
    +#include <stdio.h>
    +#include <string.h>
    +
    +#define CHAN_TERMINAL 0
    +
    +static void onPacket(void *ctx, const uint8_t *data,
    +                     int len, uint8_t channel)
    +{
    +    (void)ctx;
    +    if (channel == CHAN_TERMINAL) {
    +        fwrite(data, 1, len, stdout);
    +        fflush(stdout);
    +    }
    +}
    +
    +
    +int main(void) {
    +    // Seed the RNG from hardware entropy plus any user-derived
    +    // randomness (keyboard timing, etc.).
    +    uint8_t entropy[16];
    +    int got = secRngGatherEntropy(entropy, sizeof(entropy));
    +    secRngSeed(entropy, got);
    +
    +    // Open the link at 115200 8N1 with RTS/CTS flow control.
    +    SecLinkT *link = secLinkOpen(
    +        RS232_COM1, 115200, 8, 'N', 1,
    +        RS232_HANDSHAKE_RTSCTS,
    +        onPacket, NULL);
    +    if (!link) {
    +        fprintf(stderr, "secLinkOpen failed\n");
    +        return 1;
    +    }
    +
    +    // Run the DH key exchange.  Blocks until both sides
    +    // have swapped public keys and derived cipher keys.
    +    if (secLinkHandshake(link) != SECLINK_SUCCESS) {
    +        fprintf(stderr, "Handshake failed\n");
    +        secLinkClose(link);
    +        return 1;
    +    }
    +
    +    // Send an encrypted banner and then relay keyboard input.
    +    const char *hello = "Hello, secure BBS!\r\n";
    +    secLinkSend(link, (const uint8_t *)hello,
    +                (int)strlen(hello), CHAN_TERMINAL, true, true);
    +
    +    while (!feof(stdin)) {
    +        // Deliver any incoming packets to onPacket().
    +        secLinkPoll(link);
    +
    +        // If the user typed something, forward it encrypted.
    +        int ch = getchar();
    +        if (ch == EOF) {
    +            break;
    +        }
    +        uint8_t byte = (uint8_t)ch;
    +        secLinkSend(link, &byte, 1, CHAN_TERMINAL, true, true);
    +    }
    +
    +    secLinkClose(link);
    +    return 0;
    +}
    +

    Notes:

    +
      +
    • secLinkOpen internally calls rs232Open and pktOpen. secLinkClose tears them down in reverse order.
    • +
    • The RNG must be seeded before secLinkHandshake. secRngGatherEntropy provides roughly 20 bits of hardware entropy; supplement with user interaction timing for cryptographic use.
    • +
    • secLinkPoll must be called frequently to drain the RX ring buffer, process ACKs, and dispatch received packets to the callback.
    • +
    +

    For bulk transfers larger than SECLINK_MAX_PAYLOAD (254 bytes), use secLinkSendBuf which splits the data into chunks automatically.

    BASIC Runtime Library

    @@ -4391,10 +5868,10 @@ int32_t basVmGetCurrentLine(const BasVmT *vm);

    DVX Widget System

    -

    Complete reference for the DVX GUI widget toolkit. All widgets are implemented as dynamically loaded DXE modules. They are created via convenience macros that wrap the per-widget API function tables. The base WidgetT structure is defined in core/dvxWidget.h; individual widget headers live in widgets/.

    +

    Complete reference for the DVX GUI widget toolkit. All widgets are implemented as dynamically loaded DXE modules. They are created via convenience macros that wrap the per-widget API function tables. The base WidgetT structure is defined in libdvx/dvxWgt.h; individual widget headers live in widgets/.

    Individual widgets are documented in their own sections. See the table of contents for the full list.

    Base WidgetT (Common Properties, Events, and Operations)

    -

    Every widget inherits from the WidgetT structure defined in core/dvxWidget.h. The fields and callbacks listed here are available on all widget types.

    +

    Every widget inherits from the WidgetT structure defined in libdvx/dvxWgt.h. The fields and callbacks listed here are available on all widget types.

    Common Properties

      Field              Type                Description
       -----              ----                -----------
    @@ -4461,178 +5938,328 @@ int32_t basVmGetCurrentLine(const BasVmT *vm);

    AnsiTerm

    -

    A VT100/ANSI-compatible terminal emulator widget designed for connecting to BBS systems over the serial link. Uses a traditional text-mode cell buffer (character + attribute byte pairs) with the CP437 character set and 16-color CGA palette. Supports cursor movement, screen/line erase, scrolling regions, SGR colors, and scrollback history. Communication is abstracted through read/write function pointers, allowing the terminal to work with raw serial ports, the secLink encrypted channel, or any other byte-oriented transport.

    -

    Header: widgets/widgetAnsiTerm.h

    +

    A VT100/ANSI-compatible terminal emulator widget designed for connecting to BBS systems over the serial link. Uses a traditional text-mode cell buffer (character + attribute byte pairs) with the CP437 character set and 16-color CGA palette. Supports cursor movement, screen/line erase, scrolling regions, SGR colors, DEC private modes, and a configurable scrollback buffer. Communication is abstracted through read/write function pointers, allowing the terminal to work with raw serial ports, the secLink encrypted channel, or any other byte-oriented transport.

    +

    The widget renders through two paint paths: a full paint used by the normal widget pipeline, and a fast incremental repaint (wgtAnsiTermRepaint) that pushes dirty rows directly to the window content buffer for low-latency serial echo.

    +

    Header: widgets/ansiTerm.h

    Creation

    WidgetT *term = wgtAnsiTerm(parent, 80, 25);
    -

    Macros

    -
      Macro                                              Description
    -  -----                                              -----------
    -  wgtAnsiTerm(parent, cols, rows)                    Create an ANSI terminal widget with the given column and row dimensions.
    -  wgtAnsiTermWrite(w, data, len)                     Write raw bytes into the terminal's ANSI parser. data is a const uint8_t * buffer, len is the byte count.
    -  wgtAnsiTermClear(w)                                Clear the terminal screen and reset the cursor to the home position.
    -  wgtAnsiTermSetComm(w, ctx, readFn, writeFn)        Attach a communication channel. readFn and writeFn are I/O callbacks; ctx is passed as their first argument.
    -  wgtAnsiTermSetScrollback(w, maxLines)              Set the maximum number of scrollback lines. Lines scrolled off the top are saved in a circular buffer.
    -  wgtAnsiTermPoll(w)                                 Poll the communication channel for incoming data and feed it into the ANSI parser.
    -  wgtAnsiTermRepaint(w, outY, outH)                  Fast repaint path that renders dirty rows directly into the window's content buffer, bypassing the widget pipeline. Returns the dirty region via outY/outH.
    +

    API Functions

    +
      Function                                                      Description
    +  --------                                                      -----------
    +  WidgetT *wgtAnsiTerm(parent, cols, rows)                      Create an ANSI terminal widget with the given column/row dimensions.
    +  void wgtAnsiTermWrite(w, data, len)                           Write raw bytes into the terminal's ANSI parser. data is a const uint8_t * buffer.
    +  void wgtAnsiTermClear(w)                                      Clear the terminal screen and reset the cursor to the home position.
    +  void wgtAnsiTermSetComm(w, ctx, readFn, writeFn)              Attach a communication channel. readFn and writeFn are I/O callbacks; ctx is passed as their first argument.
    +  void wgtAnsiTermSetScrollback(w, maxLines)                    Set the maximum number of scrollback lines.
    +  int32_t wgtAnsiTermPoll(w)                                    Poll the communication channel for incoming data and feed it into the parser. Returns number of bytes consumed.
    +  int32_t wgtAnsiTermRepaint(w, outY, outH)                     Fast repaint path that renders dirty rows directly into the window's content buffer, bypassing the widget pipeline. Returns the number of rows repainted and the dirty region via outY/outH.
    +

    API Struct (wgtRegisterApi "ansiterm")

    +

    The sApi struct exposes these function pointers:

    +
      Slot             Function
    +  ----             --------
    +  create           wgtAnsiTerm
    +  write            wgtAnsiTermWrite
    +  clear            wgtAnsiTermClear
    +  setComm          wgtAnsiTermSetComm
    +  setScrollback    wgtAnsiTermSetScrollback
    +  poll             wgtAnsiTermPoll
    +  repaint          wgtAnsiTermRepaint

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       Cols         Integer    Read-only    Number of columns.
       Rows         Integer    Read-only    Number of rows.
    -  Scrollback   Integer    Write-only   Maximum scrollback lines.
    + Scrollback Integer Write-only Maximum number of scrollback lines.

    Methods (BASIC Interface)

      Method       Description
       ------       -----------
       Clear        Clear the terminal screen.
    -  Write        Write a string into the terminal.
    + Poll Process any pending bytes on the attached communication channel. + Write Write a string into the terminal (with ANSI escape processing).

    Events

    AnsiTerm uses the common events only. No widget-specific events are defined.

    +

    Keyboard Shortcuts

    +

    Within the terminal widget:

    +
      Key              Action
    +  ---              ------
    +  Ctrl+C           Copy selection to the clipboard (when a selection exists).
    +  Ctrl+V           Send the clipboard contents to the attached write function.
    +  Arrows           Send VT100 cursor escape sequences.
    +  Home/End         Send VT100 Home/End sequences.
    +  PgUp/PgDn/Del    Send the corresponding VT100 escape sequences.

    Box (VBox / HBox / Frame)

    -

    Container widgets that arrange their children in a vertical column (VBox), horizontal row (HBox), or a titled group box (Frame). These are the primary layout building blocks. Children are laid out using a flexbox-like algorithm with weight-based extra-space distribution.

    -

    Header: widgets/widgetBox.h

    +

    Container widgets that arrange their children in a vertical column (VBox), horizontal row (HBox), or a titled group box (Frame). These are the primary layout building blocks. Children are laid out using a weight-based algorithm with configurable spacing, padding, and alignment.

    +

    Frame is a labelled grouping container with a Motif-style beveled border. Its title text sits centered vertically on the top border line with a small background-filled gap behind the title, giving the classic Windows 3.1 / Motif group box appearance. Internally, Frame behaves like a VBox for layout purposes.

    +

    The widget DXE registers three separate interface entries ("vbox", "hbox", "frame") so the form designer can create each type independently.

    +

    Header: widgets/box.h

    Creation

    -
      Macro                        Description
    -  -----                        -----------
    -  wgtVBox(parent)              Create a vertical box container. Children are stacked top to bottom.
    -  wgtHBox(parent)              Create a horizontal box container. Children are placed left to right.
    -  wgtFrame(parent, title)      Create a titled group box (a VBox with a border and label).
    +
    WidgetT *row   = wgtHBox(parent);
    +WidgetT *col   = wgtVBox(parent);
    +WidgetT *group = wgtFrame(parent, "Options");
    +

    API Functions

    +
      Function                                            Description
    +  --------                                            -----------
    +  WidgetT *wgtVBox(parent)                            Create a vertical box container. Children stack top-to-bottom.
    +  WidgetT *wgtHBox(parent)                            Create a horizontal box container. Children stack left-to-right.
    +  WidgetT *wgtFrame(parent, title)                    Create a titled group box. Children stack vertically inside the bordered frame. The title string may include a '&' prefix for an accelerator key.
    +

    API Struct (wgtRegisterApi "box")

    +
      Slot     Function
    +  ----     --------
    +  vBox     wgtVBox
    +  hBox     wgtHBox
    +  frame    wgtFrame
    +

    The designer also registers per-type APIs: "vbox", "hbox", and "frame" each expose a single create slot.

    Properties

    -

    Box containers use the common WidgetT fields for layout control:

    -
      Property     Description
    -  --------     -----------
    -  align        Main-axis alignment of children. HBox: Start=left, Center=center, End=right. VBox: Start=top, Center=center, End=bottom.
    -  spacing      Gap between children (tagged size).
    -  padding      Internal padding around children (tagged size).
    -  weight       Controls how the box itself stretches within its parent.
    +

    Box containers use the common WidgetT fields for layout control. There are no widget-specific properties registered with the interface system.

    +
      Field      Description
    +  -----      -----------
    +  align      Main-axis alignment of children. HBox: Start=left, Center=center, End=right. VBox: Start=top, Center=center, End=bottom.
    +  spacing    Gap between children (tagged size).
    +  padding    Internal padding around children (tagged size).
    +  weight     Controls how the box itself stretches within its parent.
    +

    Frame text is managed via the standard wgtSetText() / wgtGetText() interface (the widget has WCLASS_HAS_TEXT). BASIC code can set its title via the generic "Caption" or "Text" property.

    Events

    -

    Containers use the common events only. No widget-specific events.

    +

    Containers use the common events only. No widget-specific events or methods are registered.

    +

    Default Event

    +

    "Click" on all three types.

    Button

    -

    A push button with a text label. Fires onClick when pressed and released. Supports keyboard activation via accelerator keys.

    -

    Header: widgets/widgetButton.h

    +

    A push button with a text label. Fires onClick when pressed and released. Uses a two-phase press model: the button visually depresses on mouse-down and fires onClick only when the mouse is released while still inside the button bounds -- dragging the mouse off cancels the press. Supports accelerator keys via '&' prefix in the text (e.g. "&OK" underlines 'O' and binds Alt+O).

    +

    Disabled buttons use the classic "embossed" rendering (highlight text offset by +1,+1 and shadow text at 0,0) for a chiseled appearance.

    +

    Header: widgets/button.h

    Creation

    -
    WidgetT *btn = wgtButton(parent, "OK");
    -

    Macro

    -
      Macro                        Description
    -  -----                        -----------
    -  wgtButton(parent, text)      Create a push button with the given label text.
    +
    WidgetT *btn = wgtButton(parent, "&OK");
    +btn->onClick = onOkClicked;
    +

    API Functions

    +
      Function                                      Description
    +  --------                                      -----------
    +  WidgetT *wgtButton(parent, text)              Create a push button with the given label text. Pass NULL for no text. Text is copied into the widget.
    +

    API Struct (wgtRegisterApi "button")

    +
      Slot     Function
    +  ----     --------
    +  create   wgtButton

    Properties

    -

    Uses common WidgetT properties. Set accelKey for keyboard shortcut. Use wgtSetText() / wgtGetText() to change the label.

    +

    Uses common WidgetT properties. Label text is managed via wgtSetText() / wgtGetText(). Set accelKey for keyboard shortcut (automatically parsed from '&' prefix in the text). The button is focusable (WCLASS_FOCUSABLE) and draws a focus rectangle when it holds keyboard focus.

    +

    No widget-specific properties are registered with the interface system. BASIC code sets the label via the generic "Caption" or "Text" property.

    +

    Methods

    +

    No widget-specific methods.

    Events

      Callback     Description
       --------     -----------
    -  onClick      Fires when the button is clicked (press + release).
    + onClick Fires when the button is clicked (mouse press and release inside, or Space/Enter when focused). +

    Default Event

    +

    "Click" (VB basName: CommandButton, namePrefix: Command).

    Canvas

    -

    A freeform drawing surface with a fixed-size pixel buffer. Provides drawing primitives (lines, rectangles, circles, text, individual pixels) and supports save/load to BMP files. Mouse interaction is available via a callback.

    -

    Header: widgets/widgetCanvas.h

    +

    A freeform drawing surface with a fixed-size pixel buffer stored in the display's native pixel format. Provides drawing primitives (lines, rectangles, circles, text, individual pixels) and supports save/load to image files (BMP/PNG/etc. via dvxLoadImage/dvxSaveImage). Mouse interaction is available via a callback. The buffer is rendered to screen as a straight blit with no per-pixel conversion.

    +

    Canvas coordinates are independent of widget position: (0,0) is the top-left of the canvas content, not the widget. The widget frames the buffer with a 2-pixel sunken bevel.

    +

    Header: widgets/canvas.h

    Creation

    WidgetT *cv = wgtCanvas(parent, 320, 200);
    -

    Macros

    -
      Macro                                    Description
    -  -----                                    -----------
    -  wgtCanvas(parent, w, h)                  Create a canvas with the given pixel dimensions.
    -  wgtCanvasClear(w, color)                 Fill the entire canvas with a solid color.
    -  wgtCanvasSetPenColor(w, color)           Set the drawing pen color.
    -  wgtCanvasSetPenSize(w, size)             Set the drawing pen size in pixels.
    -  wgtCanvasDrawLine(w, x0, y0, x1, y1)    Draw a line from (x0,y0) to (x1,y1).
    -  wgtCanvasDrawRect(w, x, y, width, height)    Draw a rectangle outline.
    -  wgtCanvasFillRect(w, x, y, width, height)    Draw a filled rectangle.
    -  wgtCanvasFillCircle(w, cx, cy, radius)   Draw a filled circle.
    -  wgtCanvasSetPixel(w, x, y, color)        Set a single pixel to the given color.
    -  wgtCanvasGetPixel(w, x, y)              Get the color of a single pixel.
    -  wgtCanvasDrawText(w, x, y, text)         Draw text at the given position using the current pen color.
    -  wgtCanvasSetMouseCallback(w, cb)         Set a mouse interaction callback. Signature: void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag). Receives canvas-relative coordinates and whether the mouse is being dragged.
    -  wgtCanvasSave(w, path)                   Save the canvas contents to a BMP file.
    -  wgtCanvasLoad(w, path)                   Load a BMP file into the canvas.
    -

    Events

    -
      Callback                                     Description
    -  --------                                     -----------
    -  onClick                                      Fires when the canvas is clicked.
    -  Mouse callback (via wgtCanvasSetMouseCallback)   Fires on mouse down and drag with canvas-local coordinates.
    +

    API Functions

    +
      Function                                                             Description
    +  --------                                                             -----------
    +  WidgetT *wgtCanvas(parent, w, h)                                     Create a canvas with the given pixel dimensions. Buffer is initialized to white.
    +  void wgtCanvasClear(w, color)                                        Fill the entire canvas with a packed color.
    +  void wgtCanvasSetPenColor(w, color)                                  Set the drawing pen color (packed display color).
    +  void wgtCanvasSetPenSize(w, size)                                    Set the drawing pen size in pixels (affects DrawLine dot thickness).
    +  void wgtCanvasDrawLine(w, x0, y0, x1, y1)                           Draw a line using Bresenham's algorithm with the current pen color and size.
    +  void wgtCanvasDrawRect(w, x, y, width, height)                      Draw a 1-pixel rectangle outline using the current pen color.
    +  void wgtCanvasFillRect(w, x, y, width, height)                      Fill a rectangle using the current pen color.
    +  void wgtCanvasFillCircle(w, cx, cy, radius)                         Fill a circle using the current pen color (integer sqrt; no FPU needed).
    +  void wgtCanvasSetPixel(w, x, y, color)                              Set a single pixel to the given packed color.
    +  uint32_t wgtCanvasGetPixel(w, x, y)                                 Read a single pixel. Returns packed color.
    +  void wgtCanvasDrawText(w, x, y, text)                               Draw text at the given canvas coordinates using the current pen color.
    +  void wgtCanvasSetMouseCallback(w, cb)                                Set a mouse callback. Signature: void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag). cx/cy are canvas-relative, drag=true on mouse-move during press.
    +  int32_t wgtCanvasSave(w, path)                                       Save the canvas buffer to an image file. Returns 0 on success, -1 on error.
    +  int32_t wgtCanvasLoad(w, path)                                       Load an image file into the canvas (resizes buffer to match). Returns 0 on success, -1 on error.
    +  void wgtCanvasResize(w, newW, newH)                                 Resize the canvas buffer. New pixels are filled with white.
    +

    API Struct (wgtRegisterApi "canvas")

    +
      Slot                 Function
    +  ----                 --------
    +  create               wgtCanvas
    +  clear                wgtCanvasClear
    +  setPenColor          wgtCanvasSetPenColor
    +  setPenSize           wgtCanvasSetPenSize
    +  setMouseCallback     wgtCanvasSetMouseCallback
    +  save                 wgtCanvasSave
    +  load                 wgtCanvasLoad
    +  drawLine             wgtCanvasDrawLine
    +  drawRect             wgtCanvasDrawRect
    +  fillRect             wgtCanvasFillRect
    +  fillCircle           wgtCanvasFillCircle
    +  setPixel             wgtCanvasSetPixel
    +  getPixel             wgtCanvasGetPixel
    +  drawText             wgtCanvasDrawText
    +  resize               wgtCanvasResize
    +

    Properties

    +

    Canvas has no widget-specific properties registered with the interface system. Drawing state (pen color, pen size) is managed by the API functions above.

    Methods (BASIC Interface)

    -
      Method       Description
    -  ------       -----------
    -  Clear        Clear the canvas to a given color.
    +
      Method                                        Description
    +  ------                                        -----------
    +  Clear color%                                  Fill the entire canvas with a 0x00RRGGBB color.
    +  DrawLine x0%, y0%, x1%, y1%, color%           Draw a line between two points.
    +  DrawRect x%, y%, w%, h%, color%               Draw a rectangle outline.
    +  DrawText x%, y%, text$                        Draw text at the given position.
    +  FillCircle cx%, cy%, radius%, color%          Fill a circle.
    +  FillRect x%, y%, w%, h%, color%               Fill a rectangle.
    +  GetPixel(x%, y%)                              Returns the 0x00RRGGBB color at a pixel.
    +  Load path$                                    Load an image file onto the canvas.
    +  Resize w%, h%                                 Resize the canvas (fills new area with white).
    +  Save path$                                    Save the canvas to an image file.
    +  SetPenColor color%                            Set the drawing color (0x00RRGGBB).
    +  SetPenSize size%                              Set the pen size in pixels.
    +  SetPixel x%, y%, color%                       Set a single pixel.
    +

    Events

    +
      Callback                                          Description
    +  --------                                          -----------
    +  onClick                                           Fires when the canvas is clicked.
    +  Mouse callback (via wgtCanvasSetMouseCallback)    Fires on mouse down and drag with canvas-local coordinates.
    +

    Default Event

    +

    "Click" (VB basName: PictureBox, namePrefix: Picture).

    Checkbox

    -

    A toggle control with a text label. Clicking toggles between checked and unchecked states.

    -

    Header: widgets/widgetCheckbox.h

    +

    A toggle control with a text label. Clicking toggles between checked and unchecked states. Fires onChange each time the state flips. Supports accelerator keys (via '&' in the text) and keyboard toggle via Space or Enter when focused.

    +

    The check mark is drawn as an "X" pattern rather than a traditional checkmark glyph. The focus rectangle wraps the label text (not the box), matching the Windows 3.1 convention.

    +

    Header: widgets/checkbox.h

    Creation

    -
    WidgetT *cb = wgtCheckbox(parent, "Enable logging");
    -

    Macros

    -
      Macro                                  Description
    -  -----                                  -----------
    -  wgtCheckbox(parent, text)              Create a checkbox with the given label text.
    -  wgtCheckboxIsChecked(w)                Returns true if the checkbox is checked.
    -  wgtCheckboxSetChecked(w, checked)      Set the checked state programmatically.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onClick      Fires when clicked (after toggle).
    -  onChange     Fires when the checked state changes.
    +
    WidgetT *cb = wgtCheckbox(parent, "Enable &logging");
    +

    API Functions

    +
      Function                                                Description
    +  --------                                                -----------
    +  WidgetT *wgtCheckbox(parent, text)                      Create a checkbox with the given label text.
    +  bool wgtCheckboxIsChecked(w)                            Returns true if the checkbox is checked.
    +  void wgtCheckboxSetChecked(w, checked)                  Set the checked state programmatically. Triggers a repaint but does NOT fire onChange.
    +

    API Struct (wgtRegisterApi "checkbox")

    +
      Slot          Function
    +  ----          --------
    +  create        wgtCheckbox
    +  isChecked     wgtCheckboxIsChecked
    +  setChecked    wgtCheckboxSetChecked

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       Value        Boolean    Read/Write   Whether the checkbox is checked.
    -
    -
    -

    ComboBox

    -

    A combination of a text input and a dropdown list. The user can either type a value or select from a list of predefined options. Unlike Dropdown, the text field is editable.

    -

    Header: widgets/widgetComboBox.h

    -

    Creation

    -
    WidgetT *cb = wgtComboBox(parent, 128);
    -const char *items[] = { "Arial", "Courier", "Times" };
    -wgtComboBoxSetItems(cb, items, 3);
    -

    Macros

    -
      Macro                                    Description
    -  -----                                    -----------
    -  wgtComboBox(parent, maxLen)              Create a combo box. maxLen is the maximum text input length.
    -  wgtComboBoxSetItems(w, items, count)     Set the dropdown items.
    -  wgtComboBoxGetSelected(w)                Get the index of the selected item (-1 if the text does not match any item).
    -  wgtComboBoxSetSelected(w, idx)           Set the selected item by index.
    +

    Label text is managed via the standard wgtSetText() / wgtGetText() interface. BASIC code uses the generic "Caption" or "Text" property.

    +

    Methods

    +

    No widget-specific methods.

    Events

      Callback     Description
       --------     -----------
    -  onChange     Fires when the text or selection changes.
    + onClick Fires when clicked (after the toggle has already applied). + onChange Fires when the checked state changes (mouse click or keyboard toggle). +

    Default Event

    +

    "Click" (VB basName: CheckBox).

    +
    +
    +

    ComboBox

    +

    A combination of a single-line text input and a drop-down list. The user can type a value or select one from the list. When the user picks a list item, its text is copied into the edit buffer. Supports full text editing (cursor movement, selection, clipboard, undo) via the texthelp library, and a popup overlay list via the listhelp library.

    +

    Depends on "texthelp" and "listhelp" helper libraries (declared in combobox.dep).

    +

    Header: widgets/comboBox.h

    +

    Creation

    +
    WidgetT *cb = wgtComboBox(parent, 128);
    +wgtComboBoxAddItem(cb, "Arial");
    +wgtComboBoxAddItem(cb, "Courier");
    +wgtComboBoxAddItem(cb, "Times");
    +

    API Functions

    +
      Function                                                Description
    +  --------                                                -----------
    +  WidgetT *wgtComboBox(parent, maxLen)                    Create a combo box. maxLen is the maximum editable text length (0 => default 256).
    +  void wgtComboBoxSetItems(w, items, count)               Set the dropdown items from a const char ** array. Items are not copied -- caller owns them.
    +  int32_t wgtComboBoxGetSelected(w)                       Get the index of the last selected item (-1 if the text was typed freely).
    +  void wgtComboBoxSetSelected(w, idx)                     Select an item by index. Copies its text into the edit buffer.
    +  void wgtComboBoxAddItem(w, text)                        Append an item to the owned list (strdup'd).
    +  void wgtComboBoxRemoveItem(w, idx)                      Remove an owned item by index.
    +  void wgtComboBoxClear(w)                                Remove all owned items and reset the selection.
    +  const char *wgtComboBoxGetItem(w, idx)                  Get the text of an item by index.
    +  int32_t wgtComboBoxGetItemCount(w)                      Get the total number of items.
    +

    API Struct (wgtRegisterApi "combobox")

    +
      Slot           Function
    +  ----           --------
    +  create         wgtComboBox
    +  setItems       wgtComboBoxSetItems
    +  getSelected    wgtComboBoxGetSelected
    +  setSelected    wgtComboBoxSetSelected
    +  addItem        wgtComboBoxAddItem
    +  removeItem     wgtComboBoxRemoveItem
    +  clear          wgtComboBoxClear
    +  getItem        wgtComboBoxGetItem
    +  getItemCount   wgtComboBoxGetItemCount

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
    -  ListIndex    Integer    Read/Write   Index of the currently selected item.
    + ListIndex Integer Read/Write Index of the currently selected item (-1 if none). +

    Editable text is accessible via the generic "Text" property. "ListCount" is available through the ListCount method.

    +

    Methods (BASIC Interface)

    +
      Method              Description
    +  ------              -----------
    +  AddItem text$       Append an item to the dropdown list.
    +  Clear               Remove all items and clear the selection.
    +  List(index%)        Return the text of the item at the given index.
    +  ListCount()         Return the total number of items.
    +  RemoveItem index%   Remove the item at the given index.
    +

    Events

    +
      Callback     Description
    +  --------     -----------
    +  onChange     Fires when the selection or text changes.
    +

    Default Event

    +

    "Click" (VB basName: ComboBox).

    DataCtrl

    -

    A VB3-style Data control for database binding. Displays a visible navigation bar that connects to a SQLite database via dvxSql* functions. Reads all rows from the RecordSource query into an in-memory cache for bidirectional navigation. Fires Reposition events when the cursor moves so bound controls can update. Supports master-detail linking between Data controls.

    -

    Header: widgets/widgetDataCtrl.h

    +

    A VB3-style Data control for database binding. Displays a visible navigation bar (|< < > >|) that connects to a SQLite database via dvxSql* functions. Reads all rows from the RecordSource query into an in-memory cache for bidirectional navigation. Fires a Reposition event each time the current row changes so bound controls can update. Supports master-detail linking between Data controls (via MasterSource / MasterField / DetailField), INSERT via AddNew, and DELETE via Delete. UPDATEs use the KeyColumn to identify the row.

    +

    The widget depends on the "dvxsql" library DXE; the navigation logic caches the full result set so there is no round-trip for MoveNext/MovePrev.

    +

    Header: widgets/dataCtrl.h

    Creation

    WidgetT *data = wgtDataCtrl(parent);
    -

    Macros

    -
      Macro                                          Description
    -  -----                                          -----------
    -  wgtDataCtrl(parent)                            Create a Data control.
    -  wgtDataCtrlRefresh(w)                          Re-execute the RecordSource query and rebuild the row cache.
    -  wgtDataCtrlMoveFirst(w)                        Move the cursor to the first row.
    -  wgtDataCtrlMovePrev(w)                         Move the cursor to the previous row.
    -  wgtDataCtrlMoveNext(w)                         Move the cursor to the next row.
    -  wgtDataCtrlMoveLast(w)                         Move the cursor to the last row.
    -  wgtDataCtrlGetField(w, colName)                Get the value of a column in the current row. Returns const char *.
    -  wgtDataCtrlSetField(w, colName, value)         Set the value of a column in the current row (marks the row dirty).
    -  wgtDataCtrlUpdateRow(w)                        Write the current row's pending changes back to the database.
    -  wgtDataCtrlUpdate(w)                           Flush all pending changes to the database.
    -  wgtDataCtrlAddNew(w)                           Begin a new row. Sets dirty state; call Update to commit.
    -  wgtDataCtrlDelete(w)                           Delete the current row from the database.
    -  wgtDataCtrlSetMasterValue(w, val)              Set the master-detail filter value for this control.
    -  wgtDataCtrlGetRowCount(w)                      Get the total number of cached rows.
    -  wgtDataCtrlGetColCount(w)                      Get the number of columns in the result set.
    -  wgtDataCtrlGetColName(w, col)                  Get the name of a column by index. Returns const char *.
    -  wgtDataCtrlGetCellText(w, row, col)            Get the text of a specific cell by row and column index. Returns const char *.
    -  wgtDataCtrlSetCurrentRow(w, row)               Set the current row by index (0-based).
    +

    Set properties (DatabaseName, RecordSource, KeyColumn, etc.) and then call dataCtrlRefresh to populate.

    +

    API Functions

    +
      Function                                                   Description
    +  --------                                                   -----------
    +  WidgetT *dataCtrlCreate(parent)                            Create a Data control. Exposed as create in the API struct.
    +  void dataCtrlRefresh(w)                                    Re-execute the RecordSource query and rebuild the row cache.
    +  void dataCtrlMoveFirst(w)                                  Move the cursor to the first row.
    +  void dataCtrlMovePrev(w)                                   Move the cursor to the previous row.
    +  void dataCtrlMoveNext(w)                                   Move the cursor to the next row.
    +  void dataCtrlMoveLast(w)                                   Move the cursor to the last row.
    +  const char *dataCtrlGetField(w, colName)                   Get the value of a column in the current row (case-insensitive).
    +  void dataCtrlSetField(w, colName, value)                   Set the value of a column in the current row. Marks the row dirty.
    +  void dataCtrlUpdate(w)                                     Flush pending changes (INSERT or UPDATE) to the database.
    +  void dataCtrlUpdateRow(w)                                  Legacy wrapper around dataCtrlUpdate.
    +  void dataCtrlAddNew(w)                                     Append a blank row and move the cursor to it. The row is dirty and new until Update is called.
    +  void dataCtrlDelete(w)                                     Delete the current row from the cache and the database.
    +  void dataCtrlSetMasterValue(w, val)                        Set the master-detail filter value. Normally managed by the form runtime.
    +  int32_t dataCtrlGetRowCount(w)                             Get the total number of cached rows.
    +  int32_t dataCtrlGetColCount(w)                             Get the number of columns in the result set.
    +  const char *dataCtrlGetColName(w, col)                     Get the name of a column by index.
    +  const char *dataCtrlGetCellText(w, row, col)               Get the text of a specific cell.
    +  void dataCtrlSetCurrentRow(w, row)                         Set the current row by index. Auto-saves the previous row if dirty.
    +

    API Struct (wgtRegisterApi "data")

    +
      Slot                 Function
    +  ----                 --------
    +  create               dataCtrlCreate
    +  refresh              dataCtrlRefresh
    +  moveFirst            dataCtrlMoveFirst
    +  movePrev             dataCtrlMovePrev
    +  moveNext             dataCtrlMoveNext
    +  moveLast             dataCtrlMoveLast
    +  getField             dataCtrlGetField
    +  setField             dataCtrlSetField
    +  updateRow            dataCtrlUpdateRow
    +  update               dataCtrlUpdate
    +  addNew               dataCtrlAddNew
    +  delete               dataCtrlDelete
    +  setMasterValue       dataCtrlSetMasterValue
    +  getRowCount          dataCtrlGetRowCount
    +  getColCount          dataCtrlGetColCount
    +  getColName           dataCtrlGetColName
    +  getCellText          dataCtrlGetCellText
    +  setCurrentRow        dataCtrlSetCurrentRow

    Properties (BASIC Interface)

      Property       Type       Access       Description
       --------       ----       ------       -----------
    @@ -4642,47 +6269,58 @@ wgtComboBoxSetItems(cb, items, 3);
    MasterSource String Read/Write Name of the master Data control for master-detail linking. MasterField String Read/Write Column in the master control to read for the filter value. DetailField String Read/Write Column in this table to filter by the master value. - Caption String Read/Write Text displayed on the navigation bar. - BOF Boolean Read-only True when the cursor is before the first row. - EOF Boolean Read-only True when the cursor is past the last row. + Caption String Read/Write Text displayed on the navigator bar. + BOF Boolean Read-only True when the cursor is at the first row (or no rows). + EOF Boolean Read-only True when the cursor is past the last row (or no rows).

    Methods (BASIC Interface)

      Method         Description
       ------         -----------
    -  AddNew         Begin a new row.
    +  AddNew         Append a blank row and move to it.
       Delete         Delete the current row.
    -  MoveFirst      Move to the first row.
    -  MoveLast       Move to the last row.
    -  MoveNext       Move to the next row.
    -  MovePrevious   Move to the previous row.
    +  MoveFirst      Move the cursor to the first row.
    +  MoveLast       Move the cursor to the last row.
    +  MoveNext       Move the cursor to the next row.
    +  MovePrevious   Move the cursor to the previous row.
       Refresh        Re-execute the query and rebuild the cache.
       Update         Write pending changes to the database.

    Events

      Event          Description
       -----          -----------
    -  Reposition     Fires when the current row changes (navigation, refresh, etc.). Default event.
    -  Validate       Fires before writing changes. Return false to cancel.
    + Reposition Fires when the current row changes. Default event. + Validate Fires before writing. Return false to cancel the write.

    DbGrid

    -

    A database grid widget that displays all records from a Data control in a scrollable, sortable table. Columns auto-populate from the Data control's column names and can be hidden, resized, and renamed by the application. Clicking a column header sorts the display. Selecting a row syncs the Data control's cursor position. The grid reads directly from the Data control's cached rows, so there is no separate copy of the data.

    -

    Header: widgets/widgetDbGrid.h

    +

    A database grid widget that displays all records from a Data control in a scrollable, sortable table. Columns auto-populate from the Data control's column names and can be hidden, resized (by dragging a column border), and renamed by the application. Clicking a column header sorts the display ascending/descending. Selecting a row syncs the Data control's cursor position. The grid reads directly from the Data control's cached rows, so there is no separate copy of the data.

    +

    The grid supports alternating row shading, vertical and horizontal scrollbars, keyboard navigation (arrows, Page Up/Down, Home, End), and double-click activation (fires onDblClick).

    +

    Header: widgets/dbGrid.h

    Creation

    WidgetT *grid = wgtDbGrid(parent);
    -wgtDbGridSetDataWidget(grid, dataCtrl);
    -

    Macros

    -
      Macro                                                  Description
    -  -----                                                  -----------
    -  wgtDbGrid(parent)                                      Create a database grid widget.
    -  wgtDbGridSetDataWidget(w, dataWidget)                  Bind the grid to a Data control. The grid reads rows from this widget.
    -  wgtDbGridRefresh(w)                                    Re-read the Data control's state and repaint the grid.
    -  wgtDbGridSetColumnVisible(w, fieldName, visible)       Show or hide a column by field name.
    -  wgtDbGridSetColumnHeader(w, fieldName, header)         Set a display header for a column (overrides the field name).
    -  wgtDbGridSetColumnWidth(w, fieldName, width)           Set the width of a column by field name (tagged size, 0 = auto).
    -  wgtDbGridGetSelectedRow(w)                             Get the index of the currently selected data row (-1 if none).
    +dbGridSetDataWidget(grid, dataCtrl); +

    API Functions

    +
      Function                                                   Description
    +  --------                                                   -----------
    +  WidgetT *dbGridCreate(parent)                              Create a database grid widget. Exposed as create in the API struct.
    +  void dbGridSetDataWidget(w, dataWidget)                    Bind the grid to a Data control. Auto-populates columns from the Data control's column names.
    +  void dbGridRefresh(w)                                      Re-read the Data control's state and repaint the grid. Rebuilds the sort index if sorting is active.
    +  void dbGridSetColumnVisible(w, fieldName, visible)         Show or hide a column by field name.
    +  void dbGridSetColumnHeader(w, fieldName, header)           Set a display header for a column (overrides the field name).
    +  void dbGridSetColumnWidth(w, fieldName, width)             Set a column's width (tagged size, 0 = auto).
    +  int32_t dbGridGetSelectedRow(w)                            Get the index of the currently selected data row (-1 if none).
    +

    API Struct (wgtRegisterApi "dbgrid")

    +
      Slot                 Function
    +  ----                 --------
    +  create               dbGridCreate
    +  setDataWidget        dbGridSetDataWidget
    +  refresh              dbGridRefresh
    +  setColumnVisible     dbGridSetColumnVisible
    +  setColumnHeader      dbGridSetColumnHeader
    +  setColumnWidth       dbGridSetColumnWidth
    +  getSelectedRow       dbGridGetSelectedRow

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
    -  GridLines    Boolean    Read/Write   Whether to draw grid lines between cells.
    + GridLines Boolean Read/Write Whether to draw grid lines between cells (default: true).

    Methods (BASIC Interface)

      Method       Description
       ------       -----------
    @@ -4690,151 +6328,247 @@ wgtDbGridSetDataWidget(grid, dataCtrl);

    Events

      Event        Description
       -----        -----------
    -  Click        Fires when a row is clicked.
    +  Click        Fires when a row is clicked (selection change).
       DblClick     Fires when a row is double-clicked. Default event.
    +

    Default Event

    +

    "DblClick" (VB basName: DBGrid, namePrefix: DBGrid).

    Dropdown

    -

    A drop-down list that displays a single selected item and expands to show all options when clicked. Read-only selection (the user cannot type into it).

    -

    Header: widgets/widgetDropdown.h

    +

    A drop-down list that displays a single selected item and expands to a popup list when clicked. Unlike ComboBox, the user cannot type free text -- only list items can be selected. Keyboard navigation: Down/Up to move, Enter to select, type-ahead search on printable keys. The popup overlay is rendered above all other widgets via the widget paint overlay mechanism.

    +

    Depends on the "listhelp" helper library (declared in dropdown.dep).

    +

    Header: widgets/dropdown.h

    Creation

    WidgetT *dd = wgtDropdown(parent);
    -const char *items[] = { "Red", "Green", "Blue" };
    -wgtDropdownSetItems(dd, items, 3);
    -

    Macros

    -
      Macro                                    Description
    -  -----                                    -----------
    -  wgtDropdown(parent)                      Create a dropdown list.
    -  wgtDropdownSetItems(w, items, count)     Set the list of items. items is a const char ** array.
    -  wgtDropdownGetSelected(w)                Get the index of the selected item (-1 if none).
    -  wgtDropdownSetSelected(w, idx)           Set the selected item by index.
    +wgtDropdownAddItem(dd, "Red"); +wgtDropdownAddItem(dd, "Green"); +wgtDropdownAddItem(dd, "Blue"); +

    API Functions

    +
      Function                                            Description
    +  --------                                            -----------
    +  WidgetT *wgtDropdown(parent)                        Create a dropdown list.
    +  void wgtDropdownSetItems(w, items, count)           Set the list from a caller-owned const char ** array.
    +  int32_t wgtDropdownGetSelected(w)                   Get the index of the selected item (-1 if none).
    +  void wgtDropdownSetSelected(w, idx)                 Set the selected item by index.
    +  void wgtDropdownAddItem(w, text)                    Append an owned item (strdup'd).
    +  void wgtDropdownRemoveItem(w, idx)                  Remove an owned item by index.
    +  void wgtDropdownClear(w)                            Remove all owned items.
    +  const char *wgtDropdownGetItem(w, idx)              Get the text of an item by index.
    +  int32_t wgtDropdownGetItemCount(w)                  Get the total number of items.
    +

    API Struct (wgtRegisterApi "dropdown")

    +
      Slot           Function
    +  ----           --------
    +  create         wgtDropdown
    +  setItems       wgtDropdownSetItems
    +  getSelected    wgtDropdownGetSelected
    +  setSelected    wgtDropdownSetSelected
    +  addItem        wgtDropdownAddItem
    +  removeItem     wgtDropdownRemoveItem
    +  clear          wgtDropdownClear
    +  getItem        wgtDropdownGetItem
    +  getItemCount   wgtDropdownGetItemCount
    +

    Properties (BASIC Interface)

    +
      Property     Type       Access       Description
    +  --------     ----       ------       -----------
    +  ListIndex    Integer    Read/Write   Index of the currently selected item (-1 if none).
    +

    Methods (BASIC Interface)

    +
      Method              Description
    +  ------              -----------
    +  AddItem text$       Append an item to the drop-down list.
    +  Clear               Remove all items.
    +  List(index%)        Return the text of the item at the given index.
    +  ListCount()         Return the total number of items.
    +  RemoveItem index%   Remove the item at the given index.

    Events

      Callback     Description
       --------     -----------
       onChange     Fires when the selected item changes.
    -

    Properties (BASIC Interface)

    -
      Property     Type       Access       Description
    -  --------     ----       ------       -----------
    -  ListIndex    Integer    Read/Write   Index of the currently selected item.
    +

    Default Event

    +

    "Click" (VB basName: DropDown).

    ImageButton

    -

    A clickable button that displays an image instead of text. Has press/release visual feedback like a regular button. Can be created from raw pixel data or a BMP file.

    -

    Header: widgets/widgetImageButton.h

    +

    A clickable button that displays an image instead of text. Has the same press/release visual feedback and focus handling as a regular Button. Can be created from raw pixel data or loaded from an image file. Typically used to populate a Toolbar.

    +

    Header: widgets/imgBtn.h

    Creation

    -
      Macro                                            Description
    -  -----                                            -----------
    -  wgtImageButton(parent, data, w, h, pitch)        Create an image button from raw pixel data.
    -  wgtImageButtonFromFile(parent, path)             Create an image button by loading a BMP file.
    -

    Methods

    -
      Macro                                                Description
    -  -----                                                -----------
    -  wgtImageButtonSetData(w, data, imgW, imgH, pitch)   Replace the image with new raw pixel data.
    -  wgtImageButtonLoadFile(w, path)                      Replace the image by loading a new file.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onClick      Fires when the image button is clicked (press + release).
    +
    WidgetT *btn = wgtImageButtonFromFile(parent, "icons/save.bmp");
    +btn->onClick = onSaveClicked;
    +

    API Functions

    +
      Function                                                             Description
    +  --------                                                             -----------
    +  WidgetT *wgtImageButton(parent, data, w, h, pitch)                   Create from raw pixel data (in display's native format).
    +  WidgetT *wgtImageButtonFromFile(parent, path)                        Create by loading an image file.
    +  void wgtImageButtonSetData(w, data, imgW, imgH, pitch)               Replace the image with new raw pixel data.
    +  void wgtImageButtonLoadFile(w, path)                                 Replace the image by loading a new file.
    +

    API Struct (wgtRegisterApi "imagebutton")

    +
      Slot         Function
    +  ----         --------
    +  create       wgtImageButton
    +  fromFile     wgtImageButtonFromFile
    +  setData      wgtImageButtonSetData
    +  loadFile     wgtImageButtonLoadFile

    Properties (BASIC Interface)

      Property      Type       Access       Description
       --------      ----       ------       -----------
    -  Picture       String     Write-only   Load an image from a file path.
    +  Picture       String     Read/Write   Setting writes a file path (loads image). Reading returns the current path.
       ImageWidth    Integer    Read-only    Width of the currently loaded image in pixels.
       ImageHeight   Integer    Read-only    Height of the currently loaded image in pixels.
    -
    +

    Methods

    +

    No widget-specific BASIC methods. Setting Picture loads a new image.

    +

    Events

    +
      Callback     Description
    +  --------     -----------
    +  onClick      Fires when the image button is clicked (press and release inside).
    +

    Default Event

    +

    "Click" (VB basName: ImageButton).

    Image

    -

    Displays a bitmap image. Can be created from raw pixel data or loaded from a BMP file on disk. The image is rendered at its natural size within the widget bounds.

    -

    Header: widgets/widgetImage.h

    +

    Displays a bitmap image. Can be created from raw pixel data or loaded from a file on disk (BMP/PNG/etc. via dvxLoadImage). Supports optional stretching to fill the widget and transparent color keys for chroma-key style masking.

    +

    Header: widgets/image.h

    Creation

    -
      Macro                                        Description
    -  -----                                        -----------
    -  wgtImage(parent, data, w, h, pitch)          Create an image widget from raw pixel data. data is a uint8_t * pixel buffer, w/h are dimensions, pitch is bytes per row.
    -  wgtImageFromFile(parent, path)               Create an image widget by loading a BMP file from disk.
    +
    WidgetT *img = wgtImageFromFile(parent, "logo.bmp");
    +// or from raw pixels:
    +// WidgetT *img = wgtImage(parent, pixelData, w, h, pitch);
    +

    API Functions

    +
      Function                                                             Description
    +  --------                                                             -----------
    +  WidgetT *wgtImage(parent, data, w, h, pitch)                         Create from raw pixel data (in display's native format).
    +  WidgetT *wgtImageFromFile(parent, path)                              Create by loading an image file.
    +  void wgtImageSetData(w, data, imgW, imgH, pitch)                     Replace the displayed image with new raw pixel data.
    +  void wgtImageLoadFile(w, path)                                       Replace by loading a new file. Static but exposed via the API struct.
    +  void wgtImageSetTransparent(w, hasTransparency, keyColor)            Set chroma-key color. Pixels matching keyColor are not drawn.
    +

    API Struct (wgtRegisterApi "image")

    +
      Slot             Function
    +  ----             --------
    +  create           wgtImage
    +  fromFile         wgtImageFromFile
    +  setData          wgtImageSetData
    +  loadFile         wgtImageLoadFile
    +  setTransparent   wgtImageSetTransparent
    +

    Properties (BASIC Interface)

    +
      Property      Type       Access       Description
    +  --------      ----       ------       -----------
    +  Picture       String     Read/Write   Setting writes a file path (loads image). Reading returns the current path.
    +  Stretch       Boolean    Read/Write   When true, the image stretches to fill the widget; when false, shown at natural size.
    +  ImageWidth    Integer    Read-only    Width of the currently loaded image in pixels.
    +  ImageHeight   Integer    Read-only    Height of the currently loaded image in pixels.

    Methods

    -
      Macro                                        Description
    -  -----                                        -----------
    -  wgtImageSetData(w, data, imgW, imgH, pitch)  Replace the displayed image with new raw pixel data.
    -  wgtImageLoadFile(w, path)                    Replace the displayed image by loading a new file.
    +

    No widget-specific BASIC methods. Setting Picture loads a new image.

    Events

      Callback     Description
       --------     -----------
       onClick      Fires when the image is clicked.
    -

    Properties (BASIC Interface)

    -
      Property      Type       Access       Description
    -  --------      ----       ------       -----------
    -  Picture       String     Write-only   Load an image from a file path.
    -  ImageWidth    Integer    Read-only    Width of the currently loaded image in pixels.
    -  ImageHeight   Integer    Read-only    Height of the currently loaded image in pixels.
    +

    Default Event

    +

    "Click" (VB basName: Image).

    Label

    -

    A static text label. Does not accept keyboard focus. Typically used to describe other widgets. Supports text alignment and accelerator keys (with WCLASS_FOCUS_FORWARD, the accelerator moves focus to the next focusable sibling).

    -

    Header: widgets/widgetLabel.h

    +

    A static text label. Does not accept keyboard focus directly, but supports accelerator keys that forward focus to the next focusable sibling (WCLASS_FOCUS_FORWARD). Useful for describing other widgets. Supports left, center, and right text alignment.

    +

    Header: widgets/label.h

    Creation

    -
    WidgetT *lbl = wgtLabel(parent, "Name:");
    -

    Macros

    -
      Macro                        Description
    -  -----                        -----------
    -  wgtLabel(parent, text)       Create a text label.
    -  wgtLabelSetAlign(w, align)   Set the text alignment (AlignStartE, AlignCenterE, AlignEndE).
    -

    Properties

    -

    Use wgtSetText() / wgtGetText() to change the text. Set accelKey for accelerator support (focus forwards to next focusable widget).

    -

    Events

    -

    Labels use the common events only. Typically no callbacks are set on labels.

    +
    WidgetT *lbl = wgtLabel(parent, "&Name:");
    +

    API Functions

    +
      Function                                            Description
    +  --------                                            -----------
    +  WidgetT *wgtLabel(parent, text)                     Create a text label.
    +  void wgtLabelSetAlign(w, align)                     Set the text alignment (AlignStartE = left, AlignCenterE = center, AlignEndE = right).
    +  WidgetAlignE wgtLabelGetAlign(w)                    Get the current text alignment.
    +

    API Struct (wgtRegisterApi "label")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtLabel
    +  setAlign   wgtLabelSetAlign

    Properties (BASIC Interface)

      Property     Type                         Access       Description
       --------     ----                         ------       -----------
       Alignment    Enum (Left, Center, Right)   Read/Write   Text alignment within the label.
    -
    +

    Label text is managed via the standard wgtSetText() / wgtGetText() interface. BASIC code uses the generic "Caption" or "Text" property. Place an '&' before a character in the caption to mark an accelerator key.

    +

    Methods

    +

    No widget-specific methods.

    +

    Events

    +

    Labels are not focusable, so they typically have no event callbacks. The common onClick callback will fire if the label is clicked.

    +

    Default Event

    +

    "Click" (VB basName: Label).

    ListBox

    -

    A scrollable list of text items. Supports single and multi-selection modes and drag-to-reorder.

    -

    Header: widgets/widgetListBox.h

    +

    A scrollable list of text items. Supports single and multi-select modes (Shift-click for range, Ctrl-click to toggle), drag-to-reorder, mouse wheel scroll, and keyboard navigation (Up/Down/PgUp/PgDn/Home/End with type-ahead search on printable keys). Depends on the "listhelp" helper library (declared in listbox.dep).

    +

    Items can either be set from a caller-owned array (wgtListBoxSetItems) or managed internally via AddItem/RemoveItem/Clear. The two modes are mutually exclusive per widget.

    +

    Header: widgets/listBox.h

    Creation

    WidgetT *lb = wgtListBox(parent);
    -const char *items[] = { "Alpha", "Beta", "Gamma" };
    -wgtListBoxSetItems(lb, items, 3);
    -

    Macros

    -
      Macro                                          Description
    -  -----                                          -----------
    -  wgtListBox(parent)                             Create a list box.
    -  wgtListBoxSetItems(w, items, count)            Set the list items.
    -  wgtListBoxGetSelected(w)                       Get the index of the selected item (-1 if none).
    -  wgtListBoxSetSelected(w, idx)                  Set the selected item by index.
    -  wgtListBoxSetMultiSelect(w, multi)             Enable or disable multi-selection mode.
    -  wgtListBoxIsItemSelected(w, idx)               Check if a specific item is selected (multi-select mode).
    -  wgtListBoxSetItemSelected(w, idx, selected)    Select or deselect a specific item.
    -  wgtListBoxSelectAll(w)                         Select all items (multi-select mode).
    -  wgtListBoxClearSelection(w)                    Deselect all items.
    -  wgtListBoxSetReorderable(w, reorderable)       Enable drag-to-reorder.
    +wgtListBoxAddItem(lb, "Alpha"); +wgtListBoxAddItem(lb, "Beta"); +wgtListBoxAddItem(lb, "Gamma"); +

    API Functions

    +
      Function                                                     Description
    +  --------                                                     -----------
    +  WidgetT *wgtListBox(parent)                                  Create a list box.
    +  void wgtListBoxSetItems(w, items, count)                     Set items from a caller-owned const char ** array.
    +  int32_t wgtListBoxGetSelected(w)                             Get the primary selected index (-1 if none).
    +  void wgtListBoxSetSelected(w, idx)                           Set the selected index. Scrolls the item into view.
    +  void wgtListBoxSetMultiSelect(w, multi)                      Enable or disable multi-select mode.
    +  bool wgtListBoxIsItemSelected(w, idx)                        Returns true if the item is selected (multi-select mode).
    +  void wgtListBoxSetItemSelected(w, idx, selected)             Select or deselect a specific item (multi-select mode).
    +  void wgtListBoxSelectAll(w)                                  Select every item (multi-select mode).
    +  void wgtListBoxClearSelection(w)                             Deselect all items.
    +  void wgtListBoxSetReorderable(w, r)                          Enable drag-to-reorder of items.
    +  void wgtListBoxAddItem(w, text)                              Append an owned item (strdup'd).
    +  void wgtListBoxRemoveItem(w, idx)                            Remove an owned item by index.
    +  void wgtListBoxClear(w)                                      Remove all owned items.
    +  const char *wgtListBoxGetItem(w, idx)                        Get the text of an item by index.
    +  int32_t wgtListBoxGetItemCount(w)                            Get the total number of items.
    +

    API Struct (wgtRegisterApi "listbox")

    +
      Slot               Function
    +  ----               --------
    +  create             wgtListBox
    +  setItems           wgtListBoxSetItems
    +  getSelected        wgtListBoxGetSelected
    +  setSelected        wgtListBoxSetSelected
    +  setMultiSelect     wgtListBoxSetMultiSelect
    +  isItemSelected     wgtListBoxIsItemSelected
    +  setItemSelected    wgtListBoxSetItemSelected
    +  selectAll          wgtListBoxSelectAll
    +  clearSelection     wgtListBoxClearSelection
    +  setReorderable     wgtListBoxSetReorderable
    +  addItem            wgtListBoxAddItem
    +  removeItem         wgtListBoxRemoveItem
    +  clear              wgtListBoxClear
    +  getItem            wgtListBoxGetItem
    +  getItemCount       wgtListBoxGetItemCount
    +

    Properties (BASIC Interface)

    +
      Property     Type       Access       Description
    +  --------     ----       ------       -----------
    +  ListIndex    Integer    Read/Write   Index of the currently selected item.
    +

    Methods (BASIC Interface)

    +
      Method                            Description
    +  ------                            -----------
    +  AddItem text$                     Append an item.
    +  Clear                             Remove all items.
    +  ClearSelection                    Deselect all items.
    +  IsItemSelected(index%)            Returns True if the item is selected (multi-select mode).
    +  List(index%)                      Return the text of the item at the given index.
    +  ListCount()                       Return the total number of items.
    +  RemoveItem index%                 Remove the item at the given index.
    +  SelectAll                         Select all items.
    +  SetItemSelected index%, sel       Select or deselect a specific item.
    +  SetItems spec$                    Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue"); replaces existing items.
    +  SetMultiSelect multi              Enable or disable multi-select mode.
    +  SetReorderable r                  Enable or disable drag-to-reorder.

    Events

      Callback     Description
       --------     -----------
       onClick      Fires when an item is clicked.
       onDblClick   Fires when an item is double-clicked.
       onChange     Fires when the selection changes.
    -

    Properties (BASIC Interface)

    -
      Property     Type       Access       Description
    -  --------     ----       ------       -----------
    -  ListIndex    Integer    Read/Write   Index of the currently selected item.
    -

    Methods (BASIC Interface)

    -
      Method           Description
    -  ------           -----------
    -  SelectAll        Select all items.
    -  ClearSelection   Deselect all items.
    -  SetMultiSelect   Enable or disable multi-selection.
    -  SetReorderable   Enable or disable drag-to-reorder.
    -  IsItemSelected   Check if a specific item is selected by index.
    -  SetItemSelected  Select or deselect a specific item by index.
    -
    +

    Default Event

    +

    "Click" (VB basName: ListBox).

    ListView

    A multi-column list with sortable headers. Supports single and multi-selection, column alignment, header click sorting, and drag-to-reorder. Data is provided as a flat array of strings (row-major order, one string per cell).

    -

    Header: widgets/widgetListView.h

    +

    Header: widgets/listView.h

    Creation

    WidgetT *lv = wgtListView(parent);
     ListViewColT cols[] = {
    @@ -4883,168 +6617,243 @@ wgtListViewSetData(lv, cells, 2);
    -------- ---- ------ ----------- ListIndex Integer Read/Write Index of the currently selected row.

    Methods (BASIC Interface)

    -
      Method           Description
    -  ------           -----------
    -  SelectAll        Select all rows.
    -  ClearSelection   Deselect all rows.
    -  SetMultiSelect   Enable or disable multi-selection.
    -  SetReorderable   Enable or disable drag-to-reorder.
    -  IsItemSelected   Check if a specific row is selected by index.
    -  SetItemSelected  Select or deselect a specific row by index.
    -
    +
      Method                          Description
    +  ------                          -----------
    +  AddItem text$                   Add a row (sets first column text).
    +  Clear                           Remove all rows.
    +  ClearSelection                  Deselect all rows.
    +  GetCell(row%, col%)             Returns the text of a cell.
    +  IsItemSelected(index%)          Check if a specific row is selected.
    +  RemoveItem index%               Remove a row by index.
    +  RowCount()                      Returns the number of rows.
    +  SelectAll                       Select all rows.
    +  SetCell row%, col%, text$       Set the text of a cell.
    +  SetColumns spec$                Define columns from a pipe-delimited spec string ("Name,Width|Name,Width").
    +  SetItemSelected idx%, sel       Select or deselect a specific row by index.
    +  SetMultiSelect multi            Enable or disable multi-selection.
    +  SetReorderable reorderable      Enable or disable drag-to-reorder.
    +  SetSort col%, dir%              Set the sort column and direction (0=none, 1=ascending, 2=descending).
    +

    Default Event

    +

    "Click" (VB basName: ListView).

    ProgressBar

    A visual indicator of progress, displayed as a filled bar. Supports both horizontal and vertical orientations. Value ranges from 0 to 100.

    -

    Header: widgets/widgetProgressBar.h

    +

    Header: widgets/progress.h

    Creation

      Macro                    Description
       -----                    -----------
       wgtProgressBar(parent)   Create a horizontal progress bar.
       wgtProgressBarV(parent)  Create a vertical progress bar.
    -

    Methods

    -
      Macro                            Description
    -  -----                            -----------
    -  wgtProgressBarSetValue(w, value) Set the progress value (0-100).
    -  wgtProgressBarGetValue(w)        Get the current progress value.
    -

    Events

    -

    ProgressBar is a display-only widget. Typically no callbacks are set.

    +

    API Functions

    +
      Function                            Description
    +  --------                            -----------
    +  WidgetT *wgtProgressBar(parent)     Create a horizontal progress bar.
    +  WidgetT *wgtProgressBarV(parent)    Create a vertical progress bar.
    +  void wgtProgressBarSetValue(w, val) Set the progress value (0-100). Clamped to 0..maxValue.
    +  int32_t wgtProgressBarGetValue(w)   Get the current progress value.
    +

    API Struct (wgtRegisterApi "progressbar")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtProgressBar
    +  createV    wgtProgressBarV
    +  setValue   wgtProgressBarSetValue
    +  getValue   wgtProgressBarGetValue

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       Value        Integer    Read/Write   Current progress value (0-100).
    -
    +

    Methods

    +

    No widget-specific methods.

    +

    Events

    +

    ProgressBar is a display-only widget. Typically no callbacks are set.

    +

    Default Event

    +

    None. (VB basName: ProgressBar.)

    Radio Button

    A mutually exclusive selection control. Radio buttons must be placed inside a radio group container. Only one radio button within a group can be selected at a time.

    -

    Header: widgets/widgetRadio.h

    +

    Header: widgets/radio.h

    Creation

    WidgetT *grp = wgtRadioGroup(parent);
     WidgetT *r1  = wgtRadio(grp, "Option A");
     WidgetT *r2  = wgtRadio(grp, "Option B");
    -

    Macros

    -
      Macro                                Description
    -  -----                                -----------
    -  wgtRadioGroup(parent)                Create a radio group container.
    -  wgtRadio(parent, text)               Create a radio button inside a group.
    -  wgtRadioGroupSetSelected(w, idx)     Set the selected radio button by index within the group.
    -  wgtRadioGetIndex(w)                  Get the index of the currently selected radio button.
    +

    API Functions

    +
      Function                                       Description
    +  --------                                       -----------
    +  WidgetT *wgtRadioGroup(parent)                 Create a radio group container.
    +  WidgetT *wgtRadio(parent, text)                Create a radio button inside a group.
    +  void wgtRadioGroupSetSelected(group, idx)      Set the selected radio button by index within the group.
    +  int32_t wgtRadioGetIndex(w)                    Get the index of this radio button within its group.
    +

    API Struct (wgtRegisterApi "radio")

    +
      Slot                Function
    +  ----                --------
    +  group               wgtRadioGroup
    +  create              wgtRadio
    +  groupSetSelected    wgtRadioGroupSetSelected
    +  getIndex            wgtRadioGetIndex
    +

    Properties (BASIC Interface)

    +
      Property     Type       Access       Description
    +  --------     ----       ------       -----------
    +  Value        Integer    Read-only    Index of this radio button within its group.
    +

    Caption is managed via the standard wgtSetText() / wgtGetText() interface (WCLASS_HAS_TEXT).

    +

    Methods (BASIC Interface)

    +
      Method                  Description
    +  ------                  -----------
    +  SetSelected idx%        Set the selected radio button by index within the group.

    Events

      Callback     Description
       --------     -----------
       onClick      Fires on the radio button when clicked.
       onChange     Fires when the selection changes.
    -

    Properties (BASIC Interface)

    -
      Property     Type       Access       Description
    -  --------     ----       ------       -----------
    -  Value        Integer    Read-only    Index of the currently selected radio button in the group.
    -

    Methods (BASIC Interface)

    -
      Method          Description
    -  ------          -----------
    -  SetSelected     Set the selected radio button by index.
    -
    +

    Default Event

    +

    "Click" (VB basName: OptionButton, namePrefix: Option).

    ScrollPane

    A scrollable container that provides vertical and/or horizontal scrollbars when its content exceeds the visible area. Place a single child (typically a VBox or HBox) inside the scroll pane.

    -

    Header: widgets/widgetScrollPane.h

    +

    Header: widgets/scrlPane.h

    Creation

    WidgetT *sp = wgtScrollPane(parent);
     WidgetT *content = wgtVBox(sp);
     // add children to content...
    -

    Macros

    -
      Macro                                      Description
    -  -----                                      -----------
    -  wgtScrollPane(parent)                      Create a scroll pane container.
    -  wgtScrollPaneScrollToChild(sp, child)      Scroll so that the given child widget is visible.
    -  wgtScrollPaneSetNoBorder(w, noBorder)      When true, removes the border around the scroll pane.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onScroll     Fires when the scroll position changes.
    +

    API Functions

    +
      Function                                          Description
    +  --------                                          -----------
    +  WidgetT *wgtScrollPane(parent)                    Create a scroll pane container.
    +  void wgtScrollPaneScrollToChild(sp, child)        Scroll so that the given child widget is visible.
    +  void wgtScrollPaneScrollToTop(w)                  Scroll back to the top-left of the content.
    +  void wgtScrollPaneSetNoBorder(w, noBorder)        When true, removes the border around the scroll pane.
    +

    API Struct (wgtRegisterApi "scrollpane")

    +
      Slot             Function
    +  ----             --------
    +  create           wgtScrollPane
    +  scrollToChild    wgtScrollPaneScrollToChild
    +  setNoBorder      wgtScrollPaneSetNoBorder
    +  scrollToTop      wgtScrollPaneScrollToTop

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       NoBorder     Boolean    Read/Write   Whether the border around the scroll pane is hidden.
    -
    +

    Methods

    +

    No widget-specific methods.

    +

    Events

    +
      Callback     Description
    +  --------     -----------
    +  onScroll     Fires when the scroll position changes.
    +

    Default Event

    +

    None. (VB basName: ScrollPane, namePrefix: Scroll.)

    Separator

    A visual dividing line used to separate groups of widgets. Available in horizontal and vertical orientations.

    -

    Header: widgets/widgetSeparator.h

    -

    Creation

    -
      Macro                    Description
    -  -----                    -----------
    -  wgtHSeparator(parent)    Create a horizontal separator line.
    -  wgtVSeparator(parent)    Create a vertical separator line.
    +

    Header: widgets/separatr.h

    +

    API Functions

    +
      Function                            Description
    +  --------                            -----------
    +  WidgetT *wgtHSeparator(parent)      Create a horizontal separator line.
    +  WidgetT *wgtVSeparator(parent)      Create a vertical separator line.
    +

    API Struct (wgtRegisterApi "separator")

    +
      Slot           Function
    +  ----           --------
    +  hSeparator     wgtHSeparator
    +  vSeparator     wgtVSeparator
    +

    Properties

    +

    No widget-specific properties.

    +

    Methods

    +

    No widget-specific methods.

    Events

    Separator is a visual-only widget. No events.

    -
    +

    Default Event

    +

    None. (VB basName: Line.)

    Slider

    A horizontal slider (track bar) for selecting an integer value within a range. The user drags the thumb or clicks the track to change the value.

    -

    Header: widgets/widgetSlider.h

    +

    Header: widgets/slider.h

    Creation

    WidgetT *sl = wgtSlider(parent, 0, 100);
    -

    Macros

    -
      Macro                                Description
    -  -----                                -----------
    -  wgtSlider(parent, minVal, maxVal)    Create a slider with the given integer range.
    -  wgtSliderSetValue(w, value)          Set the slider value programmatically.
    -  wgtSliderGetValue(w)                 Get the current slider value.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onChange     Fires when the slider value changes.
    +

    API Functions

    +
      Function                                          Description
    +  --------                                          -----------
    +  WidgetT *wgtSlider(parent, minVal, maxVal)        Create a slider with the given integer range.
    +  void wgtSliderSetValue(w, value)                  Set the slider value programmatically.
    +  int32_t wgtSliderGetValue(w)                      Get the current slider value.
    +

    API Struct (wgtRegisterApi "slider")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtSlider
    +  setValue   wgtSliderSetValue
    +  getValue   wgtSliderGetValue

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       Value        Integer    Read/Write   Current slider value.
    -
    +

    Methods

    +

    No widget-specific methods.

    +

    Events

    +
      Callback     Description
    +  --------     -----------
    +  onChange     Fires when the slider value changes.
    +

    Default Event

    +

    "Change" (VB basName: HScrollBar, namePrefix: HScroll).

    Spacer

    An invisible widget used for layout purposes. By default it has weight=100, so it absorbs available extra space. Useful for pushing other widgets apart or aligning them to edges.

    -

    Header: widgets/widgetSpacer.h

    +

    Header: widgets/spacer.h

    Creation

    WidgetT *row = wgtHBox(parent);
     wgtButton(row, "OK");
     wgtSpacer(row);           // pushes Cancel to the right
     wgtButton(row, "Cancel");
    -

    Macro

    -
      Macro                Description
    -  -----                -----------
    -  wgtSpacer(parent)    Create an invisible spacer widget.
    -

    Events

    -

    Spacer is an invisible layout widget. No events.

    -
    +

    API Functions

    +
      Function                       Description
    +  --------                       -----------
    +  WidgetT *wgtSpacer(parent)     Create an invisible spacer widget.
    +

    API Struct (wgtRegisterApi "spacer")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtSpacer
    +

    Properties, Methods, Events

    +

    Spacer is an invisible layout widget. No type-specific properties, methods, or events.

    +

    Default Event

    +

    None. (VB basName: Spacer.)

    Spinner

    A numeric input with up/down buttons for incrementing and decrementing a value within a range. Supports both integer and floating-point (real) modes.

    -

    Header: widgets/widgetSpinner.h

    +

    Header: widgets/spinner.h

    Creation

    WidgetT *sp = wgtSpinner(parent, 0, 100, 1);
    -

    Macros

    -
      Macro                                          Description
    -  -----                                          -----------
    -  wgtSpinner(parent, minVal, maxVal, step)       Create a spinner with the given integer range and step size.
    -  wgtSpinnerSetValue(w, value)                   Set the integer value.
    -  wgtSpinnerGetValue(w)                          Get the current integer value.
    -  wgtSpinnerSetRange(w, minVal, maxVal)          Set the integer range.
    -  wgtSpinnerSetStep(w, step)                     Set the integer step size.
    -  wgtSpinnerSetRealMode(w, enable)               Switch to floating-point mode.
    -  wgtSpinnerGetRealValue(w)                      Get the current floating-point value.
    -  wgtSpinnerSetRealValue(w, value)               Set the floating-point value.
    -  wgtSpinnerSetRealRange(w, minVal, maxVal)      Set the floating-point range.
    -  wgtSpinnerSetRealStep(w, step)                 Set the floating-point step size.
    -  wgtSpinnerSetDecimals(w, decimals)             Set the number of decimal places displayed in real mode.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onChange     Fires when the value changes.
    +

    API Functions

    +
      Function                                                Description
    +  --------                                                -----------
    +  WidgetT *wgtSpinner(parent, minVal, maxVal, step)       Create a spinner with the given integer range and step size.
    +  void wgtSpinnerSetValue(w, value)                       Set the integer value.
    +  int32_t wgtSpinnerGetValue(w)                           Get the current integer value.
    +  void wgtSpinnerSetRange(w, minVal, maxVal)              Set the integer range.
    +  void wgtSpinnerSetStep(w, step)                         Set the integer step size.
    +  void wgtSpinnerSetRealMode(w, enable)                   Switch to floating-point mode.
    +  double wgtSpinnerGetRealValue(w)                        Get the current floating-point value.
    +  void wgtSpinnerSetRealValue(w, value)                   Set the floating-point value.
    +  void wgtSpinnerSetRealRange(w, minVal, maxVal)          Set the floating-point range.
    +  void wgtSpinnerSetRealStep(w, step)                     Set the floating-point step size.
    +  void wgtSpinnerSetDecimals(w, decimals)                 Set the number of decimal places displayed in real mode.
    +

    API Struct (wgtRegisterApi "spinner")

    +
      Slot           Function
    +  ----           --------
    +  create         wgtSpinner
    +  setValue       wgtSpinnerSetValue
    +  getValue       wgtSpinnerGetValue
    +  setRange       wgtSpinnerSetRange
    +  setStep        wgtSpinnerSetStep
    +  setRealMode    wgtSpinnerSetRealMode
    +  getRealValue   wgtSpinnerGetRealValue
    +  setRealValue   wgtSpinnerSetRealValue
    +  setRealRange   wgtSpinnerSetRealRange
    +  setRealStep    wgtSpinnerSetRealStep
    +  setDecimals    wgtSpinnerSetDecimals

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
    @@ -5052,87 +6861,112 @@ wgtButton(row, "Cancel");
    RealMode Boolean Read/Write Whether floating-point mode is active. Decimals Integer Read/Write Number of decimal places displayed in real mode.

    Methods (BASIC Interface)

    -
      Method       Description
    -  ------       -----------
    -  SetRange     Set the integer range (min, max).
    -  SetStep      Set the integer step size.
    -
    +
      Method                Description
    +  ------                -----------
    +  SetRange min%, max%   Set the integer range.
    +  SetStep step%         Set the integer step size.
    +

    Events

    +
      Callback     Description
    +  --------     -----------
    +  onChange     Fires when the value changes.
    +

    Default Event

    +

    "Change" (VB basName: SpinButton, namePrefix: Spin).

    Splitter

    A two-pane container with a draggable divider. The user drags the splitter bar to resize the two panes. Can be oriented vertically (left/right panes) or horizontally (top/bottom panes). Add exactly two children.

    -

    Header: widgets/widgetSplitter.h

    +

    Header: widgets/splitter.h

    Creation

    WidgetT *sp = wgtSplitter(parent, true);  // vertical = left|right
     WidgetT *left  = wgtVBox(sp);
     WidgetT *right = wgtVBox(sp);
    -

    Macros

    -
      Macro                                  Description
    -  -----                                  -----------
    -  wgtSplitter(parent, vertical)          Create a splitter. vertical=true for left/right panes, false for top/bottom.
    -  wgtSplitterSetPos(w, pos)             Set the divider position in pixels.
    -  wgtSplitterGetPos(w)                  Get the current divider position in pixels.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onChange     Fires when the divider position changes.
    +

    API Functions

    +
      Function                                       Description
    +  --------                                       -----------
    +  WidgetT *wgtSplitter(parent, vertical)         Create a splitter. vertical=true for left/right panes (vertical divider), false for top/bottom.
    +  void wgtSplitterSetPos(w, pos)                 Set the divider position in pixels from the leading edge.
    +  int32_t wgtSplitterGetPos(w)                   Get the current divider position in pixels.
    +

    API Struct (wgtRegisterApi "splitter")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtSplitter
    +  setPos     wgtSplitterSetPos
    +  getPos     wgtSplitterGetPos

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       Position     Integer    Read/Write   Divider position in pixels.
    -
    +

    Methods

    +

    No widget-specific methods.

    +

    Events

    +

    Splitter has no widget-specific events. Common events apply.

    +

    Default Event

    +

    None. (VB basName: Splitter.)

    StatusBar

    A horizontal bar typically placed at the bottom of a window for displaying status text and informational widgets. Children are laid out horizontally.

    -

    Header: widgets/widgetStatusBar.h

    +

    Header: widgets/statBar.h

    Creation

    WidgetT *sb = wgtStatusBar(parent);
     wgtLabel(sb, "Ready");
    -

    Macro

    -
      Macro                  Description
    -  -----                  -----------
    -  wgtStatusBar(parent)   Create a status bar container.
    -

    Properties

    -

    Uses common container properties. Add child widgets (labels, progress bars, etc.) to populate.

    -

    Events

    -

    StatusBar itself has no widget-specific events. Events fire on the child widgets.

    -
    +

    API Functions

    +
      Function                          Description
    +  --------                          -----------
    +  WidgetT *wgtStatusBar(parent)     Create a status bar container.
    +

    API Struct (wgtRegisterApi "statusbar")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtStatusBar
    +

    Properties, Methods, Events

    +

    StatusBar has no widget-specific properties, methods, or events. Add child widgets (labels, progress bars, etc.) to populate.

    +

    Default Event

    +

    None. (VB basName: StatusBar.)

    TabControl

    A tabbed container that displays one page at a time with clickable tabs along the top. Each tab page is a container that holds its own child widgets.

    -

    Header: widgets/widgetTabControl.h

    +

    Header: widgets/tabCtrl.h

    Creation

    WidgetT *tabs = wgtTabControl(parent);
     WidgetT *page1 = wgtTabPage(tabs, "General");
     WidgetT *page2 = wgtTabPage(tabs, "Advanced");
     // add children to page1, page2...
    -

    Macros

    -
      Macro                              Description
    -  -----                              -----------
    -  wgtTabControl(parent)              Create a tab control.
    -  wgtTabPage(parent, title)          Add a tab page with the given title. Returns the page container widget.
    -  wgtTabControlSetActive(w, idx)     Set the active tab by index (0-based).
    -  wgtTabControlGetActive(w)          Get the index of the active tab.
    -

    Events

    -
      Callback     Description
    -  --------     -----------
    -  onChange     Fires when the active tab changes.
    +

    API Functions

    +
      Function                                       Description
    +  --------                                       -----------
    +  WidgetT *wgtTabControl(parent)                 Create a tab control.
    +  WidgetT *wgtTabPage(parent, title)             Add a tab page with the given title. Returns the page container widget.
    +  void wgtTabControlSetActive(w, idx)            Set the active tab by index (0-based).
    +  int32_t wgtTabControlGetActive(w)              Get the index of the active tab.
    +

    API Struct (wgtRegisterApi "tabcontrol")

    +
      Slot         Function
    +  ----         --------
    +  create       wgtTabControl
    +  page         wgtTabPage
    +  setActive    wgtTabControlSetActive
    +  getActive    wgtTabControlGetActive

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       TabIndex     Integer    Read/Write   Index of the currently active tab (0-based).

    Methods (BASIC Interface)

    -
      Method       Description
    -  ------       -----------
    -  SetActive    Set the active tab by index.
    -
    +
      Method                Description
    +  ------                -----------
    +  AddPage title$        Add a new tab page with the given title.
    +  GetActive()           Returns the index of the active tab.
    +  SetActive idx%        Set the active tab by index (0-based).
    +

    Events

    +
      Callback     Description
    +  --------     -----------
    +  onChange     Fires when the active tab changes.
    +

    Default Event

    +

    "Click" (VB basName: TabStrip).

    TextInput / TextArea

    Single-line text input, password input, masked input, and multi-line text area with optional syntax colorization, line numbers, find/replace, and gutter decorators.

    -

    Header: widgets/widgetTextInput.h

    +

    Header: widgets/textInpt.h

    Creation

      Macro                            Description
       -----                            -----------
    @@ -5140,55 +6974,89 @@ WidgetT *page2 = wgtTabPage(tabs, "Advanced");
       wgtPasswordInput(parent, maxLen) Create a password input (characters displayed as bullets).
       wgtMaskedInput(parent, mask)     Create a masked input field. The mask string defines the input format.
       wgtTextArea(parent, maxLen)      Create a multi-line text area.
    -

    Methods (TextArea-specific)

    -
      Macro                                             Description
    -  -----                                             -----------
    -  wgtTextAreaSetColorize(w, fn, ctx)                Set a syntax colorization callback. The callback receives each line and fills a color index array. Color indices: 0=default, 1=keyword, 2=string, 3=comment, 4=number, 5=operator, 6=type/builtin, 7=reserved.
    -  wgtTextAreaGoToLine(w, line)                      Scroll to and place the cursor on the given line number.
    -  wgtTextAreaSetAutoIndent(w, enable)               Enable or disable automatic indentation on newline.
    -  wgtTextAreaSetShowLineNumbers(w, show)            Show or hide line numbers in the gutter.
    -  wgtTextAreaSetCaptureTabs(w, capture)             When true, Tab key inserts a tab/spaces instead of moving focus.
    -  wgtTextAreaSetTabWidth(w, width)                  Set the tab stop width in characters.
    -  wgtTextAreaSetUseTabChar(w, useChar)              When true, insert literal tab characters; when false, insert spaces.
    -  wgtTextAreaFindNext(w, needle, caseSens, fwd)     Search for the next occurrence. Returns true if found.
    -  wgtTextAreaReplaceAll(w, needle, repl, caseSens)  Replace all occurrences. Returns the number of replacements made.
    -  wgtTextAreaSetLineDecorator(w, fn, ctx)           Set a gutter line decorator callback. The callback returns a color and receives the line number, a color output pointer, and the user context.
    -  wgtTextAreaGetCursorLine(w)                       Get the current cursor line number.
    -  wgtTextAreaSetGutterClick(w, fn)                  Set a callback for gutter clicks (e.g. for breakpoint toggling). Callback receives the widget and line number.
    +

    API Functions (TextArea-specific)

    +
      Function                                                    Description
    +  --------                                                    -----------
    +  void wgtTextAreaSetColorize(w, fn, ctx)                     Set a syntax colorization callback. The callback receives each line and fills a color index array.
    +  void wgtTextAreaGoToLine(w, line)                           Scroll to and place the cursor on the given line number.
    +  void wgtTextAreaSetAutoIndent(w, enable)                    Enable or disable automatic indentation on newline.
    +  void wgtTextAreaSetShowLineNumbers(w, show)                 Show or hide line numbers in the gutter.
    +  void wgtTextAreaSetCaptureTabs(w, capture)                  When true, Tab key inserts a tab/spaces instead of moving focus.
    +  void wgtTextAreaSetTabWidth(w, width)                       Set the tab stop width in characters.
    +  void wgtTextAreaSetUseTabChar(w, useChar)                   When true, insert literal tab characters; when false, insert spaces.
    +  bool wgtTextAreaFindNext(w, needle, caseSens, fwd)          Search for the next occurrence. Returns true if found.
    +  int32_t wgtTextAreaReplaceAll(w, needle, repl, caseSens)    Replace all occurrences. Returns the number of replacements made.
    +  void wgtTextAreaSetLineDecorator(w, fn, ctx)                Set a gutter line decorator callback. Returns a color and receives the line number.
    +  int32_t wgtTextAreaGetCursorLine(w)                         Get the current cursor line number.
    +  void wgtTextAreaSetGutterClickCallback(w, fn)               Set a callback for gutter clicks (e.g. for breakpoint toggling).
    +  int32_t wgtTextAreaGetWordAtCursor(w, buf, bufSize)         Copy the word under the cursor into buf. Returns its length.
    +  void wgtTextAreaSetSyntaxColors(w, colors, count)           Set the color palette used by the colorizer.
    +

    API Struct (wgtRegisterApi "textinput")

    +

    The combined "textinput" API exposes all four constructors plus the TextArea functions. The designer uses per-type APIs registered under the names "textbox" (single-line) and "textarea" (multi-line), each exposing only a create slot.

    +

    Properties (BASIC Interface)

    +

    TextBox has no widget-specific properties. Text is managed via the generic "Text" property. TextArea exposes:

    +
      Property     Type       Access       Description
    +  --------     ----       ------       -----------
    +  CursorLine   Integer    Read-only    Current cursor line number (0-based).
    +

    Methods (BASIC Interface, TextArea)

    +
      Method                                            Description
    +  ------                                            -----------
    +  FindNext needle$, caseSensitive, forward          Search for the next occurrence. Returns True if found.
    +  GetWordAtCursor()                                 Returns the word under the cursor.
    +  GoToLine line%                                    Scroll to and position the cursor on a line.
    +  ReplaceAll needle$, replacement$, caseSensitive   Replace all occurrences. Returns the count.
    +  SetAutoIndent enabled                             Enable or disable automatic indentation on newline.
    +  SetCaptureTabs enabled                            When True, Tab inserts whitespace instead of moving focus.
    +  SetShowLineNumbers show                           Show or hide line numbers in the gutter.
    +  SetSyntaxMode mode$                               Activate built-in syntax highlighting (e.g. "dhs", "bas"; "" to disable).
    +  SetTabWidth width%                                Set the tab stop width in characters.
    +  SetUseTabChar useChar                             When True, Tab inserts a literal tab; when False, spaces.

    Events

      Callback     Description
       --------     -----------
       onChange     Fires when the text content changes.
       onKeyPress   Fires on each key press (ASCII value).
       onValidate   Called before committing a change. Return false to cancel.
    -
    +

    Default Event

    +

    "Change" for both TextBox (basName: TextBox) and TextArea (basName: TextArea).

    Timer

    A non-visual widget that fires its onClick callback at a regular interval. Useful for animations, polling, and periodic updates. Must be explicitly started after creation.

    -

    Header: widgets/widgetTimer.h

    +

    Header: widgets/timer.h

    Creation

    WidgetT *tmr = wgtTimer(parent, 1000, true);  // 1 second, repeating
     tmr->onClick = onTimerTick;
     wgtTimerStart(tmr);
    -

    Macros

    -
      Macro                                          Description
    -  -----                                          -----------
    -  wgtTimer(parent, intervalMs, repeat)           Create a timer. intervalMs is the interval in milliseconds. repeat: true for repeating, false for one-shot.
    -  wgtTimerStart(w)                               Start the timer.
    -  wgtTimerStop(w)                                Stop the timer.
    -  wgtTimerSetInterval(w, intervalMs)             Change the timer interval.
    -  wgtTimerIsRunning(w)                           Returns true if the timer is currently running.
    -  wgtUpdateTimers()                              Global tick function. Called by the event loop to advance all active timers.
    +

    API Functions

    +
      Function                                          Description
    +  --------                                          -----------
    +  WidgetT *wgtTimer(parent, intervalMs, repeat)     Create a timer. intervalMs is the interval in milliseconds. repeat: true for repeating, false for one-shot.
    +  void wgtTimerStart(w)                             Start the timer.
    +  void wgtTimerStop(w)                              Stop the timer.
    +  void wgtTimerSetInterval(w, intervalMs)           Change the timer interval.
    +  int32_t wgtTimerGetInterval(w)                    Returns the current interval in milliseconds.
    +  bool wgtTimerIsRunning(w)                         Returns true if the timer is currently running.
    +  void wgtTimerSetEnabled(w, enabled)               Start or stop the timer based on enabled flag.
    +  void wgtUpdateTimers(void)                        Global tick function. Called by the event loop to advance all active timers.
    +

    API Struct (wgtRegisterApi "timer")

    +
      Slot           Function
    +  ----           --------
    +  create         wgtTimer
    +  start          wgtTimerStart
    +  stop           wgtTimerStop
    +  setInterval    wgtTimerSetInterval
    +  isRunning      wgtTimerIsRunning
    +  updateTimers   wgtUpdateTimers

    Events

      Callback     Description
       --------     -----------
    -  onClick      Fires each time the timer elapses.
    + onClick Fires each time the timer elapses (the BASIC layer surfaces this as the "Timer" event).

    Properties (BASIC Interface)

      Property     Type       Access       Description
       --------     ----       ------       -----------
       Enabled      Boolean    Read/Write   Whether the timer is running. Reading returns the running state; writing starts or stops it.
    -  Interval     Integer    Write-only   Timer interval in milliseconds.
    + Interval Integer Read/Write Timer interval in milliseconds.

    Methods (BASIC Interface)

      Method       Description
       ------       -----------
    @@ -5203,69 +7071,101 @@ wgtTimerStart(tmr);

    Toolbar

    A horizontal container for toolbar buttons and controls. Typically placed at the top of a window. Children (usually ImageButtons or Buttons) are laid out horizontally with toolbar-appropriate spacing.

    -

    Header: widgets/widgetToolbar.h

    +

    Header: widgets/toolbar.h

    Creation

    WidgetT *tb = wgtToolbar(parent);
     wgtImageButtonFromFile(tb, "icons/save.bmp");
     wgtImageButtonFromFile(tb, "icons/open.bmp");
    -

    Macro

    -
      Macro                Description
    -  -----                -----------
    -  wgtToolbar(parent)   Create a toolbar container.
    -

    Properties

    -

    Uses common container properties. Add children (buttons, separators, etc.) to populate the toolbar.

    -

    Events

    -

    Toolbar itself has no widget-specific events. Events fire on the child widgets.

    -
    +

    API Functions

    +
      Function                          Description
    +  --------                          -----------
    +  WidgetT *wgtToolbar(parent)       Create a toolbar container.
    +

    API Struct (wgtRegisterApi "toolbar")

    +
      Slot       Function
    +  ----       --------
    +  create     wgtToolbar
    +

    Properties, Methods, Events

    +

    Toolbar has no widget-specific properties, methods, or events. Add children (buttons, separators, etc.) to populate.

    +

    Default Event

    +

    None. (VB basName: Toolbar.)

    TreeView

    A hierarchical tree control with expandable/collapsible nodes. Supports single and multi-selection and drag-to-reorder. Tree items are added as children of the TreeView or of other tree items to create nested hierarchies.

    -

    Header: widgets/widgetTreeView.h

    +

    Header: widgets/treeView.h

    Creation

    WidgetT *tv    = wgtTreeView(parent);
     WidgetT *root  = wgtTreeItem(tv, "Root");
     WidgetT *child = wgtTreeItem(root, "Child");
    -

    Macros

    -
      Macro                                          Description
    -  -----                                          -----------
    -  wgtTreeView(parent)                            Create a tree view control.
    -  wgtTreeItem(parent, text)                      Add a tree item as a child of the tree view or another tree item.
    -  wgtTreeViewGetSelected(w)                      Get the currently selected tree item (returns WidgetT *, NULL if none).
    -  wgtTreeViewSetSelected(w, item)                Set the selected tree item.
    -  wgtTreeViewSetMultiSelect(w, multi)            Enable or disable multi-selection.
    -  wgtTreeViewSetReorderable(w, reorderable)      Enable drag-to-reorder of items.
    -  wgtTreeItemSetExpanded(w, expanded)            Expand or collapse a tree item.
    -  wgtTreeItemIsExpanded(w)                       Check if a tree item is expanded.
    -  wgtTreeItemIsSelected(w)                       Check if a tree item is selected (multi-select mode).
    -  wgtTreeItemSetSelected(w, selected)            Select or deselect a tree item.
    +

    API Functions

    +
      Function                                              Description
    +  --------                                              -----------
    +  WidgetT *wgtTreeView(parent)                          Create a tree view control.
    +  WidgetT *wgtTreeItem(parent, text)                    Add a tree item as a child of the tree view or another tree item.
    +  WidgetT *wgtTreeViewGetSelected(w)                    Get the currently selected tree item (NULL if none).
    +  void wgtTreeViewSetSelected(w, item)                  Set the selected tree item.
    +  void wgtTreeViewSetMultiSelect(w, multi)              Enable or disable multi-selection.
    +  void wgtTreeViewSetReorderable(w, reorderable)        Enable drag-to-reorder of items.
    +  void wgtTreeItemSetExpanded(w, expanded)              Expand or collapse a tree item.
    +  bool wgtTreeItemIsExpanded(w)                         Check if a tree item is expanded.
    +  bool wgtTreeItemIsSelected(w)                         Check if a tree item is selected (multi-select mode).
    +  void wgtTreeItemSetSelected(w, selected)              Select or deselect a tree item.
    +

    API Struct (wgtRegisterApi "treeview")

    +
      Slot              Function
    +  ----              --------
    +  create            wgtTreeView
    +  getSelected       wgtTreeViewGetSelected
    +  setSelected       wgtTreeViewSetSelected
    +  setMultiSelect    wgtTreeViewSetMultiSelect
    +  setReorderable    wgtTreeViewSetReorderable
    +  item              wgtTreeItem
    +  itemSetExpanded   wgtTreeItemSetExpanded
    +  itemIsExpanded    wgtTreeItemIsExpanded
    +  itemIsSelected    wgtTreeItemIsSelected
    +  itemSetSelected   wgtTreeItemSetSelected
    +

    Properties

    +

    No widget-specific properties.

    +

    Methods (BASIC Interface)

    +
      Method                                Description
    +  ------                                -----------
    +  AddChildItem parentIdx%, text$        Add a child node under the node at the given index.
    +  AddItem text$                         Add a root-level node.
    +  Clear                                 Remove all nodes.
    +  GetItemText(index%)                   Returns the text of the node at the given depth-first index.
    +  IsExpanded(index%)                    Returns True if the node is expanded.
    +  IsItemSelected(index%)                Returns True if the node is selected.
    +  ItemCount()                           Returns the total number of nodes.
    +  SetExpanded index%, expanded          Expand or collapse the node.
    +  SetItemSelected index%, selected      Select or deselect the node.
    +  SetMultiSelect multi                  Enable or disable multi-selection.
    +  SetReorderable reorderable            Enable or disable drag-to-reorder.

    Events

      Callback     Description
       --------     -----------
       onClick      Fires when a tree item is clicked.
       onDblClick   Fires when a tree item is double-clicked.
       onChange     Fires when the selection changes.
    -

    Methods (BASIC Interface)

    -
      Method           Description
    -  ------           -----------
    -  SetMultiSelect   Enable or disable multi-selection.
    -  SetReorderable   Enable or disable drag-to-reorder.
    -
    +

    Default Event

    +

    "Click" (VB basName: TreeView).

    WrapBox

    A flow/wrap layout container that arranges children left-to-right, wrapping to the next row when the available width is exceeded. Each row's height is the maximum child height in that row. Supports configurable spacing between items and rows, and per-row alignment for short rows.

    -

    Header: widgets/widgetWrapBox.h

    +

    Header: widgets/wrapBox.h

    Creation

    WidgetT *wrap = wgtWrapBox(parent);
     wgtButton(wrap, "Tag 1");
     wgtButton(wrap, "Tag 2");
     wgtButton(wrap, "Tag 3");
    -

    Macro

    -
      Macro                  Description
    -  -----                  -----------
    -  wgtWrapBox(parent)     Create a wrap box container.
    -

    Properties

    +

    API Functions

    +
      Function               Description
    +  --------               -----------
    +  wgtWrapBox(parent)     Create a wrap box container. Macro that calls the registered "wrapbox" create slot.
    +

    API Struct (wgtRegisterApi "wrapbox")

    +
      Slot       Function
    +  ----       --------
    +  create     wrapBoxCreate
    +

    Properties (Common Container Fields)

    WrapBox uses the common WidgetT container fields for layout control:

      Property     Description
       --------     -----------
    @@ -5275,8 +7175,12 @@ wgtButton(wrap, "Tag 3");
      Property     Type                         Access       Description
       --------     ----                         ------       -----------
       Alignment    Enum (Left, Center, Right)   Read/Write   Row alignment for rows that do not fill the full width.
    +

    Methods

    +

    No widget-specific methods.

    Events

    WrapBox is a container widget. It uses the common events only. No widget-specific events are defined.

    +

    Default Event

    +

    "Click" (VB basName: WrapBox, namePrefix: WrapBox).

    diff --git a/mkcd.sh b/mkcd.sh index 0976baa..87ef75a 100755 --- a/mkcd.sh +++ b/mkcd.sh @@ -22,7 +22,7 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -# mkcd.sh -- Build DVX and create a CD-ROM ISO image for 86Box +# mkcd.sh -- Build DVX and create a CD-ROM ISO image for the target emulator # # Usage: ./mkcd.sh # @@ -30,8 +30,8 @@ # an ISO 9660 image from the bin/ directory. The ISO uses short # 8.3 filenames (-iso-level 1) for DOS compatibility. # -# The ISO is placed in 86Box's data directory so it can be mounted -# as a CD-ROM drive. +# The ISO is placed in the emulator's data directory so it can be +# mounted as a CD-ROM drive. set -e @@ -58,7 +58,7 @@ while IFS= read -r f; do done < <(find "$SCRIPT_DIR/bin/widgets/" -name "*.wgt" -type f 2>/dev/null) echo "$WGT_COUNT widget modules found in bin/widgets/." -# Clean DOSBox-X local filesystem artifacts +# Clean emulator local-filesystem artifacts find "$SCRIPT_DIR/bin/" -name ".DBLOCALFILE*" -delete 2>/dev/null # Create the ISO image @@ -78,5 +78,5 @@ mkisofs \ echo "ISO created: $ISO_PATH" echo "Size: $(du -h "$ISO_PATH" | cut -f1)" echo "" -echo "In 86Box, mount $ISO_PATH as a CD-ROM drive." +echo "Mount $ISO_PATH as a CD-ROM drive in the target emulator." echo "Then from DOS: D:\\DVX.EXE (or whatever drive letter)" diff --git a/src/apps/kpunch/README.md b/src/apps/kpunch/README.md index 10f1488..f5e6615 100644 --- a/src/apps/kpunch/README.md +++ b/src/apps/kpunch/README.md @@ -1,203 +1,472 @@ # DVX Shell Applications -DXE3 application modules for the DVX Shell. Each app is a `.app` file -(DXE3 shared object) in a subdirectory under `apps/`. The Program -Manager scans this directory at startup and displays all discovered -apps as launchable icons. +Every DVX application is a DXE3 module packaged as a `.app` file, +installed under `bin/apps/kpunch//.app`. The Program +Manager scans this directory at startup and displays each discovered +app as a launchable icon on the desktop. + +This directory holds the source for the bundled applications plus +the BASIC-based sample apps. Two flavours of app live side by side: + +* **C apps** -- written directly against the DVX SDK headers, + compiled to a DXE3 by `dxe3gen`. See `progman/`, `clock/`, + `dvxdemo/`, `cpanel/`, `dvxhelp/`. +* **BASIC apps** -- `.dbp` projects compiled to `.app` files by the + host-side `bascomp`. The compiler links the BASIC bytecode into a + copy of `basstub.app`, which acts as the runtime host. See + `iconed/`, `notepad/`, `imgview/`, `basicdemo/`, `resedit/`, + `dvxhelp/helpedit/`. + +The rest of this document covers writing a C app against the SDK. +For BASIC apps, see `dvxbasic/README.md`. ## DXE App Contract -Every app exports two symbols: +Every `.app` binary exports two required symbols and one optional +symbol: -* `appDescriptor` (`AppDescriptorT`) -- name, hasMainLoop, multiInstance, stackSize, priority -* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point +```c +AppDescriptorT appDescriptor; // required +int32_t appMain(DxeAppContextT *ctx); // required +void appShutdown(void); // optional +``` -Optional: `appShutdown` (`void appShutdown(void)`) -- called during -graceful shutdown. +### `appDescriptor` -### Callback-Only Apps (hasMainLoop = false) +A statically-initialised struct read by the shell at load time to +decide how to launch the app: -`appMain()` creates windows, registers callbacks, and returns 0. The -shell drives everything through event callbacks. No dedicated task or -stack is allocated. Lifecycle ends when the last window closes. +```c +typedef struct { + char name[SHELL_APP_NAME_MAX]; // display name (<= 64 chars) + bool hasMainLoop; // see below + bool multiInstance; // may have multiple copies open + int32_t stackSize; // SHELL_STACK_DEFAULT or byte count + int32_t priority; // TS_PRIORITY_* for main-loop apps +} AppDescriptorT; +``` -### Main-Loop Apps (hasMainLoop = true) +Example (from `clock/clock.c`): -A cooperative task is created for the app. `appMain()` runs its own -loop calling `tsYield()` to share CPU. Lifecycle ends when `appMain()` -returns. +```c +AppDescriptorT appDescriptor = { + .name = "Clock", + .hasMainLoop = true, + .multiInstance = true, + .stackSize = SHELL_STACK_DEFAULT, + .priority = TS_PRIORITY_LOW +}; +``` + +### `appMain` + +Entry point. The shell calls it with a `DxeAppContextT` that exposes +the shell's GUI context, the app's ID, its install directory, a +writable config directory, launch arguments, and a help-topic query +callback for F1. + +Returning from `appMain` signals that the app has finished; the +shell unloads the DXE, frees per-app memory, and reclaims the app +slot. + +### `appShutdown` + +Called by the shell when the app is force-killed (by the Task +Manager) or when the shell is shutting down with this app still +running. Main-loop apps use it to set a flag that their main loop +observes, so the loop exits cleanly and `appMain` can return. -## Applications +## App Types + +### Callback-only apps (`hasMainLoop = false`) + +`appMain` creates windows, registers callbacks, and returns 0. The +shell drives all further work by invoking those callbacks in +response to user input and timers. No dedicated task is allocated; +no per-app stack is consumed. The app's lifetime ends when the last +window closes (or when every callback has released all references). + +Use this pattern for event-driven UI: Notepad, Program Manager, DVX +Demo, Control Panel, Image Viewer. + +### Main-loop apps (`hasMainLoop = true`) + +The shell creates a cooperative task for the app and calls +`appMain` from inside it. `appMain` runs a loop that calls +`tsYield()` periodically to share the CPU. Exit by breaking out of +the loop and returning. + +Use this pattern when the app needs continuous background work that +does not map cleanly onto event callbacks: Clock (polls the system +clock every second), terminal emulators, games. + + +## Resource Conventions + +Apps attach resources to their `.app` binary using `dvxres build` +(see `src/tools/README.md`). The standard resource names are: + +| Name | Type | Meaning | +|---------------|--------|---------| +| `icon32` | icon | 32x32 BMP shown in Program Manager and the Task Manager. | +| `name` | text | Human-readable display name (overrides `appDescriptor.name` for UI where available). | +| `author` | text | Author / maintainer name. | +| `publisher` | text | Publisher or studio. | +| `copyright` | text | Copyright notice. | +| `version` | text | Version string, free-form. | +| `description` | text | One-sentence description shown in About dialogs. | +| `helpfile` | text | Optional relative path to a `.hlp` file for F1 context help. | + +Additional application-specific resources may be attached with any +unique name. The runtime reads them via +`dvxResOpen(appPath)` / `dvxResRead(handle, name, &size)` / +`dvxResClose(handle)`. + +Typical `.res` manifest: + +``` +# clock.res +icon32 icon icon32.bmp +name text "Clock" +author text "Scott Duensing" +copyright text "Copyright 2026 Scott Duensing" +publisher text "Kangaroo Punch Studios" +description text "Digital clock with date display" +``` + + +## Bundled Applications ### Program Manager (progman) | | | |---|---| -| File | `apps/progman/progman.app` | +| File | `apps/kpunch/progman/progman.app` | | Type | Callback-only | | Multi-instance | No | -The desktop app. Scans `apps/` recursively for `.app` files and -displays them in a grid. Double-click or Enter launches an app. -Includes a Help menu with system information dialog and About box. +The desktop. Recursively scans `apps/` for `.app` files and displays +them as a launchable grid with icons, names, and publishers. Double +click or Enter to launch. Includes a Help menu with the system help +viewer and the About / System Info dialog. Registers with +`shellRegisterDesktopUpdate()` so the grid refreshes when apps are +loaded, terminated, or crash. -Registers with `shellRegisterDesktopUpdate()` to refresh when apps -are loaded, crash, or terminate. +Widget headers used: `box/box.h`, `button/button.h`, `label/label.h`, +`statusBar/statBar.h`, `textInput/textInpt.h`, `imageButton/imgBtn.h`, +`scrollPane/scrlPane.h`, `wrapBox/wrapBox.h`. -Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetLabel.h`, -`widgetStatusBar.h`, `widgetTextInput.h`. - -### Notepad (notepad) +### Notepad (notepad, BASIC) | | | |---|---| -| File | `apps/notepad/notepad.app` | -| Type | Callback-only | -| Multi-instance | Yes | +| File | `apps/kpunch/notepad/notepad.app` | +| Type | BASIC (main-loop via basstub) | +| Multi-instance | No | -Text editor with File menu (New, Open, Save, Save As) and Edit menu -(Cut, Copy, Paste, Select All). Uses the TextArea widget for all -editing. 32KB text buffer limit. Tracks dirty state for save prompts. -Keyboard accelerators for all menu commands. - -Widget headers used: `widgetTextInput.h`. +Plain-text editor with File menu (New, Open, Save, Save As) and Edit +menu (Cut, Copy, Paste, Select All). Built from a BASIC project +(`notepad.dbp` + `notepad.frm`); demonstrates the form designer, +TextInput widget, and common dialog integration. ### Clock (clock) | | | |---|---| -| File | `apps/clock/clock.app` | +| File | `apps/kpunch/clock/clock.app` | | Type | Main-loop | | Multi-instance | Yes | -Digital clock display showing time and date. Demonstrates the main-loop -app pattern -- polls the system clock every second and invalidates the -window to trigger a repaint. Uses raw `onPaint` callbacks (no widgets) -to draw centered text. - -Widget headers used: none (raw paint callbacks). +Digital clock showing time and date. Polls the system clock every +second and invalidates the window to trigger a repaint. Uses the raw +`onPaint` callback (no widgets) to draw centered text. Reference +implementation for main-loop apps. ### DVX Demo (dvxdemo) | | | |---|---| -| File | `apps/dvxdemo/dvxdemo.app` | +| File | `apps/kpunch/dvxdemo/dvxdemo.app` | | Type | Callback-only | | Multi-instance | No | -Comprehensive widget system showcase. Opens multiple windows -demonstrating 31 of the 32 widget types (all except Timer): +Widget toolkit showcase. Opens multiple windows demonstrating +virtually every widget type: -* Main window: raw paint callbacks (gradients, patterns, text) -* Widget demo: form widgets (TextInput, Checkbox, Radio, ListBox) -* Controls window: TabControl with tabs for advanced widgets - (Dropdown, ProgressBar, Slider, Spinner, TreeView, ListView, - ScrollPane, Toolbar, Canvas, Splitter, Image) -* Terminal window: AnsiTerm widget +* Main window -- raw paint callbacks (gradients, patterns, text) +* Widget demo -- form widgets (TextInput, Checkbox, Radio, ListBox) +* Controls window -- tabbed advanced widgets (Dropdown, + ProgressBar, Slider, Spinner, TreeView, ListView, ScrollPane, + Toolbar, Canvas, Splitter, Image) +* Terminal window -- AnsiTerm widget -Widget headers used: 25 of the 26 widget headers (all except widgetTimer.h). - -Resources: `logo.bmp`, `new.bmp`, `open.bmp`, `sample.bmp`, `save.bmp`. +Resources include `logo.bmp`, `new.bmp`, `open.bmp`, `sample.bmp`, +`save.bmp`. Reference implementation for widget-based UIs. ### Control Panel (cpanel) | | | |---|---| -| File | `apps/cpanel/cpanel.app` | +| File | `apps/kpunch/cpanel/cpanel.app` | | Type | Callback-only | | Multi-instance | No | System configuration with four tabs: -* **Mouse** -- scroll direction, double-click speed, acceleration -* **Colors** -- all 20 system colors with live preview, theme load/save -* **Desktop** -- wallpaper image selection and display mode -* **Video** -- resolution and color depth switching +* **Mouse** -- scroll direction, wheel speed, double-click speed, + acceleration, cursor speed +* **Colors** -- every system colour with live preview; theme load + and save +* **Desktop** -- wallpaper image and display mode +* **Video** -- resolution and colour depth switching -Changes preview live. OK saves to `DVX.INI`, Cancel reverts to the -state captured when the control panel was opened. +Changes preview live. OK writes to `CONFIG/DVX.INI`; Cancel reverts +to the snapshot taken when the panel opened. -Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetCanvas.h`, -`widgetDropdown.h`, `widgetLabel.h`, `widgetListBox.h`, `widgetSlider.h`, -`widgetSpacer.h`, `widgetTabControl.h`. - -### Image Viewer (imgview) +### Image Viewer (imgview, BASIC) | | | |---|---| -| File | `apps/imgview/imgview.app` | -| Type | Callback-only | -| Multi-instance | Yes | - -Displays BMP, PNG, JPEG, and GIF images. The image is scaled to fit -the window while preserving aspect ratio. Resize the window to zoom. -Open files via the File menu or by launching with Run in the Task -Manager. - -Widget headers used: none (raw paint callbacks with `dvxLoadImage()`). +| File | `apps/kpunch/imgview/imgview.app` | +| Type | BASIC (main-loop via basstub) | +| Multi-instance | No | +Displays BMP, PNG, JPEG, and GIF images scaled to fit the window +with aspect ratio preserved. Resize to zoom. Open via the File menu +or via launch arguments. Built from a BASIC project using the +Canvas widget. ### DVX BASIC (dvxbasic) | | | |---|---| -| File | `apps/dvxbasic/dvxbasic.app` | +| File | `apps/kpunch/dvxbasic/dvxbasic.app` | | Type | Callback-only | | Multi-instance | No | -Visual Basic 3 clone with form designer, per-procedure code editor, -project management, and stack-based bytecode compiler/VM. Supports -event-driven programming with visual forms, controls, and a full -BASIC language implementation. +Visual Basic 3 compatible IDE: form designer, per-procedure code +editor, project manager, compiler, and runtime VM. See +`dvxbasic/README.md` for a detailed architecture description. -See `apps/dvxbasic/README.md` for detailed documentation. +`dvxbasic/` also builds: -Also builds `basrt.lib` -- the BASIC runtime library (VM + values) -as a separate DXE so compiled BASIC apps can use it independently. +* `bin/libs/kpunch/basrt/basrt.lib` -- the BASIC runtime (VM + + values) as a separately loadable library, so compiled BASIC apps + do not carry their own copy of the runtime. +* `bin/apps/kpunch/dvxbasic/basstub.app` -- the stub used as the + template for every compiled BASIC app. + +### DVX Help Viewer (dvxhelp) + +| | | +|---|---| +| File | `apps/kpunch/dvxhelp/dvxhelp.app` | +| Type | Callback-only | +| Multi-instance | Yes | + +Renders compiled `.hlp` files produced by `dvxhlpc`. Supports table +of contents, keyword index, full-text search (trigram), hyperlinks, +and inline images. + +### Icon Editor (iconed, BASIC) + +| | | +|---|---| +| File | `apps/kpunch/iconed/iconed.app` | +| Type | BASIC (main-loop via basstub) | +| Multi-instance | No | + +Paint-program-style editor for creating 32x32 BMP icons. Pen / +eraser / fill tools, 16-colour palette, BMP save / load. + +### Help Editor (helpedit, BASIC) + +| | | +|---|---| +| File | `apps/kpunch/dvxhelp/helpedit.app` | +| Type | BASIC (main-loop via basstub) | +| Multi-instance | No | + +Editor for `.dhs` help source files with syntax-aware controls. Runs +the compiled system help viewer by default for live preview. + +### Resource Editor (resedit, BASIC) + +| | | +|---|---| +| File | `apps/kpunch/resedit/resedit.app` | +| Type | BASIC (main-loop via basstub) | +| Multi-instance | No | + +GUI wrapper around `dvxres`: open a `.app` / `.wgt` / `.lib`, +browse its resources, add, replace, extract, or remove them. + +### BASIC Demo (basicdemo, BASIC) + +| | | +|---|---| +| File | `apps/kpunch/basicdemo/basicdemo.app` | +| Type | BASIC (main-loop via basstub) | +| Multi-instance | No | + +Gallery of short BASIC programs demonstrating the language and the +form designer. Intended as sample source for reading. ## Build ``` -make # builds all 7 app DXE modules -make clean # removes objects and app files +make # build every app (C apps + BASIC apps) +make # build a single app +make clean # remove all app build artifacts ``` -Each app compiles to a single `.o` (or multiple for DVX BASIC), -then is packaged via `dxe3gen` into a `.app` DXE exporting -`appDescriptor` and `appMain`. +The `apps/kpunch/Makefile` orchestrates both C and BASIC builds: -Output goes to `bin/apps//.app`. +* **C apps** compile each `/.c` to an object file, run + `dxe3gen -U` on it to produce `.app`, then attach resources + with `dvxres build` if a `.res` manifest exists. +* **BASIC apps** run `bascomp .dbp -o .app -release`, + which handles project-file parsing, compilation, resource + attachment, and linking into `basstub.app`. + +Build output goes to `bin/apps/kpunch//.app`. + + +## Writing a New C App + +Minimal skeleton: + +```c +#include "dvxApp.h" +#include "dvxWgt.h" +#include "shellApp.h" + +#include +#include + +// Prototypes +static int32_t appMainImpl(DxeAppContextT *ctx); +static void onClose(WindowT *win); + +// Required descriptor +AppDescriptorT appDescriptor = { + .name = "My App", + .hasMainLoop = false, + .multiInstance = false, + .stackSize = SHELL_STACK_DEFAULT, + .priority = 0 +}; + + +static void onClose(WindowT *win) { + (void)win; + // Callback-only apps: nothing to do; the shell reaps us when + // the last window closes. +} + + +int32_t appMain(DxeAppContextT *ctx) { + return appMainImpl(ctx); +} + + +static int32_t appMainImpl(DxeAppContextT *ctx) { + AppContextT *ac = ctx->shellCtx; + + WindowT *win = dvxCreateWindow(ac, appDescriptor.name, 100, 100, 320, 240, true); + + if (!win) { + return -1; + } + + win->onClose = onClose; + return 0; +} +``` + +Add a Makefile target that mirrors the existing apps (see +`apps/kpunch/Makefile` for the pattern), drop a `.res` manifest next +to the source, and provide a 32x32 `icon32.bmp`. `make ` should +then produce `bin/apps/kpunch//.app`. + + +## Testing Locally + +After `make`, the app is deployed in-tree under `bin/apps/kpunch/`. +Launch the full system (`bin/dvx.exe`) and the Program Manager picks +up the new app automatically on the next boot. + +To iterate quickly: rebuild the single app, restart DVX, and launch +it. The log at `bin/DVX.LOG` records load errors, missing +dependencies, and per-app crash diagnostics. ## Files ``` -apps/ - Makefile top-level build for all apps +apps/kpunch/ + Makefile top-level app build + README.md this file + + # C apps progman/ - progman.c Program Manager - notepad/ - notepad.c text editor + progman.c Program Manager + dvxhelp.hcf help-compiler config (system reference) clock/ - clock.c digital clock + clock.c digital clock (main-loop reference) + clock.res + icon32.bmp dvxdemo/ - dvxdemo.c widget demo - logo.bmp DVX logo bitmap - new.bmp toolbar icon - open.bmp toolbar icon - sample.bmp sample image - save.bmp toolbar icon + dvxdemo.c widget showcase + dvxdemo.res + icon32.bmp + logo.bmp / new.bmp / open.bmp / sample.bmp / save.bmp cpanel/ - cpanel.c control panel + cpanel.c Control Panel + cpanel.res + icon32.bmp + dvxhelp/ + dvxhelp.c help viewer + dvxhelp.res + help.dhs source for the dvxhelp app's own help + hlpformat.h binary format shared with the compiler + sample.dhs small sample source + icon32.bmp + helpedit/ BASIC project: help editor + helpedit.dbp + helpedit.frm + ICON32.BMP + + # BASIC apps + iconed/ + iconed.dbp + iconed.frm + ICON32.BMP + notepad/ + notepad.dbp + notepad.frm + ICON32.BMP imgview/ - imgview.c image viewer - dvxbasic/ - compiler/ BASIC compiler (lexer, parser, codegen) - runtime/ VM and value system - formrt/ form runtime (BASIC <-> DVX widgets) - ide/ IDE (editor, designer, project, properties) - samples/ sample .bas, .frm, .dbp files + imgview.dbp + imgview.frm + ICON32.BMP + basicdemo/ + basicdemo.dbp + basicdemo.frm + ICON32.BMP + resedit/ + resedit.dbp + resedit.frm + ICON32.BMP + + dvxbasic/ see dvxbasic/README.md + compiler/ lexer, parser, codegen, strip, obfuscate + runtime/ VM + value system + formrt/ form runtime (BASIC <-> DVX widgets) + ide/ IDE front end + stub/ bascomp + basstub + ... ``` diff --git a/src/apps/kpunch/cpanel/cpanel.c b/src/apps/kpunch/cpanel/cpanel.c index 0a78efb..7690816 100644 --- a/src/apps/kpunch/cpanel/cpanel.c +++ b/src/apps/kpunch/cpanel/cpanel.c @@ -234,7 +234,7 @@ static void applyMouseConfig(void) { static void buildColorsTab(WidgetT *page) { // Color list on the left, sliders on the right WidgetT *hbox = wgtHBox(page); - hbox->weight = 100; + hbox->weight = WGT_WEIGHT_FILL; hbox->spacing = wgtPixels(CP_SPACING); // Left side: color name list + theme controls @@ -252,7 +252,7 @@ static void buildColorsTab(WidgetT *page) { } sColorList = wgtListBox(leftVbox); - sColorList->weight = 100; + sColorList->weight = WGT_WEIGHT_FILL; sColorList->onChange = onColorSelect; wgtListBoxSetItems(sColorList, colorNames, ColorCountE); wgtListBoxSetSelected(sColorList, 0); @@ -265,7 +265,7 @@ static void buildColorsTab(WidgetT *page) { scanThemes(); sThemeList = wgtDropdown(themeRow); - sThemeList->weight = 100; + sThemeList->weight = WGT_WEIGHT_FILL; wgtDropdownSetItems(sThemeList, sThemeLabels, arrlen(sThemeEntries)); WidgetT *loadBtn = wgtButton(themeRow, "Apply"); @@ -314,7 +314,7 @@ static void buildColorsTab(WidgetT *page) { sColorSwatch = wgtCanvas(sliderBox, CP_SWATCH_W, CP_SWATCH_H); // Absorb remaining vertical space below the sliders - wgtSpacer(rightVbox)->weight = 100; + wgtSpacer(rightVbox)->weight = WGT_WEIGHT_FILL; updateColorSliders(); } @@ -325,7 +325,7 @@ static void buildDesktopTab(WidgetT *page) { scanWallpapers(); sWpaperList = wgtListBox(page); - sWpaperList->weight = 100; + sWpaperList->weight = WGT_WEIGHT_FILL; sWpaperList->onDblClick = onApplyWallpaper; wgtListBoxSetItems(sWpaperList, sWpaperLabels, arrlen(sWpaperEntries)); @@ -346,7 +346,7 @@ static void buildDesktopTab(WidgetT *page) { clearBtn->onClick = onClearWallpaper; clearBtn->prefW = wgtPixels(CP_WPAPER_BTN_W); - wgtSpacer(btnRow)->weight = 100; + wgtSpacer(btnRow)->weight = WGT_WEIGHT_FILL; wgtLabel(btnRow, "Mode:"); static const char *modeItems[] = {"Stretch", "Tile", "Center"}; @@ -367,7 +367,7 @@ static void buildMouseTab(WidgetT *page) { static const char *wheelItems[] = {"Normal", "Reversed"}; sWheelDrop = wgtDropdown(wheelRow); - sWheelDrop->weight = 100; + sWheelDrop->weight = WGT_WEIGHT_FILL; sWheelDrop->onChange = onWheelChange; wgtDropdownSetItems(sWheelDrop, wheelItems, 2); wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0); @@ -382,7 +382,7 @@ static void buildMouseTab(WidgetT *page) { wgtLabel(wheelStepRow, "Slow"); sWheelStepSldr = wgtSlider(wheelStepRow, MOUSE_WHEEL_STEP_MIN, MOUSE_WHEEL_STEP_MAX); - sWheelStepSldr->weight = 100; + sWheelStepSldr->weight = WGT_WEIGHT_FILL; sWheelStepSldr->onChange = onWheelStepSlider; int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT); @@ -402,7 +402,7 @@ static void buildMouseTab(WidgetT *page) { wgtLabel(dblRow, "Fast"); sDblClickSldr = wgtSlider(dblRow, 200, 900); - sDblClickSldr->weight = 100; + sDblClickSldr->weight = WGT_WEIGHT_FILL; sDblClickSldr->onChange = onDblClickSlider; int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", MOUSE_DBLCLICK_DEFAULT_MS); @@ -421,7 +421,7 @@ static void buildMouseTab(WidgetT *page) { static const char *accelItems[] = {"Off", "Low", "Medium", "High"}; sAccelDrop = wgtDropdown(accelRow); - sAccelDrop->weight = 100; + sAccelDrop->weight = WGT_WEIGHT_FILL; sAccelDrop->onChange = onAccelChange; wgtDropdownSetItems(sAccelDrop, accelItems, 4); @@ -447,7 +447,7 @@ static void buildMouseTab(WidgetT *page) { wgtLabel(speedRow, "Slow"); sSpeedSldr = wgtSlider(speedRow, 2, 32); - sSpeedSldr->weight = 100; + sSpeedSldr->weight = WGT_WEIGHT_FILL; sSpeedSldr->onChange = onSpeedSlider; int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT); @@ -501,7 +501,7 @@ static void buildVideoTab(WidgetT *page) { } sVideoList = wgtListBox(page); - sVideoList->weight = 100; + sVideoList->weight = WGT_WEIGHT_FILL; sVideoList->onDblClick = onVideoApply; wgtListBoxSetItems(sVideoList, sVideoLabels, sVideoCount); @@ -1153,7 +1153,7 @@ int32_t appMain(DxeAppContextT *ctx) { // Tab control fills the window WidgetT *tabs = wgtTabControl(root); - tabs->weight = 100; + tabs->weight = WGT_WEIGHT_FILL; WidgetT *mouseTab = wgtTabPage(tabs, "Mouse"); WidgetT *colorsTab = wgtTabPage(tabs, "Colors"); diff --git a/src/apps/kpunch/dvxbasic/README.md b/src/apps/kpunch/dvxbasic/README.md index a18cb99..f2b6a9f 100644 --- a/src/apps/kpunch/dvxbasic/README.md +++ b/src/apps/kpunch/dvxbasic/README.md @@ -1,97 +1,55 @@ # DVX BASIC -A Visual Basic 3 clone for the DVX GUI system. Provides a visual form -designer, per-procedure code editor, project management, and a -stack-based bytecode compiler/VM for running event-driven BASIC programs. +A Visual Basic 3 clone for the DVX GUI system. Provides a visual form +designer, per-procedure code editor, project management, and a stack- +based bytecode compiler and VM for running event-driven BASIC programs. -## Architecture +## Documentation -DVX BASIC is built as a DXE3 app (dvxbasic.app) loaded by the DVX shell. -It consists of four major subsystems: +For BASIC authors, the complete user reference is split across several +`.dhs` help sources in this directory, all compiled into the DVX BASIC +Reference: -### Compiler (compiler/) +- `langref.dhs` -- language reference: data types, keywords, operators, + control flow, built-in functions, I/O, error codes. +- `ideguide.dhs` -- IDE guide: menus, toolbar, project model, form + designer, code editor, debugger, preferences. +- `ctrlover.dhs` -- control overview: common widget properties and + events from a BASIC author's perspective. +- `form.dhs` -- form lifecycle, properties, events. +- `basrt.dhs` -- BASIC runtime reference: DECLARE LIBRARY mechanism, + shipped include files. -Single-pass compiler that translates BASIC source to stack-based p-code. +After `make`, the compiled form is `bin/apps/kpunch/dvxbasic/dvxbasic.hlp` +(viewable via the Help Viewer app or from within the IDE via F1) and +`docs/dvx_basic_reference.html`. -- **lexer.c** -- Tokenizer with keyword lookup, type suffixes, hex literals, - line continuation (`_`), and `?` as PRINT shortcut. -- **parser.c** -- Recursive descent parser. Handles all VB3 statements, - expressions, operator precedence (VB-correct: `^` binds tighter than - unary `-`), Sub/Function with forward references, bare sub calls, - OPTION EXPLICIT, STATIC, CONST, DEF FN, SELECT CASE, ON ERROR GOTO, - UDTs (nested), arrays of UDTs, DECLARE LIBRARY. -- **codegen.c** -- Bytecode emitter. Dynamic arrays (stb_ds) for code, - constants, and proc table. Module building, forward reference patching, - unresolved reference detection. -- **symtab.c** -- Symbol table with local/global scoping, type tracking. -- **opcodes.h** -- ~90 bytecode instructions. OP_END (explicit END statement) - vs OP_HALT (implicit end of module). +## Subdirectories (for maintainers) -### Runtime (runtime/) - -- **vm.c** -- Stack-based virtual machine. Step-limited execution for - cooperative multitasking. Event handler dispatch via basVmCallSub - (runs to completion, no step limit). basVmCallSubWithArgsOut for - events that return values (QueryUnload Cancel parameter). -- **values.c** -- Tagged value system: Integer, Long, Single, Double, - String (ref-counted), Boolean, Array (ref-counted, multi-dim), UDT - (ref-counted, nested), Object (opaque host pointer), Ref (ByRef). - -### Form Runtime (formrt/) - -- **formrt.c** -- Bridge between BASIC VM and DVX widgets. Loads .frm - files at runtime, creates windows and controls, dispatches events to - BASIC code. Supports property get/set, method calls, and control - creation via the widget interface system. - -Events: Click, DblClick, Change, GotFocus, LostFocus, KeyPress, -KeyDown, KeyUp, MouseDown, MouseUp, MouseMove, Scroll, Load, -QueryUnload, Unload, Resize, Activate, Deactivate, Timer. - -### IDE (ide/) - -- **ideMain.c** -- Main IDE orchestration. File switching via - `activateFile()`, per-procedure editing, Object/Event dropdowns - with hash-based syntax highlighting, dirty tracking with - `sEditorFileIdx` ownership, compile-and-run with IDE window - hiding, VB-style event loop. -- **ideDesigner.c** -- Visual form designer. Live widget creation, - drag/resize handles, widget interface property persistence - (including WGT_IFACE_ENUM with named values), code-in-FRM files. -- **ideProject.c** -- Project system (.dbp files, INI format). Project - window with tree view, project properties dialog with startup form - dropdown and icon browser. All files loaded into memory at project - open. -- **ideProperties.c** -- Property editor. ListView with type-aware - editing (bool toggle, int spinner, enum cycling, string input). - Interface property display via widget descriptors. -- **ideToolbox.c** -- Widget palette loaded from registered widget - interfaces. +- `compiler/` -- lexer, parser, codegen, symbol table, opcodes. + Single-pass compiler that translates BASIC source to stack-based + p-code. +- `runtime/` -- VM and tagged value system (values.c, vm.c, serialize.c). +- `formrt/` -- form runtime, the bridge between the BASIC VM and the + DVX widget system. +- `ide/` -- the IDE itself (main, designer, project, properties, + toolbox, menu editor). +- `stub/` -- `basstub` (runtime loader for compiled BASIC `.app` + files) and `bascomp` (standalone command-line compiler). ## Project Files project.dbp INI-format project file module.bas BASIC module (code only) - form.frm Form file (layout + code section after "End") + form.frm Form file (layout + code after the form's End) -.bas modules are compiled before .frm code sections so CONST -declarations are available to form event handlers. +`.bas` modules compile before `.frm` code sections, so CONST +declarations are visible to form event handlers. ## Build - make -C apps/dvxbasic # cross-compile for DJGPP - make -C apps/dvxbasic tests # build native test programs + make -C src/apps/kpunch/dvxbasic # cross-compile for DOS + make -C src/apps/kpunch/dvxbasic tests # build native test programs -Test programs: test_compiler, test_vm, test_lex, test_quick. -162 test sections covering the compiler, VM, and language features. - -## File Structure - - apps/dvxbasic/ - compiler/ Lexer, parser, codegen, symbol table, opcodes - runtime/ VM and tagged value system - formrt/ Form runtime (BASIC <-> DVX widget bridge) - ide/ IDE (main, designer, project, properties, toolbox) - samples/ Sample .bas, .frm, .dbp files - dvxbasic.res App resources (icons, toolbar buttons) - Makefile Builds basrt.lib (runtime) + dvxbasic.app (IDE) +Test programs: `test_compiler`, `test_vm`, `test_lex`, `test_quick`, +`test_compact`. diff --git a/src/apps/kpunch/dvxbasic/compiler/compact.c b/src/apps/kpunch/dvxbasic/compiler/compact.c index e5817d9..c857114 100644 --- a/src/apps/kpunch/dvxbasic/compiler/compact.c +++ b/src/apps/kpunch/dvxbasic/compiler/compact.c @@ -559,7 +559,7 @@ static bool remapRelI16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffse int32_t newOffset = newTarget - newPcAfter; - if (newOffset < -32768 || newOffset > 32767) { + if (newOffset < INT16_MIN || newOffset > INT16_MAX) { return false; } diff --git a/src/apps/kpunch/dvxbasic/compiler/lexer.c b/src/apps/kpunch/dvxbasic/compiler/lexer.c index fd11149..35bdc88 100644 --- a/src/apps/kpunch/dvxbasic/compiler/lexer.c +++ b/src/apps/kpunch/dvxbasic/compiler/lexer.c @@ -40,137 +40,142 @@ typedef struct { const char *text; + uint8_t textLen; // precomputed so lookupKeyword() can length-reject cheaply BasTokenTypeE type; } KeywordEntryT; +#define KW(s, t) { s, (uint8_t)(sizeof(s) - 1), t } + static const KeywordEntryT sKeywords[] = { - { "AND", TOK_AND }, - { "APP", TOK_APP }, - { "APPEND", TOK_APPEND }, - { "AS", TOK_AS }, - { "BASE", TOK_BASE }, - { "BINARY", TOK_BINARY }, - { "BOOLEAN", TOK_BOOLEAN }, - { "BYVAL", TOK_BYVAL }, - { "CALL", TOK_CALL }, - { "CASE", TOK_CASE }, - { "CHDIR", TOK_CHDIR }, - { "CHDRIVE", TOK_CHDRIVE }, - { "CLOSE", TOK_CLOSE }, - { "CREATECONTROL", TOK_CREATECONTROL }, - { "CREATEFORM", TOK_CREATEFORM }, - { "CURDIR", TOK_CURDIR }, - { "CURDIR$", TOK_CURDIR }, - { "CONST", TOK_CONST }, - { "DATA", TOK_DATA }, - { "DECLARE", TOK_DECLARE }, - { "DEF", TOK_DEF }, - { "DEFDBL", TOK_DEFDBL }, - { "DEFINT", TOK_DEFINT }, - { "DEFLNG", TOK_DEFLNG }, - { "DEFSNG", TOK_DEFSNG }, - { "DEFSTR", TOK_DEFSTR }, - { "DIM", TOK_DIM }, - { "DIR", TOK_DIR }, - { "DIR$", TOK_DIR }, - { "DO", TOK_DO }, - { "DOEVENTS", TOK_DOEVENTS }, - { "DOUBLE", TOK_DOUBLE }, - { "ELSE", TOK_ELSE }, - { "ELSEIF", TOK_ELSEIF }, - { "END", TOK_END }, - { "EOF", TOK_EOF_KW }, - { "EQV", TOK_EQV }, - { "ERASE", TOK_ERASE }, - { "ERR", TOK_ERR }, - { "ERROR", TOK_ERROR_KW }, - { "EXPLICIT", TOK_EXPLICIT }, - { "EXIT", TOK_EXIT }, - { "FALSE", TOK_FALSE_KW }, - { "FILECOPY", TOK_FILECOPY }, - { "FILELEN", TOK_FILELEN }, - { "FOR", TOK_FOR }, - { "FUNCTION", TOK_FUNCTION }, - { "GET", TOK_GET }, - { "GETATTR", TOK_GETATTR }, - { "GOSUB", TOK_GOSUB }, - { "GOTO", TOK_GOTO }, - { "HIDE", TOK_HIDE }, - { "IF", TOK_IF }, - { "IMP", TOK_IMP }, - { "INIREAD", TOK_INIREAD }, - { "INIREAD$", TOK_INIREAD }, - { "INIWRITE", TOK_INIWRITE }, - { "INPUT", TOK_INPUT }, - { "INTEGER", TOK_INTEGER }, - { "IS", TOK_IS }, - { "KILL", TOK_KILL }, - { "LBOUND", TOK_LBOUND }, - { "LET", TOK_LET }, - { "LINE", TOK_LINE }, - { "LOAD", TOK_LOAD }, - { "LONG", TOK_LONG }, - { "LOOP", TOK_LOOP }, - { "ME", TOK_ME }, - { "MKDIR", TOK_MKDIR }, - { "MOD", TOK_MOD }, - { "INPUTBOX", TOK_INPUTBOX }, - { "INPUTBOX$", TOK_INPUTBOX }, - { "MSGBOX", TOK_MSGBOX }, - { "NAME", TOK_NAME }, - { "NEXT", TOK_NEXT }, - { "NOT", TOK_NOT }, - { "NOTHING", TOK_NOTHING }, - { "ON", TOK_ON }, - { "OPEN", TOK_OPEN }, - { "OPTIONAL", TOK_OPTIONAL }, - { "OPTION", TOK_OPTION }, - { "OR", TOK_OR }, - { "OUTPUT", TOK_OUTPUT }, - { "PRESERVE", TOK_PRESERVE }, - { "PRINT", TOK_PRINT }, - { "PUT", TOK_PUT }, - { "RANDOM", TOK_RANDOM }, - { "RANDOMIZE", TOK_RANDOMIZE }, - { "READ", TOK_READ }, - { "REDIM", TOK_REDIM }, - { "REM", TOK_REM }, - { "REMOVECONTROL", TOK_REMOVECONTROL }, - { "RESTORE", TOK_RESTORE }, - { "RESUME", TOK_RESUME }, - { "RETURN", TOK_RETURN }, - { "RMDIR", TOK_RMDIR }, - { "SEEK", TOK_SEEK }, - { "SELECT", TOK_SELECT }, - { "SET", TOK_SET }, - { "SETATTR", TOK_SETATTR }, - { "SETEVENT", TOK_SETEVENT }, - { "SHARED", TOK_SHARED }, - { "SHELL", TOK_SHELL }, - { "SHOW", TOK_SHOW }, - { "SINGLE", TOK_SINGLE }, - { "SLEEP", TOK_SLEEP }, - { "STATIC", TOK_STATIC }, - { "STEP", TOK_STEP }, - { "STRING", TOK_STRING_KW }, - { "SUB", TOK_SUB }, - { "SWAP", TOK_SWAP }, - { "THEN", TOK_THEN }, - { "TIMER", TOK_TIMER }, - { "TO", TOK_TO }, - { "TRUE", TOK_TRUE_KW }, - { "TYPE", TOK_TYPE }, - { "UBOUND", TOK_UBOUND }, - { "UNLOAD", TOK_UNLOAD }, - { "UNTIL", TOK_UNTIL }, - { "WEND", TOK_WEND }, - { "WHILE", TOK_WHILE }, - { "WITH", TOK_WITH }, - { "WRITE", TOK_WRITE }, - { "XOR", TOK_XOR }, - { NULL, TOK_ERROR } + KW("AND", TOK_AND), + KW("APP", TOK_APP), + KW("APPEND", TOK_APPEND), + KW("AS", TOK_AS), + KW("BASE", TOK_BASE), + KW("BINARY", TOK_BINARY), + KW("BOOLEAN", TOK_BOOLEAN), + KW("BYVAL", TOK_BYVAL), + KW("CALL", TOK_CALL), + KW("CASE", TOK_CASE), + KW("CHDIR", TOK_CHDIR), + KW("CHDRIVE", TOK_CHDRIVE), + KW("CLOSE", TOK_CLOSE), + KW("CREATECONTROL", TOK_CREATECONTROL), + KW("CREATEFORM", TOK_CREATEFORM), + KW("CURDIR", TOK_CURDIR), + KW("CURDIR$", TOK_CURDIR), + KW("CONST", TOK_CONST), + KW("DATA", TOK_DATA), + KW("DECLARE", TOK_DECLARE), + KW("DEF", TOK_DEF), + KW("DEFDBL", TOK_DEFDBL), + KW("DEFINT", TOK_DEFINT), + KW("DEFLNG", TOK_DEFLNG), + KW("DEFSNG", TOK_DEFSNG), + KW("DEFSTR", TOK_DEFSTR), + KW("DIM", TOK_DIM), + KW("DIR", TOK_DIR), + KW("DIR$", TOK_DIR), + KW("DO", TOK_DO), + KW("DOEVENTS", TOK_DOEVENTS), + KW("DOUBLE", TOK_DOUBLE), + KW("ELSE", TOK_ELSE), + KW("ELSEIF", TOK_ELSEIF), + KW("END", TOK_END), + KW("EOF", TOK_EOF_KW), + KW("EQV", TOK_EQV), + KW("ERASE", TOK_ERASE), + KW("ERR", TOK_ERR), + KW("ERROR", TOK_ERROR_KW), + KW("EXPLICIT", TOK_EXPLICIT), + KW("EXIT", TOK_EXIT), + KW("FALSE", TOK_FALSE_KW), + KW("FILECOPY", TOK_FILECOPY), + KW("FILELEN", TOK_FILELEN), + KW("FOR", TOK_FOR), + KW("FUNCTION", TOK_FUNCTION), + KW("GET", TOK_GET), + KW("GETATTR", TOK_GETATTR), + KW("GOSUB", TOK_GOSUB), + KW("GOTO", TOK_GOTO), + KW("HIDE", TOK_HIDE), + KW("IF", TOK_IF), + KW("IMP", TOK_IMP), + KW("INIREAD", TOK_INIREAD), + KW("INIREAD$", TOK_INIREAD), + KW("INIWRITE", TOK_INIWRITE), + KW("INPUT", TOK_INPUT), + KW("INTEGER", TOK_INTEGER), + KW("IS", TOK_IS), + KW("KILL", TOK_KILL), + KW("LBOUND", TOK_LBOUND), + KW("LET", TOK_LET), + KW("LINE", TOK_LINE), + KW("LOAD", TOK_LOAD), + KW("LONG", TOK_LONG), + KW("LOOP", TOK_LOOP), + KW("ME", TOK_ME), + KW("MKDIR", TOK_MKDIR), + KW("MOD", TOK_MOD), + KW("INPUTBOX", TOK_INPUTBOX), + KW("INPUTBOX$", TOK_INPUTBOX), + KW("MSGBOX", TOK_MSGBOX), + KW("NAME", TOK_NAME), + KW("NEXT", TOK_NEXT), + KW("NOT", TOK_NOT), + KW("NOTHING", TOK_NOTHING), + KW("ON", TOK_ON), + KW("OPEN", TOK_OPEN), + KW("OPTIONAL", TOK_OPTIONAL), + KW("OPTION", TOK_OPTION), + KW("OR", TOK_OR), + KW("OUTPUT", TOK_OUTPUT), + KW("PRESERVE", TOK_PRESERVE), + KW("PRINT", TOK_PRINT), + KW("PUT", TOK_PUT), + KW("RANDOM", TOK_RANDOM), + KW("RANDOMIZE", TOK_RANDOMIZE), + KW("READ", TOK_READ), + KW("REDIM", TOK_REDIM), + KW("REM", TOK_REM), + KW("REMOVECONTROL", TOK_REMOVECONTROL), + KW("RESTORE", TOK_RESTORE), + KW("RESUME", TOK_RESUME), + KW("RETURN", TOK_RETURN), + KW("RMDIR", TOK_RMDIR), + KW("SEEK", TOK_SEEK), + KW("SELECT", TOK_SELECT), + KW("SET", TOK_SET), + KW("SETATTR", TOK_SETATTR), + KW("SETEVENT", TOK_SETEVENT), + KW("SHARED", TOK_SHARED), + KW("SHELL", TOK_SHELL), + KW("SHOW", TOK_SHOW), + KW("SINGLE", TOK_SINGLE), + KW("SLEEP", TOK_SLEEP), + KW("STATIC", TOK_STATIC), + KW("STEP", TOK_STEP), + KW("STRING", TOK_STRING_KW), + KW("SUB", TOK_SUB), + KW("SWAP", TOK_SWAP), + KW("THEN", TOK_THEN), + KW("TIMER", TOK_TIMER), + KW("TO", TOK_TO), + KW("TRUE", TOK_TRUE_KW), + KW("TYPE", TOK_TYPE), + KW("UBOUND", TOK_UBOUND), + KW("UNLOAD", TOK_UNLOAD), + KW("UNTIL", TOK_UNTIL), + KW("WEND", TOK_WEND), + KW("WHILE", TOK_WHILE), + KW("WITH", TOK_WITH), + KW("WRITE", TOK_WRITE), + KW("XOR", TOK_XOR), + { NULL, 0, TOK_ERROR } }; +#undef KW + #define KEYWORD_COUNT (sizeof(sKeywords) / sizeof(sKeywords[0]) - 1) @@ -507,26 +512,29 @@ const char *basTokenName(BasTokenTypeE type) { static BasTokenTypeE lookupKeyword(const char *text, int32_t len) { - // Case-insensitive keyword lookup - for (int32_t i = 0; i < (int32_t)KEYWORD_COUNT; i++) { - const char *kw = sKeywords[i].text; - int32_t kwLen = (int32_t)strlen(kw); + // Case-insensitive keyword lookup. Short-circuits on length mismatch + // (via cached keyword length) and on the very first character, both of + // which reject the vast majority of entries before doing a full scan. + char firstUp = (text[0] >= 'a' && text[0] <= 'z') ? (char)(text[0] - 32) : text[0]; - if (kwLen != len) { + for (int32_t i = 0; i < (int32_t)KEYWORD_COUNT; i++) { + const KeywordEntryT *kw = &sKeywords[i]; + + if (kw->textLen != len || kw->text[0] != firstUp) { continue; } bool match = true; - for (int32_t j = 0; j < len; j++) { - if (upperChar(text[j]) != kw[j]) { + for (int32_t j = 1; j < len; j++) { + if (upperChar(text[j]) != kw->text[j]) { match = false; break; } } if (match) { - return sKeywords[i].type; + return kw->type; } } @@ -809,7 +817,7 @@ static BasTokenTypeE tokenizeNumber(BasLexerT *lex) { long val = atol(lex->token.text); - if (val >= -32768 && val <= 32767) { + if (val >= INT16_MIN && val <= INT16_MAX) { lex->token.intVal = (int32_t)val; return TOK_INT_LIT; } diff --git a/src/apps/kpunch/dvxbasic/compiler/lexer.h b/src/apps/kpunch/dvxbasic/compiler/lexer.h index 75a0559..6f875d7 100644 --- a/src/apps/kpunch/dvxbasic/compiler/lexer.h +++ b/src/apps/kpunch/dvxbasic/compiler/lexer.h @@ -214,6 +214,7 @@ typedef enum { // ============================================================ #define BAS_MAX_TOKEN_LEN 256 +#define BAS_LEX_ERROR_LEN 256 typedef struct { BasTokenTypeE type; @@ -243,7 +244,7 @@ typedef struct { int32_t line; // current line (1-based) int32_t col; // current column (1-based) BasTokenT token; // current token - char error[256]; + char error[BAS_LEX_ERROR_LEN]; } BasLexerT; // ============================================================ diff --git a/src/apps/kpunch/dvxbasic/compiler/opcodes.h b/src/apps/kpunch/dvxbasic/compiler/opcodes.h index 44dec4e..3a27ef6 100644 --- a/src/apps/kpunch/dvxbasic/compiler/opcodes.h +++ b/src/apps/kpunch/dvxbasic/compiler/opcodes.h @@ -28,6 +28,34 @@ #ifndef DVXBASIC_OPCODES_H #define DVXBASIC_OPCODES_H +// ============================================================ +// Variable scope tags +// Emitted in bytecode (e.g. OP_FOR scopeTag byte) and consumed +// by the VM to choose between globals, call-frame locals, and +// form-scoped variables. Numeric values are part of the bytecode +// ABI -- do not reorder. +// ============================================================ + +typedef enum { + SCOPE_GLOBAL = 0, + SCOPE_LOCAL = 1, + SCOPE_FORM = 2 // per-form variable (persists while form is loaded) +} BasScopeE; + +// ============================================================ +// File channel modes (BasFileChannelT.mode and OP_FILE_OPEN arg). +// Numeric values are part of the bytecode ABI -- do not reorder. +// ============================================================ + +typedef enum { + BAS_FILE_MODE_CLOSED = 0, + BAS_FILE_MODE_INPUT = 1, + BAS_FILE_MODE_OUTPUT = 2, + BAS_FILE_MODE_APPEND = 3, + BAS_FILE_MODE_RANDOM = 4, + BAS_FILE_MODE_BINARY = 5 +} BasFileModeE; + // ============================================================ // Data type tags (used in Value representation) // ============================================================ diff --git a/src/apps/kpunch/dvxbasic/compiler/parser.c b/src/apps/kpunch/dvxbasic/compiler/parser.c index ed81d8e..fddc2bb 100644 --- a/src/apps/kpunch/dvxbasic/compiler/parser.c +++ b/src/apps/kpunch/dvxbasic/compiler/parser.c @@ -589,7 +589,7 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) { } if (argc < minArgs || argc > sym->paramCount) { - char buf[256]; + char buf[BAS_PARSE_ERR_SCRATCH]; if (minArgs == sym->paramCount) { snprintf(buf, sizeof(buf), "Function '%s' expects %d arguments, got %d", sym->name, (int)sym->paramCount, (int)argc); } else { @@ -862,7 +862,7 @@ static void error(BasParserT *p, const char *msg) { static void errorExpected(BasParserT *p, const char *what) { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Expected %s, got %s", what, basTokenName(p->lex.token.type)); error(p, buf); } @@ -897,7 +897,7 @@ static void expect(BasParserT *p, BasTokenTypeE type) { return; } if (p->lex.token.type != type) { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Expected %s, got %s", basTokenName(type), basTokenName(p->lex.token.type)); error(p, buf); return; @@ -1094,7 +1094,7 @@ static void parseAssignOrCall(BasParserT *p) { } int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text); if (fieldIdx < 0) { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name); error(p, buf); return; @@ -1333,7 +1333,7 @@ static void parseAssignOrCall(BasParserT *p) { } int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text); if (fieldIdx < 0) { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name); error(p, buf); return; @@ -1413,7 +1413,7 @@ static void parseAssignOrCall(BasParserT *p) { } } if (!p->hasError && argc != sym->paramCount) { - char buf[256]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Sub '%s' expects %d arguments, got %d", sym->name, (int)sym->paramCount, (int)argc); error(p, buf); return; @@ -2225,7 +2225,7 @@ static void parseDim(BasParserT *p) { // Check for duplicate BasSymbolT *existing = basSymTabFind(&p->sym, name); if (existing != NULL && existing->isDefined) { - char buf[256]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Variable '%s' already declared", name); error(p, buf); return; @@ -2575,7 +2575,7 @@ static void parseFor(BasParserT *p) { // Emit FOR_INIT -- sets up the for-loop state in the VM basEmit8(&p->cg, OP_FOR_INIT); basEmitU16(&p->cg, (uint16_t)loopVar->index); - basEmit8(&p->cg, loopVar->scope == SCOPE_LOCAL ? 1 : (loopVar->scope == SCOPE_FORM ? 2 : 0)); + basEmit8(&p->cg, (uint8_t)loopVar->scope); int32_t loopBody = basCodePos(&p->cg); @@ -2602,7 +2602,7 @@ static void parseFor(BasParserT *p) { // Emit FOR_NEXT with backward jump to loop body basEmit8(&p->cg, OP_FOR_NEXT); basEmitU16(&p->cg, (uint16_t)loopVar->index); - basEmit8(&p->cg, loopVar->scope == SCOPE_LOCAL ? 1 : (loopVar->scope == SCOPE_FORM ? 2 : 0)); + basEmit8(&p->cg, (uint8_t)loopVar->scope); int16_t backOffset = (int16_t)(loopBody - (basCodePos(&p->cg) + 2)); basEmit16(&p->cg, backOffset); @@ -3224,7 +3224,7 @@ static void parseModule(BasParserT *p) { BasSymbolT *sym = p->sym.symbols[i]; if ((sym->kind == SYM_SUB || sym->kind == SYM_FUNCTION) && !sym->isDefined && !sym->isExtern) { - char buf[256]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Undefined %s: %s", sym->kind == SYM_SUB ? "Sub" : "Function", sym->name); error(p, buf); @@ -3480,19 +3480,19 @@ static void parseOpen(BasParserT *p) { uint8_t mode; if (check(p, TOK_INPUT)) { - mode = 1; // INPUT + mode = BAS_FILE_MODE_INPUT; advance(p); } else if (check(p, TOK_OUTPUT)) { - mode = 2; // OUTPUT + mode = BAS_FILE_MODE_OUTPUT; advance(p); } else if (check(p, TOK_APPEND)) { - mode = 3; // APPEND + mode = BAS_FILE_MODE_APPEND; advance(p); } else if (check(p, TOK_RANDOM)) { - mode = 4; // RANDOM + mode = BAS_FILE_MODE_RANDOM; advance(p); } else if (check(p, TOK_BINARY)) { - mode = 5; // BINARY + mode = BAS_FILE_MODE_BINARY; advance(p); } else { error(p, "Expected INPUT, OUTPUT, APPEND, RANDOM, or BINARY after FOR"); @@ -3632,7 +3632,7 @@ static void parsePrimary(BasParserT *p) { // Integer literal if (tt == TOK_INT_LIT) { int32_t val = p->lex.token.intVal; - if (val >= -32768 && val <= 32767) { + if (val >= INT16_MIN && val <= INT16_MAX) { basEmit8(&p->cg, OP_PUSH_INT16); basEmit16(&p->cg, (int16_t)val); } else { @@ -4001,7 +4001,7 @@ static void parsePrimary(BasParserT *p) { } if (argc < builtin->minArgs || argc > builtin->maxArgs) { - char buf[256]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Built-in '%s' expects %d-%d arguments, got %d", builtin->name, (int)builtin->minArgs, (int)builtin->maxArgs, (int)argc); error(p, buf); return; @@ -4061,7 +4061,7 @@ static void parsePrimary(BasParserT *p) { } int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text); if (fieldIdx < 0) { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name); error(p, buf); return; @@ -4142,7 +4142,7 @@ static void parsePrimary(BasParserT *p) { } int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text); if (fieldIdx < 0) { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name); error(p, buf); return; @@ -5415,7 +5415,7 @@ static void parseStatement(BasParserT *p) { sym->isDefined = true; sym->codeAddr = basCodePos(&p->cg); } else { - char buf[512]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Name '%s' already used", labelName); error(p, buf); } @@ -5436,7 +5436,7 @@ static void parseStatement(BasParserT *p) { break; default: { - char buf[256]; + char buf[BAS_PARSE_ERR_SCRATCH]; snprintf(buf, sizeof(buf), "Unexpected token: %s", basTokenName(tt)); error(p, buf); break; diff --git a/src/apps/kpunch/dvxbasic/compiler/parser.h b/src/apps/kpunch/dvxbasic/compiler/parser.h index 3e3fe7f..8227a0c 100644 --- a/src/apps/kpunch/dvxbasic/compiler/parser.h +++ b/src/apps/kpunch/dvxbasic/compiler/parser.h @@ -43,11 +43,17 @@ // Parser state // ============================================================ +// Parse-error string sizes. PARSE_ERROR_LEN is the final message stored +// on the parser; PARSE_ERR_SCRATCH is used for temporary formatting before +// copying into it (with a "Line N: " prefix). +#define BAS_PARSE_ERROR_LEN 1024 +#define BAS_PARSE_ERR_SCRATCH 512 + typedef struct { BasLexerT lex; BasCodeGenT cg; BasSymTabT sym; - char error[1024]; + char error[BAS_PARSE_ERROR_LEN]; bool hasError; int32_t errorLine; int32_t prevLine; // line of the previous token (for error reporting) diff --git a/src/apps/kpunch/dvxbasic/compiler/symtab.c b/src/apps/kpunch/dvxbasic/compiler/symtab.c index 8c17ea5..284030d 100644 --- a/src/apps/kpunch/dvxbasic/compiler/symtab.c +++ b/src/apps/kpunch/dvxbasic/compiler/symtab.c @@ -41,6 +41,7 @@ void basSymTabInit(BasSymTabT *tab); int32_t basSymTabLeaveFormScope(BasSymTabT *tab); void basSymTabLeaveLocal(BasSymTabT *tab); static bool namesEqual(const char *a, const char *b); +static uint32_t nameHashCI(const char *name); BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) { // Determine scope: local > form > global. @@ -55,13 +56,15 @@ BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, ui scope = SCOPE_GLOBAL; } + uint32_t h = nameHashCI(name); + // Check for duplicate in current scope (skip ended form symbols) for (int32_t i = 0; i < tab->count; i++) { if (tab->symbols[i]->formScopeEnded) { continue; } - if (tab->symbols[i]->scope == scope && namesEqual(tab->symbols[i]->name, name)) { + if (tab->symbols[i]->nameHash == h && tab->symbols[i]->scope == scope && namesEqual(tab->symbols[i]->name, name)) { return NULL; // duplicate } } @@ -74,6 +77,7 @@ BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, ui strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1); sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0'; + sym->nameHash = h; sym->kind = kind; sym->scope = scope; sym->dataType = dataType; @@ -119,24 +123,28 @@ void basSymTabEnterLocal(BasSymTabT *tab) { BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) { + uint32_t h = nameHashCI(name); + // Search local scope first if (tab->inLocalScope) { for (int32_t i = tab->count - 1; i >= 0; i--) { - if (tab->symbols[i]->scope == SCOPE_LOCAL && namesEqual(tab->symbols[i]->name, name)) { - return tab->symbols[i]; + BasSymbolT *s = tab->symbols[i]; + if (s->nameHash == h && s->scope == SCOPE_LOCAL && namesEqual(s->name, name)) { + return s; } } } // Search form scope and global scope for (int32_t i = tab->count - 1; i >= 0; i--) { - if (tab->symbols[i]->formScopeEnded) { + BasSymbolT *s = tab->symbols[i]; + if (s->formScopeEnded) { continue; } - if ((tab->symbols[i]->scope == SCOPE_FORM || tab->symbols[i]->scope == SCOPE_GLOBAL) && - namesEqual(tab->symbols[i]->name, name)) { - return tab->symbols[i]; + if (s->nameHash == h && (s->scope == SCOPE_FORM || s->scope == SCOPE_GLOBAL) && + namesEqual(s->name, name)) { + return s; } } @@ -145,13 +153,16 @@ BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) { BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) { + uint32_t h = nameHashCI(name); + for (int32_t i = 0; i < tab->count; i++) { - if (tab->symbols[i]->formScopeEnded) { + BasSymbolT *s = tab->symbols[i]; + if (s->formScopeEnded) { continue; } - if (tab->symbols[i]->scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i]->name, name)) { - return tab->symbols[i]; + if (s->nameHash == h && s->scope == SCOPE_GLOBAL && namesEqual(s->name, name)) { + return s; } } @@ -209,6 +220,29 @@ void basSymTabLeaveLocal(BasSymTabT *tab) { } +// ============================================================ +// Case-insensitive FNV-1a hash used to accelerate symbol-table lookups. +// Caller computes hash of search-name once; each entry stores its own +// precomputed hash, so per-entry comparison is a fast uint32 compare +// that nearly always rejects non-matches without calling namesEqual. +// ============================================================ +static uint32_t nameHashCI(const char *name) { + uint32_t h = 0x811C9DC5u; + + while (*name) { + char c = *name; + if (c >= 'a' && c <= 'z') { + c -= 32; + } + h ^= (uint32_t)(uint8_t)c; + h *= 0x01000193u; + name++; + } + + return h; +} + + // ============================================================ // Case-insensitive name comparison // ============================================================ diff --git a/src/apps/kpunch/dvxbasic/compiler/symtab.h b/src/apps/kpunch/dvxbasic/compiler/symtab.h index 50134a7..7952eb9 100644 --- a/src/apps/kpunch/dvxbasic/compiler/symtab.h +++ b/src/apps/kpunch/dvxbasic/compiler/symtab.h @@ -49,15 +49,7 @@ typedef enum { SYM_TYPE_DEF // user-defined TYPE } BasSymKindE; -// ============================================================ -// Symbol scope -// ============================================================ - -typedef enum { - SCOPE_GLOBAL, - SCOPE_LOCAL, - SCOPE_FORM // per-form variable (persists while form is loaded) -} BasScopeE; +// BasScopeE moved to opcodes.h (shared with runtime). // ============================================================ // Symbol entry @@ -75,6 +67,7 @@ typedef struct { typedef struct { char name[BAS_MAX_SYMBOL_NAME]; + uint32_t nameHash; // FNV-1a over uppercase(name); fast-reject during lookup BasSymKindE kind; BasScopeE scope; uint8_t dataType; // BAS_TYPE_* for variables/functions diff --git a/src/apps/kpunch/dvxbasic/ctrlover.dhs b/src/apps/kpunch/dvxbasic/ctrlover.dhs index 152eb23..ee2c14d 100644 --- a/src/apps/kpunch/dvxbasic/ctrlover.dhs +++ b/src/apps/kpunch/dvxbasic/ctrlover.dhs @@ -52,8 +52,8 @@ Every control in DVX BASIC inherits a set of common properties, events, and meth Weight Integer R/W Layout weight. 0 = fixed size, >0 = share extra space proportionally. Visible Boolean R/W Whether the control is visible. Enabled Boolean R/W Whether the control accepts user input. - BackColor Long R/W Background color as a 32-bit ARGB value. - ForeColor Long R/W Foreground (text) color as a 32-bit ARGB value. + BackColor Long R/W Background color as a 24-bit RGB value packed in a Long (use the RGB function to construct). + ForeColor Long R/W Foreground (text) color as a 24-bit RGB value packed in a Long (use the RGB function to construct). TabIndex Integer R Accepted for VB compatibility but ignored. DVX has no tab order. .endtable diff --git a/src/apps/kpunch/dvxbasic/form.dhs b/src/apps/kpunch/dvxbasic/form.dhs index 1fdc6a6..9b7fbd7 100644 --- a/src/apps/kpunch/dvxbasic/form.dhs +++ b/src/apps/kpunch/dvxbasic/form.dhs @@ -51,7 +51,7 @@ The Form is the top-level container representing a DVX window. It is declared in Left Integer 0 Initial X position. Used when Centered is False. Top Integer 0 Initial Y position. Used when Centered is False. Layout String "VBox" Content box layout: "VBox" (vertical) or "HBox" (horizontal). - AutoSize Boolean True When True, the window shrink-wraps to fit its content. + AutoSize Boolean False When True, the window shrink-wraps to fit its content. Resizable Boolean True Whether the user can resize the window at runtime. Centered Boolean True When True, the window is centered on screen. When False, Left/Top are used. .endtable diff --git a/src/apps/kpunch/dvxbasic/formrt/formrt.c b/src/apps/kpunch/dvxbasic/formrt/formrt.c index dca988f..82ea4db 100644 --- a/src/apps/kpunch/dvxbasic/formrt/formrt.c +++ b/src/apps/kpunch/dvxbasic/formrt/formrt.c @@ -751,7 +751,7 @@ WidgetT *basFormRtCreateContentBox(WidgetT *root, const char *layout) { if (api) { WidgetT *(*createFn)(WidgetT *) = *(WidgetT *(*const *)(WidgetT *))api; WidgetT *box = createFn(root); - box->weight = 100; + box->weight = WGT_WEIGHT_FILL; return box; } } @@ -2728,7 +2728,7 @@ int32_t CommIsReady(int32_t handle) { // SecLink receive callback: routes data into per-channel ring buffers static void commOnRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) { CommSlotT *slot = (CommSlotT *)ctx; - CommChanBufT *cb = &slot->channels[channel & 0x7F]; + CommChanBufT *cb = &slot->channels[channel & SECLINK_MAX_CHANNEL]; for (int i = 0; i < len; i++) { int32_t next = (cb->head + 1) % SECLINK_CHAN_BUF_SIZE; diff --git a/src/apps/kpunch/dvxbasic/ide/ideMain.c b/src/apps/kpunch/dvxbasic/ide/ideMain.c index 507e374..60a5205 100644 --- a/src/apps/kpunch/dvxbasic/ide/ideMain.c +++ b/src/apps/kpunch/dvxbasic/ide/ideMain.c @@ -157,6 +157,7 @@ #define SYNTAX_STRING 2 #define SYNTAX_COMMENT 3 #define SYNTAX_NUMBER 4 +#define SYNTAX_OPERATOR 5 #define SYNTAX_TYPE 6 // View mode for activateFile @@ -698,15 +699,17 @@ static const char *sSyntaxColorNames[] = { "Types", // 6 = SYNTAX_TYPE }; -// Default syntax colors (0x00RRGGBB; 0 = use widget default) +// Default syntax colors (0x00RRGGBB; 0 = use widget default). +// Designated initializers keep the SYNTAX_* index and the color aligned +// so there's no drift if indices are reordered later. static const uint32_t sDefaultSyntaxColors[SYNTAX_COLOR_COUNT] = { - 0x00000000, // default -- not used (widget fg) - 0x00000080, // keyword -- dark blue - 0x00800000, // string -- dark red - 0x00008000, // comment -- dark green - 0x00800080, // number -- purple - 0x00808000, // operator -- dark yellow - 0x00008080, // type -- teal + [SYNTAX_DEFAULT] = 0, // widget's fg color + [SYNTAX_KEYWORD] = 0x00000080, // dark blue + [SYNTAX_STRING] = 0x00800000, // dark red + [SYNTAX_COMMENT] = 0x00008000, // dark green + [SYNTAX_NUMBER] = 0x00800080, // purple + [SYNTAX_OPERATOR] = 0x00808000, // dark yellow + [SYNTAX_TYPE] = 0x00008080, // teal }; // Preferences dialog state (used by several onPrefs* handlers) @@ -1143,7 +1146,7 @@ static void buildWindow(void) { dvxAddAccel(accel, KEY_F5, ACCEL_CTRL, CMD_RUN_NOCMP); dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE); dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN); - dvxAddAccel(accel, 0x1B, 0, CMD_STOP); + dvxAddAccel(accel, KEY_ESCAPE, 0, CMD_STOP); dvxAddAccel(accel, 'F', ACCEL_CTRL, CMD_FIND); dvxAddAccel(accel, 'H', ACCEL_CTRL, CMD_REPLACE); dvxAddAccel(accel, KEY_F3, 0, CMD_FIND_NEXT); @@ -1219,7 +1222,7 @@ static void buildWindow(void) { sStatusBar = wgtStatusBar(tbRoot); WidgetT *statusBar = sStatusBar; sStatus = wgtLabel(statusBar, ""); - sStatus->weight = 100; + sStatus->weight = WGT_WEIGHT_FILL; // Fit height to content, keeping full screen width dvxFitWindowH(sAc, sWin); @@ -5211,7 +5214,7 @@ static void onBreakpointListKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) (void)shift; // Delete key - if (keyCode != (0x53 | 0x100) && keyCode != 127) { + if (keyCode != KEY_DELETE && keyCode != KEY_ASCII_DEL) { return; } @@ -5773,13 +5776,13 @@ static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) { static void onFormWinKey(WindowT *win, int32_t key, int32_t mod) { // Ctrl+C: copy selected control - if (key == 3 && (mod & ACCEL_CTRL)) { + if (key == KEY_CTRL_C && (mod & ACCEL_CTRL)) { dsgnCopySelected(); return; } // Ctrl+X: cut selected control - if (key == 24 && (mod & ACCEL_CTRL)) { + if (key == KEY_CTRL_X && (mod & ACCEL_CTRL)) { dsgnCopySelected(); if (sDesigner.selectedIdx >= 0) { @@ -5796,7 +5799,7 @@ static void onFormWinKey(WindowT *win, int32_t key, int32_t mod) { } // Ctrl+V: paste control - if (key == 22 && (mod & ACCEL_CTRL)) { + if (key == KEY_CTRL_V && (mod & ACCEL_CTRL)) { dsgnPasteControl(); return; } @@ -6552,7 +6555,7 @@ static void onWatchListKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { } // Delete key (scancode 0x53 with extended flag) - if (keyCode != (0x53 | 0x100) && keyCode != 127) { + if (keyCode != KEY_DELETE && keyCode != KEY_ASCII_DEL) { return; } @@ -6603,7 +6606,7 @@ static void openFindDialog(bool showReplace) { findRow->spacing = wgtPixels(4); wgtLabel(findRow, "Find:"); sFindInput = wgtTextInput(findRow, 256); - sFindInput->weight = 100; + sFindInput->weight = WGT_WEIGHT_FILL; wgtSetText(sFindInput, sFindText); // Replace checkbox + input @@ -6613,7 +6616,7 @@ static void openFindDialog(bool showReplace) { wgtCheckboxSetChecked(sReplCheck, showReplace); sReplCheck->onChange = onReplCheckChange; sReplInput = wgtTextInput(replRow, 256); - sReplInput->weight = 100; + sReplInput->weight = WGT_WEIGHT_FILL; wgtSetText(sReplInput, sReplaceText); // Options row: scope + direction + case @@ -7799,7 +7802,7 @@ static void showBreakpointWindow(void) { sBreakpointList = wgtListView(root); if (sBreakpointList) { - sBreakpointList->weight = 100; + sBreakpointList->weight = WGT_WEIGHT_FILL; sBreakpointList->onKeyDown = onBreakpointListKeyDown; sBreakpointList->onDblClick = onBreakpointListDblClick; wgtListViewSetMultiSelect(sBreakpointList, true); @@ -7846,7 +7849,7 @@ static void showCallStackWindow(void) { sCallStackList = wgtListView(root); if (sCallStackList) { - sCallStackList->weight = 100; + sCallStackList->weight = WGT_WEIGHT_FILL; static const ListViewColT cols[] = { { "Procedure", wgtChars(16), ListViewAlignLeftE }, @@ -7892,19 +7895,19 @@ static void showCodeWindow(void) { wgtLabel(dropdownRow, "Object:"); sObjDropdown = wgtDropdown(dropdownRow); - sObjDropdown->weight = 100; + sObjDropdown->weight = WGT_WEIGHT_FILL; sObjDropdown->onChange = onObjDropdownChange; wgtDropdownSetItems(sObjDropdown, NULL, 0); wgtLabel(dropdownRow, "Function:"); sEvtDropdown = wgtDropdown(dropdownRow); - sEvtDropdown->weight = 100; + sEvtDropdown->weight = WGT_WEIGHT_FILL; sEvtDropdown->onChange = onEvtDropdownChange; wgtDropdownSetItems(sEvtDropdown, NULL, 0); sEditor = wgtTextArea(codeRoot, IDE_MAX_SOURCE); - sEditor->weight = 100; + sEditor->weight = WGT_WEIGHT_FILL; wgtTextAreaSetColorize(sEditor, basicColorize, NULL); // Apply saved syntax colors @@ -7959,7 +7962,7 @@ static void showImmediateWindow(void) { sImmediate = wgtTextArea(immRoot, IDE_MAX_IMM); if (sImmediate) { - sImmediate->weight = 100; + sImmediate->weight = WGT_WEIGHT_FILL; sImmediate->readOnly = false; sImmediate->onChange = onImmediateChange; } else { @@ -7999,7 +8002,7 @@ static void showLocalsWindow(void) { sLocalsList = wgtListView(root); if (sLocalsList) { - sLocalsList->weight = 100; + sLocalsList->weight = WGT_WEIGHT_FILL; static const ListViewColT cols[] = { { "Name", wgtChars(12), ListViewAlignLeftE }, @@ -8034,7 +8037,7 @@ static void showOutputWindow(void) { WidgetT *outRoot = wgtInitWindow(sAc, sOutWin); sOutput = wgtTextArea(outRoot, IDE_MAX_OUTPUT); - sOutput->weight = 100; + sOutput->weight = WGT_WEIGHT_FILL; sOutput->readOnly = true; if (sOutputLen > 0) { @@ -8061,7 +8064,7 @@ static void showPreferencesDialog(void) { // ---- Tab control ---- WidgetT *tabs = wgtTabControl(root); - tabs->weight = 100; + tabs->weight = WGT_WEIGHT_FILL; // ======== General tab ======== WidgetT *generalPage = wgtTabPage(tabs, "General"); @@ -8093,14 +8096,14 @@ static void showPreferencesDialog(void) { // Project Defaults section WidgetT *prjFrame = wgtFrame(generalPage, "New Project Defaults"); prjFrame->spacing = wgtPixels(2); - prjFrame->weight = 100; + prjFrame->weight = WGT_WEIGHT_FILL; WidgetT *r1 = wgtHBox(prjFrame); r1->spacing = wgtPixels(4); WidgetT *l1 = wgtLabel(r1, "Author:"); l1->minW = wgtPixels(80); sPrefsDlg.defAuthor = wgtTextInput(r1, 64); - sPrefsDlg.defAuthor->weight = 100; + sPrefsDlg.defAuthor->weight = WGT_WEIGHT_FILL; wgtSetText(sPrefsDlg.defAuthor, prefsGetString(sPrefs, "defaults", "author", "")); WidgetT *r2 = wgtHBox(prjFrame); @@ -8108,7 +8111,7 @@ static void showPreferencesDialog(void) { WidgetT *l2 = wgtLabel(r2, "Publisher:"); l2->minW = wgtPixels(80); sPrefsDlg.defPublisher = wgtTextInput(r2, 64); - sPrefsDlg.defPublisher->weight = 100; + sPrefsDlg.defPublisher->weight = WGT_WEIGHT_FILL; wgtSetText(sPrefsDlg.defPublisher, prefsGetString(sPrefs, "defaults", "publisher", "")); WidgetT *r3 = wgtHBox(prjFrame); @@ -8116,7 +8119,7 @@ static void showPreferencesDialog(void) { WidgetT *l3 = wgtLabel(r3, "Version:"); l3->minW = wgtPixels(80); sPrefsDlg.defVersion = wgtTextInput(r3, 16); - sPrefsDlg.defVersion->weight = 100; + sPrefsDlg.defVersion->weight = WGT_WEIGHT_FILL; wgtSetText(sPrefsDlg.defVersion, prefsGetString(sPrefs, "defaults", "version", "1.0")); WidgetT *r4 = wgtHBox(prjFrame); @@ -8124,12 +8127,12 @@ static void showPreferencesDialog(void) { WidgetT *l4 = wgtLabel(r4, "Copyright:"); l4->minW = wgtPixels(80); sPrefsDlg.defCopyright = wgtTextInput(r4, 64); - sPrefsDlg.defCopyright->weight = 100; + sPrefsDlg.defCopyright->weight = WGT_WEIGHT_FILL; wgtSetText(sPrefsDlg.defCopyright, prefsGetString(sPrefs, "defaults", "copyright", "")); wgtLabel(prjFrame, "Description:"); sPrefsDlg.defDescription = wgtTextArea(prjFrame, 512); - sPrefsDlg.defDescription->weight = 100; + sPrefsDlg.defDescription->weight = WGT_WEIGHT_FILL; sPrefsDlg.defDescription->minH = wgtPixels(48); wgtSetText(sPrefsDlg.defDescription, prefsGetString(sPrefs, "defaults", "description", "")); @@ -8146,11 +8149,11 @@ static void showPreferencesDialog(void) { WidgetT *colorsHBox = wgtHBox(colorsPage); colorsHBox->spacing = wgtPixels(8); - colorsHBox->weight = 100; + colorsHBox->weight = WGT_WEIGHT_FILL; // Left: color list sPrefsDlg.colorList = wgtListBox(colorsHBox); - sPrefsDlg.colorList->weight = 100; + sPrefsDlg.colorList->weight = WGT_WEIGHT_FILL; sPrefsDlg.colorList->onChange = onColorListChange; wgtListBoxSetItems(sPrefsDlg.colorList, sSyntaxColorNames, SYNTAX_COLOR_COUNT); @@ -8158,22 +8161,22 @@ static void showPreferencesDialog(void) { // Right: RGB sliders + value labels + swatch preview WidgetT *sliderBox = wgtVBox(colorsHBox); sliderBox->spacing = wgtPixels(2); - sliderBox->weight = 100; + sliderBox->weight = WGT_WEIGHT_FILL; wgtLabel(sliderBox, "Red:"); - sPrefsDlg.sliderR = wgtSlider(sliderBox, 0, 255); + sPrefsDlg.sliderR = wgtSlider(sliderBox, 0, BAS_RGB_COMPONENT_MAX); sPrefsDlg.sliderR->onChange = onColorSliderChange; sPrefsDlg.lblR = wgtLabel(sliderBox, "0"); wgtLabelSetAlign(sPrefsDlg.lblR, AlignEndE); wgtLabel(sliderBox, "Green:"); - sPrefsDlg.sliderG = wgtSlider(sliderBox, 0, 255); + sPrefsDlg.sliderG = wgtSlider(sliderBox, 0, BAS_RGB_COMPONENT_MAX); sPrefsDlg.sliderG->onChange = onColorSliderChange; sPrefsDlg.lblG = wgtLabel(sliderBox, "0"); wgtLabelSetAlign(sPrefsDlg.lblG, AlignEndE); wgtLabel(sliderBox, "Blue:"); - sPrefsDlg.sliderB = wgtSlider(sliderBox, 0, 255); + sPrefsDlg.sliderB = wgtSlider(sliderBox, 0, BAS_RGB_COMPONENT_MAX); sPrefsDlg.sliderB->onChange = onColorSliderChange; sPrefsDlg.lblB = wgtLabel(sliderBox, "0"); wgtLabelSetAlign(sPrefsDlg.lblB, AlignEndE); @@ -8358,7 +8361,7 @@ static void showWatchWindow(void) { sWatchList = wgtListView(root); if (sWatchList) { - sWatchList->weight = 100; + sWatchList->weight = WGT_WEIGHT_FILL; sWatchList->onKeyDown = onWatchListKeyDown; sWatchList->onDblClick = onWatchListDblClick; diff --git a/src/apps/kpunch/dvxbasic/ide/ideMenuEditor.c b/src/apps/kpunch/dvxbasic/ide/ideMenuEditor.c index 67e0313..0a25898 100644 --- a/src/apps/kpunch/dvxbasic/ide/ideMenuEditor.c +++ b/src/apps/kpunch/dvxbasic/ide/ideMenuEditor.c @@ -253,7 +253,7 @@ bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) { WidgetT *capLbl = wgtLabel(capRow, "Caption:"); capLbl->minW = wgtPixels(60); sMed.captionInput = wgtTextInput(capRow, DSGN_MAX_TEXT); - sMed.captionInput->weight = 100; + sMed.captionInput->weight = WGT_WEIGHT_FILL; sMed.captionInput->onChange = onCaptionChange; // Name row @@ -262,7 +262,7 @@ bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) { WidgetT *namLbl = wgtLabel(namRow, "Name:"); namLbl->minW = wgtPixels(60); sMed.nameInput = wgtTextInput(namRow, DSGN_MAX_NAME); - sMed.nameInput->weight = 100; + sMed.nameInput->weight = WGT_WEIGHT_FILL; sMed.nameInput->onChange = onNameChange; // Check row @@ -275,7 +275,7 @@ bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) { // Listbox sMed.listBox = wgtListBox(root); - sMed.listBox->weight = 100; + sMed.listBox->weight = WGT_WEIGHT_FILL; sMed.listBox->onChange = onListClick; // Arrow buttons diff --git a/src/apps/kpunch/dvxbasic/ide/ideProject.c b/src/apps/kpunch/dvxbasic/ide/ideProject.c index b70df52..b7e4853 100644 --- a/src/apps/kpunch/dvxbasic/ide/ideProject.c +++ b/src/apps/kpunch/dvxbasic/ide/ideProject.c @@ -159,7 +159,7 @@ static WidgetT *ppdAddRow(WidgetT *parent, const char *labelText, const char *va lbl->minW = wgtPixels(PPD_LABEL_W); WidgetT *input = wgtTextInput(row, maxLen); - input->weight = 100; + input->weight = WGT_WEIGHT_FILL; wgtSetText(input, value); return input; @@ -383,7 +383,7 @@ WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onCli WidgetT *root = wgtInitWindow(ctx, sPrjWin); sTree = wgtTreeView(root); - sTree->weight = 100; + sTree->weight = WGT_WEIGHT_FILL; sTree->onChange = onTreeSelChanged; prjRebuildTree(prj); @@ -658,7 +658,7 @@ bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath) sfLbl->minW = wgtPixels(PPD_LABEL_W); sPpd.startupForm = wgtDropdown(sfRow); - sPpd.startupForm->weight = 100; + sPpd.startupForm->weight = WGT_WEIGHT_FILL; // Populate with form names from the project sPpd.formNames = NULL; @@ -732,7 +732,7 @@ bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath) hlpLbl->minW = wgtPixels(PPD_LABEL_W); sPpd.helpFileInput = wgtTextInput(hlpRow, DVX_MAX_PATH); - sPpd.helpFileInput->weight = 100; + sPpd.helpFileInput->weight = WGT_WEIGHT_FILL; wgtSetText(sPpd.helpFileInput, prj->helpFile); WidgetT *hlpBrowse = wgtButton(hlpRow, "Browse..."); @@ -742,7 +742,7 @@ bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath) // Description: label above, textarea below (matches Preferences layout) wgtLabel(root, "Description:"); sPpd.description = wgtTextArea(root, PRJ_MAX_DESC); - sPpd.description->weight = 100; + sPpd.description->weight = WGT_WEIGHT_FILL; sPpd.description->minH = wgtPixels(PPD_DESC_H); wgtSetText(sPpd.description, prj->description); diff --git a/src/apps/kpunch/dvxbasic/ide/ideProperties.c b/src/apps/kpunch/dvxbasic/ide/ideProperties.c index 51423b5..93ef7ff 100644 --- a/src/apps/kpunch/dvxbasic/ide/ideProperties.c +++ b/src/apps/kpunch/dvxbasic/ide/ideProperties.c @@ -1253,7 +1253,7 @@ WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) { // Splitter: tree on top, property list on bottom WidgetT *splitter = wgtSplitter(root, false); - splitter->weight = 100; + splitter->weight = WGT_WEIGHT_FILL; wgtSplitterSetPos(splitter, (PRP_WIN_H - CHROME_TOTAL_TOP - CHROME_TOTAL_BOTTOM) / 2); // Control tree (top pane) diff --git a/src/apps/kpunch/dvxbasic/ideguide.dhs b/src/apps/kpunch/dvxbasic/ideguide.dhs index 32c7099..3bbb5ac 100644 --- a/src/apps/kpunch/dvxbasic/ideguide.dhs +++ b/src/apps/kpunch/dvxbasic/ideguide.dhs @@ -31,7 +31,7 @@ .h1 DVX BASIC IDE Guide -DVX BASIC is a Visual BASIC development environment for the DVX GUI System. It provides a VB3-style integrated development environment with a code editor, form designer, project system, and a full interactive debugger -- all running natively on DOS under the DVX windowing system. +DVX BASIC is a Visual BASIC development environment for DVX. It provides a VB3-style integrated development environment with a code editor, form designer, project system, and a full interactive debugger -- all integrated into DVX itself. This guide covers every feature of the IDE: menus, toolbar, editor, form designer, project management, debugger, and auxiliary windows. @@ -51,7 +51,7 @@ The DVX BASIC IDE is modeled after Visual Basic 3.0. It consists of several floa .item Debug Windows -- Locals, Call Stack, Watch, and Breakpoints windows that appear automatically when debugging. .endlist -The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine (VM). Programs execute in cooperative slices (10,000 VM steps per slice), yielding to the DVX event loop between slices so the GUI remains responsive. +The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine. Programs execute in cooperative slices, yielding to DVX between slices so the GUI remains responsive. Call DoEvents in long-running loops to keep the event loop flowing. .link ide.menu.file File Menu .link ide.menu.edit Edit Menu @@ -109,6 +109,8 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual Save File Ctrl+S Save the active file to disk. Save All Save all modified files in the project. --- + Make Executable... Compile the project and save it as a standalone .app file. Prompts for a debug or release build. + --- Remove File Remove the selected file from the project (prompts to save if modified). --- Exit Close the IDE (prompts to save unsaved changes). @@ -182,6 +184,8 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual Step Out Ctrl+Shift+F8 Run until the current SUB/FUNCTION returns. Run to Cursor Ctrl+F8 Run until execution reaches the line where the cursor is positioned. --- + Output Window to Log Checkbox: when enabled, PRINT output is also mirrored to the DVX log. Persisted in preferences. + --- Toggle Breakpoint F9 Toggle a breakpoint on the current editor line. --- Clear Output Clear the Output window. @@ -274,9 +278,12 @@ The IDE compiles BASIC source into bytecode and runs it in an integrated virtual .h1 Help Menu .table - Menu Item Description - --------- ----------- - About DVX BASIC... Show the About dialog with version and copyright information. + Menu Item Shortcut Description + --------- -------- ----------- + DVX BASIC Help F1 Open the DVX BASIC help (language reference, IDE guide, and control reference). + DVX API Reference Open the DVX developer API reference. + --- + About DVX BASIC... Show the About dialog with version and copyright information. .endtable .link ide.overview Back to Overview @@ -358,16 +365,17 @@ The editor shows one procedure at a time. Each procedure has its own buffer, and .h2 Syntax Highlighting -The editor applies real-time syntax coloring as you type. The following categories are highlighted in distinct colors: +The editor applies real-time syntax coloring as you type. Seven categories are highlighted in distinct colors; the colors themselves are editable in Tools > Preferences > Colors. .table Category Examples -------- -------- + Default Text Identifiers, whitespace. Keywords IF, THEN, FOR, NEXT, SUB, FUNCTION, DIM, PRINT, SELECT, CASE, DO, LOOP, WHILE, WEND, END, EXIT, CALL, GOSUB, GOTO, RETURN, DECLARE, CONST, TYPE, AND, OR, NOT, XOR, MOD, etc. - Type names INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN, BYTE, TRUE, FALSE - String literals "Hello, World!" + Types INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN, TRUE, FALSE + Strings "Hello, World!" Comments ' This is a comment, REM This is a comment - Numbers 42, 3.14 + Numbers 42, 3.14, &HFF Operators =, <, >, +, -, *, /, \, & .endtable @@ -400,7 +408,7 @@ The Form Designer provides a visual design surface for editing .frm files. Switc .h2 Design Surface .list -.item Grid snapping -- controls snap to an 8-pixel grid (DSGN_GRID_SIZE) when placed or resized. +.item Grid snapping -- controls snap to an 8-pixel grid when placed or resized. .item Selection -- click a control to select it. The selected control is highlighted with grab handles. .item Grab handles -- 6x6 pixel handles appear on the right edge (E), bottom edge (S), and bottom-right corner (SE) of the selected control. Drag a handle to resize the control. .item Reordering -- drag a control vertically to reorder it within the form's layout (VBox/HBox). @@ -436,12 +444,12 @@ A DVX BASIC project is stored as a .dbp file (DVX BASIC Project). The project fi .item Name -- the project display name (up to 32 characters). .item Startup Form -- which form to show automatically when the program starts. .item Metadata -- Author, Company, Version, Copyright, Description, and Icon Path (for compiled binaries). -.item File list -- relative paths (8.3 DOS names) of all .bas and .frm files in the project. Each entry tracks whether it is a form file. +.item File list -- relative paths of all .bas and .frm files in the project. Each entry tracks whether it is a form file. .endlist -.h2 Source Map +.h2 How Projects are Compiled -When the project is compiled, all files are concatenated into a single source stream. A source map tracks which lines belong to which file, enabling accurate error reporting and debugger navigation across multiple files. For .frm files, an injected BEGINFORM directive is prepended to the code section. +When the project is compiled, all source files are concatenated into a single source stream. The IDE tracks which lines belong to which file so error messages and debugger locations point to the correct .bas or .frm file. The code section of each .frm file is preceded by a hidden BEGINFORM marker that ties its code to its form. .h2 Project Operations @@ -494,7 +502,7 @@ Each control type exposes different properties (e.g., Caption, Text, Width, Heig .h1 Toolbox -The Toolbox (Window > Toolbox) is a floating palette of buttons, one for each available control type. Controls are loaded dynamically from the widget plugin registry -- any widget DXE that provides a basName appears in the toolbox. +The Toolbox (Window > Toolbox) is a floating palette of buttons, one for each available control type. Every widget registered with DVX that has a BASIC name appears in the toolbox. Click a tool to select it (the active tool name is stored in the designer state), then click on the form to place a new instance. Click the same tool again to deselect it and return to pointer mode. @@ -547,20 +555,17 @@ Click a tool to select it (the active tool name is stored in the designer state) .index Breakpoints .index Stepping .index Debug Mode -.index DBG_IDLE -.index DBG_RUNNING -.index DBG_PAUSED .h1 Debugger -The DVX BASIC IDE includes a full interactive debugger. The debugger operates as a state machine with three states: +The DVX BASIC IDE includes a full interactive debugger. The debugger has three states: .table - State Description - ----- ----------- - DBG_IDLE No program loaded or running. - DBG_RUNNING Program is executing (VM running in slices). - DBG_PAUSED Execution is paused at a breakpoint or step point. The IDE GUI is fully interactive. + State Description + ----- ----------- + Idle No program loaded or running. + Running The program is executing. + Paused Execution is paused at a breakpoint or step point. The IDE is fully interactive -- you can inspect and change variables, set or clear breakpoints, step, continue, or stop. .endtable .h2 Starting a Debug Session @@ -593,7 +598,7 @@ Not every line can have a breakpoint. The IDE validates the line content and sil .h3 Breakpoint Storage -Each breakpoint records the project file index, the code line number within the file, the procedure index, and the procedure name (as Object.Event). When the project is compiled, breakpoints are converted to concatenated source line numbers that match the VM's OP_LINE opcodes. +Each breakpoint records the file it belongs to, the line number within that file, and the procedure name (Object.Event). Breakpoints are stored in the project and survive compilation. .h3 Visual Indicators @@ -617,20 +622,20 @@ When lines are added or removed in the editor, breakpoints below the edit point Run to Cursor Ctrl+F8 Run until execution reaches the line under the cursor. .endtable -.h2 The Debug Run Loop +.h2 Debug Run Behavior -When a program is running in debug mode, the IDE enters a cooperative loop: +When a program is running under the debugger: .list -.item The VM executes up to 10,000 steps per slice. -.item If the VM hits a breakpoint (BAS_VM_BREAKPOINT), the state transitions to DBG_PAUSED. The IDE navigates the code editor to the breakpoint line, highlights it in yellow, auto-opens the Locals and Call Stack windows, and updates all debug windows. -.item While paused, the IDE pumps dvxUpdate() continuously, keeping the GUI responsive. The user can inspect variables, modify them in the Immediate window, step, continue, or stop. -.item When the user resumes (F5/Shift+F5/F8/etc.), the state transitions back to DBG_RUNNING and the loop continues. +.item The program runs in short cooperative slices so the IDE remains responsive. +.item When the program hits a breakpoint, execution pauses immediately. The IDE switches to Code View, navigates to the breakpoint line, highlights it in yellow, and opens the Locals and Call Stack windows if they are not already visible. +.item While paused, you can inspect variables in Locals and Watch, evaluate expressions in the Immediate window, assign new values to variables, toggle breakpoints, step, continue, or stop. +.item Resuming (F5, Shift+F5, or any Step command) returns the program to Running state. .endlist .h2 Stopping -Press Esc or click the Stop toolbar button at any time to halt execution. The VM is destroyed, debug state resets to DBG_IDLE, and the IDE restores the designer windows that were hidden at run start. +Press Esc or click the Stop toolbar button at any time to halt execution. The program is terminated, the debugger returns to Idle, and the IDE restores any designer or code windows that were hidden at the start of the run. .link ide.debug.locals Locals Window .link ide.debug.callstack Call Stack Window @@ -790,7 +795,7 @@ Parse or runtime errors are displayed inline with an Error: prefix. .h2 Inspecting Variables While Paused -When the debugger is paused at a breakpoint, the Immediate window has access to the running VM's state. Global variable values are copied into the evaluation VM, so expressions like count or name$ & " test" display live values. +When the debugger is paused at a breakpoint, the Immediate window has access to the running program's state. Expressions like count or name$ & " test" display live values. .h2 Assigning Variables While Paused @@ -815,7 +820,7 @@ Assignment works for: .item Combined -- items(0).price = 9.99 .endlist -The new value is written directly into the VM's live variable slot (local, global, or form scope). A confirmation message is displayed, and the Locals and Watch windows update automatically to reflect the change. +The new value is written directly into the running program's variable (local, global, or form scope). A confirmation message is displayed, and the Locals and Watch windows update automatically to reflect the change. If the assignment target cannot be resolved (unknown variable, out-of-bounds index, wrong type), an error message is displayed. @@ -896,33 +901,39 @@ F3 repeats the last search (Find Next) without opening the dialog. .h1 Preferences -Open via Tools > Preferences. Settings are saved to dvxbasic.ini in the app's config directory. +Open via Tools > Preferences. Settings are saved to dvxbasic.ini in the app's config directory. The dialog has two tabs. -.h2 Editor Section +.h2 General Tab + +.h3 Editor Section .table - Setting Description Default - ------- ----------- ------- - Skip comments/strings when renaming When renaming a control or form, skip occurrences inside comments and string literals. On - Require variable declaration (OPTION EXPLICIT) When enabled, variables must be declared with DIM before use. Off - Tab width Number of spaces per tab stop (1-8). 3 - Insert spaces instead of tabs When enabled, pressing Tab inserts spaces. When disabled, inserts a real tab character. On + Setting Description Default + ------- ----------- ------- + Skip comments/strings when renaming When renaming a control or form, skip occurrences inside comments and string literals. On + Require variable declaration (OPTION EXPLICIT) When enabled, newly created projects have OPTION EXPLICIT on by default. Off + Tab width Number of spaces per tab stop. 3 + Insert spaces instead of tabs When enabled, pressing Tab inserts spaces. When disabled, inserts a real tab character. On .endtable -.h2 New Project Defaults Section +.h3 New Project Defaults Section These fields set the default values for new project metadata: .table - Field Description Default - ----- ----------- ------- - Author Default author name. (empty) - Company Default company name. (empty) - Version Default version string. 1.0 - Copyright Default copyright notice. (empty) - Description Default project description (multi-line). (empty) + Field Description Default + ----- ----------- ------- + Author Default author name. (empty) + Publisher Default publisher/company name. (empty) + Version Default version string. 1.0 + Copyright Default copyright notice. (empty) + Description Default project description (multi-line). (empty) .endtable +.h2 Colors Tab + +Customizes the syntax-highlighting colors used in the code editor. The left side is a list of seven color entries (Default Text, Keywords, Strings, Comments, Numbers, Operators, Types). Select an entry and adjust its red, green, and blue components using the three sliders on the right. The Preview swatch shows the current color. Changes are applied to the open editor when you click OK. + .link ide.overview Back to Overview .topic ide.shortcuts @@ -946,6 +957,7 @@ These fields set the default values for new project metadata: Ctrl+F Find Ctrl+H Replace Ctrl+E Menu Editor + F1 DVX BASIC Help F3 Find Next F5 Run Shift+F5 Debug diff --git a/src/apps/kpunch/dvxbasic/langref.dhs b/src/apps/kpunch/dvxbasic/langref.dhs index e8a4080..c47cda5 100644 --- a/src/apps/kpunch/dvxbasic/langref.dhs +++ b/src/apps/kpunch/dvxbasic/langref.dhs @@ -77,20 +77,29 @@ name$ = "Hello" ' String .h2 Numeric Literals .table - Form Example Description - ---- ------- ----------- - Decimal integer 42 Values -32768..32767 are Integer; larger are Long - Hex integer &HFF Hexadecimal literal - Long suffix 42&, &HFF& Force Long type - Floating-point 3.14, 1.5E10 Double by default - Single suffix 3.14! Force Single type - Double suffix 3.14# Force Double type + Form Example Description + ---- ------- ----------- + Decimal integer 42 Values -32768..32767 are Integer; larger are Long + Hex integer &HFF, &H1234 Hexadecimal literal + Integer suffix 42% Force Integer type + Long suffix 42&, &HFF& Force Long type + Floating-point 3.14, 1.5E10, 2.5D3 Any number containing a decimal point or exponent is Double by default + Single suffix 3.14! Force Single type + Double suffix 3.14# Force Double type .endtable +.note info +Both E and D can introduce an exponent (e.g. 1.5E10 and 2.5D3 are equivalent forms of a scientific-notation double). +.endnote + .h2 Type Promotion When mixing types in expressions, values are automatically promoted to a common type: Integer -> Long -> Single -> Double. Strings are not automatically converted to numbers (use VAL and STR$). +.h2 Boolean Values + +Boolean values use -1 for True and 0 for False. Any non-zero numeric value is treated as True in a conditional context. The keywords True and False are reserved and may be used anywhere a Boolean value is expected. + .link lang.func.conversion See also: Conversion Functions .topic lang.operators @@ -108,32 +117,81 @@ When mixing types in expressions, values are automatically promoted to a common .h1 Operators -Operators listed from highest precedence (evaluated first) to lowest precedence (evaluated last). +Operators listed from highest precedence (evaluated first) to lowest precedence (evaluated last). Parentheses override precedence and may be nested freely. .table Precedence Operator Description ---------- -------- ----------- 1 (highest) ^ Exponentiation - 2 - (unary) Negation - 3 * / \ MOD Multiply, float div, integer div, modulus - 4 + - Addition, subtraction + 2 - (unary), + (unary) Negation (unary plus is a no-op) + 3 * / \ MOD Multiply, float divide, integer divide, modulus + 4 + - Addition, subtraction (+ also concatenates strings) 5 & String concatenation 6 = <> < > <= >= Comparison (returns Boolean) 7 NOT Logical/bitwise NOT 8 AND Logical/bitwise AND - 9 XOR Logical/bitwise XOR + 9 XOR Logical/bitwise exclusive-or 10 OR Logical/bitwise OR 11 EQV Logical/bitwise equivalence 12 (lowest) IMP Logical/bitwise implication .endtable +.h2 Arithmetic Operators + +.table + Operator Description + -------- ----------- + + Addition (or string concatenation when both operands are strings) + - Subtraction (or negation when used as a unary prefix) + * Multiplication + / Floating-point division (result is always Single or Double) + \ Integer division (result is Long, fractional part discarded) + MOD Integer modulus (remainder) + ^ Exponentiation (result is Double) +.endtable + +.h2 Comparison Operators + +Comparison operators return True (-1) or False (0). + +.table + Operator Description + -------- ----------- + = Equal to + <> Not equal to + < Less than + > Greater than + <= Less than or equal to + >= Greater than or equal to +.endtable + +String comparisons are controlled by OPTION COMPARE. With OPTION COMPARE BINARY (the default) strings are compared by byte value. With OPTION COMPARE TEXT comparisons are case-insensitive. + +.h2 Logical / Bitwise Operators + +The logical operators work on Boolean values and also perform bitwise operations on integer values. + +.table + Operator Description + -------- ----------- + NOT Logical/bitwise complement + AND Logical/bitwise AND + OR Logical/bitwise OR + XOR Logical/bitwise exclusive-or + EQV Logical/bitwise equivalence (same as NOT (a XOR b)) + IMP Logical/bitwise implication (same as (NOT a) OR b) +.endtable + .h2 String Concatenation -Use & to concatenate strings. The + operator also concatenates when both operands are strings. +Use & to concatenate strings. The + operator also concatenates when both operands are strings. Prefer & because it does not depend on operand types. .code result$ = "Hello" & " " & "World" result$ = firstName$ & " " & lastName$ + +' + also concatenates when both operands are strings +greeting$ = "Hi " + name$ .endcode .topic lang.statements @@ -145,15 +203,26 @@ result$ = firstName$ & " " & lastName$ .h1 Statements -Multiple statements can appear on one line separated by :. Lines can be continued with _ at the end. Comments start with ' or REM. +Multiple statements can appear on one line separated by : (colon). A line can be continued onto the next physical line by ending it with an underscore (_) preceded by whitespace. Comments start with ' (apostrophe) or the REM keyword and run to the end of the line. The character ? is a shorthand for PRINT. -.link lang.declarations Declaration Statements (DIM, REDIM, CONST, TYPE) +.code +a = 10 : b = 20 : c = a + b ' three statements on one line + +total = price * quantity _ + + shipping + tax ' one statement continued across lines + +REM This is a comment +' So is this +? "Hello" ' same as PRINT "Hello" +.endcode + +.link lang.declarations Declaration Statements (DIM, REDIM, CONST, TYPE, DECLARE) .link lang.conditionals Conditional Statements (IF, SELECT CASE) .link lang.loops Loop Statements (FOR, DO, WHILE) .link lang.procedures Procedures (SUB, FUNCTION, DEF FN) .link lang.flow Flow Control (EXIT, CALL, GOTO, GOSUB, ON) .link lang.io Input/Output (PRINT, INPUT, DATA/READ) -.link lang.misc Miscellaneous Statements +.link lang.misc Miscellaneous Statements (ON ERROR, SHELL, SLEEP, END) .topic lang.declarations .title Declaration Statements @@ -184,7 +253,7 @@ Multiple statements can appear on one line separated by :. Lines can be continue .h2 DIM -Declares variables and arrays with an explicit type. +Declares variables and arrays with an explicit type. If no type is given, the type defaults to whatever DEFtype (if any) is in effect for the variable's first letter; without a DEFtype statement the default is Single. .code DIM variable AS type @@ -196,6 +265,12 @@ DIM variable AS STRING * n DIM SHARED variable AS type .endcode +Multiple variables can be declared at module level so their values persist between procedure calls. Arrays can have up to 8 dimensions. A variable declared with type suffix does not need the AS clause: + +.code +Dim name$, count%, total& ' suffixes infer the types +.endcode + Examples: .code @@ -209,9 +284,11 @@ Dim fixedStr As String * 20 .endcode .note info -DIM SHARED makes a variable accessible from all procedures without passing it as a parameter. +DIM SHARED at module level makes a variable accessible from every procedure without passing it as a parameter. Inside a SUB or FUNCTION, DIM declares a local variable that is recreated on each call (use STATIC to retain its value between calls). .endnote +Fixed-length strings (STRING * n) are padded with spaces and truncated when assigned so their length is always exactly n. + .h2 REDIM Reallocates a dynamic array, optionally preserving existing data. @@ -228,10 +305,11 @@ ReDim Preserve scores(1 To newCount) As Integer .h2 CONST -Declares a named constant. The value must be a literal (integer, float, string, or boolean). +Declares a named constant. The value must be a literal (integer, long, float, string, or boolean). Constants are not variables -- you cannot assign a new value to them or pass them by reference. .code CONST name = value +CONST name AS type = value .endcode .code @@ -239,8 +317,11 @@ Const MAX_SIZE = 100 Const PI = 3.14159265 Const APP_NAME = "DVX App" Const DEBUG_MODE = True +Const HEADER As String = "=== Report ===" .endcode +Constants are scoped to the file or procedure in which they are declared. Constants declared at module level are visible to all procedures in the same module. + .h2 TYPE...END TYPE Defines a user-defined type (record/structure). @@ -268,16 +349,16 @@ UDT fields can themselves be UDTs (nested types). .h2 DECLARE -Forward-declares a SUB or FUNCTION. Required when a procedure is called before it is defined. +Forward-declares a SUB or FUNCTION. This is rarely required because the compiler supports forward references within a module, but it is still accepted for compatibility: .code -DECLARE SUB name ([BYVAL] param AS type, ...) -DECLARE FUNCTION name ([BYVAL] param AS type, ...) AS returnType +DECLARE SUB name ([BYVAL] [OPTIONAL] param AS type, ...) +DECLARE FUNCTION name ([BYVAL] [OPTIONAL] param AS type, ...) AS returnType .endcode .h2 DECLARE LIBRARY -Declares external native functions from a dynamically loaded library. This allows BASIC programs to call functions exported by DXE libraries. +Declares external functions from a native library bundled with DVX. This is how BASIC programs reach operating-system services, serial ports, databases, and other native facilities. Rather than writing DECLARE LIBRARY blocks yourself, add one of the shipped include files (such as commdlg.bas or sql.bas) to your project; the include file contains the proper declarations. .code DECLARE LIBRARY "libraryName" @@ -287,13 +368,14 @@ END DECLARE .endcode .code -Declare Library "rs232" - Declare Function ComOpen(ByVal port As Integer) As Integer - Declare Sub ComClose(ByVal port As Integer) - Declare Sub ComSend(ByVal port As Integer, ByVal data$ As String) +Declare Library "basrt" + Declare Function SQLOpen(ByVal path As String) As Integer + Declare Sub SQLClose(ByVal db As Integer) End Declare .endcode +.link lang.runtime See also: BASIC Runtime Libraries + .h2 STATIC Declares a local variable that retains its value between calls. @@ -354,7 +436,7 @@ The LET keyword is optional and supported for compatibility. .h2 SWAP -Exchanges the values of two variables. +Exchanges the values of two variables. The variables must be the same type. .code SWAP variable1, variable2 @@ -364,9 +446,25 @@ SWAP variable1, variable2 Swap a, b .endcode +.h2 SET + +Assigns an object reference (form or control) to a variable. Required when creating a form or control with CreateForm / CreateControl so that the returned reference is stored in the variable: + +.code +SET variable = objectExpression +.endcode + +.code +Dim frm As Long +Set frm = CreateForm("MyForm", 320, 240) +frm.Caption = "Built in code" +.endcode + +For ordinary numeric or string assignment, SET is not used. + .h2 ERASE -Frees the memory of an array and resets it. +Frees the memory of a dynamic array and resets it to undimensioned state. Fixed-size arrays (declared with constant bounds) reset their elements but keep their shape. .code ERASE arrayName @@ -550,7 +648,6 @@ Wend .index END FUNCTION .index DEF FN .index BYVAL -.index BYREF .index EXIT SUB .index EXIT FUNCTION @@ -561,7 +658,7 @@ Wend Defines a subroutine (no return value). .code -SUB name ([BYVAL] param AS type, ...) +SUB name ([BYVAL] [OPTIONAL] param AS type, ...) statements END SUB .endcode @@ -572,14 +669,37 @@ Sub Greet(ByVal name As String) End Sub .endcode -Parameters are passed ByRef by default. Use ByVal for value semantics. Use EXIT SUB to return early. +Parameters are passed by reference by default. Use ByVal for value semantics; there is no separate ByRef keyword (omitting ByVal is the by-reference form). Use EXIT SUB to return early. A SUB is called either with or without parentheses; when used as a statement, parentheses are optional: + +.code +Greet "World" +Greet("World") +Call Greet("World") +.endcode + +.h3 Optional Parameters + +Mark a parameter OPTIONAL to allow callers to omit it. An optional parameter must be positioned after all required parameters and receives an empty/zero default when not supplied. + +.code +Sub Announce(ByVal msg As String, Optional ByVal loud As Integer) + If loud Then + Print UCase$(msg) + Else + Print msg + End If +End Sub + +Announce "hello" ' loud defaults to 0 +Announce "hello", True ' loud is -1 +.endcode .h2 FUNCTION...END FUNCTION Defines a function with a return value. .code -FUNCTION name ([BYVAL] param AS type, ...) AS returnType +FUNCTION name ([BYVAL] [OPTIONAL] param AS type, ...) AS returnType statements name = returnValue END FUNCTION @@ -595,7 +715,7 @@ Assign to the function name to set the return value. Use EXIT FUNCTION to return .h2 DEF FN -Defines a single-expression function. +Defines a single-expression function. The function name begins with FN and is invoked just like a normal function. DEF FN functions cannot span multiple statements. .code DEF FNname(params) = expression @@ -604,6 +724,9 @@ DEF FNname(params) = expression .code Def FnSquare(x) = x * x Print FnSquare(5) ' prints 25 + +Def FnCelsius(f As Double) = (f - 32) * 5 / 9 +Print FnCelsius(212) ' prints 100 .endcode .topic lang.flow @@ -689,18 +812,18 @@ If the expression evaluates to 1, control goes to the first label; 2, the second .h2 PRINT -Outputs text to the console or to a file channel. +Writes expressions to the output window (or to a file channel). .code -PRINT [expression] [{; | ,} expression] ... -PRINT #channel, expression +PRINT [expression [{; | ,} expression] ...] +PRINT #channel, expression [; expression] ... PRINT USING format$; expression [; expression] ... .endcode .list -.item ; between items -- no separator (items are concatenated) +.item ; between items -- no separator (items appear next to each other, or separated by a space for numbers) .item , between items -- advance to the next 14-column tab zone -.item Trailing ; or , suppresses the newline +.item A trailing ; or , suppresses the newline normally written at the end of the statement .item ? is an alias for PRINT .endlist @@ -708,15 +831,44 @@ Special functions inside PRINT: .list .item SPC(n) -- print n spaces -.item TAB(n) -- advance to column n +.item TAB(n) -- advance to column n (first column is 1) .endlist .code Print "Name:"; Tab(20); name$ -Print Using "###.##"; total +Print "X="; x, "Y="; y ' comma = next 14-column zone Print #1, "Written to file" .endcode +.h3 PRINT USING + +PRINT USING formats each following expression according to a format string and prints the result. If more expressions are supplied than the format string consumes, the format string is reused from the start for each. + +.table + Format character Meaning + ---------------- ------- + # Digit (replaced with a digit or space to pad on the left) + . Decimal-point position + , Insert thousands separator + + At start or end: always show a sign character + - At end: show a trailing minus sign only when negative + ** At start: fill leading spaces with asterisks + $$ At start: floating dollar sign + ^^^^ Anywhere: format the value in scientific notation + ! First character of a string only + & Entire string (variable length) + \ ... \ Fixed-length string field (width = 2 + number of spaces between backslashes) +.endtable + +.code +Print Using "###.##"; 3.14159 ' " 3.14" +Print Using "$$#,##0.00"; 1234567.89 ' "$1,234,567.89" +Print Using "**#,##0.00"; 42.5 ' "*****42.50" +Print Using "####.####^^^^"; 0.000123 ' scientific notation +Print Using "\ \"; "Hello" ' fixed-width 4-char: "Hell" +Print Using "& likes &"; name$; food$ +.endcode + .h2 INPUT Reads a line of text from the user or from a file channel. @@ -771,13 +923,13 @@ Restore .code ON ERROR GOTO label ' Enable error handler -ON ERROR GOTO 0 ' Disable error handler -RESUME ' Retry the statement that caused the error -RESUME NEXT ' Continue at the next statement after the error -ERROR n ' Raise a runtime error with error number n +ON ERROR GOTO 0 ' Disable error handler +RESUME ' Retry the statement that caused the error +RESUME NEXT ' Continue at the next statement after the error +ERROR n ' Raise a runtime error with error number n .endcode -The ERR keyword returns the current error number in expressions. +The ERR keyword returns the current error number in expressions (it is 0 when no error is active). .code On Error GoTo ErrorHandler @@ -789,15 +941,37 @@ ErrorHandler: Resume Next .endcode +.h3 Common Error Numbers + +.table + Number Meaning + ------ ------- + 1 FOR loop error (NEXT without FOR, NEXT variable mismatch, FOR stack underflow) + 4 Out of DATA + 7 Out of memory + 9 Subscript out of range / invalid variable or field index + 11 Division by zero + 13 Type mismatch / not an array / not a TYPE instance + 26 FOR loop nesting too deep + 51 Internal error (bad opcode) + 52 Bad file number or file not open + 53 File not found + 54 Bad file mode + 58 File already exists or rename failed + 67 Too many files open + 75 Path/file access error + 76 Path not found +.endtable + .h2 SHELL -Executes an operating system command. +Executes an operating-system command. .code SHELL "command" .endcode -When used as a function, returns the exit code of the command. +When used as a function, SHELL returns the exit code of the command. .code Shell "DIR /B" @@ -806,15 +980,15 @@ exitCode = Shell("COPY A.TXT B.TXT") .h2 SLEEP -Pauses execution for a specified number of seconds. +Pauses execution for the given number of seconds. With no argument, sleeps for 1 second. .code -SLEEP seconds +SLEEP [seconds] .endcode .h2 RANDOMIZE -Seeds the random number generator. +Seeds the random number generator. Without an argument (not shown here) and without a prior RANDOMIZE, the sequence is the same on every run. Use RANDOMIZE TIMER to get a different sequence each run. .code RANDOMIZE seed @@ -823,7 +997,7 @@ RANDOMIZE TIMER ' Seed from system clock .h2 END -Terminates program execution immediately. +Terminates program execution immediately. Any form that is loaded is unloaded before the program exits. .code END @@ -930,10 +1104,141 @@ PUT #channel, [recordNum], variable Sets the file position. As a function, returns the current position. .code -SEEK #channel, position ' Statement: set position +SEEK #channel, position ' Statement: set position pos = SEEK(channel) ' Function: get current position .endcode +.topic lang.filesystem +.title File System Statements +.toc 1 File System Statements +.index KILL +.index NAME +.index FILECOPY +.index MKDIR +.index RMDIR +.index CHDIR +.index CHDRIVE +.index GETATTR +.index SETATTR +.index File Attributes + +.h1 File System Statements + +DVX BASIC provides statements for working with the file system -- creating, deleting, renaming, and copying files and directories, and reading or setting file attributes. + +.h2 KILL + +Deletes a file. + +.code +KILL filename$ +.endcode + +.code +Kill "temp.bak" +.endcode + +.h2 NAME ... AS + +Renames (or moves) a file. + +.code +NAME oldName$ AS newName$ +.endcode + +.code +Name "draft.txt" As "final.txt" +.endcode + +.h2 FILECOPY + +Copies a file. If the destination already exists it is overwritten. + +.code +FILECOPY source$, destination$ +.endcode + +.code +FileCopy "data.db", "data.bak" +.endcode + +.h2 MKDIR / RMDIR + +Creates or removes a directory. + +.code +MKDIR path$ +RMDIR path$ +.endcode + +.code +MkDir App.Data & "\backups" +RmDir "C:\OLD" +.endcode + +.h2 CHDIR / CHDRIVE + +Changes the current working directory or drive. + +.code +CHDIR path$ +CHDRIVE drive$ +.endcode + +.code +ChDir "C:\DVX\PROJECTS" +ChDrive "D" +.endcode + +.h2 GETATTR / SETATTR + +GETATTR returns the attributes of a file (as a function). SETATTR sets them (as a statement). Attributes are a bitmask of the vb file-attribute constants. + +.code +attrs = GETATTR(filename$) +SETATTR filename$, attrs +.endcode + +.table + Constant Value Attribute + -------- ----- --------- + vbNormal 0 Normal file (no attributes set) + vbReadOnly 1 Read-only + vbHidden 2 Hidden + vbSystem 4 System file + vbDirectory 16 Entry is a directory + vbArchive 32 File has been modified since last backup +.endtable + +.code +If (GetAttr("readme.txt") And vbReadOnly) <> 0 Then + Print "File is read-only." +End If + +SetAttr "secret.dat", vbHidden + vbReadOnly +.endcode + +.h2 CURDIR$ / DIR$ + +CURDIR$ returns the current directory. DIR$ returns the first file matching a pattern; subsequent calls with no arguments return additional matches (or "" when done). + +.code +cwd$ = CURDIR$ +first$ = DIR$("*.txt") +WHILE first$ <> "" + Print first$ + first$ = DIR$ ' next match, no argument +WEND +.endcode + +.h2 FILELEN + +Returns the length of a file in bytes. + +.code +bytes = FILELEN(filename$) +.endcode + .topic lang.func.string .title String Functions .toc 1 Built-in Functions @@ -962,35 +1267,62 @@ pos = SEEK(channel) ' Function: get current position .table Function Returns Description -------- ------- ----------- - ASC(s$) Integer ASCII code of first character of s$ + ASC(s$) Integer ASCII code of the first character of s$ CHR$(n) String Character with ASCII code n - FORMAT$(value, fmt$) String Formats a numeric value using format string - HEX$(n) String Hexadecimal representation of n + FORMAT$(value, fmt$) String Formats a numeric value using a format string + HEX$(n) String Hexadecimal representation of n (uppercase, no leading &H) INSTR(s$, find$) Integer Position of find$ in s$ (1-based), 0 if not found - INSTR(start, s$, find$) Integer Search starting at position start + INSTR(start, s$, find$) Integer Search starting at position start (1-based) LCASE$(s$) String Converts s$ to lowercase LEFT$(s$, n) String Leftmost n characters of s$ - LEN(s$) Integer Length of s$ + LEN(s$) Integer Length of s$ in characters LTRIM$(s$) String Removes leading spaces from s$ - MID$(s$, start [, length]) String Substring from position start (1-based) + MID$(s$, start) String Substring from start (1-based) to end of string + MID$(s$, start, length) String Substring of length characters starting at start RIGHT$(s$, n) String Rightmost n characters of s$ RTRIM$(s$) String Removes trailing spaces from s$ SPACE$(n) String String of n spaces - STR$(n) String Converts number n to string - STRING$(n, char) String String of n copies of char + STR$(n) String Converts number n to string (leading space for non-negative) + STRING$(n, char) String String of n copies of char (char can be an ASCII code or single-character string) TRIM$(s$) String Removes leading and trailing spaces from s$ UCASE$(s$) String Converts s$ to uppercase - VAL(s$) Double Converts string s$ to a numeric value + VAL(s$) Double Converts string s$ to a numeric value; stops at first non-numeric character .endtable +.h2 FORMAT$ + +FORMAT$ formats a numeric value using a BASIC-style format string. The format characters are the same as the ones used by PRINT USING. + +.code +s$ = FORMAT$(value, fmt$) +.endcode + +.code +Print Format$(3.14159, "###.##") ' " 3.14" +Print Format$(42, "00000") ' "00042" +Print Format$(1234.5, "#,##0.00") ' "1,234.50" +Print Format$(0.5, "PERCENT") ' "50%" +.endcode + +The accepted format characters are # (digit or pad space), 0 (digit or pad zero), . (decimal point), , (thousands separator), + and - (sign placement), $$ (floating dollar sign), ** (asterisk fill), and the literal word PERCENT (multiplies by 100 and appends %). See PRINT USING for details on each. + .h2 MID$ Assignment -MID$ can also be used on the left side of an assignment to replace a portion of a string: +MID$ can also be used on the left side of an assignment to replace a portion of a string without changing its length: .code -Mid$(s$, 3, 2) = "XY" ' Replace 2 characters starting at position 3 +Mid$(s$, start [, length]) = replacement$ .endcode +.code +Dim s$ +s$ = "Hello, World!" +Mid$(s$, 8, 5) = "DVX--" +Print s$ ' Hello, DVX--! +.endcode + +If length is omitted, MID$ replaces up to LEN(replacement$) characters. The target string's total length never changes -- extra characters in replacement$ are truncated and shorter replacements leave trailing characters in place. + .topic lang.func.math .title Math Functions .toc 2 Math Functions @@ -1014,24 +1346,44 @@ Mid$(s$, 3, 2) = "XY" ' Replace 2 characters starting at position 3 Function Returns Description -------- ------- ----------- ABS(n) Double Absolute value of n - ATN(n) Double Arctangent of n (in radians) - COS(n) Double Cosine of n (radians) + ATN(n) Double Arctangent of n (result in radians) + COS(n) Double Cosine of n (n in radians) EXP(n) Double e raised to the power n - FIX(n) Integer Truncates n toward zero (removes fractional part) + FIX(n) Integer Truncates n toward zero (drops the fractional part) INT(n) Integer Largest integer less than or equal to n (floor) LOG(n) Double Natural logarithm (base e) of n - RND[(n)] Double Random number between 0 (inclusive) and 1 (exclusive) - SGN(n) Integer Sign of n: -1, 0, or 1 - SIN(n) Double Sine of n (radians) + RND[(n)] Double Random number in the range 0 <= RND < 1 + SGN(n) Integer Sign of n: -1 if negative, 0 if zero, 1 if positive + SIN(n) Double Sine of n (n in radians) SQR(n) Double Square root of n - TAN(n) Double Tangent of n (radians) - TIMER Double Number of seconds since midnight + TAN(n) Double Tangent of n (n in radians) + TIMER Double Seconds since midnight .endtable .note info -RND with a negative argument seeds and returns. RND(0) returns the previous value. Use RANDOMIZE to seed the generator. +RND with no argument returns the next number in the sequence. Use RANDOMIZE or RANDOMIZE TIMER to seed the generator so you don't get the same sequence each run. .endnote +.h2 Color Functions + +The color functions work with 24-bit RGB colors packed into a Long. + +.table + Function Returns Description + -------- ------- ----------- + RGB(r, g, b) Long Combine red, green, and blue components (0-255) into a 24-bit color + GETRED(color) Integer Red component (0-255) of a color built with RGB + GETGREEN(color) Integer Green component (0-255) of a color built with RGB + GETBLUE(color) Integer Blue component (0-255) of a color built with RGB +.endtable + +.code +Dim c As Long +c = RGB(255, 128, 0) ' bright orange +Print GetRed(c); GetGreen(c); GetBlue(c) ' 255 128 0 +Me.BackColor = RGB(0, 0, 128) ' dark blue background +.endcode + .topic lang.func.conversion .title Conversion Functions .toc 2 Conversion Functions @@ -1047,7 +1399,7 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu Function Returns Description -------- ------- ----------- CDBL(n) Double Converts n to Double - CINT(n) Integer Converts n to Integer (with banker's rounding) + CINT(n) Integer Converts n to Integer (rounds half away from zero) CLNG(n) Long Converts n to Long CSNG(n) Single Converts n to Single CSTR(n) String Converts n to its String representation @@ -1069,14 +1421,19 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu .table Function Returns Description -------- ------- ----------- - EOF(channel) Boolean True if file pointer is at end of file - FREEFILE Integer Next available file channel number - INPUT$(n, #channel) String Reads n characters from the file + EOF(channel) Boolean True if the file pointer is at end of file + FREEFILE Integer Next available file channel number (1..16) + INPUT$(n, #channel) String Reads exactly n characters from the file LOC(channel) Long Current read/write position in the file LOF(channel) Long Length of the file in bytes SEEK(channel) Long Current file position (function form) - LBOUND(array [, dim]) Integer Lower bound of an array dimension - UBOUND(array [, dim]) Integer Upper bound of an array dimension + FILELEN(path$) Long Length of the named file in bytes (no OPEN needed) + GETATTR(path$) Integer File attribute bits (see vbReadOnly, vbHidden, etc.) + CURDIR$ String Current working directory + DIR$(pattern$) String First filename matching pattern, or "" + DIR$ String Next match from the last DIR$(pattern$), or "" when done + LBOUND(array [, dim]) Integer Lower bound of an array dimension (default dim = 1) + UBOUND(array [, dim]) Integer Upper bound of an array dimension (default dim = 1) .endtable .topic lang.func.misc @@ -1092,9 +1449,11 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu Function Returns Description -------- ------- ----------- DATE$ String Current date as "MM-DD-YYYY" - TIME$ String Current time as "HH:MM:SS" - ENVIRON$(name$) String Value of environment variable name$ + TIME$ String Current time as "HH:MM:SS" (24-hour) + TIMER Double Seconds since midnight + ENVIRON$(name$) String Value of the environment variable; empty string if not set ERR Integer Current runtime error number (0 if no error) + SHELL(cmd$) Integer Exit code of the command (also usable as a statement, discarding the code) .endtable .topic lang.forms @@ -1105,19 +1464,25 @@ RND with a negative argument seeds and returns. RND(0) returns the previous valu .index Show .index Hide .index Me +.index Nothing .index DoEvents .index DOEVENTS .index MsgBox .index MSGBOX .index InputBox$ .index INPUTBOX$ +.index CreateForm +.index CreateControl +.index SetEvent +.index RemoveControl +.index With .index vbModal .index Control Arrays .index Event Handlers .h1 Form and Control Statements -DVX BASIC supports Visual Basic-style forms and controls for building graphical user interfaces. Forms are defined in .frm files and loaded at runtime. +DVX BASIC supports Visual Basic-style forms and controls for building graphical user interfaces. A form is normally designed visually in the IDE and saved as a .frm file, but forms and their controls can also be built entirely in code. .h2 Loading and Unloading Forms @@ -1126,18 +1491,18 @@ LOAD FormName UNLOAD FormName .endcode -LOAD creates the form and its controls in memory. UNLOAD destroys the form and frees its resources. +LOAD creates the form and its controls in memory. It fires Form_Load when the form is first loaded. UNLOAD destroys the form, firing Form_QueryUnload (which can cancel the close) and then Form_Unload. The form name here is the literal name of the form as it appears in its .frm file. .h2 Showing and Hiding Forms .code -FormName.Show [modal] +FormName.Show [mode] FormName.Hide -Me.Show [modal] +Me.Show [mode] Me.Hide .endcode -Pass vbModal (1) to Show for a modal dialog. +Pass vbModal (1) to Show for a modal dialog that blocks until the user closes it. Omit the mode for a modeless (non-blocking) window. .code Form2.Show vbModal @@ -1155,7 +1520,7 @@ value = ControlName.Property .code Text1.Text = "Hello" -label1.Caption = "Name: " & name$ +Label1.Caption = "Name: " & name$ x = Text1.Left .endcode @@ -1172,7 +1537,7 @@ List1.Clear .h2 Me Keyword -Me refers to the current form. Use it to access the form's own properties, controls, and methods from within event handlers. +Me refers to the current form -- that is, the form whose event handler is running. Use it to access the form's own properties, controls, and methods from within event handlers, especially when the form's name may change or when the same code runs from multiple forms. .code Me.Caption = "Updated Title" @@ -1180,6 +1545,14 @@ Me.Text1.Text = "" Me.Hide .endcode +.h2 Nothing + +Nothing is a null object reference. Assign it to an object-typed variable to indicate "no object". + +.code +Set myCtrl = Nothing +.endcode + .h2 Control Arrays Multiple controls can share a name with unique indices. Access individual controls with parenthesized indices: @@ -1190,9 +1563,87 @@ Label1(idx).Caption = "Item " & Str$(idx) Me.Label1(i).Visible = True .endcode +.h2 WITH + +The WITH block provides a shorthand for setting several properties on the same control or for making several method calls. Inside the block, begin each property or method with a dot and omit the control name. + +.code +WITH ControlName + .Property1 = value1 + .Property2 = value2 + .Method arg1, arg2 +END WITH +.endcode + +.code +With Label1 + .Caption = "Status: Ready" + .ForeColor = RGB(0, 128, 0) + .Visible = True +End With +.endcode + +.h2 Building Forms in Code + +In addition to loading forms from .frm files, you can build a form entirely from code with CreateForm, CreateControl, SetEvent, and RemoveControl. The returned reference should be stored with SET. + +.h3 CreateForm + +.code +SET formVar = CreateForm(name$, width%, height%) +.endcode + +Creates a new form with the given name and initial size. Returns a form reference. + +.h3 CreateControl + +.code +SET ctrlVar = CreateControl(formRef, typeName$, ctrlName$ [, parentCtrlRef]) +.endcode + +Creates a control on the given form. typeName$ is the control type (e.g. "CommandButton", "Label", "TextBox"). ctrlName$ is the unique name for the new control. If a parentCtrlRef is supplied, the new control is nested inside it (useful for placing controls inside containers like HBox or Frame). + +.h3 SetEvent + +.code +SETEVENT ctrlRef, eventName$, handlerName$ +.endcode + +Wires an arbitrary SUB as the handler for a control's event. The default event-handler convention (ControlName_EventName) is used automatically for controls defined in a .frm file; SetEvent is for controls you create dynamically or for reusing a single handler across multiple controls. + +.h3 RemoveControl + +.code +REMOVECONTROL formRef, ctrlName$ +.endcode + +Removes a control from a form. + +.code +Dim frm As Long +Dim lbl As Long + +Set frm = CreateForm("MyDlg", 300, 200) +frm.Caption = "Built in code" + +Set lbl = CreateControl(frm, "Label", "StatusLabel") +StatusLabel.Caption = "Hello, world!" + +Dim btn As Long +Set btn = CreateControl(frm, "CommandButton", "BtnClose") +BtnClose.Caption = "Close" +SetEvent btn, "Click", "OnCloseClick" + +frm.Show + +Sub OnCloseClick + Unload Me +End Sub +.endcode + .h2 DoEvents -Yields control to the DVX event loop, allowing the GUI to process pending events. Call this in long-running loops to keep the UI responsive. +Yields control to DVX so the GUI can process pending events. Call this in long-running loops to keep the UI responsive (otherwise the window will appear frozen). .code DOEVENTS @@ -1207,16 +1658,18 @@ Next .h2 MsgBox -Displays a message box dialog. Can be used as a statement (discards result) or as a function (returns the button clicked). +Displays a message-box dialog. Can be used as a statement (discards the result) or as a function (returns the button clicked). .code -MSGBOX message$ [, flags] -result = MSGBOX(message$ [, flags]) +MSGBOX message$ [, flags [, title$]] +result = MSGBOX(message$ [, flags [, title$]]) .endcode +flags is the sum of a button-style constant and an icon constant. See Predefined Constants for the complete list. + .code MsgBox "Operation complete" -answer = MsgBox("Continue?", vbYesNo + vbQuestion) +answer = MsgBox("Continue?", vbYesNo + vbQuestion, "Confirm") If answer = vbYes Then ' proceed End If @@ -1224,7 +1677,7 @@ End If .h2 InputBox$ -Displays an input dialog and returns the user's text entry. +Displays an input dialog and returns the user's text entry. Returns an empty string if the user cancels. .code result$ = INPUTBOX$(prompt$ [, title$ [, default$]]) @@ -1232,6 +1685,9 @@ result$ = INPUTBOX$(prompt$ [, title$ [, default$]]) .code name$ = InputBox$("Enter your name:", "Name Entry", "") +If name$ <> "" Then + Print "Hello, "; name$ +End If .endcode .h2 Event Handler Convention @@ -1255,23 +1711,27 @@ End Sub .h3 Common Events .table - Event Description - ----- ----------- - Click Control was clicked - DblClick Control was double-clicked - Change Control value/text changed - KeyPress Key was pressed (receives key code) - KeyDown Key went down (receives key code and shift state) - KeyUp Key was released - MouseDown Mouse button pressed - MouseUp Mouse button released - MouseMove Mouse moved over control - GotFocus Control received input focus - LostFocus Control lost input focus - Form_Load Form is being loaded - Form_Unload Form is being unloaded - Form_Resize Form was resized - Timer Timer interval elapsed + Event Description + ----- ----------- + Click Control was clicked + DblClick Control was double-clicked + Change Control value or text changed + KeyPress Printable key was pressed (receives ASCII code) + KeyDown Any key was pressed (receives key code and shift state) + KeyUp Key was released + MouseDown Mouse button pressed (receives button, X, Y) + MouseUp Mouse button released + MouseMove Mouse moved over the control + Scroll Control was scrolled (mouse wheel or scrollbar) + GotFocus Control received input focus + LostFocus Control lost input focus + Form_Load Form is being loaded (fires once, after controls are created) + Form_QueryUnload Form is about to close; set Cancel = 1 to abort + Form_Unload Form is being unloaded + Form_Resize Form was resized + Form_Activate Form gained focus + Form_Deactivate Form lost focus + Timer1_Timer Timer control interval elapsed (Timer control's default event) .endtable .topic lang.sql @@ -1294,16 +1754,16 @@ End Sub .h1 SQL Functions -DVX BASIC includes built-in SQLite database support through a set of SQL functions. All functions use database handles and result set handles (integers) returned by SQLOpen and SQLQuery. +DVX BASIC includes SQLite database support through the sql.bas include library. Add sql.bas to your project to gain access to these functions. All functions use database handles and result-set handles (integers) returned by SQLOpen and SQLQuery. .h2 Opening and Closing Databases .h3 SQLOpen -Opens a SQLite database file and returns a database handle. +Opens a SQLite database file and returns a database handle (> 0) or 0 on failure. The file is created if it does not exist. .code -db = SQLOPEN(path$) +db = SQLOpen(path$) .endcode .code @@ -1315,18 +1775,17 @@ db = SQLOpen(App.Data & "\mydata.db") Closes an open database. .code -SQLCLOSE db +SQLClose db .endcode .h2 Executing SQL .h3 SQLExec -Executes a SQL statement that does not return data (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Can be used as a statement or as a function returning a Boolean success flag. +Executes a SQL statement that does not return data (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Returns True on success. .code -SQLEXEC db, sql$ -ok = SQLEXEC(db, sql$) +ok = SQLExec(db, sql$) .endcode .code @@ -1340,17 +1799,17 @@ ok = SQLExec(db, "DELETE FROM users WHERE id = 5") Returns the number of rows affected by the last INSERT, UPDATE, or DELETE. .code -count = SQLAFFECTED(db) +count = SQLAffected(db) .endcode .h2 Querying Data .h3 SQLQuery -Executes a SELECT query and returns a result set handle. +Executes a SELECT query and returns a result-set cursor. .code -rs = SQLQUERY(db, sql$) +rs = SQLQuery(db, sql$) .endcode .code @@ -1359,35 +1818,42 @@ rs = SQLQuery(db, "SELECT id, name FROM users ORDER BY name") .h3 SQLNext -Advances to the next row in the result set. Can be used as a statement or as a function returning True if a row is available. +Advances the cursor to the next row. Returns True if a row is available, False when there are no more rows. .code -SQLNEXT rs -hasRow = SQLNEXT(rs) +hasRow = SQLNext(rs) .endcode .h3 SQLEof -Returns True if there are no more rows in the result set. +Returns True if the cursor is past the last row. .code -done = SQLEOF(rs) +done = SQLEof(rs) .endcode .h2 Reading Fields -.h3 SQLField$ +.h3 SQLField$ / SQLFieldText$ -Returns a field value as a string. The field can be specified by column index (0-based) or by column name. +Returns a field value as a string. Use SQLField$ to look up by column name; use SQLFieldText$ to look up by 0-based column index. .code -value$ = SQLFIELD$(rs, columnIndex) -value$ = SQLFIELD$(rs, "columnName") +value$ = SQLField$(rs, columnName$) +value$ = SQLFieldText$(rs, columnIndex) .endcode .code -name$ = SQLField$(rs, "name") -first$ = SQLField$(rs, 0) +name$ = SQLField$(rs, "name") +first$ = SQLFieldText$(rs, 0) +.endcode + +.h3 SQLFieldName$ + +Returns the column name for a given 0-based column index. + +.code +colName$ = SQLFieldName$(rs, columnIndex) .endcode .h3 SQLFieldInt @@ -1411,17 +1877,17 @@ value# = SQLFIELDDBL(rs, columnIndex) Returns the number of columns in the result set. .code -count = SQLFIELDCOUNT(rs) +count = SQLFieldCount(rs) .endcode .h2 Result Set Cleanup .h3 SQLFreeResult -Frees a result set. Always call this when done iterating a query. +Frees a result-set cursor. Always call this when finished iterating a query. .code -SQLFREERESULT rs +SQLFreeResult rs .endcode .h2 Error Information @@ -1431,22 +1897,21 @@ SQLFREERESULT rs Returns the last error message for the database. .code -msg$ = SQLERROR$(db) +msg$ = SQLError$(db) .endcode .h2 Complete SQL Example .code -Dim db As Long -Dim rs As Long +Dim db As Integer +Dim rs As Integer db = SQLOpen(App.Data & "\contacts.db") SQLExec db, "CREATE TABLE IF NOT EXISTS contacts (name TEXT, phone TEXT)" SQLExec db, "INSERT INTO contacts VALUES ('Alice', '555-1234')" rs = SQLQuery(db, "SELECT name, phone FROM contacts") -Do While Not SQLEof(rs) - SQLNext rs +Do While SQLNext(rs) Print SQLField$(rs, "name"); Tab(20); SQLField$(rs, "phone") Loop SQLFreeResult rs @@ -1488,14 +1953,14 @@ Print "Running from: " & App.Path .h1 INI Functions -DVX BASIC provides built-in functions for reading and writing standard INI configuration files. +DVX BASIC provides built-in functions for reading and writing standard INI configuration files (the same text format used by DVX system configuration). .h2 IniRead -Reads a value from an INI file. Returns the default value if the key is not found. +Reads a value from an INI file. Returns the default value if the file, section, or key is not found. All four arguments are required. .code -value$ = INIREAD(file$, section$, key$, default$) +value$ = IniRead(file$, section$, key$, default$) .endcode .code @@ -1533,13 +1998,19 @@ IniWrite App.Config & "\app.ini", "Display", "FontSize", Str$(fontSize) .index vbYes .index vbNo .index vbRetry +.index vbNormal +.index vbReadOnly +.index vbHidden +.index vbSystem +.index vbDirectory +.index vbArchive .index True .index False .index Predefined Constants .h1 Predefined Constants -The following constants are predefined by the compiler and available in all programs. +The following constants are predefined by the compiler and available in all programs. No DIM or CONST declaration is required to use them. .h2 MsgBox Button Style Flags @@ -1555,15 +2026,15 @@ The following constants are predefined by the compiler and available in all prog .h2 MsgBox Icon Flags -Add an icon flag to the button style to display an icon in the message box. +Add an icon flag to the button style (with + or OR) to display an icon in the message box. .table Constant Value Description -------- ----- ----------- - vbInformation &H10 Information icon - vbExclamation &H20 Warning icon - vbCritical &H30 Error/critical icon - vbQuestion &H40 Question mark icon + vbInformation &H10 Information icon (i) + vbExclamation &H20 Warning icon (!) + vbCritical &H30 Error/critical icon (X) + vbQuestion &H40 Question icon (?) .endtable .h2 MsgBox Return Values @@ -1586,6 +2057,21 @@ Add an icon flag to the button style to display an icon in the message box. vbModal 1 Show form as modal dialog .endtable +.h2 File Attribute Flags + +Bit values used with GETATTR and SETATTR. + +.table + Constant Value Description + -------- ----- ----------- + vbNormal 0 Normal file (no attributes) + vbReadOnly 1 Read-only + vbHidden 2 Hidden + vbSystem 4 System file + vbDirectory 16 Entry is a directory + vbArchive 32 File has been modified since last backup +.endtable + .h2 Boolean Constants .table @@ -1594,3 +2080,46 @@ Add an icon flag to the button style to display an icon in the message box. True -1 Boolean true False 0 Boolean false .endtable + +.topic lang.runtime +.title Include Libraries +.toc 1 Include Libraries +.index Include Libraries +.index commdlg.bas +.index sql.bas +.index comm.bas +.index resource.bas +.index help.bas +.index DECLARE LIBRARY + +.h1 Include Libraries + +DVX ships a set of BASIC include files (.bas) in the sdk/include/basic directory. Each file is a small library of DECLARE LIBRARY wrappers around native DVX runtime functions. Add an include file to your project (the IDE's Add File command, or editing the .dbp with another tool) and the declared functions become available to your code. + +.table + File Provides + ---- -------- + commdlg.bas File Open/Save dialogs, input dialogs, choice dialogs, save-prompt dialogs + sql.bas SQLite database access (SQLOpen, SQLQuery, SQLNext, SQLField$, etc.) + comm.bas Serial-port I/O (raw and packet) and optional encrypted links + resource.bas Read and write DVX resource blocks (icons, text, binary data) in bundled files + help.bas Compile help source files and open the DVX help viewer +.endtable + +.h2 Using an Include Library + +.code +' With commdlg.bas added to the project +Dim path As String +path = basFileOpen("Open a file", "Text Files (*.txt)|All Files (*.*)") + +If path <> "" Then + Print "Selected: " & path +End If +.endcode + +.note info +DVX BASIC does not have an $INCLUDE directive. Libraries are added to the project so that the compiler sees their DECLARE LIBRARY blocks. +.endnote + +.link lib.basrt See also: BASIC Runtime Library diff --git a/src/apps/kpunch/dvxbasic/runtime/values.c b/src/apps/kpunch/dvxbasic/runtime/values.c index bc61c74..d4e8599 100644 --- a/src/apps/kpunch/dvxbasic/runtime/values.c +++ b/src/apps/kpunch/dvxbasic/runtime/values.c @@ -68,19 +68,11 @@ void basUdtFree(BasUdtT *udt); BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount); BasUdtT *basUdtRef(BasUdtT *udt); void basUdtUnref(BasUdtT *udt); -BasValueT basValBool(bool v); int32_t basValCompare(BasValueT a, BasValueT b); int32_t basValCompareCI(BasValueT a, BasValueT b); -BasValueT basValCopy(BasValueT v); -BasValueT basValDouble(double v); BasStringT *basValFormatString(BasValueT v); -BasValueT basValInteger(int16_t v); bool basValIsTruthy(BasValueT v); -BasValueT basValLong(int32_t v); -BasValueT basValObject(void *obj); uint8_t basValPromoteType(uint8_t a, uint8_t b); -void basValRelease(BasValueT *v); -BasValueT basValSingle(float v); BasValueT basValString(BasStringT *s); BasValueT basValStringFromC(const char *text); BasValueT basValToBool(BasValueT v); @@ -407,16 +399,8 @@ void basUdtUnref(BasUdtT *udt) { // ============================================================ -// Value constructors +// Value constructors (trivial ones moved to values.h as static inline) // ============================================================ -BasValueT basValBool(bool v) { - BasValueT val; - val.type = BAS_TYPE_BOOLEAN; - val.boolVal = v ? -1 : 0; - return val; -} - - int32_t basValCompare(BasValueT a, BasValueT b) { // String comparison if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) { @@ -461,27 +445,6 @@ int32_t basValCompareCI(BasValueT a, BasValueT b) { } -BasValueT basValCopy(BasValueT v) { - if (v.type == BAS_TYPE_STRING && v.strVal) { - basStringRef(v.strVal); - } else if (v.type == BAS_TYPE_ARRAY && v.arrVal) { - basArrayRef(v.arrVal); - } else if (v.type == BAS_TYPE_UDT && v.udtVal) { - basUdtRef(v.udtVal); - } - - return v; -} - - -BasValueT basValDouble(double v) { - BasValueT val; - val.type = BAS_TYPE_DOUBLE; - val.dblVal = v; - return val; -} - - BasStringT *basValFormatString(BasValueT v) { char buf[64]; @@ -515,14 +478,6 @@ BasStringT *basValFormatString(BasValueT v) { } -BasValueT basValInteger(int16_t v) { - BasValueT val; - val.type = BAS_TYPE_INTEGER; - val.intVal = v; - return val; -} - - bool basValIsTruthy(BasValueT v) { switch (v.type) { case BAS_TYPE_INTEGER: @@ -549,22 +504,6 @@ bool basValIsTruthy(BasValueT v) { } -BasValueT basValLong(int32_t v) { - BasValueT val; - val.type = BAS_TYPE_LONG; - val.longVal = v; - return val; -} - - -BasValueT basValObject(void *obj) { - BasValueT val; - val.type = BAS_TYPE_OBJECT; - val.objVal = obj; - return val; -} - - uint8_t basValPromoteType(uint8_t a, uint8_t b) { // String stays string (concat, not arithmetic) if (a == BAS_TYPE_STRING || b == BAS_TYPE_STRING) { @@ -590,28 +529,6 @@ uint8_t basValPromoteType(uint8_t a, uint8_t b) { } -void basValRelease(BasValueT *v) { - if (v->type == BAS_TYPE_STRING) { - basStringUnref(v->strVal); - v->strVal = NULL; - } else if (v->type == BAS_TYPE_ARRAY) { - basArrayUnref(v->arrVal); - v->arrVal = NULL; - } else if (v->type == BAS_TYPE_UDT) { - basUdtUnref(v->udtVal); - v->udtVal = NULL; - } -} - - -BasValueT basValSingle(float v) { - BasValueT val; - val.type = BAS_TYPE_SINGLE; - val.sngVal = v; - return val; -} - - BasValueT basValString(BasStringT *s) { BasValueT val; val.type = BAS_TYPE_STRING; diff --git a/src/apps/kpunch/dvxbasic/runtime/values.h b/src/apps/kpunch/dvxbasic/runtime/values.h index 95aeba6..f300faa 100644 --- a/src/apps/kpunch/dvxbasic/runtime/values.h +++ b/src/apps/kpunch/dvxbasic/runtime/values.h @@ -31,6 +31,8 @@ #ifndef DVXBASIC_VALUES_H #define DVXBASIC_VALUES_H +#include "../compiler/opcodes.h" // BAS_TYPE_* + #include #include #include @@ -157,21 +159,89 @@ struct BasValueTag { }; }; -// Create values -BasValueT basValInteger(int16_t v); -BasValueT basValLong(int32_t v); -BasValueT basValSingle(float v); -BasValueT basValDouble(double v); +// Create values -- trivial constructors inlined so they're fast in vm.c's +// hot path (PUSH_INT16, PUSH_TRUE, etc.). +static inline BasValueT basValInteger(int16_t v) { + BasValueT val; + val.type = BAS_TYPE_INTEGER; + val.intVal = v; + return val; +} + + +static inline BasValueT basValLong(int32_t v) { + BasValueT val; + val.type = BAS_TYPE_LONG; + val.longVal = v; + return val; +} + + +static inline BasValueT basValSingle(float v) { + BasValueT val; + val.type = BAS_TYPE_SINGLE; + val.sngVal = v; + return val; +} + + +static inline BasValueT basValDouble(double v) { + BasValueT val; + val.type = BAS_TYPE_DOUBLE; + val.dblVal = v; + return val; +} + + +static inline BasValueT basValBool(bool v) { + BasValueT val; + val.type = BAS_TYPE_BOOLEAN; + val.boolVal = v ? -1 : 0; + return val; +} + + +static inline BasValueT basValObject(void *obj) { + BasValueT val; + val.type = BAS_TYPE_OBJECT; + val.objVal = obj; + return val; +} + + BasValueT basValString(BasStringT *s); BasValueT basValStringFromC(const char *text); -BasValueT basValBool(bool v); -BasValueT basValObject(void *obj); -// Copy a value (increments string refcount if applicable). -BasValueT basValCopy(BasValueT v); +// Copy a value (increments string/array/udt refcount if applicable). +// Inlined so the hot-path case (integer/float/bool) is a single struct +// return -- no function call, no branch beyond the type test. +static inline BasValueT basValCopy(BasValueT v) { + if (v.type == BAS_TYPE_STRING && v.strVal) { + basStringRef(v.strVal); + } else if (v.type == BAS_TYPE_ARRAY && v.arrVal) { + basArrayRef(v.arrVal); + } else if (v.type == BAS_TYPE_UDT && v.udtVal) { + basUdtRef(v.udtVal); + } -// Release a value (decrements string refcount if applicable). -void basValRelease(BasValueT *v); + return v; +} + + +// Release a value (decrements refcount if applicable). Integer/float/bool +// types are a no-op -- the common case is an immediately-returning branch. +static inline void basValRelease(BasValueT *v) { + if (v->type == BAS_TYPE_STRING) { + basStringUnref(v->strVal); + v->strVal = NULL; + } else if (v->type == BAS_TYPE_ARRAY) { + basArrayUnref(v->arrVal); + v->arrVal = NULL; + } else if (v->type == BAS_TYPE_UDT) { + basUdtUnref(v->udtVal); + v->udtVal = NULL; + } +} // Convert a value to a specific type. Returns the converted value. // The original is NOT released -- caller manages lifetime. diff --git a/src/apps/kpunch/dvxbasic/runtime/vm.c b/src/apps/kpunch/dvxbasic/runtime/vm.c index 847d08e..6492dcf 100644 --- a/src/apps/kpunch/dvxbasic/runtime/vm.c +++ b/src/apps/kpunch/dvxbasic/runtime/vm.c @@ -963,7 +963,7 @@ BasVmResultE basVmStep(BasVmT *vm) { // Get pointer to the loop variable BasValueT *varSlot; - if (scopeTag == 1) { + if (scopeTag == SCOPE_LOCAL) { BasCallFrameT *frame = currentFrame(vm); if (!frame || varIdx >= (uint16_t)frame->localCount) { @@ -972,7 +972,7 @@ BasVmResultE basVmStep(BasVmT *vm) { } varSlot = &frame->locals[varIdx]; - } else if (scopeTag == 2) { + } else if (scopeTag == SCOPE_FORM) { if (!vm->currentFormVars || varIdx >= (uint16_t)vm->currentFormVarCount) { runtimeError(vm, 9, "Invalid form variable index"); return BAS_VM_ERROR; @@ -1164,8 +1164,8 @@ BasVmResultE basVmStep(BasVmT *vm) { case OP_PRINT_SPC: { uint8_t n = readUint8(vm); - char spaces[256]; - int32_t count = n < 255 ? n : 255; + char spaces[BAS_PRINT_SPC_MAX + 1]; + int32_t count = n; memset(spaces, ' ', count); spaces[count] = '\0'; @@ -1190,11 +1190,11 @@ BasVmResultE basVmStep(BasVmT *vm) { count = 0; } - if (count > 255) { - count = 255; + if (count > BAS_PRINT_SPC_MAX) { + count = BAS_PRINT_SPC_MAX; } - char spaces[256]; + char spaces[BAS_PRINT_SPC_MAX + 1]; memset(spaces, ' ', count); spaces[count] = '\0'; @@ -1219,13 +1219,13 @@ BasVmResultE basVmStep(BasVmT *vm) { col = 1; } - if (col > 255) { - col = 255; + if (col > BAS_PRINT_SPC_MAX) { + col = BAS_PRINT_SPC_MAX; } // TAB outputs spaces to reach the specified column // For simplicity, just output (col-1) spaces - char spaces[256]; + char spaces[BAS_PRINT_SPC_MAX + 1]; int32_t count = col - 1; memset(spaces, ' ', count); spaces[count] = '\0'; @@ -1543,12 +1543,12 @@ BasVmResultE basVmStep(BasVmT *vm) { basValRelease(&vg); basValRelease(&vb); - if (r < 0) { r = 0; } - if (r > 255) { r = 255; } - if (g < 0) { g = 0; } - if (g > 255) { g = 255; } - if (b < 0) { b = 0; } - if (b > 255) { b = 255; } + if (r < 0) { r = 0; } + if (r > BAS_RGB_COMPONENT_MAX) { r = BAS_RGB_COMPONENT_MAX; } + if (g < 0) { g = 0; } + if (g > BAS_RGB_COMPONENT_MAX) { g = BAS_RGB_COMPONENT_MAX; } + if (b < 0) { b = 0; } + if (b > BAS_RGB_COMPONENT_MAX) { b = BAS_RGB_COMPONENT_MAX; } if (!push(vm, basValLong((r << 16) | (g << 8) | b))) { return BAS_VM_STACK_OVERFLOW; @@ -1665,8 +1665,8 @@ BasVmResultE basVmStep(BasVmT *vm) { count = 0; } - if (count > 32767) { - count = 32767; + if (count > INT16_MAX) { + count = INT16_MAX; } BasStringT *s = basStringAlloc(count + 1); @@ -3162,8 +3162,9 @@ BasVmResultE basVmStep(BasVmT *vm) { // Pop args to keep stack balanced for (int32_t i = 0; i < argc; i++) { BasValueT tmp; - pop(vm, &tmp); - basValRelease(&tmp); + if (pop(vm, &tmp)) { + basValRelease(&tmp); + } } char msg[256]; @@ -3245,10 +3246,14 @@ BasVmResultE basVmStep(BasVmT *vm) { return BAS_VM_BREAKPOINT; } - // Breakpoint check (linear scan, typically < 20 entries) - for (int32_t i = 0; i < vm->breakpointCount; i++) { - if (vm->breakpoints[i] == (int32_t)lineNum) { - return BAS_VM_BREAKPOINT; + // Breakpoint check (linear scan, typically < 20 entries). + // Gated on non-zero count so release/no-breakpoint programs pay + // no per-line overhead at all. + if (vm->breakpointCount > 0) { + for (int32_t i = 0; i < vm->breakpointCount; i++) { + if (vm->breakpoints[i] == (int32_t)lineNum) { + return BAS_VM_BREAKPOINT; + } } } @@ -3556,6 +3561,68 @@ static BasVmResultE execArith(BasVmT *vm, uint8_t op) { return BAS_VM_OK; } + // Integer-integer fast path -- avoids double conversion and FPU for + // tight integer loops. Only promotes on overflow/division range. + if (op != OP_POW + && op != OP_DIV_FLT + && (a.type == BAS_TYPE_INTEGER || a.type == BAS_TYPE_LONG) + && (b.type == BAS_TYPE_INTEGER || b.type == BAS_TYPE_LONG)) { + int32_t ia = (a.type == BAS_TYPE_INTEGER) ? (int32_t)a.intVal : a.longVal; + int32_t ib = (b.type == BAS_TYPE_INTEGER) ? (int32_t)b.intVal : b.longVal; + int32_t ir = 0; + bool handled = true; + + switch (op) { + case OP_ADD_INT: + case OP_ADD_FLT: { + int64_t t = (int64_t)ia + (int64_t)ib; + if (t < INT32_MIN || t > INT32_MAX) { handled = false; break; } + ir = (int32_t)t; + break; + } + case OP_SUB_INT: + case OP_SUB_FLT: { + int64_t t = (int64_t)ia - (int64_t)ib; + if (t < INT32_MIN || t > INT32_MAX) { handled = false; break; } + ir = (int32_t)t; + break; + } + case OP_MUL_INT: + case OP_MUL_FLT: { + int64_t t = (int64_t)ia * (int64_t)ib; + if (t < INT32_MIN || t > INT32_MAX) { handled = false; break; } + ir = (int32_t)t; + break; + } + case OP_IDIV_INT: + if (ib == 0) { + runtimeError(vm, 11, "Division by zero"); + return BAS_VM_DIV_BY_ZERO; + } + ir = ia / ib; + break; + case OP_MOD_INT: + if (ib == 0) { + runtimeError(vm, 11, "Division by zero"); + return BAS_VM_DIV_BY_ZERO; + } + ir = ia % ib; + break; + default: + handled = false; + break; + } + + if (handled) { + if (ir >= INT16_MIN && ir <= INT16_MAX) { + push(vm, basValInteger((int16_t)ir)); + } else { + push(vm, basValLong(ir)); + } + return BAS_VM_OK; + } + } + double na = basValToNumber(a); double nb = basValToNumber(b); basValRelease(&a); @@ -3617,9 +3684,9 @@ static BasVmResultE execArith(BasVmT *vm, uint8_t op) { // Return appropriate type if (op == OP_ADD_INT || op == OP_SUB_INT || op == OP_MUL_INT || op == OP_IDIV_INT || op == OP_MOD_INT) { - if (result >= -32768.0 && result <= 32767.0) { + if (result >= (double)INT16_MIN && result <= (double)INT16_MAX) { push(vm, basValInteger((int16_t)result)); - } else if (result >= -2147483648.0 && result <= 2147483647.0) { + } else if (result >= (double)INT32_MIN && result <= (double)INT32_MAX) { push(vm, basValLong((int32_t)result)); } else { push(vm, basValDouble(result)); @@ -3640,7 +3707,17 @@ static BasVmResultE execCompare(BasVmT *vm, uint8_t op) { return BAS_VM_STACK_UNDERFLOW; } - int32_t cmp = vm->compareTextMode ? basValCompareCI(a, b) : basValCompare(a, b); + int32_t cmp; + + // Same-type integer fast path: loop conditions like i < n hit this on + // every iteration. Skip basValCompare's mixed-type handling. + if (a.type == BAS_TYPE_INTEGER && b.type == BAS_TYPE_INTEGER) { + cmp = (a.intVal < b.intVal) ? -1 : (a.intVal > b.intVal ? 1 : 0); + } else if (a.type == BAS_TYPE_LONG && b.type == BAS_TYPE_LONG) { + cmp = (a.longVal < b.longVal) ? -1 : (a.longVal > b.longVal ? 1 : 0); + } else { + cmp = vm->compareTextMode ? basValCompareCI(a, b) : basValCompare(a, b); + } basValRelease(&a); basValRelease(&b); @@ -3661,13 +3738,6 @@ static BasVmResultE execCompare(BasVmT *vm, uint8_t op) { } -// File mode constants (matches compiler/parser.c emission) -#define FILE_MODE_INPUT 1 -#define FILE_MODE_OUTPUT 2 -#define FILE_MODE_APPEND 3 -#define FILE_MODE_RANDOM 4 -#define FILE_MODE_BINARY 5 - static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) { switch (op) { case OP_FILE_OPEN: { @@ -3695,23 +3765,23 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) { if (vm->files[channel].handle) { fclose((FILE *)vm->files[channel].handle); vm->files[channel].handle = NULL; - vm->files[channel].mode = 0; + vm->files[channel].mode = BAS_FILE_MODE_CLOSED; } const char *modeStr; switch (mode) { - case FILE_MODE_INPUT: + case BAS_FILE_MODE_INPUT: modeStr = "rb"; break; - case FILE_MODE_OUTPUT: + case BAS_FILE_MODE_OUTPUT: modeStr = "wb"; break; - case FILE_MODE_APPEND: + case BAS_FILE_MODE_APPEND: modeStr = "ab"; break; - case FILE_MODE_RANDOM: - case FILE_MODE_BINARY: + case BAS_FILE_MODE_RANDOM: + case BAS_FILE_MODE_BINARY: modeStr = "r+b"; break; default: @@ -3721,7 +3791,7 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) { } // For RANDOM/BINARY: create file if it doesn't exist, then reopen r+b - if (mode == FILE_MODE_RANDOM || mode == FILE_MODE_BINARY) { + if (mode == BAS_FILE_MODE_RANDOM || mode == BAS_FILE_MODE_BINARY) { FILE *test = fopen(fnStr.strVal->data, "r"); if (!test) { // Create the file @@ -3765,7 +3835,7 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) { if (vm->files[channel].handle) { fclose((FILE *)vm->files[channel].handle); vm->files[channel].handle = NULL; - vm->files[channel].mode = 0; + vm->files[channel].mode = BAS_FILE_MODE_CLOSED; } break; @@ -4268,8 +4338,8 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) { n = 0; } - if (n > 32767) { - n = 32767; + if (n > INT16_MAX) { + n = INT16_MAX; } char *buf = (char *)malloc(n + 1); @@ -5123,8 +5193,8 @@ static BasVmResultE execStringOp(BasVmT *vm, uint8_t op) { n = 0; } - if (n > 32767) { - n = 32767; + if (n > INT16_MAX) { + n = INT16_MAX; } BasStringT *s = basStringAlloc(n + 1); @@ -5263,23 +5333,23 @@ static bool push(BasVmT *vm, BasValueT val) { } -static int16_t readInt16(BasVmT *vm) { - int16_t val; - memcpy(&val, &vm->module->code[vm->pc], sizeof(int16_t)); +// x86 tolerates unaligned loads at cost of ~1 cycle; cast-through-pointer is +// faster than memcpy because the compiler can emit a single 16-bit load. +static inline int16_t readInt16(BasVmT *vm) { + int16_t val = *(const int16_t *)&vm->module->code[vm->pc]; vm->pc += sizeof(int16_t); return val; } -static uint16_t readUint16(BasVmT *vm) { - uint16_t val; - memcpy(&val, &vm->module->code[vm->pc], sizeof(uint16_t)); +static inline uint16_t readUint16(BasVmT *vm) { + uint16_t val = *(const uint16_t *)&vm->module->code[vm->pc]; vm->pc += sizeof(uint16_t); return val; } -static uint8_t readUint8(BasVmT *vm) { +static inline uint8_t readUint8(BasVmT *vm) { return vm->module->code[vm->pc++]; } diff --git a/src/apps/kpunch/dvxbasic/runtime/vm.h b/src/apps/kpunch/dvxbasic/runtime/vm.h index 8533a9a..3d1bc18 100644 --- a/src/apps/kpunch/dvxbasic/runtime/vm.h +++ b/src/apps/kpunch/dvxbasic/runtime/vm.h @@ -55,6 +55,10 @@ #define BAS_VM_MAX_FILES 16 // open file channels #define BAS_VM_DEFAULT_STEP_SLICE 10000 // bytecode steps per yield +#define BAS_RGB_COMPONENT_MAX 255 // RGB(r,g,b): max per-channel value +#define BAS_PRINT_SPC_MAX 255 // PRINT SPC()/TAB(): max column count +#define BAS_VM_ERROR_MSG_LEN 256 // runtime error description buffer + // ============================================================ // Result codes // ============================================================ @@ -267,7 +271,7 @@ typedef struct { typedef struct { int32_t varIdx; // loop variable slot index - uint8_t scopeTag; // 0 = global, 1 = local, 2 = form + uint8_t scopeTag; // BasScopeE value BasValueT limit; // upper bound BasValueT step; // step value int32_t loopTop; // PC of the loop body start @@ -279,7 +283,7 @@ typedef struct { typedef struct { void *handle; // FILE* or platform-specific - int32_t mode; // 0=closed, 1=input, 2=output, 3=append, 4=random, 5=binary + int32_t mode; // BasFileModeE value } BasFileChannelT; // ============================================================ @@ -411,7 +415,7 @@ typedef struct { int32_t errorPc; // PC of the instruction that caused the error (for RESUME) int32_t errorNextPc; // PC of the next instruction after error (for RESUME NEXT) bool inErrorHandler; // true when executing error handler code - char errorMsg[256]; // current error description + char errorMsg[BAS_VM_ERROR_MSG_LEN]; // current error description // I/O callbacks BasPrintFnT printFn; diff --git a/src/apps/kpunch/dvxdemo/dvxdemo.c b/src/apps/kpunch/dvxdemo/dvxdemo.c index e9c6dab..c47e65b 100644 --- a/src/apps/kpunch/dvxdemo/dvxdemo.c +++ b/src/apps/kpunch/dvxdemo/dvxdemo.c @@ -384,9 +384,8 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) { static const char *lines[] = { "DVX GUI Compositor", "", - "A DOS Visual eXecutive windowed GUI", - "compositor for DOS, targeting", - "DJGPP/DPMI.", + "A DOS Visual eXecutive windowed", + "GUI compositor for DOS.", "", "Features:", " - VESA VBE 2.0+ LFB", @@ -508,7 +507,7 @@ static void setupControlsWindow(void) { wgtLabel(page1, "&Progress:"); WidgetT *pbRow = wgtHBox(page1); WidgetT *pb = wgtProgressBar(pbRow); - pb->weight = 100; + pb->weight = WGT_WEIGHT_FILL; wgtProgressBarSetValue(pb, 65); wgtSetTooltip(pb, "Task progress: 65%"); WidgetT *pbV = wgtProgressBarV(pbRow); @@ -577,13 +576,13 @@ static void setupControlsWindow(void) { wgtListViewSetSelected(lv, 0); wgtListViewSetMultiSelect(lv, true); wgtListViewSetReorderable(lv, true); - lv->weight = 100; + lv->weight = WGT_WEIGHT_FILL; // --- Tab 4: ScrollPane --- WidgetT *page4sp = wgtTabPage(tabs, "&Scroll"); WidgetT *sp = wgtScrollPane(page4sp); - sp->weight = 100; + sp->weight = WGT_WEIGHT_FILL; sp->padding = wgtPixels(4); sp->spacing = wgtPixels(4); @@ -671,7 +670,7 @@ static void setupControlsWindow(void) { wgtLabel(page7e, "TextArea:"); WidgetT *ta = wgtTextArea(page7e, 512); - ta->weight = 100; + ta->weight = WGT_WEIGHT_FILL; wgtSetText(ta, "Multi-line text editor.\n\nFeatures:\n- Word wrap\n- Selection\n- Copy/Paste\n- Undo (Ctrl+Z)"); wgtHSeparator(page7e); @@ -695,7 +694,7 @@ static void setupControlsWindow(void) { WidgetT *page8s = wgtTabPage(tabs, "S&plit"); WidgetT *hSplit = wgtSplitter(page8s, false); - hSplit->weight = 100; + hSplit->weight = WGT_WEIGHT_FILL; wgtSplitterSetPos(hSplit, 120); // Top pane: vertical splitter (tree | list) @@ -735,11 +734,11 @@ static void setupControlsWindow(void) { // Tests that wgtSetEnabled(w, false) correctly greys out each widget type. WidgetT *page9d = wgtTabPage(tabs, "&Disabled"); WidgetT *disRow = wgtHBox(page9d); - disRow->weight = 100; + disRow->weight = WGT_WEIGHT_FILL; // Enabled column WidgetT *enCol = wgtVBox(disRow); - enCol->weight = 100; + enCol->weight = WGT_WEIGHT_FILL; wgtLabel(enCol, "Enabled:"); wgtHSeparator(enCol); wgtLabel(enCol, "A &label"); @@ -773,7 +772,7 @@ static void setupControlsWindow(void) { // Disabled column WidgetT *disCol = wgtVBox(disRow); - disCol->weight = 100; + disCol->weight = WGT_WEIGHT_FILL; wgtLabel(disCol, "Disabled:"); wgtHSeparator(disCol); @@ -818,7 +817,7 @@ static void setupControlsWindow(void) { // Status bar at bottom (outside tabs) WidgetT *sb = wgtStatusBar(root); WidgetT *sbLabel = wgtLabel(sb, "Ready"); - sbLabel->weight = 100; + sbLabel->weight = WGT_WEIGHT_FILL; wgtSetName(sbLabel, "advStatus"); wgtLabel(sb, "Line 1, Col 1"); } @@ -977,7 +976,7 @@ static void setupTerminalWindow(void) { WidgetT *root = wgtInitWindow(sAc, win); WidgetT *term = wgtAnsiTerm(root, 80, 25); - term->weight = 100; + term->weight = WGT_WEIGHT_FILL; wgtAnsiTermSetScrollback(term, 500); // Feed some ANSI content to demonstrate the terminal @@ -1100,7 +1099,7 @@ static void setupWidgetDemo(void) { WidgetT *lb = wgtListBox(listRow); wgtListBoxSetItems(lb, listItems, 5); wgtListBoxSetReorderable(lb, true); - lb->weight = 100; + lb->weight = WGT_WEIGHT_FILL; // Context menu on the list box MenuT *lbCtx = wmCreateMenu(); @@ -1116,7 +1115,7 @@ static void setupWidgetDemo(void) { WidgetT *mlb = wgtListBox(listRow); wgtListBoxSetMultiSelect(mlb, true); wgtListBoxSetItems(mlb, multiItems, 7); - mlb->weight = 100; + mlb->weight = WGT_WEIGHT_FILL; wgtHSeparator(root); diff --git a/src/apps/kpunch/dvxhelp/dvxhelp.c b/src/apps/kpunch/dvxhelp/dvxhelp.c index fcf123d..282bc16 100644 --- a/src/apps/kpunch/dvxhelp/dvxhelp.c +++ b/src/apps/kpunch/dvxhelp/dvxhelp.c @@ -1570,7 +1570,7 @@ int32_t appMain(DxeAppContextT *ctx) { // Splitter with TOC tree and content scroll pane WidgetT *splitter = wgtSplitter(root, true); - splitter->weight = 100; + splitter->weight = WGT_WEIGHT_FILL; sTocTree = wgtTreeView(splitter); sTocTree->weight = 0; @@ -1578,7 +1578,7 @@ int32_t appMain(DxeAppContextT *ctx) { sTocTree->onChange = onTocChange; sContentScroll = wgtScrollPane(splitter); - sContentScroll->weight = 100; + sContentScroll->weight = WGT_WEIGHT_FILL; sContentBox = wgtVBox(sContentScroll); sContentBox->spacing = wgtPixels(HELP_PARA_SPACING); diff --git a/src/apps/kpunch/dvxhelp/sample.dhs b/src/apps/kpunch/dvxhelp/sample.dhs index 58c364c..1dbdcdc 100644 --- a/src/apps/kpunch/dvxhelp/sample.dhs +++ b/src/apps/kpunch/dvxhelp/sample.dhs @@ -40,7 +40,7 @@ DVX features include: .list .item A window manager with drag, resize, and minimize support .item Over 25 built-in widget types including buttons, lists, and trees -.item Dynamic application loading via DXE3 modules +.item Dynamic application loading at runtime .item Theme support with customizable color schemes .item VESA VBE 2.0+ linear framebuffer rendering .endlist @@ -106,7 +106,7 @@ and the public API used by applications. .h1 Widget System DVX provides a rich set of widgets (controls) for building -application user interfaces. Widgets are loaded as DXE3 plugin +application user interfaces. Widgets are loaded as plugin modules at startup. .h2 Layout Widgets diff --git a/src/apps/kpunch/progman/progman.c b/src/apps/kpunch/progman/progman.c index a6ba8ef..2944712 100644 --- a/src/apps/kpunch/progman/progman.c +++ b/src/apps/kpunch/progman/progman.c @@ -220,7 +220,7 @@ static void buildPmWindow(void) { // App button grid in a labeled frame. weight=100 tells the layout engine // this frame should consume all available vertical space (flex weight). WidgetT *appFrame = wgtFrame(root, "Applications"); - appFrame->weight = 100; + appFrame->weight = WGT_WEIGHT_FILL; if (sAppCount == 0) { wgtLabel(appFrame, "(No applications found in apps/ directory)"); @@ -229,11 +229,11 @@ static void buildPmWindow(void) { // WrapBox inside flows cells left-to-right, wrapping to // the next row when the window width is exceeded. WidgetT *scroll = wgtScrollPane(appFrame); - scroll->weight = 100; + scroll->weight = WGT_WEIGHT_FILL; wgtScrollPaneSetNoBorder(scroll, true); WidgetT *wrap = wgtWrapBox(scroll); - wrap->weight = 100; + wrap->weight = WGT_WEIGHT_FILL; wrap->spacing = wgtPixels(PM_GRID_SPACING); wrap->align = AlignCenterE; @@ -280,7 +280,7 @@ static void buildPmWindow(void) { // width so text can be left-aligned naturally. WidgetT *statusBar = wgtStatusBar(root); sStatusLabel = wgtLabel(statusBar, ""); - sStatusLabel->weight = 100; + sStatusLabel->weight = WGT_WEIGHT_FILL; updateStatusText(); } @@ -563,7 +563,7 @@ static void showSystemInfo(void) { WidgetT *root = wgtInitWindow(sAc, win); WidgetT *ta = wgtTextArea(root, 4096); - ta->weight = 100; + ta->weight = WGT_WEIGHT_FILL; wgtSetText(ta, info); // Don't disable -- wgtSetEnabled(false) blocks all input including scrollbar diff --git a/src/apps/kpunch/resedit/resedit.frm b/src/apps/kpunch/resedit/resedit.frm index 5f4f9fb..fc5431f 100644 --- a/src/apps/kpunch/resedit/resedit.frm +++ b/src/apps/kpunch/resedit/resedit.frm @@ -1,7 +1,7 @@ VERSION DVX 1.00 ' resedit.frm -- DVX Resource Editor ' -' Graphical editor for the resource blocks appended to DXE3 +' Graphical editor for the resource blocks appended to binary ' files (.app, .wgt, .lib). View, add, remove, and extract ' resources of type icon, text, or binary. ' diff --git a/src/include/basic/resource.bas b/src/include/basic/resource.bas index 84ba96c..d6cbdef 100644 --- a/src/include/basic/resource.bas +++ b/src/include/basic/resource.bas @@ -22,7 +22,7 @@ ' resource.bas -- DVX Resource File Library for DVX BASIC ' -' Provides access to DVX resource blocks appended to DXE3 +' Provides access to DVX resource blocks appended to binary ' files (.app, .wgt, .lib). Resources are named data entries ' of type icon, text, or binary. ' diff --git a/src/libs/kpunch/dvxshell/README.md b/src/libs/kpunch/dvxshell/README.md index f8684f4..caf0051 100644 --- a/src/libs/kpunch/dvxshell/README.md +++ b/src/libs/kpunch/dvxshell/README.md @@ -1,180 +1,28 @@ -# DVX Shell (dvxshell.lib) +# dvxshell -- DVX Shell Library -The DVX Shell is a DXE3 module loaded by the DVX loader at startup. -It initializes the GUI subsystem, loads DXE3 application modules on -demand, runs the cooperative main loop, and provides crash recovery so -a faulting app does not bring down the entire system. +The DVX shell drives the GUI after the loader hands off. It initializes +the GUI subsystem, loads application modules on demand, runs the +cooperative main loop, and provides crash recovery so one app's fault +does not bring down the whole system. +## Documentation -## Entry Point +Full reference is in `dvxshell.dhs` (this directory), compiled into the +DVX System Reference. Open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -The loader finds and calls `shellMain()` after all libs and widgets -are loaded. `shellMain()`: +Topics covered: `AppDescriptorT` / `DxeAppContextT` contracts, how apps +register (`appMain`, `appDescriptor`, `appShutdown`), callback-only vs +main-loop apps, shell event callbacks, and force-kill behavior. -1. Loads preferences from `CONFIG/DVX.INI` -2. Initializes the GUI via `dvxInit()` with configured video mode -3. Applies saved mouse, color, and wallpaper settings from INI -4. Shows a splash screen ("DVX - DOS Visual eXecutive / Loading...") -5. Initializes the cooperative task system (`tsInit()`) -6. Sets shell task (task 0) to `TS_PRIORITY_HIGH` -7. Gathers system information via the platform layer -8. Initializes the app slot table -9. Points the memory tracker at `currentAppId` for per-app attribution -10. Registers idle callback, Ctrl+Esc handler, and title change handler -11. Installs the crash handler (before loading apps, so init crashes are caught) -12. Loads the desktop app (default: `apps/progman/progman.app`) -13. Dismisses the splash screen -14. Enters the main loop - - -## Main Loop - -Each iteration of the main loop does four things: - -1. `dvxUpdate()` -- process input events, dispatch callbacks, composite - dirty rects, flush to LFB -2. `tsYield()` -- give CPU time to app tasks (if any are active) -3. `shellReapApps()` -- clean up any apps that terminated this frame -4. `shellDesktopUpdate()` -- notify desktop app if apps were reaped - -An idle callback (`idleYield`) is also registered so that `dvxUpdate()` -yields to app tasks during quiet frames. - - -## App Lifecycle - -### DXE App Contract - -Every DXE app exports two symbols: - -* `appDescriptor` (`AppDescriptorT`) -- metadata: - - `name` -- display name (max 64 chars) - - `hasMainLoop` -- true for main-loop apps, false for callback-only - - `multiInstance` -- true to allow multiple instances via temp copy - - `stackSize` -- `SHELL_STACK_DEFAULT` (32KB) or explicit byte count - - `priority` -- `TS_PRIORITY_NORMAL` or custom - -* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point - -Optional export: `appShutdown` (`void appShutdown(void)`) -- called -during graceful shutdown. - -### Callback-Only Apps (hasMainLoop = false) - -`appMain()` is called directly in the shell's task 0. It creates -windows, registers callbacks, and returns immediately. The app lives -entirely through GUI callbacks. The shell reaps callback-only apps -automatically when their last window closes -- `shellReapApps()` checks -each frame for running callback apps with zero remaining windows. - -### Main-Loop Apps (hasMainLoop = true) - -A dedicated cooperative task is created. `appMain()` runs in that task -and can do its own polling/processing loop, calling `tsYield()` to -share CPU. Lifecycle ends when `appMain()` returns or the task is -killed. - -### App States - -``` -Free -> Loaded -> Running -> Terminating -> Free -``` - -| State | Description | -|-------|-------------| -| `AppStateFreeE` | Slot available for reuse | -| `AppStateLoadedE` | DXE loaded, not yet started (transient) | -| `AppStateRunningE` | Entry point called, active | -| `AppStateTerminatingE` | Shutdown in progress, awaiting reap | - -### App Slots - -App slots are managed as a stb_ds dynamic array (no fixed max). Each -slot tracks: app ID, name, path, DXE handle, state, task ID, entry/ -shutdown function pointers, and a pointer to the `DxeAppContextT` -passed to the app. - -`DxeAppContextT` is heap-allocated (via `calloc`) so its address is -stable across `sApps` array reallocs -- apps save this pointer in their -static globals and it must not move. The shell frees it during reap. - -The `DxeAppContextT` gives each app: -- `shellCtx` -- pointer to the shell's `AppContextT` -- `appId` -- this app's unique ID -- `appDir` -- directory containing the `.app` file (for resources) -- `configDir` -- writable config directory (`CONFIG//`) - -### App ID Tracking - -`ctx->currentAppId` on AppContextT tracks which app is currently -executing. The shell sets this before calling app code. -`dvxCreateWindow()` stamps `win->appId` directly so the shell can -associate windows with apps for cleanup. - -For main-loop apps, `appTaskWrapper` receives the app ID (as an int -cast to `void *`), not a direct pointer to `ShellAppT`. This is because -the `sApps` dynamic array may reallocate between `tsCreate` and the -first time the task runs, which would invalidate a direct pointer. - -The shell calls `dvxSetBusy()` before `dlopen` to show the hourglass -cursor during app loading, and clears it after `appMain` returns (for -callback apps) or after task creation (for main-loop apps). - - -## Crash Recovery - -The platform layer installs signal handlers for SIGSEGV, SIGFPE, and -SIGILL via `platformInstallCrashHandler()`. If a crash occurs: - -1. Platform handler logs signal name and register dump (DJGPP) -2. Handler `longjmp`s to the `setjmp` point in `shellMain()` -3. `tsRecoverToMain()` fixes the scheduler's bookkeeping -4. Shell logs app-specific info (name, path, task ID) -5. Crashed app is force-killed (`shellForceKillApp()`) -6. Error dialog is shown to the user -7. Desktop is notified to refresh -8. Main loop continues normally - -This gives Windows 3.1-style fault tolerance -- one bad app does not -take down the whole system. - - -## Task Manager Integration - -The Task Manager is a separate DXE (`taskmgr.lib` in `taskmgr/`), not -built into the shell. It registers itself at load time via a DXE -constructor that sets the `shellCtrlEscFn` function pointer. The shell -calls this pointer on Ctrl+Esc. If `taskmgr.lib` is not loaded, -`shellCtrlEscFn` is NULL and Ctrl+Esc does nothing. - -See `taskmgr/README.md` for full Task Manager documentation. - - -## Desktop Update Notifications - -Apps (especially the desktop app) register callbacks via -`shellRegisterDesktopUpdate()` to be notified when app state changes -(load, reap, crash, title change). Multiple callbacks are supported. - - -## Files - -| File | Description | -|------|-------------| -| `shellMain.c` | Entry point, main loop, crash recovery, splash screen, idle callback | -| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE`; `shellCtrlEscFn` extern | -| `shellApp.c` | App loading, reaping, task creation, DXE management, per-app memory tracking | -| `shellInfo.h` | System information wrapper | -| `shellInfo.c` | Gathers and caches hardware info via platform layer | -| `Makefile` | Builds `bin/libs/dvxshell.lib` + config/themes/wallpapers | +## Source Files +- `shellApp.h` -- application contract (AppDescriptorT, DxeAppContextT) +- `shellInf.h` -- shell-internal interfaces exposed to apps +- `shellApp.c` -- app registry, load, unload, lifecycle +- `shellInfo.c` -- system info service +- `shellMain.c` -- shell entry point, main loop, crash handler ## Build -``` -make # builds dvxshell.lib + dvxshell.dep + config files -make clean # removes objects, library, and config output -``` - -Depends on: `libtasks.lib`, `libdvx.lib`, `texthelp.lib`, `listhelp.lib` -(via dvxshell.dep). +`make -C src/libs/kpunch/dvxshell` (invoked by the top-level `make`). diff --git a/src/libs/kpunch/dvxshell/dvxshell.dhs b/src/libs/kpunch/dvxshell/dvxshell.dhs index c67aa8f..c7cb135 100644 --- a/src/libs/kpunch/dvxshell/dvxshell.dhs +++ b/src/libs/kpunch/dvxshell/dvxshell.dhs @@ -33,7 +33,9 @@ The DVX shell library manages the lifecycle of DXE applications: loading, launching, tracking, and reaping. It provides the bridge between the DVX GUI compositor and dynamically loaded DXE3 application modules. -Header: shell/shellApp.h +Header: dvxshell/shellApp.h + +Loaded as: bin/libs/dvxshell.lib .h2 App Model @@ -58,6 +60,30 @@ Every .app DXE module must export these symbols (COFF convention uses leading un _appShutdown void (*)(void) No .endtable +The shell resolves appDescriptor at dlopen time; if missing, the load fails with an error dialog. appMain is called immediately for callback-only apps or from the new task for main-loop apps. appShutdown, if present, is called during graceful reap (but not during force kill from crashed apps). + +.h2 Callback-Only vs Main-Loop Apps + +The app descriptor's hasMainLoop flag selects between two very different lifecycles: + +.list +.item hasMainLoop = false (callback-only). The shell calls appMain directly on task 0 at dlopen time. The function creates windows, registers event callbacks, and returns. After that the app has no executing thread of its own -- it exists purely through GUI callbacks dispatched by dvxUpdate. The shell reaps the app automatically when its last window closes. Best for modal tools, dialogs, and event-driven utilities. +.item hasMainLoop = true (main-loop). The shell creates a dedicated cooperative task (via tsCreate) with the descriptor's stackSize (or TS_DEFAULT_STACK_SIZE) and priority. appMain runs in that task and can do its own work loop, calling tsYield or any GUI function that yields. The app terminates when appMain returns (the wrapper sets AppStateTerminatingE) or when forced via shellForceKillApp. Best for terminal emulators, games, and any app with continuous background work. +.endlist + +Both app types use the same export interface; only the descriptor's flags differ. Apps cannot switch modes at runtime. + +.h2 Icon Conventions + +Shell-level UI (Program Manager, Task Manager) displays app icons at 16x16 and 32x32. Icons are not handled by the shell itself; each app embeds its own icons via the DVX resource system (DVX_RES_ICON): + +.list +.item 16x16 BMP for toolbar entries and list rows +.item 32x32 BMP for desktop shortcuts and Program Manager tiles +.endlist + +The Program Manager reads the app's 32x32 icon resource when building shortcut tiles. Apps without an icon resource fall back to a default shell-provided bitmap. + .h2 State Machine App slots progress through four states: @@ -116,22 +142,24 @@ Exported by every DXE app as a global named appDescriptor. The shell reads it at .h2 DxeAppContextT -Passed as the sole argument to appMain(). Gives the app access to the shell's GUI context and its own identity. +Passed as the sole argument to appMain(). Gives the app access to the shell's GUI context and its own identity. Heap-allocated by shellLoadApp so its address is stable across reallocations of the internal app slot table (apps may safely cache the pointer in their static globals). .table - Field Type Description - ----- ---- ----------- - shellCtx AppContextT * The shell's GUI context (for creating windows, drawing, etc.). - appId int32_t This app's unique ID (slot index, 1-based). - appPath[DVX_MAX_PATH] char[] Full path to the .app DXE file. - appDir[DVX_MAX_PATH] char[] Directory containing the .app file (for loading resources). - configDir[DVX_MAX_PATH] char[] Writable config directory (e.g. CONFIG/APPS/KPUNCH/DVXBASIC/). - args[1024] char[] Launch arguments (empty string if none). - helpFile[DVX_MAX_PATH] char[] Help file path (for F1 context help). - helpTopic[128] char[] Current help topic ID (updated by the app at runtime). + Field Type Description + ----- ---- ----------- + shellCtx AppContextT * The shell's GUI context (for creating windows, drawing, etc.). + appId int32_t This app's unique ID (slot index, 1-based). + appPath[DVX_MAX_PATH] char[] Full path to the .app DXE file. + appDir[DVX_MAX_PATH] char[] Directory containing the .app file (for loading resources). + configDir[DVX_MAX_PATH] char[] Writable config directory (e.g. CONFIG/APPS/KPUNCH/DVXBASIC/). + args[1024] char[] Launch arguments (empty string if none). + helpFile[DVX_MAX_PATH] char[] Help file path (for F1 context help). + helpTopic[128] char[] Current help topic ID (updated by the app at runtime). + onHelpQuery void (*)(void *ctx) Optional callback the shell fires on F1 so the app can refresh helpTopic from context. + helpQueryCtx void * Opaque context pointer passed to onHelpQuery. .endtable -The appDir field is derived from the .app file path at load time so apps can find their own resources via relative paths. This is necessary because the working directory is shared by all apps in DOS. +The appDir field is derived from the .app file path at load time so apps can find their own resources via relative paths. This is necessary because the working directory is shared by all apps in DOS. The config directory is mirrored from the app path: an app at APPS/KPUNCH/DVXBASIC/dvxbasic.app gets CONFIG/APPS/KPUNCH/DVXBASIC/. .h2 AppStateE @@ -229,9 +257,9 @@ Gracefully shut down a single app. Calls the app's shutdownFn (if present), dest void shellForceKillApp(AppContextT *ctx, ShellAppT *app); .endcode -Forcibly kill an app without calling its shutdown hook. Used by the Task Manager "End Task" or when an app has crashed and cannot be trusted to run cleanup code. +Forcibly kill an app. Used by the Task Manager "End Task" or as part of crash recovery. Calls the app's shutdownFn (if present) so it can unregister callbacks before its DXE is unmapped, then destroys all of the app's windows, kills its task (for main-loop apps), and finally closes the DXE handle. -Cleanup order: windows first (removes from compositor), then task (frees stack), then DXE handle (unmaps code). Closing the DXE before destroying windows would cause callbacks into unmapped code. +Cleanup order: shutdownFn first (so the app can unregister callbacks while still mapped), then windows (removes from compositor), then task (frees stack), then DXE handle (unmaps code). Closing the DXE before destroying windows would cause callbacks into unmapped code. .h2 shellTerminateAllApps @@ -319,7 +347,18 @@ shellConfigPath(ctx, "settings.ini", path, sizeof(path)); .h1 Desktop Callbacks -The shell provides a notification mechanism for app state changes (load, reap, crash). Desktop managers register a callback to refresh their display when apps come and go. +The shell wires several AppContextT event hooks (onCtrlEsc, onF1, onTitleChange, idleCallback) during startup and exposes them to other DXEs via function-pointer extern and registration API. Desktop managers register a callback to refresh their display when apps come and go. The Task Manager DXE registers its Ctrl+Esc handler via the shellCtrlEscFn extern pointer (see Task Manager documentation). + +.h2 Shell Event Hooks + +.table + AppContextT field Handler Purpose + ----------------- ------- ------- + idleCallback idleYield Yields to cooperative tasks when dvxUpdate has no input events and no dirty rects. + onCtrlEsc ctrlEscHandler Invokes shellCtrlEscFn if the Task Manager DXE is loaded. + onF1 f1HelpHandler Launches the help viewer, optionally passing the focused app's helpFile and helpTopic. + onTitleChange titleChangeHandler Runs shellDesktopUpdate so Program Manager window lists refresh. +.endtable .h2 shellRegisterDesktopUpdate @@ -361,7 +400,7 @@ Function pointer set by the taskmgr DXE's constructor. The shell calls this when .h1 System Information -Header: shell/shellInfo.h +Header: dvxshell/shellInf.h Thin wrapper around the platform layer's hardware detection. Gathers system information at startup, logs it, and caches the result for display in dialogs. diff --git a/src/libs/kpunch/dvxshell/shellMain.c b/src/libs/kpunch/dvxshell/shellMain.c index eec11e4..f86b853 100644 --- a/src/libs/kpunch/dvxshell/shellMain.c +++ b/src/libs/kpunch/dvxshell/shellMain.c @@ -239,9 +239,9 @@ int shellMain(int argc, char *argv[]) { // Initialize GUI — switch from VGA splash to VESA mode as late as possible // so the VGA loading splash stays visible through all the init above. { - int32_t videoW = prefsGetInt(sPrefs, "video", "width", 640); - int32_t videoH = prefsGetInt(sPrefs, "video", "height", 480); - int32_t videoBpp = prefsGetInt(sPrefs, "video", "bpp", 16); + int32_t videoW = prefsGetInt(sPrefs, "video", "width", DVX_DEFAULT_VIDEO_W); + int32_t videoH = prefsGetInt(sPrefs, "video", "height", DVX_DEFAULT_VIDEO_H); + int32_t videoBpp = prefsGetInt(sPrefs, "video", "bpp", DVX_DEFAULT_VIDEO_BPP); dvxLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp); int32_t result = dvxInit(&sCtx, videoW, videoH, videoBpp); @@ -273,10 +273,10 @@ int shellMain(int argc, char *argv[]) { const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", MOUSE_ACCEL_DEFAULT); int32_t accelVal = 0; - if (strcmp(accelStr, "off") == 0) { accelVal = 10000; } - else if (strcmp(accelStr, "low") == 0) { accelVal = 100; } - else if (strcmp(accelStr, "medium") == 0) { accelVal = 64; } - else if (strcmp(accelStr, "high") == 0) { accelVal = 32; } + if (strcmp(accelStr, "off") == 0) { accelVal = MOUSE_ACCEL_OFF; } + else if (strcmp(accelStr, "low") == 0) { accelVal = MOUSE_ACCEL_LOW; } + else if (strcmp(accelStr, "medium") == 0) { accelVal = MOUSE_ACCEL_MEDIUM; } + else if (strcmp(accelStr, "high") == 0) { accelVal = MOUSE_ACCEL_HIGH; } int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT); int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT); diff --git a/src/libs/kpunch/libdvx/README.md b/src/libs/kpunch/libdvx/README.md index 5702071..bbc99c7 100644 --- a/src/libs/kpunch/libdvx/README.md +++ b/src/libs/kpunch/libdvx/README.md @@ -1,449 +1,45 @@ -# DVX Core Library (libdvx.lib) +# libdvx -- DVX Core Library -The core GUI infrastructure for DVX, built as a DXE3 module. Provides -VESA video setup, 2D drawing primitives, dirty-rectangle compositing, -a window manager with Motif-style chrome, and the widget infrastructure -(layout engine, event dispatch, class registration). Individual widget -type implementations live in `../widgets/` as separate `.wgt` DXE -modules that register themselves at runtime via `wgtRegisterClass()`. +The core windowing GUI: VESA video backend, drawing primitives, dirty-rect +compositor, window manager, and the public application API. Every DVX +application links against this library. -Core knows nothing about individual widget types. There is no -WidgetTypeE enum, no widget union, and no per-widget structs in -dvxWidget.h. All widget-specific behavior is dispatched through the -WidgetClassT dispatch table. +## Documentation +Full reference is authored in the `.dhs` help sources in this directory +and compiles into the DVX System Reference: -## 5-Layer Architecture - -| Layer | Header | Source | Description | -|-------|--------|--------|-------------| -| 1. Video | `dvxVideo.h` | `dvxVideo.c` | VESA VBE init, LFB mapping, backbuffer, pixel format, `packColor()` | -| 2. Draw | `dvxDraw.h` | `dvxDraw.c` | Rect fills, bevels, text, bitmap cursors, focus rects, lines | -| 3. Compositor | `dvxComp.h` | `dvxComp.c` | Dirty rect tracking, merge, clip, LFB flush | -| 4. Window Manager | `dvxWm.h` | `dvxWm.c` | Window stack, chrome, drag/resize, menus, scrollbars, hit test | -| 5. Application | `dvxApp.h` | `dvxApp.c` | Event loop, input polling, color schemes, wallpaper, public API | - -Additional modules built into libdvx.lib: - -| Header | Source | Description | -|--------|--------|-------------| -| `dvxDialog.h` | `dvxDialog.c` | Modal message box and file open/save dialogs | -| `dvxPrefs.h` | `dvxPrefs.c` | INI-based preferences (read/write with typed accessors) | -| `dvxResource.h` | `dvxResource.c` | Resource system -- icons, text, and binary data appended to DXE files | -| `dvxMem.h` | (header only) | Per-app memory tracking API declarations | -| `dvxWidget.h` | `widgetClass.c`, `widgetCore.c`, `widgetEvent.c`, `widgetLayout.c`, `widgetOps.c`, `widgetScrollbar.c` | Widget infrastructure | -| `dvxWidgetPlugin.h` | (header only) | Plugin API for widget DXE modules | -| -- | `dvxImage.c` | Image loading via stb_image (BMP, PNG, JPEG, GIF) | -| -- | `dvxImageWrite.c` | PNG export via stb_image_write | +- `sysdoc.dhs` -- documentation index (start here) +- `arch.dhs` -- five-layer architecture, display pipeline, module system +- `apiref.dhs` -- every public function, struct, enum, and constant +After `make`, the compiled form is `bin/apps/kpunch/progman/dvxhelp.hlp` +(viewable via the Help Viewer app) and `docs/dvx_system_reference.html`. ## Source Files -| File | Description | -|------|-------------| -| `dvxVideo.c` | VESA mode negotiation, LFB mapping via DPMI, backbuffer alloc, `packColor()` | -| `dvxDraw.c` | `rectFill()`, `rectCopy()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, cursor rendering | -| `dvxComp.c` | `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` | -| `dvxWm.c` | Window create/destroy, Z-order, chrome drawing, drag/resize, menu bar, scrollbars, minimize/maximize | -| `dvxApp.c` | `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, screenshots | -| `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops | -| `dvxPrefs.c` | `prefsLoad()`, `prefsSave()`, typed get/set for string/int/bool | -| `dvxResource.c` | `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` -- resource system | -| `dvxImage.c` | `dvxLoadImage()`, `dvxLoadImageFromMemory()` -- stb_image loader, converts to native pixel format | -| `dvxImageWrite.c` | `dvxSaveImage()` -- PNG writer for screenshots | -| `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table | -| `widgetCore.c` | Widget allocation, tree ops, focus management, clipboard, hit testing, cursor blink | -| `widgetEvent.c` | `widgetOnMouse()`, `widgetOnKey()`, `widgetOnPaint()`, `widgetOnResize()`, scrollbar management | -| `widgetLayout.c` | Two-pass flexbox layout: bottom-up `calcMinSize`, top-down space allocation with weights | -| `widgetOps.c` | `wgtPaint()`, `wgtLayout()`, `wgtInitWindow()`, text get/set, invalidation | -| `widgetScrollbar.c` | Scrollbar drawing (H/V), thumb calculation, hit testing, drag update | - - -## Public Headers - -| Header | Purpose | -|--------|---------| -| `dvxTypes.h` | All shared types: DisplayT, RectT, BlitOpsT, BevelStyleT, BitmapFontT, ColorSchemeT, WindowT, MenuT, ScrollbarT, CursorT, PopupStateT | -| `dvxVideo.h` | `videoInit()`, `videoShutdown()`, `packColor()`, `setClipRect()`, `resetClipRect()` | -| `dvxDraw.h` | All drawing functions: `rectFill()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, etc. | -| `dvxComp.h` | Dirty list operations: `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` | -| `dvxWm.h` | Window management: `wmCreateWindow()`, `wmDestroyWindow()`, `wmRaiseWindow()`, menus, scrollbars, chrome | -| `dvxApp.h` | Application API: `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, image I/O | -| `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` | -| `dvxPrefs.h` | INI preferences: `prefsLoad()`, `prefsSave()`, typed accessors | -| `dvxResource.h` | Resource system: `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` | -| `dvxMem.h` | Per-app memory tracking: `dvxMalloc()`, `dvxFree()`, `dvxMemGetAppUsage()`, etc. | -| `dvxWidget.h` | Widget system public API: WidgetT, WidgetClassT, size tags, layout, API registry, `wclsFoo()` dispatch helpers | -| `dvxWidgetPlugin.h` | Plugin API for widget DXE authors: tree ops, focus, scrollbar helpers, shared state | -| `dvxFont.h` | Embedded 8x14 and 8x16 bitmap font data (CP437) | -| `dvxCursor.h` | Mouse cursor AND/XOR mask data (arrow, resize H/V/diag, busy) | -| `dvxPalette.h` | Default 256-color VGA palette for 8-bit mode | - - -## Platform Layer - -| File | Description | -|------|-------------| -| `platform/dvxPlatform.h` | Platform abstraction API (video, input, spans, DXE, crash recovery, memory tracking) | -| `platform/dvxPlatformDos.c` | DJGPP/DPMI implementation (VESA VBE, INT 33h mouse, INT 16h keyboard, asm spans) | - -The platform layer is compiled into dvx.exe (the loader), not into -libdvx.lib. Platform functions are exported to all DXE modules via -`platformRegisterDxeExports()`. - - -## Third-Party Libraries - -| File | Description | -|------|-------------| -| `thirdparty/stb_image.h` | Image loading (implementation compiled into dvxImage.c) | -| `thirdparty/stb_image_write.h` | PNG writing (implementation compiled into dvxImageWrite.c) | -| `thirdparty/stb_ds.h` | Dynamic arrays/hash maps (implementation in loader, exported to all DXEs) | - - -## Dynamic Limits - -All major data structures grow dynamically via realloc. There are no -fixed-size limits for: - -- **Windows** -- `WindowStackT.windows` is a dynamic array -- **Menus** -- `MenuBarT.menus` and `MenuT.items` are dynamic arrays -- **Accelerator entries** -- `AccelTableT.entries` is a dynamic array -- **Dirty rectangles** -- `DirtyListT.rects` is a dynamic array -- **Submenu depth** -- `PopupStateT.parentStack` is a dynamic array - -The only fixed-size buffers remaining are per-element string fields -(`MAX_TITLE_LEN = 128`, `MAX_MENU_LABEL = 32`, `MAX_WIDGET_NAME = 32`) -and the system menu (`SYS_MENU_MAX_ITEMS = 10`). - - -## Resource System - -Resources are appended to DXE3 files (.app, .wgt, .lib) after the -normal DXE content. The DXE loader never reads past the DXE header, -so appended data is invisible to dlopen. - -File layout: - - [DXE3 content] - [resource data entries] -- sequential, variable length - [resource directory] -- fixed-size entries (48 bytes each) - [footer] -- magic + directory offset + count (16 bytes) - -### Resource Types - -| Define | Value | Description | -|--------|-------|-------------| -| `DVX_RES_ICON` | 1 | Image data (BMP icon: 16x16, 32x32, etc.) | -| `DVX_RES_TEXT` | 2 | Null-terminated string (author, copyright, etc.) | -| `DVX_RES_BINARY` | 3 | Arbitrary binary data (app-specific) | - -### Resource API - -| Function | Description | -|----------|-------------| -| `dvxResOpen(path)` | Open a resource handle by reading the footer and directory. Returns NULL if no resources. | -| `dvxResRead(h, name, outSize)` | Find a resource by name and read its data into a malloc'd buffer. Caller frees. | -| `dvxResFind(h, name)` | Find a resource by name and return its directory entry pointer. | -| `dvxResClose(h)` | Close the handle and free associated memory. | - -### Key Types - -| Type | Description | -|------|-------------| -| `DvxResDirEntryT` | Directory entry: name[32], type, offset, size, reserved (48 bytes) | -| `DvxResFooterT` | Footer: magic (`0x52585644` = "DVXR"), dirOffset, entryCount, reserved (16 bytes) | -| `DvxResHandleT` | Runtime handle: path, entries array, entry count | - - -## Memory Tracking (dvxMem.h) - -Per-app memory tracking wraps malloc/free/calloc/realloc/strdup with a -small header per allocation that records the owning app ID and size. -DXE code does not need to include dvxMem.h -- the DXE export table maps -the standard allocator names to these wrappers transparently. - -| Function | Description | -|----------|-------------| -| `dvxMalloc(size)` | Tracked malloc | -| `dvxCalloc(nmemb, size)` | Tracked calloc | -| `dvxRealloc(ptr, size)` | Tracked realloc | -| `dvxFree(ptr)` | Tracked free (falls through to real free on non-tracked pointers) | -| `dvxStrdup(s)` | Tracked strdup | -| `dvxMemSnapshotLoad(appId)` | Record baseline memory for leak detection | -| `dvxMemGetAppUsage(appId)` | Query current memory usage for an app (bytes) | -| `dvxMemResetApp(appId)` | Free all allocations charged to an app | - -The global `dvxMemAppIdPtr` pointer is set by the shell to -`&ctx->currentAppId` so the allocator knows which app to charge. - - -## WidgetT Structure - -The WidgetT struct is generic -- no widget-specific fields or union: - -```c -typedef struct WidgetT { - int32_t type; // assigned by wgtRegisterClass() - const struct WidgetClassT *wclass; // dispatch table pointer - char name[MAX_WIDGET_NAME]; - - // Tree linkage - struct WidgetT *parent, *firstChild, *lastChild, *nextSibling; - WindowT *window; - - // Geometry (relative to window content area) - int32_t x, y, w, h; - int32_t calcMinW, calcMinH; // computed minimum size - - // Size hints (tagged: wgtPixels/wgtChars/wgtPercent, 0 = auto) - int32_t minW, minH, maxW, maxH; - int32_t prefW, prefH; - int32_t weight; // extra-space distribution (0 = fixed) - - // Container properties - WidgetAlignE align; - int32_t spacing, padding; // tagged sizes - - // Colors (0 = use color scheme defaults) - uint32_t fgColor, bgColor; - - // State - bool visible, enabled, readOnly, focused; - char accelKey; - - // User data and callbacks - void *userData; - void *data; // widget-private data (allocated by widget DXE) - const char *tooltip; - MenuT *contextMenu; - void (*onClick)(struct WidgetT *w); - void (*onDblClick)(struct WidgetT *w); - void (*onChange)(struct WidgetT *w); - void (*onFocus)(struct WidgetT *w); - void (*onBlur)(struct WidgetT *w); -} WidgetT; -``` - - -## WidgetClassT Dispatch Table - -WidgetClassT is an ABI-stable dispatch table. Method IDs are fixed -constants that never change -- adding new methods appends new IDs -without shifting existing ones. Widget DXEs compiled against an older -DVX version continue to work unmodified. - -```c -#define WGT_CLASS_VERSION 1 // bump on breaking ABI change -#define WGT_METHOD_MAX 32 // room for future methods - -typedef struct WidgetClassT { - uint32_t version; - uint32_t flags; - void *handlers[WGT_METHOD_MAX]; -} WidgetClassT; -``` - -### Method ID Table - -21 methods are currently defined (IDs 0--20). WGT_METHOD_MAX is 32, -leaving room for 11 future methods without a version bump. - -| ID | Method ID | Signature | Purpose | -|----|-----------|-----------|---------| -| 0 | `WGT_METHOD_PAINT` | `void (w, d, ops, font, colors)` | Render the widget | -| 1 | `WGT_METHOD_PAINT_OVERLAY` | `void (w, d, ops, font, colors)` | Render overlay (dropdown popup) | -| 2 | `WGT_METHOD_CALC_MIN_SIZE` | `void (w, font)` | Compute minimum size (bottom-up pass) | -| 3 | `WGT_METHOD_LAYOUT` | `void (w, font)` | Position children (top-down pass) | -| 4 | `WGT_METHOD_GET_LAYOUT_METRICS` | `void (w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout | -| 5 | `WGT_METHOD_ON_MOUSE` | `void (w, root, vx, vy)` | Handle mouse click | -| 6 | `WGT_METHOD_ON_KEY` | `void (w, key, mod)` | Handle keyboard input | -| 7 | `WGT_METHOD_ON_ACCEL_ACTIVATE` | `void (w, root)` | Handle accelerator key match | -| 8 | `WGT_METHOD_DESTROY` | `void (w)` | Free widget-private data | -| 9 | `WGT_METHOD_ON_CHILD_CHANGED` | `void (parent, child)` | Notification when a child changes | -| 10 | `WGT_METHOD_GET_TEXT` | `const char *(w)` | Return widget text | -| 11 | `WGT_METHOD_SET_TEXT` | `void (w, text)` | Set widget text | -| 12 | `WGT_METHOD_CLEAR_SELECTION` | `bool (w)` | Clear text/item selection | -| 13 | `WGT_METHOD_CLOSE_POPUP` | `void (w)` | Close dropdown popup | -| 14 | `WGT_METHOD_GET_POPUP_RECT` | `void (w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle | -| 15 | `WGT_METHOD_ON_DRAG_UPDATE` | `void (w, root, x, y)` | Mouse move during drag | -| 16 | `WGT_METHOD_ON_DRAG_END` | `void (w, root, x, y)` | Mouse release after drag | -| 17 | `WGT_METHOD_GET_CURSOR_SHAPE` | `int32_t (w, vx, vy)` | Return cursor ID for this position | -| 18 | `WGT_METHOD_POLL` | `void (w, win)` | Periodic polling (AnsiTerm comms) | -| 19 | `WGT_METHOD_QUICK_REPAINT` | `int32_t (w, outY, outH)` | Fast incremental repaint | -| 20 | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` | `void (parent, child)` | Scroll to make a child visible | - -### Typed Dispatch Helpers - -Each `wclsFoo()` inline function extracts a handler by stable method -ID, casts it to the correct function pointer type, and calls it with -a NULL check. This gives callers type-safe dispatch with the same -codegen as a direct struct field call. - -| Helper | Wraps Method ID | -|--------|-----------------| -| `wclsHas(w, methodId)` | Check if a method is implemented (non-NULL) | -| `wclsPaint(w, d, ops, font, colors)` | `WGT_METHOD_PAINT` | -| `wclsPaintOverlay(w, d, ops, font, colors)` | `WGT_METHOD_PAINT_OVERLAY` | -| `wclsCalcMinSize(w, font)` | `WGT_METHOD_CALC_MIN_SIZE` | -| `wclsLayout(w, font)` | `WGT_METHOD_LAYOUT` | -| `wclsGetLayoutMetrics(w, font, pad, gap, extraTop, borderW)` | `WGT_METHOD_GET_LAYOUT_METRICS` | -| `wclsOnMouse(w, root, vx, vy)` | `WGT_METHOD_ON_MOUSE` | -| `wclsOnKey(w, key, mod)` | `WGT_METHOD_ON_KEY` | -| `wclsOnAccelActivate(w, root)` | `WGT_METHOD_ON_ACCEL_ACTIVATE` | -| `wclsDestroy(w)` | `WGT_METHOD_DESTROY` | -| `wclsOnChildChanged(parent, child)` | `WGT_METHOD_ON_CHILD_CHANGED` | -| `wclsGetText(w)` | `WGT_METHOD_GET_TEXT` | -| `wclsSetText(w, text)` | `WGT_METHOD_SET_TEXT` | -| `wclsClearSelection(w)` | `WGT_METHOD_CLEAR_SELECTION` | -| `wclsClosePopup(w)` | `WGT_METHOD_CLOSE_POPUP` | -| `wclsGetPopupRect(w, font, contentH, popX, popY, popW, popH)` | `WGT_METHOD_GET_POPUP_RECT` | -| `wclsOnDragUpdate(w, root, x, y)` | `WGT_METHOD_ON_DRAG_UPDATE` | -| `wclsOnDragEnd(w, root, x, y)` | `WGT_METHOD_ON_DRAG_END` | -| `wclsGetCursorShape(w, vx, vy)` | `WGT_METHOD_GET_CURSOR_SHAPE` | -| `wclsPoll(w, win)` | `WGT_METHOD_POLL` | -| `wclsQuickRepaint(w, outY, outH)` | `WGT_METHOD_QUICK_REPAINT` | -| `wclsScrollChildIntoView(parent, child)` | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` | - -### WidgetClassT Flags - -| Flag | Value | Description | -|------|-------|-------------| -| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus | -| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses VBox/HBox layout algorithm | -| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally | -| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering | -| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here | -| `WCLASS_FOCUS_FORWARD` | 0x0020 | Accel hit forwards focus to next focusable | -| `WCLASS_HAS_POPUP` | 0x0040 | Has dropdown popup overlay | -| `WCLASS_SCROLLABLE` | 0x0080 | Accepts mouse wheel events | -| `WCLASS_SCROLL_CONTAINER` | 0x0100 | Scroll container (ScrollPane) | -| `WCLASS_NEEDS_POLL` | 0x0200 | Needs periodic polling | -| `WCLASS_SWALLOWS_TAB` | 0x0400 | Tab key goes to widget, not focus nav | -| `WCLASS_RELAYOUT_ON_SCROLL` | 0x0800 | Full relayout on scrollbar drag | -| `WCLASS_PRESS_RELEASE` | 0x1000 | Click = press+release (buttons) | -| `WCLASS_ACCEL_WHEN_HIDDEN` | 0x2000 | Accel matching works when invisible | - - -## Widget Registration - -Each widget DXE exports `wgtRegister()`, called by the loader after -`dlopen`. The WidgetClassT uses the `handlers[]` array indexed by -method IDs: - -```c -static int32_t sButtonType; - -static const WidgetClassT sButtonClass = { - .version = WGT_CLASS_VERSION, - .flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE, - .handlers = { - [WGT_METHOD_PAINT] = buttonPaint, - [WGT_METHOD_CALC_MIN_SIZE] = buttonCalcMinSize, - [WGT_METHOD_ON_MOUSE] = buttonOnMouse, - [WGT_METHOD_ON_KEY] = buttonOnKey, - [WGT_METHOD_DESTROY] = buttonDestroy, - [WGT_METHOD_GET_TEXT] = buttonGetText, - [WGT_METHOD_SET_TEXT] = buttonSetText, - [WGT_METHOD_ON_ACCEL_ACTIVATE] = buttonAccelActivate, - } -}; - -static const ButtonApiT sApi = { .create = buttonCreate }; - -void wgtRegister(void) { - sButtonType = wgtRegisterClass(&sButtonClass); - wgtRegisterApi("button", &sApi); -} -``` - - -## Per-Widget API Registry - -The monolithic WidgetApiT is gone. Each widget registers a small API -struct under a name via `wgtRegisterApi()`. Callers retrieve it via -`wgtGetApi()` and cast to the widget-specific type. Per-widget headers -(e.g. `widgetButton.h`) provide typed accessors and convenience macros: - -```c -// widgetButton.h -typedef struct { - WidgetT *(*create)(WidgetT *parent, const char *text); -} ButtonApiT; - -static inline const ButtonApiT *dvxButtonApi(void) { - static const ButtonApiT *sApi; - if (!sApi) { sApi = (const ButtonApiT *)wgtGetApi("button"); } - return sApi; -} - -#define wgtButton(parent, text) dvxButtonApi()->create(parent, text) -``` - - -## Tagged Size Values - -Size hints encode both unit type and numeric value in a single int32_t: - -| Macro | Encoding | Example | -|-------|----------|---------| -| `wgtPixels(v)` | Bits 31:30 = 00 | `w->minW = wgtPixels(200);` | -| `wgtChars(v)` | Bits 31:30 = 01 | `w->minW = wgtChars(40);` | -| `wgtPercent(v)` | Bits 31:30 = 10 | `w->minW = wgtPercent(50);` | -| `0` | -- | Auto (use computed minimum) | - - -## Layout Algorithm - -Two-pass flexbox-like layout: - -1. **Bottom-up** (`calcMinSize`): Each widget computes its minimum size. - Containers sum children along the main axis, max across the cross axis. -2. **Top-down** (`layout`): Available space is allocated to children. - Extra space beyond minimum is distributed proportionally to weights. - `weight=0` means fixed size, `weight=100` is the default flexible weight. - -### Cross-Axis Centering - -When a child widget has a `maxW` (in a VBox) or `maxH` (in an HBox) -that constrains it smaller than the available cross-axis space, the -layout engine automatically centers the child on the cross axis. This -means setting `maxW` or `maxH` on a child inside a container will both -cap its size and center it within the remaining space. - - -## Image Loading - -Two image loading functions are available: - -| Function | Description | -|----------|-------------| -| `dvxLoadImage(ctx, path, outW, outH, outPitch)` | Load from a file path | -| `dvxLoadImageFromMemory(ctx, data, dataLen, outW, outH, outPitch)` | Load from a memory buffer (e.g. resource data) | - -Both convert to the display's native pixel format. Caller frees the -returned buffer with `dvxFreeImage()`. Supported formats: BMP, PNG, -JPEG, GIF (via stb_image). - - -## Exported Symbols - -libdvx.lib exports symbols matching these prefixes: - -``` -dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*, -resetClip*, stbi*, stbi_write*, dirtyList*, widget*, -accelParse*, clipboard*, multiClick*, -sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*, -sOpen*, sDrag*, sClosed*, sKey* -``` - +Public headers live alongside implementation: + +- `dvxTypes.h` -- shared types (DisplayT, RectT, BlitOpsT, ColorSchemeT, etc.) +- `dvxVideo.h` / `.c` -- layer 1: VESA init, LFB mapping, backbuffer +- `dvxDraw.h` / `.c` -- layer 2: rect fill, bevel, text, bitmap blits +- `dvxComp.h` / `.c` -- layer 3: dirty rect list, merge, LFB flush +- `dvxWm.h` / `.c` -- layer 4: window manager, chrome, drag, resize +- `dvxApp.h` / `.c` -- layer 5: event loop, public API, aggregator +- `dvxWgt.h` -- widget base (WidgetT, WidgetClassT, WgtIfaceT) +- `dvxWgtP.h` -- widget-internal helpers +- `dvxDlg.h` / `dvxDialog.c` -- message/input/choice/file dialogs +- `dvxRes.h` / `dvxResource.c` -- .res resource system +- `dvxMem.h` -- memory allocation wrappers +- `dvxPrefs.h` / `.c` -- INI-format preferences +- `dvxFont.h` -- bundled 8x16 bitmap font +- `dvxCur.h` -- mouse cursor bitmaps +- `dvxPal.h` -- 16-color CGA palette +- `widgetCore.c` / `widgetLayout.c` / `widgetEvent.c` / `widgetClass.c` + -- widget runtime helpers shared across all widget plugins +- `dvxImage.c` / `dvxImageWrite.c` -- image loader/writer (BMP, PNG via stb) +- `platform/dvxPlat.h` -- platform-layer interface (implemented per target) ## Build -``` -make # builds bin/libs/libdvx.lib + bin/libs/libdvx.dep -make clean # removes objects and library -``` - -Depends on: `libtasks.lib` (via libdvx.dep). +`make -C src/libs/kpunch/libdvx` (invoked by the top-level `make`). diff --git a/src/libs/kpunch/libdvx/apiref.dhs b/src/libs/kpunch/libdvx/apiref.dhs index c17e016..752ab71 100644 --- a/src/libs/kpunch/libdvx/apiref.dhs +++ b/src/libs/kpunch/libdvx/apiref.dhs @@ -30,31 +30,47 @@ .h1 DVX GUI API Reference -DOS Visual eXecutive -- Complete public API documentation generated from source headers. +DOS Visual eXecutive -- Complete public API documentation covering every public function, type, and constant in the libdvx headers. -The DVX GUI is built as a five-layer architecture. Each layer is defined in its own header file. This reference covers every public function, type, and constant. +The DVX GUI is built as a five-layer architecture. Each layer is defined in its own header file. Additional headers cover dialogs, resources, preferences, memory tracking, the platform abstraction, and the widget system. -.h2 Layers +.h2 Headers .list -.item dvxTypes.h -- Shared type definitions -.item dvxCursor.h -- Cursor definitions -.item dvxVideo.h -- Layer 1: VESA VBE Video Backend -.item dvxDraw.h -- Layer 2: Drawing Primitives -.item dvxComp.h -- Layer 3: Dirty Rectangle Compositor -.item dvxWm.h -- Layer 4: Window Manager +.item dvxTypes.h -- Shared type definitions and constants +.item dvxCur.h -- Embedded mouse cursor bitmaps +.item dvxFont.h -- Embedded 8x16 CP437 bitmap font +.item dvxPal.h -- 8-bit mode 256-color palette +.item dvxVideo.h -- Layer 1: VESA VBE video backend +.item dvxDraw.h -- Layer 2: 2D drawing primitives +.item dvxComp.h -- Layer 3: Dirty rectangle compositor +.item dvxWm.h -- Layer 4: Window manager .item dvxApp.h -- Layer 5: Application API -.item dvxWidget.h -- Widget System +.item dvxDlg.h -- Modal dialog boxes +.item dvxRes.h -- DXE-embedded resource system +.item dvxPrefs.h -- INI preferences system +.item dvxMem.h -- Per-app memory tracking +.item dvxWgt.h -- Widget system public API +.item dvxWgtP.h -- Widget plugin API +.item platform/dvxPlat.h -- Platform abstraction layer .endlist .link api.types dvxTypes.h -- Shared Type Definitions -.link api.cursor dvxCursor.h -- Cursor Definitions -.link api.video dvxVideo.h -- Layer 1: VESA VBE Video Backend +.link api.cursor dvxCur.h -- Cursor Definitions +.link api.font dvxFont.h -- Bitmap Font Data +.link api.palette dvxPal.h -- 8-bit Palette +.link api.video dvxVideo.h -- Layer 1: Video Backend .link api.draw dvxDraw.h -- Layer 2: Drawing Primitives -.link api.comp dvxComp.h -- Layer 3: Dirty Rectangle Compositor +.link api.comp dvxComp.h -- Layer 3: Compositor .link api.wm dvxWm.h -- Layer 4: Window Manager .link api.app dvxApp.h -- Layer 5: Application API -.link api.widget dvxWidget.h -- Widget System +.link api.dialog dvxDlg.h -- Modal Dialogs +.link api.resource dvxRes.h -- Resource System +.link api.prefs dvxPrefs.h -- Preferences +.link api.mem dvxMem.h -- Memory Tracking +.link api.widget dvxWgt.h -- Widget System +.link api.widgetplugin dvxWgtP.h -- Widget Plugin API +.link api.platform platform/dvxPlat.h -- Platform Layer .topic api.types .title dvxTypes.h -- Shared Type Definitions @@ -161,7 +177,7 @@ Fixed-width 8-pixel-wide bitmap font descriptor. One size provided: 8x16 (standa Field Description ----- ----------- int32_t charWidth Fixed width per glyph (always 8) - int32_t charHeight Glyph height (14 or 16) + int32_t charHeight Glyph height (16 in the bundled font) int32_t firstChar ASCII code of first glyph (typically 0) int32_t numChars Number of glyphs (typically 256) const uint8_t *glyphData Packed 1bpp data, charHeight bytes per glyph @@ -220,7 +236,7 @@ Central window object. Each window owns a persistent content backbuffer and rece char title[MAX_TITLE_LEN] Window title text (max 128 chars) bool visible, focused, minimized, maximized, resizable, modal Window state flags bool iconNeedsRefresh true when contentBuf changed (for minimized icon refresh) - bool needsPaint true until first onPaint call + uint8_t paintNeeded PAINT_NONE / PAINT_PARTIAL / PAINT_FULL int32_t maxW, maxH Maximum dimensions int32_t preMaxX, preMaxY, preMaxW, preMaxH Saved geometry before maximize uint8_t *contentBuf Per-window content backbuffer @@ -240,17 +256,17 @@ Callbacks: .table Callback Description -------- ----------- - onPaint(WindowT *win, RectT *dirtyArea) Content repaint requested - onKey(WindowT *win, int32_t key, int32_t mod) Key press - onKeyUp(WindowT *win, int32_t scancode, int32_t mod) Key release - onMouse(WindowT *win, int32_t x, int32_t y, int32_t btn) Mouse event (content-relative) - onResize(WindowT *win, int32_t newW, int32_t newH) Window resized - onClose(WindowT *win) Close requested - onMenu(WindowT *win, int32_t menuId) Menu item or accelerator activated - onScroll(WindowT *win, ScrollbarOrientE orient, int32_t val) Scrollbar value changed - onCursorQuery(WindowT *win, int32_t x, int32_t y) Return CURSOR_* for hit position - onFocus(WindowT *win) Window gained focus - onBlur(WindowT *win) Window lost focus + onPaint(WindowT *win, RectT *dirtyArea) Content repaint requested + onKey(WindowT *win, int32_t key, int32_t mod) Key press + onKeyUp(WindowT *win, int32_t scancode, int32_t mod) Key release + onMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) Mouse event (content-relative) + onResize(WindowT *win, int32_t newW, int32_t newH) Window resized + onClose(WindowT *win) Close requested + onMenu(WindowT *win, int32_t menuId) Menu item or accelerator activated + onScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) Scrollbar value changed + int32_t onCursorQuery(WindowT *win, int32_t x, int32_t y) Return CURSOR_* for hit position + onFocus(WindowT *win) Window gained focus + onBlur(WindowT *win) Window lost focus .endtable .h3 WindowStackT @@ -258,13 +274,15 @@ Callbacks: Z-ordered window stack (front-to-back: index count-1 is topmost). Owns system-wide drag/resize/scroll interaction state. .table - Field Description - ----- ----------- - WindowT **windows Dynamic array of window pointers - int32_t count, cap Current count and allocated capacity - int32_t focusedIdx Stack index of focused window - int32_t dragWindow, dragOffX, dragOffY Active drag state - int32_t resizeWindow, resizeEdge Active resize state + Field Description + ----- ----------- + WindowT **windows Dynamic array of window pointers + int32_t count, cap Current count and allocated capacity + int32_t focusedIdx Stack index of focused window + int32_t dragWindow, dragOffX, dragOffY Active drag state + int32_t dragStartX, dragStartY Mouse position at drag begin (for deadzone) + bool dragActive True once mouse moved past deadzone + int32_t resizeWindow, resizeEdge Active resize state int32_t scrollWindow, scrollOrient, scrollDragOff Active scroll drag state .endtable @@ -273,15 +291,24 @@ Z-ordered window stack (front-to-back: index count-1 is topmost). Owns system-wi Menu system types. Fixed-size label buffers (MAX_MENU_LABEL = 32). Cascading submenus supported via MenuItemT.subMenu pointer. .table - Field Description - ----- ----------- - MenuItemT.label Item text (supports & accelerator markers) - MenuItemT.id Application-defined command ID - MenuItemT.type MenuItemNormalE, MenuItemCheckE, or MenuItemRadioE - MenuItemT.separator true = horizontal divider line - MenuItemT.enabled, checked Item state - MenuItemT.subMenu Child menu for cascading (NULL if leaf) - MenuBarT.activeIdx Open popup index (-1 = none) + Field Description + ----- ----------- + MenuItemT.label Item text (supports & accelerator markers) + MenuItemT.id Application-defined command ID + MenuItemT.type MenuItemNormalE, MenuItemCheckE, or MenuItemRadioE + MenuItemT.separator true = horizontal divider line + MenuItemT.enabled, checked Item state + MenuItemT.accelKey Lowercase accelerator character (0 = none) + MenuItemT.subMenu Child menu for cascading (NULL if leaf) + MenuT.label Menu bar label (e.g. "File") + MenuT.items Dynamic array of MenuItemT + MenuT.itemCount, itemCap Current count and allocated capacity + MenuT.barX, barW Computed position/width on the menu bar + MenuT.accelKey Lowercase accelerator character (0 = none) + MenuBarT.menus Dynamic array of MenuT + MenuBarT.menuCount, menuCap Current count and allocated capacity + MenuBarT.activeIdx Open popup index (-1 = none) + MenuBarT.positionsDirty True when barX/barW need recomputation .endtable .h3 ScrollbarT @@ -308,6 +335,10 @@ Per-window keyboard accelerator table. Entries are matched against keystrokes in AccelEntryT.key ASCII character or KEY_Fxx constant AccelEntryT.modifiers Bitmask of ACCEL_CTRL, ACCEL_SHIFT, ACCEL_ALT AccelEntryT.cmdId Command ID passed to onMenu + AccelEntryT.normKey Pre-normalized key (uppercased) for fast matching + AccelEntryT.normMods Pre-masked modifiers (CTRL|ALT only) for fast matching + AccelTableT.entries Dynamic array of AccelEntryT + AccelTableT.count, cap Current count and allocated capacity .endtable .h3 VideoModeInfoT @@ -434,16 +465,16 @@ Software-rendered 16x16 cursor using AND/XOR mask encoding. .endtable .topic api.cursor -.title dvxCursor.h -- Cursor Definitions -.toc 1 dvxCursor.h -- Cursor Definitions -.index dvxCursor.h +.title dvxCur.h -- Cursor Definitions +.toc 1 dvxCur.h -- Cursor Definitions +.index dvxCur.h .index Cursor Shapes .index CURSOR_ARROW .index CURSOR_BUSY -.h1 dvxCursor.h -- Cursor Definitions +.h1 dvxCur.h -- Cursor Definitions -Embedded 16x16 mouse cursor bitmaps compiled as static const data. No external cursor files. Uses the standard AND/XOR mask encoding from the IBM VGA hardware cursor spec. +Embedded 16x16 mouse cursor bitmaps compiled as static const data. No external cursor files. Uses the standard AND/XOR mask encoding from the IBM VGA hardware cursor spec. The AND mask selects transparency (1 = transparent, 0 = opaque) and the XOR data selects black vs. white for opaque pixels. .h2 Cursor Shape IDs @@ -464,7 +495,125 @@ Embedded 16x16 mouse cursor bitmaps compiled as static const data. No external c .h3 dvxCursors[CURSOR_COUNT] -Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each entry includes the AND mask, XOR data, dimensions, and hot spot coordinates. +Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each entry includes the AND mask, XOR data (both as 16-element uint16_t arrays), dimensions (16x16), and hot spot coordinates. + +.h3 dvxCursor + +Legacy alias for backward compatibility with code that predates multi-cursor support. Equivalent to dvxCursors[CURSOR_ARROW]. + +.topic api.font +.title dvxFont.h -- Bitmap Font Data +.toc 1 dvxFont.h -- Bitmap Font Data +.index dvxFont.h +.index Font +.index CP437 +.index BitmapFontT + +.h1 dvxFont.h -- Embedded Bitmap Font Data + +Raw glyph bitmaps for the standard 8x16 VGA ROM font covering the full IBM Code Page 437 character set (256 glyphs). Compiled in as a static const array, avoiding a real-mode INT 10h call at startup. + +Glyph format: 1 bit per pixel, 8 pixels wide, MSB = leftmost pixel. Each glyph occupies 16 consecutive bytes (one byte per scanline). The 8-pixel width means per-scanline rendering never needs bit shifting across byte boundaries. + +CP437 covers: + +.list +.item 0-31 -- Smiley, card suits, arrows, notes (classic extended ASCII glyphs) +.item 32-126 -- Printable ASCII +.item 127 -- House glyph +.item 128-175 -- Accented Latin letters, currency, fractions +.item 176-223 -- Box-drawing characters (essential for chrome gadgets) +.item 224-254 -- Greek letters, math symbols, super/subscripts +.item 255 -- Non-breaking space +.endlist + +.h2 Data + +.h3 font8x16 + +Static const uint8_t array of 256 * 16 = 4096 bytes containing the packed 1bpp glyph bitmaps, in ASCII code order. + +.h3 dvxFont8x16 + +Static const BitmapFontT struct describing the 8x16 font, ready to pass to drawText/drawTextN/drawChar. Use this as the default font for all text rendering. + +.h2 Constants + +.table + Define Value Description + ------ ----- ----------- + FONT_CHAR_WIDTH 8 Fixed glyph width in pixels (defined in dvxTypes.h) +.endtable + +.topic api.palette +.title dvxPal.h -- 8-bit Palette +.toc 1 dvxPal.h -- 8-bit Palette +.index dvxPal.h +.index Palette +.index 8-bit mode + +.h1 dvxPal.h -- 256-Color Palette for 8-bit Mode + +Defines the 256-color VGA/VESA palette used in 8-bit video modes. Layout: + +.table + Range Purpose + ----- ------- + 0-215 6x6x6 color cube -- uniformly distributed RGB (6 levels per channel: 0, 51, 102, 153, 204, 255). Index = r*36 + g*6 + b. + 216-231 16-step grey ramp for smooth gradients + 232-239 Dedicated UI chrome colors (highlight, shadow, title bar, desktop) + 240-255 Reserved (black) +.endtable + +The cube layout enables O(1) color lookup: snapping an RGB triplet to the nearest cube vertex is integer division by 51. The grey ramp and chrome slots hold colors that need exact values not available in the cube. + +.h2 Chrome Color Indices + +.table + Define Value Description + ------ ----- ----------- + PAL_CHROME_HIGHLIGHT 232 White (bevel highlight) + PAL_CHROME_SHADOW 233 Dark grey (bevel shadow) + PAL_CHROME_ACTIVE_BG 234 Navy (focused title bar) + PAL_CHROME_INACTIVE_BG 235 Grey (unfocused title bar) + PAL_CHROME_DESKTOP 236 Steel blue (desktop) + PAL_CHROME_SELECTION 237 Navy (selection highlight) + PAL_CHROME_TEXT 238 Black (text) + PAL_CHROME_WHITE 239 Bright white +.endtable + +.h2 Inline Functions + +.h3 dvxGeneratePalette + +.code +static inline void dvxGeneratePalette(uint8_t *pal); +.endcode + +Populate a 768-byte (256 * 3) RGB buffer with the default DVX 8-bit palette. Called once at init in 8-bit mode to program the DAC. + +.table + Parameter Description + --------- ----------- + pal Output RGB buffer (768 bytes) +.endtable + +.h3 dvxNearestPalEntry + +.code +static inline uint8_t dvxNearestPalEntry(const uint8_t *pal, uint8_t r, uint8_t g, uint8_t b); +.endcode + +Find the nearest palette entry for an RGB color using minimum Euclidean distance. Two-phase: snap to color cube vertex (O(1)), then scan grey ramp and chrome entries (216-239) for a closer match. Called by packColor() in 8-bit mode. + +.table + Parameter Description + --------- ----------- + pal Active 768-byte RGB palette + r, g, b Color components (0-255) +.endtable + +Returns: Palette index (0-239) of the nearest match. .topic api.video .title dvxVideo.h -- Layer 1: VESA VBE Video Backend @@ -531,6 +680,22 @@ Pack an RGB triplet into the display's native pixel format. For direct-color mod Returns: Native pixel value suitable for direct framebuffer write. +.h2 unpackColor + +.code +void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b); +.endcode + +Reverse of packColor -- decode a native pixel value to 8-bit RGB components. For direct-color modes, reverses the shift/mask arithmetic. For 8-bit mode, looks up the palette entry at the given index. + +.table + Parameter Description + --------- ----------- + d Display context + color Packed native pixel value + r, g, b Output: color components (0-255) +.endtable + .h2 setClipRect .code @@ -657,6 +822,26 @@ Copy a rectangle with grayscale conversion. Each pixel's RGB is converted to lum w, h Rectangle dimensions .endtable +.h2 rectCopyTransparent + +.code +void rectCopyTransparent(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h, uint32_t keyColor); +.endcode + +Copy with color-key transparency. Pixels matching keyColor are skipped, letting the destination show through. Used to draw image buttons and icons with transparent backgrounds. + +.table + Parameter Description + --------- ----------- + d Display context + ops Blit operations vtable + dstX, dstY Destination position + srcBuf, srcPitch Source buffer and pitch + srcX, srcY Source origin + w, h Rectangle dimensions + keyColor Packed color treated as transparent +.endtable + .h2 drawBevel .code @@ -1301,6 +1486,37 @@ Insert a horizontal separator line. Separators are not interactive. menu Menu to append separator to .endtable +.h3 wmRemoveMenuItem + +.code +bool wmRemoveMenuItem(MenuT *menu, int32_t id); +.endcode + +Remove a menu item by command ID. Searches the menu's items array and shifts remaining items down to close the gap. + +.table + Parameter Description + --------- ----------- + menu Menu to remove item from + id Command ID to remove +.endtable + +Returns: true if a matching item was found and removed. + +.h3 wmClearMenuItems + +.code +void wmClearMenuItems(MenuT *menu); +.endcode + +Remove all items from a menu while preserving the menu itself on the menu bar. Use when rebuilding a menu's contents dynamically (e.g. a recent-files submenu). + +.table + Parameter Description + --------- ----------- + menu Menu to empty +.endtable + .h3 wmMenuItemIsChecked .code @@ -1503,6 +1719,27 @@ Draw icons for all minimized windows along the bottom of the screen. Each icon s clipTo Dirty rectangle .endtable +.h3 wmDrawVScrollbarAt + +.code +void wmDrawVScrollbarAt(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t h, int32_t scrollPos, int32_t visibleItems, int32_t totalItems); +.endcode + +Draw a standalone vertical scrollbar at the given screen coordinates, without a ScrollbarT struct. Used by popup lists (dropdown, combobox) that need a scrollbar inline within a popup overlay. Thumb size and position are computed from scrollPos / visibleItems / totalItems. + +.table + Parameter Description + --------- ----------- + d Display context + ops Blit operations vtable + colors Color scheme + x, y Screen position (top of scrollbar) + h Track height in pixels + scrollPos Current top visible item index + visibleItems Visible item count (for thumb size) + totalItems Total item count +.endtable + .h2 Hit Testing .h3 wmHitTest @@ -1859,6 +2096,8 @@ Returns: 0 on success, -1 on failure. .index dvxDestroyWindow .index dvxRaiseWindow .index dvxFitWindow +.index dvxFitWindowH +.index dvxFitWindowW .index dvxResizeWindow .index dvxMinimizeWindow .index dvxMaximizeWindow @@ -1890,7 +2129,11 @@ Returns: 0 on success, -1 on failure. .index dvxAddAccel .index dvxCascadeWindows .index dvxTileWindows +.index dvxTileWindowsH +.index dvxTileWindowsV .index dvxLoadImage +.index dvxLoadImageAlpha +.index dvxLoadImageFromMemory .index dvxFreeImage .index dvxImageInfo .index dvxSaveImage @@ -2536,10 +2779,10 @@ Change the wallpaper display mode and re-render. No effect if no wallpaper is lo .h3 dvxSetMouseConfig .code -void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold); +void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio, int32_t wheelStep); .endcode -Configure mouse behavior. +Configure mouse behavior. Updates the in-memory AppContextT fields and programs any INT 33h settings that need driver-side adjustment. .table Parameter Description @@ -2548,6 +2791,8 @@ Configure mouse behavior. wheelDir 1 = normal, -1 = reversed dblClickMs Double-click speed in milliseconds (e.g. 500) accelThreshold Double-speed threshold in mickeys/sec (0 = don't change) + mickeyRatio Mickeys per 8 pixels (0 = don't change, 8 = default speed) + wheelStep Lines to scroll per wheel notch (1-10, default 3) .endtable .h2 Accelerators @@ -2678,7 +2923,7 @@ Returns: Pixel buffer, or NULL on failure. uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch); .endcode -Load an image from a memory buffer. Same output format as dvxLoadImage(). Caller must free with dvxFreeImage(). +Load an image from a memory buffer (typically resource data). Same output format as dvxLoadImage(). Caller must free with dvxFreeImage(). .table Parameter Description @@ -2692,6 +2937,28 @@ Load an image from a memory buffer. Same output format as dvxLoadImage(). Caller Returns: Pixel buffer, or NULL on failure. +.h3 dvxLoadImageAlpha + +.code +uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor); +.endcode + +Load an image from memory with alpha transparency. Pixels with alpha < 128 are replaced with a packed magenta key color. If any transparent pixels are found, *outHasAlpha is set to true and *outKeyColor receives the packed key color for use with rectCopyTransparent. Caller must free with dvxFreeImage. + +.table + Parameter Description + --------- ----------- + ctx Application context + data Image data buffer + dataLen Buffer size in bytes + outW, outH Output: image dimensions + outPitch Output: row pitch + outHasAlpha Output: true if any transparent pixels found + outKeyColor Output: packed key color (valid when outHasAlpha is true) +.endtable + +Returns: Pixel buffer, or NULL on failure. + .h3 dvxFreeImage .code @@ -2891,9 +3158,9 @@ Compute a djb2-xor hash for cheap dirty detection. Compare at save time with the Returns: 32-bit hash value. .topic api.widget -.title dvxWidget.h -- Widget System -.toc 1 dvxWidget.h -- Widget System -.index dvxWidget.h +.title dvxWgt.h -- Widget System +.toc 1 dvxWgt.h -- Widget System +.index dvxWgt.h .index WidgetT .index WidgetClassT .index wgtInitWindow @@ -2925,7 +3192,7 @@ Returns: 32-bit hash value. .index wgtChars .index wgtPercent -.h1 dvxWidget.h -- Widget System +.h1 dvxWgt.h -- Widget System Retained-mode widget toolkit layered on the DVX window manager. Widgets form a tree (parent-child) rooted at a per-window VBox container. Layout is automatic: measure minimum sizes bottom-up, then allocate space top-down with flexbox-like weighted distribution. Widget types are registered dynamically at runtime via DXE plugins. @@ -2950,7 +3217,11 @@ Core widget structure. Generic across all widget types; type-specific data lives uint32_t fgColor, bgColor Custom colors (0 = use scheme defaults) bool visible, enabled, readOnly State flags bool swallowTab Tab key goes to widget, not focus navigation + bool paintDirty Needs repaint (set by wgtInvalidatePaint) + bool childDirty A descendant needs repaint (for WCLASS_PAINTS_CHILDREN) + bool pressed WCLASS_PRESS_RELEASE: set while button is pressed char accelKey Accelerator character (0 = none) + int32_t contentOffX, contentOffY Content offset for mouse event coordinates void *userData, *data Application data and widget-private data const char *tooltip Tooltip text (NULL = none) MenuT *contextMenu Right-click menu (NULL = none) @@ -2969,9 +3240,9 @@ Universal Callbacks: onKeyPress(WidgetT *w, int32_t keyAscii) ASCII key press onKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) Key down onKeyUp(WidgetT *w, int32_t keyCode, int32_t shift) Key up - onMouseDown(WidgetT *w, int32_t btn, int32_t x, int32_t y) Mouse button pressed - onMouseUp(WidgetT *w, int32_t btn, int32_t x, int32_t y) Mouse button released - onMouseMove(WidgetT *w, int32_t btn, int32_t x, int32_t y) Mouse moved + onMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y) Mouse button pressed + onMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y) Mouse button released + onMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y) Mouse moved onScroll(WidgetT *w, int32_t delta) Mouse wheel onValidate(WidgetT *w) Return false to cancel a write .endtable @@ -3286,19 +3557,20 @@ Execute the full two-pass layout algorithm. Pass 1 (bottom-up): compute minimum .h3 wgtPaint .code -void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); +void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint); .endcode Paint the entire widget tree by depth-first traversal. Each widget's clip rect is set to its bounds. Overlays (popups, tooltips) are painted in a second pass on top. .table - Parameter Description - --------- ----------- - root Root widget - d Display context - ops Blit operations vtable - font Bitmap font - colors Color scheme + Parameter Description + --------- ----------- + root Root widget + d Display context + ops Blit operations vtable + font Bitmap font + colors Color scheme + fullRepaint true = repaint every widget; false = only paintDirty widgets .endtable .h2 Debug @@ -3520,3 +3792,1701 @@ The following inline functions provide type-safe dispatch through the WidgetClas wclsQuickRepaint(w, outY, outH) WGT_METHOD_QUICK_REPAINT Fast partial repaint wclsScrollChildIntoView(parent, child) WGT_METHOD_SCROLL_CHILD_INTO_VIEW Scroll child visible .endtable + +.h2 Method IDs + +Fixed constants used to index into WidgetClassT.handlers[]. Method IDs are stable ABI -- new methods are appended at the next sequential ID, never reordered or reused. + +.table + ID Symbol Signature + -- ------ --------- + 0 WGT_METHOD_PAINT void (w, d, ops, font, colors) + 1 WGT_METHOD_PAINT_OVERLAY void (w, d, ops, font, colors) + 2 WGT_METHOD_CALC_MIN_SIZE void (w, font) + 3 WGT_METHOD_LAYOUT void (w, font) + 4 WGT_METHOD_GET_LAYOUT_METRICS void (w, font, pad, gap, extraTop, borderW) + 5 WGT_METHOD_ON_MOUSE void (w, root, vx, vy) + 6 WGT_METHOD_ON_KEY void (w, key, mod) + 7 WGT_METHOD_ON_ACCEL_ACTIVATE void (w, root) + 8 WGT_METHOD_DESTROY void (w) + 9 WGT_METHOD_ON_CHILD_CHANGED void (parent, child) + 10 WGT_METHOD_GET_TEXT const char *(w) + 11 WGT_METHOD_SET_TEXT void (w, text) + 12 WGT_METHOD_CLEAR_SELECTION bool (w) + 13 WGT_METHOD_CLOSE_POPUP void (w) + 14 WGT_METHOD_GET_POPUP_RECT void (w, font, contentH, popX, popY, popW, popH) + 15 WGT_METHOD_ON_DRAG_UPDATE void (w, root, x, y) + 16 WGT_METHOD_ON_DRAG_END void (w, root, x, y) + 17 WGT_METHOD_GET_CURSOR_SHAPE int32_t (w, vx, vy) + 18 WGT_METHOD_POLL void (w, win) + 19 WGT_METHOD_QUICK_REPAINT int32_t (w, outY, outH) + 20 WGT_METHOD_SCROLL_CHILD_INTO_VIEW void (parent, child) + -- WGT_METHOD_COUNT = 21, WGT_METHOD_MAX = 32 +.endtable + +WGT_CLASS_VERSION is bumped on breaking ABI changes. The framework rejects WidgetClassT structs whose version does not match. + +.h2 Widget Class Dispatch Table + +.h3 WidgetClassT + +.code +typedef struct WidgetClassT { + uint32_t version; + uint32_t flags; + void *handlers[WGT_METHOD_MAX]; +} WidgetClassT; +.endcode + +.table + Field Description + ----- ----------- + version Must equal WGT_CLASS_VERSION at registration + flags Bitmask of WCLASS_xxx flags + handlers Array of 32 function pointers indexed by WGT_METHOD_xxx +.endtable + +.h2 Size Encoding Constants + +.table + Define Value Description + ------ ----- ----------- + WGT_SIZE_TYPE_MASK 0xC0000000 Top 2 bits: unit type selector + WGT_SIZE_VAL_MASK 0x3FFFFFFF Bottom 30 bits: numeric value + WGT_SIZE_PIXELS 0x00000000 Unit tag: pixels + WGT_SIZE_CHARS 0x40000000 Unit tag: character widths + WGT_SIZE_PERCENT 0x80000000 Unit tag: percentage of parent + WGT_WEIGHT_FILL 100 Default "flexible" weight value +.endtable + +.h2 Widget Interface Descriptors + +.h3 WgtPropDescT + +Describes a BASIC-exposed property on a widget type. Used by the form runtime and IDE for generic get/set dispatch. + +.table + Field Description + ----- ----------- + name BASIC property name (e.g. "Caption", "Value") + type Data type: WGT_IFACE_STRING, WGT_IFACE_INT, WGT_IFACE_BOOL, WGT_IFACE_FLOAT, or WGT_IFACE_ENUM + getFn Getter function pointer (NULL if write-only) + setFn Setter function pointer (NULL if read-only) + enumNames For WGT_IFACE_ENUM: NULL-terminated array of value names +.endtable + +.h3 WgtMethodDescT + +Describes an invokable method on a widget type. + +.table + Field Description + ----- ----------- + name BASIC method name (e.g. "Clear", "SetFocus") + sig Calling convention ID (WGT_SIG_xxx) + fn Function pointer +.endtable + +.h3 WgtEventDescT + +Describes an event beyond the standard common set (Click, DblClick, Change, GotFocus, LostFocus). + +.table + Field Description + ----- ----------- + name Event name (e.g. "Change", "Timer") +.endtable + +.h3 WgtIfaceT + +Top-level interface descriptor registered by each widget DXE. + +.table + Field Description + ----- ----------- + basName VB-style name (e.g. "CommandButton"), or NULL + props Array of WgtPropDescT + propCount Length of props array + methods Array of WgtMethodDescT + methodCount Length of methods array + events Array of WgtEventDescT (extras beyond common set) + eventCount Length of events array + createSig How to call the widget's create() function (WgtCreateSigE) + createArgs Default numeric args for design-time instantiation + isContainer true = widget can hold child widgets + defaultEvent Default event name for form designer + namePrefix Auto-name prefix (NULL = derive from basName) +.endtable + +.h2 Property Data Type Constants + +.table + Define Value Description + ------ ----- ----------- + WGT_IFACE_STRING 0 const char * + WGT_IFACE_INT 1 int32_t + WGT_IFACE_BOOL 2 bool + WGT_IFACE_FLOAT 3 float + WGT_IFACE_ENUM 4 int32_t with named values +.endtable + +.h2 Create Function Signature Constants + +.table + Define Value Form + ------ ----- ---- + WGT_CREATE_PARENT 0 fn(parent) + WGT_CREATE_PARENT_TEXT 1 fn(parent, const char *text) + WGT_CREATE_PARENT_INT 2 fn(parent, int32_t) + WGT_CREATE_PARENT_INT_INT 3 fn(parent, int32_t, int32_t) + WGT_CREATE_PARENT_INT_INT_INT 4 fn(parent, int32_t, int32_t, int32_t) + WGT_CREATE_PARENT_INT_BOOL 5 fn(parent, int32_t, bool) + WGT_CREATE_PARENT_BOOL 6 fn(parent, bool) + WGT_CREATE_PARENT_DATA 7 fn(parent, data, w, h, pitch) -- not auto-creatable +.endtable + +WGT_MAX_CREATE_ARGS is 3. + +.topic api.dialog +.title dvxDlg.h -- Modal Dialogs +.toc 1 dvxDlg.h -- Modal Dialogs +.index dvxDlg.h +.index dvxMessageBox +.index dvxFileDialog +.index dvxInputBox +.index dvxIntInputBox +.index dvxChoiceDialog +.index dvxPromptSave +.index dvxErrorBox +.index dvxInfoBox + +.h1 dvxDlg.h -- Modal Dialogs + +Pre-built modal dialog boxes that block the caller and run their own event loop via dvxUpdate() until the user dismisses them. Modal dialogs set ctx->modalWindow to prevent input from reaching other windows for the dialog's lifetime. + +.h2 Message Box Flags + +Button configurations (low nibble): + +.table + Define Value Description + ------ ----- ----------- + MB_OK 0x0000 OK button only (default) + MB_OKCANCEL 0x0001 OK and Cancel + MB_YESNO 0x0002 Yes and No + MB_YESNOCANCEL 0x0003 Yes, No, and Cancel + MB_RETRYCANCEL 0x0004 Retry and Cancel +.endtable + +Icon types (high nibble, OR with button flags): + +.table + Define Value Description + ------ ----- ----------- + MB_ICONINFO 0x0010 Information icon + MB_ICONWARNING 0x0020 Warning icon + MB_ICONERROR 0x0030 Error icon + MB_ICONQUESTION 0x0040 Question mark icon +.endtable + +Return values: + +.table + Define Value Button pressed + ------ ----- -------------- + ID_OK 1 OK + ID_CANCEL 2 Cancel or dialog closed + ID_YES 3 Yes + ID_NO 4 No + ID_RETRY 5 Retry +.endtable + +.h2 dvxMessageBox + +.code +int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags); +.endcode + +Display a modal message box. Blocks the caller by running dvxUpdate() in a loop until a button is pressed or the dialog is closed. The window is automatically destroyed on return. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Window title (NULL falls back to a generic default) + message Body text + flags MB_xxx button + icon flags, OR'd together +.endtable + +Returns: ID_xxx of the button that was pressed. + +.code +// Example: yes/no confirmation +int32_t r = dvxMessageBox(ctx, "Save", "Save changes before closing?", MB_YESNOCANCEL | MB_ICONQUESTION); +if (r == ID_YES) { saveFile(); } +.endcode + +.h2 dvxErrorBox + +.code +int32_t dvxErrorBox(AppContextT *ctx, const char *title, const char *message); +.endcode + +Convenience wrapper for MB_OK | MB_ICONERROR. Title defaults to "Error" when NULL. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Dialog title, or NULL for "Error" + message Error message +.endtable + +Returns: ID_OK. + +.h2 dvxInfoBox + +.code +int32_t dvxInfoBox(AppContextT *ctx, const char *title, const char *message); +.endcode + +Convenience wrapper for MB_OK | MB_ICONINFO. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Dialog title + message Informational message +.endtable + +Returns: ID_OK. + +.h2 File Dialog + +.h3 FileFilterT + +Describes a filter entry in the file dialog's format dropdown. The glob pattern is extracted from inside the parentheses. + +.table + Field Description + ----- ----------- + label e.g. "Text Files (*.txt)" or "Images (*.bmp;*.png;*.jpg;*.gif)" +.endtable + +.h3 File Dialog Flags + +.table + Define Value Description + ------ ----- ----------- + FD_OPEN 0x0000 File open dialog (default) + FD_SAVE 0x0001 File save dialog (with overwrite prompt) +.endtable + +.h3 dvxFileDialog + +.code +bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize); +.endcode + +Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a filename input, and an optional filter dropdown. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Window title + flags FD_OPEN or FD_SAVE + initialDir Starting directory (NULL = current working directory) + filters Array of FileFilterT, or NULL + filterCount Number of filter entries + outPath Output buffer for selected path + outPathSize Capacity of outPath buffer +.endtable + +Returns: true if the user selected a file, false if cancelled or closed. + +.h2 Input Boxes + +.h3 dvxInputBox + +.code +bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize); +.endcode + +Display a modal input box with a prompt label, text field, and OK/Cancel buttons. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Window title + prompt Prompt label above the input field + defaultText Initial text (NULL = empty) + outBuf Output buffer + outBufSize Capacity of outBuf +.endtable + +Returns: true if OK was pressed, false if cancelled. + +.h3 dvxIntInputBox + +.code +bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal); +.endcode + +Display a modal integer input box with a spinner (up/down arrows) for clamped numeric input. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Window title + prompt Prompt label + defaultVal Initial value + minVal Minimum allowed value + maxVal Maximum allowed value + step Increment per arrow click + outVal Output: entered value +.endtable + +Returns: true if OK pressed, false if cancelled. + +.h2 Choice Dialog + +.h3 dvxChoiceDialog + +.code +bool dvxChoiceDialog(AppContextT *ctx, const char *title, const char *prompt, const char **items, int32_t itemCount, int32_t defaultIdx, int32_t *outIdx); +.endcode + +Display a modal dialog with a listbox of choices. The user picks one item and clicks OK, or cancels. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Window title + prompt Prompt label above the list + items Array of choice strings + itemCount Number of items + defaultIdx Initially highlighted item (-1 = none) + outIdx Output: selected index +.endtable + +Returns: true if a choice was made, false if cancelled. + +.h2 Save Prompt + +Return codes for dvxPromptSave: + +.table + Define Value Action + ------ ----- ------ + DVX_SAVE_YES 1 Save, then proceed + DVX_SAVE_NO 2 Discard, then proceed + DVX_SAVE_CANCEL 3 Abort the operation +.endtable + +.h3 dvxPromptSave + +.code +int32_t dvxPromptSave(AppContextT *ctx, const char *title); +.endcode + +Display a canonical "Save changes?" dialog with Yes/No/Cancel buttons and an appropriate question icon. + +.table + Parameter Description + --------- ----------- + ctx Application context + title Document name for message context +.endtable + +Returns: DVX_SAVE_YES, DVX_SAVE_NO, or DVX_SAVE_CANCEL. + +.topic api.resource +.title dvxRes.h -- Resource System +.toc 1 dvxRes.h -- Resource System +.index dvxRes.h +.index Resources +.index DvxResHandleT +.index dvxResOpen +.index dvxResRead +.index dvxResFind +.index dvxResClose +.index dvxResAppend +.index dvxResRemove + +.h1 dvxRes.h -- DXE-Embedded Resource System + +Resources are appended to DXE3 files (.app, .wgt, .lib) after the normal DXE content. The DXE loader never reads past its own header-specified sections, so the appended resource block is invisible to dlopen. This lets each DXE carry its own icons, localized text, help content, and binary data without separate resource files. + +.h2 File Layout + +.code +[DXE3 content] -- untouched, loaded by dlopen +[resource data entries] -- sequential, variable length +[resource directory] -- fixed-size entries (48 bytes each) +[footer] -- magic + directory offset + entry count (16 bytes) +.endcode + +Reading starts from the end: seek to EOF - sizeof(footer), verify the magic, then seek to the directory. + +.h2 Constants + +.table + Define Value Description + ------ ----- ----------- + DVX_RES_ICON 1 Image data (BMP, PNG, etc.) + DVX_RES_TEXT 2 Null-terminated string + DVX_RES_BINARY 3 Arbitrary binary data (app-specific) + DVX_RES_MAGIC 0x52585644 Footer signature ("DVXR" little-endian) + DVX_RES_NAME_MAX 32 Max resource name length including null +.endtable + +.h2 Structures + +.h3 DvxResDirEntryT + +Directory entry describing one resource (48 bytes on disk). + +.table + Field Description + ----- ----------- + name Resource name (up to 32 chars, null-terminated) + type DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY + offset Absolute file offset of the data + size Data size in bytes + reserved Padding (must be zero) +.endtable + +.h3 DvxResFooterT + +Footer at the very end of a DXE file with appended resources (16 bytes). + +.table + Field Description + ----- ----------- + magic DVX_RES_MAGIC + dirOffset Absolute file offset of the resource directory + entryCount Number of directory entries + reserved Padding (must be zero) +.endtable + +.h3 DvxResHandleT + +Opaque runtime handle representing an open resource block. + +.table + Field Description + ----- ----------- + path Source file path + entries Directory entries (sorted by name for bsearch) + entryCount Number of entries +.endtable + +.h2 Runtime API + +.h3 dvxResOpen + +.code +DvxResHandleT *dvxResOpen(const char *path); +.endcode + +Open a resource handle by reading the footer and directory. Returns NULL if the file has no resource block or cannot be read. Entries are sorted on open for O(log n) name lookup via bsearch. + +.table + Parameter Description + --------- ----------- + path Path to the DXE (or any file) with appended resources +.endtable + +Returns: Handle pointer, or NULL. + +.h3 dvxResRead + +.code +void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize); +.endcode + +Find a resource by name and read its data into a freshly malloc'd buffer. Caller must free the returned buffer. + +.table + Parameter Description + --------- ----------- + h Resource handle + name Resource name + outSize Output: data size in bytes +.endtable + +Returns: Data buffer, or NULL if not found or I/O failed. + +.h3 dvxResFind + +.code +const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name); +.endcode + +Look up a resource's directory entry by name. The returned pointer is valid until dvxResClose. + +.table + Parameter Description + --------- ----------- + h Resource handle + name Resource name +.endtable + +Returns: Pointer to directory entry, or NULL if not found. + +.h3 dvxResClose + +.code +void dvxResClose(DvxResHandleT *h); +.endcode + +Close a resource handle and free its memory. + +.table + Parameter Description + --------- ----------- + h Handle to close +.endtable + +.h3 dvxResAppend + +.code +int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize); +.endcode + +Append a resource to an existing DXE file. Preserves existing resources and adds the new one. Thin wrapper over the resource writer in tools/dvxResWrite.h. + +.table + Parameter Description + --------- ----------- + path Target DXE path + name Resource name (up to DVX_RES_NAME_MAX - 1 chars) + type DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY + data Resource payload + dataSize Payload size in bytes +.endtable + +Returns: 0 on success, -1 on failure. + +.h3 dvxResRemove + +.code +int32_t dvxResRemove(const char *path, const char *name); +.endcode + +Remove a resource from a DXE file by name. If this was the last resource, the file is truncated back to the original DXE content. + +.table + Parameter Description + --------- ----------- + path Target DXE path + name Resource name to remove +.endtable + +Returns: 0 on success, -1 if not found or I/O failed. + +.h2 See Also + +.link api.app dvxResLoadIcon / dvxResLoadText / dvxResLoadData -- higher-level helpers on top of this API + +.topic api.prefs +.title dvxPrefs.h -- Preferences System +.toc 1 dvxPrefs.h -- Preferences System +.index dvxPrefs.h +.index Preferences +.index PrefsHandleT +.index prefsLoad +.index prefsSave +.index prefsGetString +.index prefsGetInt +.index prefsGetBool +.index prefsSetString +.index prefsSetInt +.index prefsSetBool + +.h1 dvxPrefs.h -- INI Preferences System + +Handle-based API over classic INI files. Each prefsLoad/prefsCreate returns a PrefsHandleT that must be passed to all subsequent calls and freed with prefsClose when done. Multiple INI files can be open simultaneously. + +.h2 Constants + +.table + Define Value Description + ------ ----- ----------- + DVX_INI_PATH "CONFIG" DVX_PATH_SEP "DVX.INI" Canonical system INI path +.endtable + +.h2 Types + +.h3 PrefsHandleT + +Opaque handle representing an in-memory INI file. Fields are not exposed. + +.h2 Lifecycle + +.h3 prefsCreate + +.code +PrefsHandleT *prefsCreate(void); +.endcode + +Create an empty preferences handle with no associated file. Use this when building an INI from scratch before calling prefsSaveAs. + +Returns: New handle, or NULL on allocation failure. + +.h3 prefsLoad + +.code +PrefsHandleT *prefsLoad(const char *filename); +.endcode + +Load an INI file into a new handle. If the file does not exist, returns a valid empty handle with the path stored so prefsSave can write a new file later. + +.table + Parameter Description + --------- ----------- + filename Path to INI file +.endtable + +Returns: Handle, or NULL on allocation failure. + +.h3 prefsSave + +.code +bool prefsSave(PrefsHandleT *h); +.endcode + +Save the in-memory state back to the file that was loaded (or captured by prefsLoad for a non-existent file). + +.table + Parameter Description + --------- ----------- + h Preferences handle +.endtable + +Returns: true on success. + +.h3 prefsSaveAs + +.code +bool prefsSaveAs(PrefsHandleT *h, const char *filename); +.endcode + +Save to a specific file, overriding the handle's stored path. + +.table + Parameter Description + --------- ----------- + h Preferences handle + filename Output file path +.endtable + +Returns: true on success. + +.h3 prefsClose + +.code +void prefsClose(PrefsHandleT *h); +.endcode + +Release all memory held by the handle. Does not save -- call prefsSave first if needed. + +.table + Parameter Description + --------- ----------- + h Handle to close +.endtable + +.h2 Getters + +.h3 prefsGetString + +.code +const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal); +.endcode + +Retrieve a string value from [section]/key. The returned pointer is valid until the key is modified or the handle is closed. + +.table + Parameter Description + --------- ----------- + h Preferences handle + section Section name (without brackets) + key Key name + defaultVal Returned if the key is not present +.endtable + +Returns: Key value or defaultVal. + +.h3 prefsGetInt + +.code +int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal); +.endcode + +Retrieve an integer value. Supports decimal and negative numbers. + +.table + Parameter Description + --------- ----------- + h Preferences handle + section Section name + key Key name + defaultVal Returned if the key is absent or non-numeric +.endtable + +Returns: Parsed int, or defaultVal. + +.h3 prefsGetBool + +.code +bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal); +.endcode + +Retrieve a boolean value. Recognises "true"/"yes"/"1" as true and "false"/"no"/"0" as false (case-insensitive). + +.table + Parameter Description + --------- ----------- + h Preferences handle + section Section name + key Key name + defaultVal Returned if the key is absent or unrecognised +.endtable + +Returns: Parsed bool, or defaultVal. + +.h2 Setters + +.h3 prefsSetString + +.code +void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value); +.endcode + +Set a string value. Creates the section and key if they do not already exist. + +.h3 prefsSetInt + +.code +void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value); +.endcode + +Set an integer value (stored in decimal). + +.h3 prefsSetBool + +.code +void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value); +.endcode + +Set a boolean value (stored as "true"/"false"). + +.h3 prefsRemove + +.code +void prefsRemove(PrefsHandleT *h, const char *section, const char *key); +.endcode + +Remove a key from a section. No-op if the key does not exist. + +.topic api.mem +.title dvxMem.h -- Memory Tracking +.toc 1 dvxMem.h -- Memory Tracking +.index dvxMem.h +.index dvxMalloc +.index dvxFree +.index dvxCalloc +.index dvxRealloc +.index dvxStrdup +.index dvxMemGetAppUsage +.index dvxMemResetApp +.index dvxMemSnapshotLoad + +.h1 dvxMem.h -- Per-App Memory Tracking + +Wraps malloc/free/calloc/realloc/strdup with a 16-byte header that records the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected when an app is terminated. + +DXE code does NOT need to include this header for tracking to work. The DXE export table maps malloc/free/calloc/realloc/strdup to these wrappers transparently. Call sites that need the explicit dvxXxx names (e.g. dvxMemGetAppUsage) or the dvxMemAppIdPtr declaration should include dvxMem.h directly. + +.h2 Globals + +.h3 dvxMemAppIdPtr + +.code +extern int32_t *dvxMemAppIdPtr; +.endcode + +Pointer to the current owning app ID. The shell points this at its currentAppId field during init so every allocation is charged to the running app. When dvxMemAppIdPtr is NULL or points to zero, allocations are charged to the shell. + +.h2 Allocation Wrappers + +.h3 dvxMalloc + +.code +void *dvxMalloc(size_t size); +.endcode + +Tracked malloc. Returns a pointer to user memory (header is hidden in front of it). + +.h3 dvxCalloc + +.code +void *dvxCalloc(size_t nmemb, size_t size); +.endcode + +Tracked calloc. + +.h3 dvxRealloc + +.code +void *dvxRealloc(void *ptr, size_t size); +.endcode + +Tracked realloc. Works correctly with tracked pointers only -- the magic header identifies them. + +.h3 dvxFree + +.code +void dvxFree(void *ptr); +.endcode + +Tracked free. Falls through to the real free() if the pointer's header magic does not match, so mixing tracked and untracked pointers is safe. + +.h3 dvxStrdup + +.code +char *dvxStrdup(const char *s); +.endcode + +Tracked strdup. + +.h2 Accounting + +.h3 dvxMemSnapshotLoad + +.code +void dvxMemSnapshotLoad(int32_t appId); +.endcode + +Record a baseline memory snapshot for the given app. Called right before app code starts so later calls to dvxMemGetAppUsage can report net growth. + +.table + Parameter Description + --------- ----------- + appId App ID to snapshot +.endtable + +.h3 dvxMemGetAppUsage + +.code +uint32_t dvxMemGetAppUsage(int32_t appId); +.endcode + +Return the total bytes currently charged to the given app ID. + +.table + Parameter Description + --------- ----------- + appId App ID to query +.endtable + +Returns: Bytes allocated for this app. + +.h3 dvxMemResetApp + +.code +void dvxMemResetApp(int32_t appId); +.endcode + +Free every tracked allocation charged to the given app. Called by the shell after an app exits or crashes to reclaim its memory. + +.table + Parameter Description + --------- ----------- + appId App ID to flush +.endtable + +.topic api.widgetplugin +.title dvxWgtP.h -- Widget Plugin API +.toc 1 dvxWgtP.h -- Widget Plugin API +.index dvxWgtP.h +.index widgetAlloc +.index widgetAddChild +.index widgetDestroyChildren +.index widgetHitTest +.index widgetFindNextFocusable +.index widgetFindPrevFocusable +.index widgetFindByAccel +.index widgetDrawScrollbarH +.index widgetDrawScrollbarV + +.h1 dvxWgtP.h -- Widget Plugin API + +Included by widget .c files (DXE modules) to access core infrastructure for tree manipulation, focus management, scrollbar drawing, layout helpers, and shared interaction state. This header is NOT intended for application code -- use dvxWgt.h for the public widget API. + +.h2 Validation Macros + +.h3 VALIDATE_WIDGET / VALIDATE_WIDGET_VOID + +.code +VALIDATE_WIDGET(w, wtype, retval); // returns retval on mismatch +VALIDATE_WIDGET_VOID(w, wtype); // returns void on mismatch +.endcode + +Bail out of a widget method if the widget pointer is NULL or its type does not match the expected wtype. Use at the top of every widget-specific API function. + +.h2 Core Constants + +.table + Define Value Description + ------ ----- ----------- + DEFAULT_SPACING 4 Default pixel spacing between VBox/HBox children + DEFAULT_PADDING 4 Default internal padding for containers + SB_MIN_THUMB 14 Minimum scrollbar thumb size in pixels + WGT_SB_W 14 Widget-internal scrollbar width + KEY_MOD_SHIFT 0x03 Shift modifier bits + KEY_MOD_CTRL 0x04 Ctrl modifier bit + KEY_MOD_ALT 0x08 Alt modifier bit +.endtable + +.h2 Inline Helpers + +.h3 clampInt + +.code +static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi); +.endcode + +Clamp val into the [lo, hi] range. + +.h3 drawTextEmbossed + +.code +static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors); +.endcode + +Draw text twice -- once offset by (1,1) in highlight color, then overlaid in shadow color at (x,y) -- for a classic Motif-style embossed look used on disabled labels. + +.h3 drawTextAccelEmbossed + +.code +static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors); +.endcode + +Embossed text with & accelerator markers. Same offset logic as drawTextEmbossed, but processes & markers. + +.h2 Shared Interaction State + +Extern variables defined in widgetCore.c. These hold system-wide widget state: only one focused widget, one open popup, one pressed button at a time. + +.table + Variable Type Purpose + -------- ---- ------- + sCursorBlinkOn bool Current state of the text cursor blink + sDblClickTicks clock_t Double-click interval in clock() ticks + sDebugLayout bool When true, draw colored debug borders around containers + sClosedPopup WidgetT* Widget whose popup just closed (suppresses re-open) + sFocusedWidget WidgetT* Currently focused widget + sKeyPressedBtn WidgetT* Button being pressed via keyboard (for release tracking) + sOpenPopup WidgetT* Currently open popup (dropdown/combo list) + sDragWidget WidgetT* Widget being dragged (any drag type) + sPollWidgetCount int32_t Count of widgets with WCLASS_NEEDS_POLL flag set + sPollWidgets WidgetT** Dynamic array of poll widgets (stb_ds) + sCursorBlinkFn fn ptr Callback invoked when the blink flips (repaints focused text) +.endtable + +.h2 Core Widget Functions + +.h3 widgetAlloc + +.code +WidgetT *widgetAlloc(WidgetT *parent, int32_t type); +.endcode + +Allocate a new WidgetT, initialize its fields to defaults, link it as the last child of parent (if non-NULL), and assign its class pointer from widgetClassTable[type]. + +.h3 widgetAddChild + +.code +void widgetAddChild(WidgetT *parent, WidgetT *child); +.endcode + +Append child to parent's child list. Updates parent's firstChild/lastChild and child's nextSibling pointers. + +.h3 widgetRemoveChild + +.code +void widgetRemoveChild(WidgetT *parent, WidgetT *child); +.endcode + +Unlink child from parent's child list. Does not destroy the child. + +.h3 widgetDestroyChildren + +.code +void widgetDestroyChildren(WidgetT *w); +.endcode + +Recursively destroy all descendants of w, calling each one's wclsDestroy handler. + +.h2 Focus Management + +.h3 widgetFindNextFocusable + +.code +WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after); +.endcode + +Find the next focusable widget in tab order after the given widget. Wraps to the beginning at the end of the tree. + +.h3 widgetFindPrevFocusable + +.code +WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before); +.endcode + +Find the previous focusable widget in tab order. Wraps to the end at the beginning of the tree. + +.h3 widgetFindByAccel + +.code +WidgetT *widgetFindByAccel(WidgetT *root, char key); +.endcode + +Find a focusable widget whose accelKey (lowercase) matches the given character. Used for Alt+letter hotkeys on buttons and labels. + +.h2 Hit Testing + +.h3 widgetHitTest + +.code +WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y); +.endcode + +Find the deepest widget at (x, y) within w's subtree. Respects WCLASS_NO_HIT_RECURSE so certain widget types can prevent children from being hit-tested. + +.h2 Utility Queries + +.h3 widgetCountVisibleChildren + +.code +int32_t widgetCountVisibleChildren(const WidgetT *w); +.endcode + +Count children whose visible flag is true. + +.h3 widgetFrameBorderWidth + +.code +int32_t widgetFrameBorderWidth(const WidgetT *w); +.endcode + +Return the effective border width for a container widget (used to inset child layout). + +.h3 widgetIsFocusable + +.code +bool widgetIsFocusable(int32_t type); +.endcode + +True if the widget class with this type ID has the WCLASS_FOCUSABLE flag. + +.h3 widgetIsHorizContainer + +.code +bool widgetIsHorizContainer(int32_t type); +.endcode + +True if the widget class lays out children horizontally (WCLASS_HORIZ_CONTAINER). + +.h3 multiClickDetect + +.code +int32_t multiClickDetect(int32_t vx, int32_t vy); +.endcode + +Track consecutive clicks at roughly the same position within sDblClickTicks. Returns the current click count (1 for single, 2 for double, etc.). + +.h2 Clipboard + +.h3 clipboardCopy + +.code +void clipboardCopy(const char *text, int32_t len); +.endcode + +Copy text to the process-wide clipboard buffer. Same underlying storage as dvxClipboardCopy. + +.h3 clipboardGet + +.code +const char *clipboardGet(int32_t *outLen); +.endcode + +Retrieve clipboard text. + +.h3 clipboardMaxLen + +.code +int32_t clipboardMaxLen(void); +.endcode + +Return the fixed capacity of the clipboard buffer in bytes. + +.h2 Scrollbar Helpers + +.h3 ScrollHitE + +Scrollbar hit-test result. + +.table + Value Description + ----- ----------- + ScrollHitNoneE Click fell outside the scrollbar + ScrollHitArrowDecE Up/left arrow + ScrollHitArrowIncE Down/right arrow + ScrollHitPageDecE Trough above/before the thumb + ScrollHitPageIncE Trough below/after the thumb + ScrollHitThumbE The draggable thumb +.endtable + +.h3 widgetScrollbarThumb + +.code +void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize); +.endcode + +Compute thumb position and size for a scrollbar with the given geometry. Enforces SB_MIN_THUMB. + +.h3 widgetDrawScrollbarV / widgetDrawScrollbarVEx + +.code +void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); +void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW); +.endcode + +Draw a vertical scrollbar inside a widget. The Ex variant lets the caller specify bar width for non-standard scrollbars (e.g. combo popups). Standard variant uses WGT_SB_W. + +.h3 widgetDrawScrollbarH / widgetDrawScrollbarHEx + +.code +void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); +void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW); +.endcode + +Draw a horizontal scrollbar inside a widget. Standard variant uses WGT_SB_W. + +.h3 widgetScrollbarHitTest + +.code +ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); +.endcode + +Classify a click position along a scrollbar into ScrollHitE. sbLen is the scrollbar length, relPos is the click position along the bar. + +.h2 Layout Functions + +.h3 widgetCalcMinSizeBox / widgetCalcMinSizeTree + +.code +void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font); +void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font); +.endcode + +Generic box-container min-size calculation (bottom-up layout pass). widgetCalcMinSizeBox handles a single VBox/HBox; widgetCalcMinSizeTree recurses through the whole subtree. + +.h3 widgetLayoutBox / widgetLayoutChildren + +.code +void widgetLayoutBox(WidgetT *w, const BitmapFontT *font); +void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font); +.endcode + +Generic box-container layout (top-down pass). widgetLayoutBox lays out a single container; widgetLayoutChildren processes each child's layout handler. + +.h2 Event Dispatch + +.h3 widgetOnPaint / widgetOnMouse / widgetOnKey / etc. + +.code +void widgetOnPaint(WindowT *win, RectT *dirtyArea); +void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons); +void widgetOnKey(WindowT *win, int32_t key, int32_t mod); +void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod); +void widgetOnResize(WindowT *win, int32_t newW, int32_t newH); +void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value); +void widgetOnFocus(WindowT *win); +void widgetOnBlur(WindowT *win); +.endcode + +Window-level event handlers that dispatch to individual widgets in the tree. wgtInitWindow installs these as the window's onPaint/onMouse/onKey/etc. callbacks. + +.h3 widgetManageScrollbars + +.code +void widgetManageScrollbars(WindowT *win, AppContextT *ctx); +.endcode + +Add, remove, or resize window scrollbars based on the widget tree's current min-size vs. the window content area. + +.h2 Paint Helpers + +.h3 widgetPaintOne + +.code +void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); +.endcode + +Paint a single widget (sets clip rect, calls the widget's PAINT handler). Does not recurse. + +.h3 widgetPaintOverlays + +.code +void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); +.endcode + +Paint the overlay pass: dropdown popups and tooltips drawn on top of the main tree. + +.h2 Pressable Button Helpers + +Shared state machine for WCLASS_PRESS_RELEASE widgets (Button, ImageButton). The w->pressed flag tracks the visual state; these helpers fire the widget's onClick callback on release-within-bounds. + +.h3 widgetPressableOnMouse / widgetPressableOnKey / widgetPressableOnDragUpdate / widgetPressableOnDragEnd / widgetPressableOnAccelActivate + +.code +void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy); +void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod); +void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y); +void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y); +void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root); +.endcode + +Register these directly as the widget class's handlers for WGT_METHOD_ON_MOUSE, WGT_METHOD_ON_KEY, WGT_METHOD_ON_DRAG_UPDATE, WGT_METHOD_ON_DRAG_END, and WGT_METHOD_ON_ACCEL_ACTIVATE. + +.h2 Generic Text Handlers + +For widgets whose data struct has a (const char *) as its first field and the WCLASS_HAS_TEXT flag set, these implement GET_TEXT / SET_TEXT directly. + +.h3 widgetTextGet / widgetTextSet + +.code +const char *widgetTextGet(const WidgetT *w); +void widgetTextSet(WidgetT *w, const char *text); +.endcode + +Register these directly as the widget class's handlers for WGT_METHOD_GET_TEXT and WGT_METHOD_SET_TEXT. The text is strdup'd on set and freed automatically on destroy. + +.h2 Class Table + +.h3 widgetClassTable + +.code +extern const WidgetClassT **widgetClassTable; +.endcode + +stb_ds dynamic array. Each widget DXE appends its class pointer via wgtRegisterClass() during wgtRegister(). Indexed by the runtime type ID assigned at registration. + +.topic api.platform +.title platform/dvxPlat.h -- Platform Layer +.toc 1 platform/dvxPlat.h -- Platform Layer +.index dvxPlat.h +.index Platform Layer +.index PlatformKeyEventT +.index platformVideoInit +.index platformFlushRect +.index platformMousePoll +.index platformKeyboardRead +.index dvxLog + +.h1 platform/dvxPlat.h -- Platform Abstraction Layer + +All OS-specific and CPU-specific code is isolated behind this interface. Port DVX to a new platform by implementing a new dvxPlatformXxx.c against this header. Currently the only implementation is dvxPlatformDos.c for DJGPP/DPMI. + +Platform functions must be stateless (or manage their own internal state) and must not reference AppContextT or any layer above dvxTypes.h. + +.h2 Constants + +.table + Define Value Description + ------ ----- ----------- + DVX_MAX_PATH 260 Maximum file path length + PLATFORM_SYSINFO_MAX 4096 Maximum size of formatted system info text + DVX_PATH_SEP "\\" Native directory separator on DOS +.endtable + +.h2 Types + +.h3 PlatformKeyEventT + +Keyboard event with ASCII and scancode separated. Extended keys (arrows, F-keys) have ascii == 0 and a non-zero scancode. + +.table + Field Description + ----- ----------- + ascii ASCII value, 0 for extended/function keys + scancode PC scancode (e.g. 0x48 = Up, 0x50 = Down) +.endtable + +.h3 PlatformLogFnT + +.code +typedef void (*PlatformLogFnT)(const char *fmt, ...); +.endcode + +Log function signature used by the crash handler. + +.h3 PlatformSymOverrideT + +Name/function-pointer pair for registering symbol overrides with the DXE loader. + +.table + Field Description + ----- ----------- + name Symbol name as seen by DXEs + func Function pointer to use instead of the default +.endtable + +.h2 Lifecycle + +.h3 dvxLog + +.code +void dvxLog(const char *fmt, ...); +.endcode + +Append a formatted line to dvx.log. Implemented in dvx.exe and exported to every DXE via the loader. + +.h3 platformInit + +.code +void platformInit(void); +.endcode + +One-time platform initialization. On DOS this installs signal handlers for clean shutdown on Ctrl+C/Ctrl+Break. + +.h3 platformYield + +.code +void platformYield(void); +.endcode + +Cooperative CPU yield, used between frames when the event loop has nothing to do. On DOS this calls __dpmi_yield() to be friendly to multitaskers. + +.h2 Video + +.h3 platformVideoInit / platformVideoShutdown + +.code +int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp); +void platformVideoShutdown(DisplayT *d); +.endcode + +Low-level video mode setup and teardown. Called by videoInit / videoShutdown. + +.h3 platformVideoEnumModes + +.code +void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData); +.endcode + +Enumerate LFB-capable graphics modes. Invokes cb once per available mode. + +.h3 platformVideoSetPalette + +.code +void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count); +.endcode + +Program the VGA/VESA DAC palette registers (8-bit mode only). + +.h3 platformVideoFreeBuffers + +.code +void platformVideoFreeBuffers(DisplayT *d); +.endcode + +Free the backbuffer and palette without restoring text mode. Used when switching between graphics modes live. + +.h2 Framebuffer Flush + +.h3 platformFlushRect + +.code +void platformFlushRect(const DisplayT *d, const RectT *r); +.endcode + +Copy a rectangle from the system RAM backbuffer to the LFB. The single critical path where PCI bus write speed matters. On DOS, each scanline uses rep movsd for aligned 32-bit writes. + +.h2 Span Operations + +.h3 platformSpanFill8/16/32 + +.code +void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count); +void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count); +void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count); +.endcode + +Fill count pixels at dst with color. Three depth variants because the fill instruction differs by size. On DOS these use inline rep stosl / rep stosw / rep stosb asm. + +.h3 platformSpanCopy8/16/32 + +.code +void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count); +void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count); +void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count); +.endcode + +Copy count pixels from src to dst. On DOS these use rep movsd. + +.h2 Mouse Input + +.h3 platformMouseInit + +.code +void platformMouseInit(int32_t screenW, int32_t screenH); +.endcode + +Initialize the mouse driver and constrain movement to the screen bounds. + +.h3 platformMousePoll + +.code +void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons); +.endcode + +Poll current mouse state. Buttons is a bitmask: bit 0 = left, bit 1 = right, bit 2 = middle. + +.h3 platformMouseWheelInit / platformMouseWheelPoll + +.code +bool platformMouseWheelInit(void); +int32_t platformMouseWheelPoll(void); +.endcode + +Detect and activate the CuteMouse Wheel API. platformMouseWheelPoll returns the accumulated delta (positive = scroll down) since the last poll. + +.h3 platformMouseSetAccel / platformMouseSetMickeys + +.code +void platformMouseSetAccel(int32_t threshold); +void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys); +.endcode + +Set the double-speed threshold and mickey-to-pixel ratio. See dvxSetMouseConfig for a higher-level interface. + +.h3 platformMouseWarp + +.code +void platformMouseWarp(int32_t x, int32_t y); +.endcode + +Move the mouse cursor to an absolute screen position. Used to clamp the cursor to window edges during resize. + +.h2 Keyboard Input + +.h3 platformKeyboardGetModifiers + +.code +int32_t platformKeyboardGetModifiers(void); +.endcode + +Return the current modifier key state in BIOS shift-state format. + +.h3 platformKeyboardRead + +.code +bool platformKeyboardRead(PlatformKeyEventT *evt); +.endcode + +Non-blocking read of the next key from the keyboard buffer. Returns true if a key was available. + +.h3 platformKeyUpInit / platformKeyUpShutdown / platformKeyUpRead + +.code +void platformKeyUpInit(void); +void platformKeyUpShutdown(void); +bool platformKeyUpRead(PlatformKeyEventT *evt); +.endcode + +Key-up event detection. Install/uninstall the INT 9 hook around process lifetime. platformKeyUpRead returns true if a key release was detected since the last call. + +.h3 platformAltScanToChar + +.code +char platformAltScanToChar(int32_t scancode); +.endcode + +Translate an Alt+key scancode to its corresponding ASCII character. Returns 0 for non-printable scancodes. + +.h2 System Information + +.h3 platformGetSystemInfo + +.code +const char *platformGetSystemInfo(const DisplayT *display); +.endcode + +Return a pre-formatted text string with hardware/OS information (CPU, clock, memory, video, mouse, disks). Valid for the lifetime of the process. + +.h3 platformGetMemoryInfo + +.code +bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb); +.endcode + +Query total and free physical memory. + +.h2 File System + +.h3 platformValidateFilename + +.code +const char *platformValidateFilename(const char *name); +.endcode + +Validate a filename against platform-specific rules (8.3 on DOS). Returns NULL if valid, or a human-readable error string. + +.h3 platformMkdirRecursive + +.code +int32_t platformMkdirRecursive(const char *path); +.endcode + +Create a directory and all parent directories (like mkdir -p). Existing directories are not an error. + +.h3 platformChdir + +.code +int32_t platformChdir(const char *path); +.endcode + +Change the working directory, including drive letter on DOS (calls setdisk() first when the path starts with "X:"). + +.h3 platformPathDirEnd + +.code +char *platformPathDirEnd(const char *path); +.endcode + +Return a pointer to the last directory separator in path, or NULL. On DOS accepts both "/" and "\\". + +.h3 platformPathBaseName + +.code +const char *platformPathBaseName(const char *path); +.endcode + +Return a pointer to the leaf (basename) portion of path. + +.h3 platformReadFile + +.code +char *platformReadFile(const char *path, int32_t *outLen); +.endcode + +Slurp a whole file into a freshly malloc'd, NUL-terminated buffer. Caller frees. Binary-safe: the NUL is past the end of the reported length. + +.h3 platformGlobMatch + +.code +bool platformGlobMatch(const char *pattern, const char *name); +.endcode + +Simple glob pattern match. Case-insensitive. Supports * (zero or more chars) and ? (single char). + +.h3 platformLineEnding / platformStripLineEndings + +.code +const char *platformLineEnding(void); +int32_t platformStripLineEndings(char *buf, int32_t len); +.endcode + +Return the native line ending string, or strip line-ending characters from a buffer in place. + +.h2 String Helpers + +.h3 dvxSkipWs + +.code +const char *dvxSkipWs(const char *s); +.endcode + +Advance past leading spaces and tabs. Returns s unchanged if NULL or already non-whitespace. + +.h3 dvxTrimRight + +.code +int32_t dvxTrimRight(char *buf); +.endcode + +Strip trailing spaces, tabs, carriage returns, and newlines from buf in place. Returns the new length. + +.h2 DXE Module Support + +.h3 platformRegisterDxeExports + +.code +void platformRegisterDxeExports(void); +.endcode + +Register platform and C runtime symbols with the dynamic module loader so DXE modules can resolve them at load time. + +.h3 platformRegisterSymOverrides + +.code +void platformRegisterSymOverrides(const PlatformSymOverrideT *entries); +.endcode + +Register function pointer overrides for subsequently loaded modules. entries is terminated by {NULL, NULL}. + +.h2 Crash Recovery + +.h3 platformInstallCrashHandler + +.code +void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn); +.endcode + +Install signal handlers for SIGSEGV, SIGFPE, SIGILL. On crash, logs platform-specific diagnostics via logFn, stores the signal in *crashSignal, then longjmps to recoveryBuf. + +.h3 platformLogCrashDetail + +.code +void platformLogCrashDetail(int sig, PlatformLogFnT logFn); +.endcode + +Log platform-specific crash diagnostics. Called internally from the crash handler and exposed for manual invocation. + +.h2 VGA Splash Screen + +.h3 platformSplashInit / platformSplashShutdown + +.code +void platformSplashInit(void); +void platformSplashShutdown(void); +.endcode + +Enter/leave VGA mode 13h (320x200x256) used for the boot splash. + +.h3 platformSplashLoadRaw + +.code +bool platformSplashLoadRaw(const char *path); +.endcode + +Load and display a raw splash file (768-byte palette + 64000-byte pixel data). + +.h3 platformSplashFillRect + +.code +void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color); +.endcode + +Fill a rectangle on the mode 13h screen with a palette index. Used to draw the progress bar during module loading. diff --git a/src/libs/kpunch/libdvx/arch.dhs b/src/libs/kpunch/libdvx/arch.dhs index b345679..dd7f2d5 100644 --- a/src/libs/kpunch/libdvx/arch.dhs +++ b/src/libs/kpunch/libdvx/arch.dhs @@ -42,7 +42,7 @@ DVX (DOS Visual eXecutive) is a complete windowing GUI compositor targeting DJGP .item VESA VBE 2.0+ LFB only -- no bank switching. If the hardware cannot provide a linear framebuffer, initialization fails. .item 486 baseline -- all hot paths are written to be fast on a 486, with Pentium-specific paths where the gain is significant. .item Single-tasking cooperative model -- applications yield the CPU via tsYield(); there is no preemptive scheduler. -.item 86Box is the trusted reference platform for testing. DOSBox-X is not used; any bugs observed are treated as DVX bugs. +.item A single emulator is the trusted reference platform for testing; any bugs observed there are treated as DVX bugs. .item No external font or cursor files -- all bitmaps are compiled in as static const data. .endlist @@ -54,6 +54,10 @@ The runtime environment consists of a bootstrap loader (dvx.exe) that loads core .link arch.pipeline Display Pipeline .link arch.windows Window System .link arch.widgets Widget System +.link arch.dialogs Modal Dialogs +.link arch.resources Resource System +.link arch.prefs Preferences System +.link arch.memtrack Per-App Memory Tracking .link arch.dxe DXE Module System .link arch.events Event Model .link arch.fonts Font System @@ -97,7 +101,7 @@ DVX is organized into five layers, each implemented as a single .h/.c pair. Ever | +------------------------------------------+ | | | | +------------------------------------------+ | - | | Platform Layer (dvxPlatform.h) | | dvxPlatformDos.c + | | Platform Layer (dvxPlat.h) | | dvxPlatformDos.c | | OS-specific: video, input, asm spans | | | +------------------------------------------+ | | | @@ -270,7 +274,7 @@ wmHitTest() iterates the stack front-to-back and returns a hit-part identifier: .index Menus .index Submenus -Menus use fixed-size arrays with inline char buffers (no heap strings). Up to 8 menus per bar, items dynamically allocated. Supports cascading submenus via MenuItemT.subMenu pointer. Item types: normal, checkbox, radio. Separators are non-interactive items. The popup state (PopupStateT) tracks a stack of parent frames for cascading submenu nesting. +Menus use fixed-size inline char buffers for labels (MAX_MENU_LABEL = 32, no heap strings). Both menus per bar and items per menu are stored in dynamic arrays that grow on demand. Supports cascading submenus via MenuItemT.subMenu pointer. Item types: normal, checkbox, radio. Separators are non-interactive items. The popup state (PopupStateT) tracks a stack of parent frames for cascading submenu nesting. .h2 Minimized Windows @@ -287,7 +291,7 @@ Minimized windows display as 64x64 icons at the bottom of the screen with bevele .h1 Widget System -The widget system (dvxWidget.h) is a retained-mode toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container. +The widget system (dvxWgt.h) is a retained-mode toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container. .h2 WidgetT Base Structure @@ -378,6 +382,143 @@ Each widget DXE registers a small API struct under a name during wgtRegister(). Each widget can register an interface descriptor that describes its BASIC-facing properties, methods, and events. These descriptors are used by the form runtime and IDE for generic dispatch and property panel enumeration. Properties have typed getters/setters (WGT_IFACE_STRING, WGT_IFACE_INT, WGT_IFACE_BOOL, WGT_IFACE_ENUM). +.topic arch.dialogs +.title Modal Dialogs +.toc 1 Modal Dialogs +.index Dialogs +.index Message Box +.index File Dialog +.index Input Box + +.h1 Modal Dialogs (dvxDlg.h) + +Pre-built modal dialog boxes block the caller and run their own event loop via dvxUpdate() until the user dismisses them. Each dialog sets ctx->modalWindow to prevent input from reaching other windows while open. + +.h2 Dialog Types + +.table + Function Purpose + -------- ------- + dvxMessageBox Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.) + dvxErrorBox Shortcut for MB_OK + MB_ICONERROR + dvxInfoBox Shortcut for MB_OK + MB_ICONINFO + dvxFileDialog File open/save dialog with directory navigation and filter dropdown + dvxInputBox Single-line text input with OK/Cancel + dvxIntInputBox Integer input with spinner, clamp range, and step + dvxChoiceDialog List selection with OK/Cancel + dvxPromptSave Canonical "Save changes?" Yes/No/Cancel dialog +.endtable + +.h2 Flag Encoding + +dvxMessageBox flags split into two nibbles: button configuration (low) and icon type (high). This matches the Win16 MessageBox() convention so OR'd flags read naturally: + +.code +dvxMessageBox(ctx, "Confirm", "Delete file?", MB_YESNO | MB_ICONQUESTION); +.endcode + +Return values are ID_OK / ID_CANCEL / ID_YES / ID_NO / ID_RETRY. + +.topic arch.resources +.title Resource System +.toc 1 Resource System +.index Resources +.index DXE Resources +.index DvxResHandleT + +.h1 Resource System (dvxRes.h) + +Resources are appended to DXE3 files (.app, .wgt, .lib) after the normal DXE content. The DXE loader never reads past the DXE header, so the appended data is invisible to dlopen. Every DXE can carry its own icons, text strings, and binary data without separate resource files. + +.h2 On-Disk Layout + +.code +[DXE3 content] -- untouched, loaded by dlopen +[resource data entries] -- sequential, variable length +[resource directory] -- fixed-size entries (48 bytes each) +[footer] -- magic + directory offset + count (16 bytes) +.endcode + +Readers start from the end: seek to EOF - sizeof(footer), verify the magic (DVX_RES_MAGIC = "DVXR"), then seek to the directory. Entries are sorted at open time so lookups are O(log n) via bsearch. + +.h2 Types + +.table + Type ID Value Payload + ------- ----- ------- + DVX_RES_ICON 1 Image data (BMP or PNG, typically 16x16 or 32x32 icons) + DVX_RES_TEXT 2 Null-terminated string (localisable UI text, author, copyright) + DVX_RES_BINARY 3 Arbitrary data (app-specific) +.endtable + +.h2 Runtime API + +.table + Function Description + -------- ----------- + dvxResOpen Open a handle by reading the footer and directory + dvxResRead Look up a resource by name and read its data into a malloc'd buffer + dvxResFind Look up a resource's directory entry (no data copy) + dvxResClose Release the handle + dvxResAppend Append a new resource to a DXE file + dvxResRemove Remove a resource by name +.endtable + +The dvxApp.h wrappers (dvxResLoadIcon, dvxResLoadText, dvxResLoadData) combine open + read + close for the common case. + +.topic arch.prefs +.title Preferences System +.toc 1 Preferences System +.index Preferences +.index INI files +.index PrefsHandleT + +.h1 Preferences System (dvxPrefs.h) + +Handle-based API over classic INI files. Multiple files can be open at once. Each prefsLoad / prefsCreate returns a PrefsHandleT opaque pointer passed to subsequent calls and freed with prefsClose. The system INI lives at DVX_INI_PATH = "CONFIG\\DVX.INI". + +.h2 Usage Pattern + +.code +PrefsHandleT *h = prefsLoad(DVX_INI_PATH); +int32_t w = prefsGetInt(h, "video", "width", 640); +bool sound = prefsGetBool(h, "audio", "enable", true); +const char *bg = prefsGetString(h, "desktop", "wallpaper", ""); + +prefsSetInt(h, "video", "width", 800); +prefsSave(h); +prefsClose(h); +.endcode + +Boolean getters recognise "true"/"yes"/"1" and "false"/"no"/"0" (case-insensitive). If the file does not exist, prefsLoad still returns a valid empty handle with the path captured so prefsSave can write a new file. + +.topic arch.memtrack +.title Per-App Memory Tracking +.toc 1 Per-App Memory Tracking +.index Memory Tracking +.index dvxMalloc +.index dvxFree + +.h1 Per-App Memory Tracking (dvxMem.h) + +All allocations route through dvxMalloc/dvxFree wrappers that prepend a 16-byte header recording the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected at app termination. + +.h2 Transparent Interception + +DXE code does NOT need to include dvxMem.h. The DXE export table maps malloc / free / calloc / realloc / strdup to the dvxXxx wrappers, so standard C code is tracked automatically. + +Explicit use (e.g. in the Task Manager) can include dvxMem.h to call: + +.table + Function Purpose + -------- ------- + dvxMemSnapshotLoad Baseline a newly-loaded app's memory state + dvxMemGetAppUsage Query current bytes allocated for an app + dvxMemResetApp Free every tracked allocation charged to an app +.endtable + +The dvxMemAppIdPtr pointer is set by the shell to &ctx->currentAppId so the allocator always knows which app to charge. + .topic arch.dxe .title DXE Module System .toc 1 DXE Module System @@ -625,7 +766,7 @@ Bevels are the defining visual element of the Motif aesthetic. Convenience macro .title Platform Layer .toc 1 Platform Layer .index Platform Layer -.index dvxPlatform +.index dvxPlat.h .index VESA .index VBE .index INT 33h @@ -636,7 +777,7 @@ Bevels are the defining visual element of the Motif aesthetic. Convenience macro .h1 Platform Layer -All OS-specific and CPU-specific code is isolated behind dvxPlatform.h. To port DVX, implement a new dvxPlatformXxx.c against this header. +All OS-specific and CPU-specific code is isolated behind platform/dvxPlat.h. To port DVX, implement a new dvxPlatformXxx.c against this header. .h2 Implementations @@ -694,7 +835,7 @@ DVX is cross-compiled from Linux using a DJGPP cross-compiler (i586-pc-msdosdjgp .code make -- build everything - ./mkcd.sh -- build + create ISO for 86Box + ./mkcd.sh -- build + create ISO for the target emulator .endcode .h2 Build Targets @@ -744,7 +885,7 @@ The -E flag specifies exported symbols (prefixed with underscore per DJGPP conve .item Verifies critical outputs exist (dvx.exe, libtasks.lib, libdvx.lib, dvxshell.lib). .item Counts widget modules. .item Creates an ISO 9660 image from bin/ using mkisofs: -iso-level 1 (strict 8.3 filenames for DOS), -J (Joliet extensions for long names), -V DVX (volume label). -.item Places the ISO at ~/.var/app/net._86box._86Box/data/86Box/dvx.iso for 86Box to mount as CD-ROM. +.item Places the ISO at the target emulator's CD-ROM mount path. .endlist .h2 Compiler Flags @@ -761,7 +902,7 @@ The -E flag specifies exported symbols (prefixed with underscore per DJGPP conve .code dvxgui/ +-- core/ Core library sources (dvxVideo, dvxDraw, dvxComp, dvxWm, dvxApp, widget infra) - | +-- platform/ Platform abstraction (dvxPlatform.h, dvxPlatformDos.c) + | +-- platform/ Platform abstraction (dvxPlat.h, dvxPlatformDos.c, dvxPlatformUtil.c) | +-- thirdparty/ stb_image, stb_ds, stb_image_write +-- loader/ Bootstrap loader (dvx.exe) +-- tasks/ Cooperative task switcher (libtasks.lib) @@ -788,7 +929,7 @@ The -E flag specifies exported symbols (prefixed with underscore per DJGPP conve +-- security/ DH key exchange, XTEA cipher, DRBG RNG +-- seclink/ Encrypted channel wrapper +-- serial/ Combined serial stack DXE - +-- proxy/ Linux proxy (86Box <-> secLink <-> telnet) + +-- proxy/ Linux proxy (emulator <-> secLink <-> telnet) +-- sql/ SQLite integration +-- bin/ Build output (dvx.exe, libs/, widgets/, apps/, config/) +-- obj/ Intermediate object files diff --git a/src/libs/kpunch/libdvx/dvxApp.c b/src/libs/kpunch/libdvx/dvxApp.c index f9ec3f8..26a27fe 100644 --- a/src/libs/kpunch/libdvx/dvxApp.c +++ b/src/libs/kpunch/libdvx/dvxApp.c @@ -1422,7 +1422,7 @@ static void dispatchEvents(AppContextT *ctx) { if (target && wclsHas(target, WGT_METHOD_ON_KEY)) { int32_t delta = ctx->mouseWheel * ctx->wheelDirection; - int32_t arrowKey = (delta > 0) ? (0x50 | 0x100) : (0x48 | 0x100); + int32_t arrowKey = (delta > 0) ? KEY_DOWN : KEY_UP; int32_t steps = abs(delta) * ctx->wheelStep; for (int32_t s = 0; s < steps; s++) { @@ -1664,7 +1664,7 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c // ============================================================ static void enumModeCb(int32_t w, int32_t h, int32_t bpp, void *userData) { - if (w < 640 || h < 480) { + if (w < DVX_DEFAULT_VIDEO_W || h < DVX_DEFAULT_VIDEO_H) { return; } @@ -2450,7 +2450,7 @@ static void pollKeyboard(AppContextT *ctx) { } // Ctrl+Esc -- system-wide hotkey (e.g. task manager) - if (scancode == 0x01 && ascii == 0x1B && (shiftFlags & KEY_MOD_CTRL)) { + if (scancode == 0x01 && ascii == KEY_ESCAPE && (shiftFlags & KEY_MOD_CTRL)) { if (ctx->onCtrlEsc) { ctx->onCtrlEsc(ctx->ctrlEscCtx); } @@ -2493,7 +2493,7 @@ static void pollKeyboard(AppContextT *ctx) { continue; } - if (ascii == 0x1B) { + if (ascii == KEY_ESCAPE) { // Cancel -- restore original position/size dirtyListAdd(&ctx->dirty, kbWin->x, kbWin->y, kbWin->w, kbWin->h); kbWin->x = ctx->kbMoveResize.origX; @@ -2647,7 +2647,7 @@ static void pollKeyboard(AppContextT *ctx) { if (ascii == 0 && platformAltScanToChar(scancode)) { closeSysMenu(ctx); // Fall through to dispatchAccelKey below - } else if (ascii == 0x1B) { + } else if (ascii == KEY_ESCAPE) { closeSysMenu(ctx); continue; } else if (ascii == 0 && scancode == 0x48) { @@ -2911,7 +2911,7 @@ static void pollKeyboard(AppContextT *ctx) { } // ESC closes open dropdown/combobox popup - if (sOpenPopup && ascii == 0x1B) { + if (sOpenPopup && ascii == KEY_ESCAPE) { // Dirty the popup list area WindowT *popWin = sOpenPopup->window; int32_t popX; @@ -2931,7 +2931,7 @@ static void pollKeyboard(AppContextT *ctx) { } // ESC closes one popup level (or all if at top level) - if (ctx->popup.active && ascii == 0x1B) { + if (ctx->popup.active && ascii == KEY_ESCAPE) { closePopupLevel(ctx); continue; } diff --git a/src/libs/kpunch/libdvx/dvxComp.c b/src/libs/kpunch/libdvx/dvxComp.c index 8e03a34..7b1f30b 100644 --- a/src/libs/kpunch/libdvx/dvxComp.c +++ b/src/libs/kpunch/libdvx/dvxComp.c @@ -49,10 +49,9 @@ // Rects within this many pixels of each other get merged even if they don't // overlap. A small gap tolerance absorbs jitter from mouse movement and // closely-spaced UI invalidations (e.g. title bar + content during a drag) -// without bloating merged rects excessively. The value 4 was chosen to match -// the chrome border width -- adjacent chrome/content invalidations merge -// naturally. -#define DIRTY_MERGE_GAP 4 +// without bloating merged rects excessively. Derived from CHROME_BORDER_WIDTH +// so adjacent chrome/content invalidations merge naturally. +#define DIRTY_MERGE_GAP CHROME_BORDER_WIDTH // ============================================================ // Prototypes @@ -123,6 +122,12 @@ void dirtyListClear(DirtyListT *dl) { void dirtyListInit(DirtyListT *dl) { dl->count = 0; + // Pre-allocate to the soft cap so per-frame dirtyListAdd never reallocs. + // Worth the ~1KB of heap to take realloc out of the hot path entirely. + if (!dl->rects) { + dl->rects = (RectT *)malloc(DIRTY_SOFT_CAP * sizeof(RectT)); + dl->cap = dl->rects ? DIRTY_SOFT_CAP : 0; + } } diff --git a/src/libs/kpunch/libdvx/dvxDialog.c b/src/libs/kpunch/libdvx/dvxDialog.c index e007387..7e11c45 100644 --- a/src/libs/kpunch/libdvx/dvxDialog.c +++ b/src/libs/kpunch/libdvx/dvxDialog.c @@ -83,9 +83,11 @@ // distance-squared test (d2 between INNER_R2 and OUTER_R2) to draw // a ring without needing floating-point sqrt. #define ICON_GLYPH_SIZE 24 // icon glyph drawing area (pixels) -#define ICON_GLYPH_CENTER 12 // center of icon glyph (ICON_GLYPH_SIZE / 2) -#define ICON_OUTER_R2 144 // outer circle radius squared (12*12) -#define ICON_INNER_R2 100 // inner circle radius squared (10*10) +#define ICON_GLYPH_CENTER (ICON_GLYPH_SIZE / 2) +#define ICON_OUTER_R ICON_GLYPH_CENTER +#define ICON_INNER_R (ICON_GLYPH_CENTER - 2) +#define ICON_OUTER_R2 (ICON_OUTER_R * ICON_OUTER_R) +#define ICON_INNER_R2 (ICON_INNER_R * ICON_INNER_R) #define FD_DIALOG_WIDTH 360 // file dialog window width #define FD_DIALOG_HEIGHT 340 // file dialog window height @@ -380,7 +382,7 @@ bool dvxChoiceDialog(AppContextT *ctx, const char *title, const char *prompt, co } WidgetT *lb = wgtListBox(root); - lb->weight = 100; + lb->weight = WGT_WEIGHT_FILL; lb->minH = wgtPixels(CB_LIST_HEIGHT); lb->onDblClick = cbOnDblClick; wgtListBoxSetItems(lb, items, itemCount); @@ -482,7 +484,7 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha // File list sFd.fileList = wgtListBox(root); - sFd.fileList->weight = 100; + sFd.fileList->weight = WGT_WEIGHT_FILL; sFd.fileList->onChange = fdOnListClick; sFd.fileList->onDblClick = fdOnListDblClick; diff --git a/src/libs/kpunch/libdvx/dvxResource.c b/src/libs/kpunch/libdvx/dvxResource.c index 9adcd37..20428b7 100644 --- a/src/libs/kpunch/libdvx/dvxResource.c +++ b/src/libs/kpunch/libdvx/dvxResource.c @@ -46,18 +46,22 @@ void dvxResClose(DvxResHandleT *h) { } +static int dvxResEntryCmp(const void *a, const void *b) { + return strcmp(((const DvxResDirEntryT *)a)->name, ((const DvxResDirEntryT *)b)->name); +} + + const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) { if (!h || !name) { return NULL; } - for (uint32_t i = 0; i < h->entryCount; i++) { - if (strcmp(h->entries[i].name, name) == 0) { - return &h->entries[i]; - } - } - - return NULL; + // Entries are sorted at open() time so bsearch gives O(log n) lookup. + DvxResDirEntryT key; + strncpy(key.name, name, sizeof(key.name) - 1); + key.name[sizeof(key.name) - 1] = '\0'; + return (const DvxResDirEntryT *)bsearch(&key, h->entries, h->entryCount, + sizeof(DvxResDirEntryT), dvxResEntryCmp); } @@ -123,6 +127,9 @@ DvxResHandleT *dvxResOpen(const char *path) { h->entries = entries; h->entryCount = footer.entryCount; + // Sort once so dvxResFind can bsearch. + qsort(h->entries, h->entryCount, sizeof(DvxResDirEntryT), dvxResEntryCmp); + return h; } diff --git a/src/libs/kpunch/libdvx/dvxTypes.h b/src/libs/kpunch/libdvx/dvxTypes.h index 059fed4..aac77f5 100644 --- a/src/libs/kpunch/libdvx/dvxTypes.h +++ b/src/libs/kpunch/libdvx/dvxTypes.h @@ -306,7 +306,10 @@ typedef struct { #define CHROME_TITLE_HEIGHT 20 #define CHROME_TITLE_PAD 4 #define CHROME_INNER_BORDER 2 -#define CHROME_MENU_HEIGHT 20 +// Menu bar height: matches the title bar so accent chrome and menu bar +// stack as a uniform vertical rhythm. If the two ever need to differ, +// split these back into independent values. +#define CHROME_MENU_HEIGHT CHROME_TITLE_HEIGHT #define CHROME_TOTAL_TOP (CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT + CHROME_INNER_BORDER) #define CHROME_TOTAL_SIDE (CHROME_BORDER_WIDTH + CHROME_INNER_BORDER) #define CHROME_TOTAL_BOTTOM (CHROME_BORDER_WIDTH + CHROME_INNER_BORDER) @@ -421,26 +424,44 @@ typedef struct { #define ACCEL_CTRL 0x04 #define ACCEL_ALT 0x08 -// Non-ASCII keys are encoded as (scancode | 0x100) to distinguish them -// from regular ASCII values in a single int32_t keycode space. -#define KEY_F1 (0x3B | 0x100) -#define KEY_F2 (0x3C | 0x100) -#define KEY_F3 (0x3D | 0x100) -#define KEY_F4 (0x3E | 0x100) -#define KEY_F5 (0x3F | 0x100) -#define KEY_F6 (0x40 | 0x100) -#define KEY_F7 (0x41 | 0x100) -#define KEY_F8 (0x42 | 0x100) -#define KEY_F9 (0x43 | 0x100) -#define KEY_F10 (0x44 | 0x100) -#define KEY_F11 (0x57 | 0x100) -#define KEY_F12 (0x58 | 0x100) -#define KEY_INSERT (0x52 | 0x100) -#define KEY_DELETE (0x53 | 0x100) -#define KEY_HOME (0x47 | 0x100) -#define KEY_END (0x4F | 0x100) -#define KEY_PGUP (0x49 | 0x100) -#define KEY_PGDN (0x51 | 0x100) +// Non-ASCII keys are encoded as (scancode | KEY_EXT_FLAG) to distinguish +// them from regular ASCII values in a single int32_t keycode space. +#define KEY_EXT_FLAG 0x100 + +#define KEY_F1 (0x3B | KEY_EXT_FLAG) +#define KEY_F2 (0x3C | KEY_EXT_FLAG) +#define KEY_F3 (0x3D | KEY_EXT_FLAG) +#define KEY_F4 (0x3E | KEY_EXT_FLAG) +#define KEY_F5 (0x3F | KEY_EXT_FLAG) +#define KEY_F6 (0x40 | KEY_EXT_FLAG) +#define KEY_F7 (0x41 | KEY_EXT_FLAG) +#define KEY_F8 (0x42 | KEY_EXT_FLAG) +#define KEY_F9 (0x43 | KEY_EXT_FLAG) +#define KEY_F10 (0x44 | KEY_EXT_FLAG) +#define KEY_F11 (0x57 | KEY_EXT_FLAG) +#define KEY_F12 (0x58 | KEY_EXT_FLAG) +#define KEY_INSERT (0x52 | KEY_EXT_FLAG) +#define KEY_DELETE (0x53 | KEY_EXT_FLAG) +#define KEY_HOME (0x47 | KEY_EXT_FLAG) +#define KEY_END (0x4F | KEY_EXT_FLAG) +#define KEY_PGUP (0x49 | KEY_EXT_FLAG) +#define KEY_PGDN (0x51 | KEY_EXT_FLAG) +#define KEY_UP (0x48 | KEY_EXT_FLAG) +#define KEY_DOWN (0x50 | KEY_EXT_FLAG) +#define KEY_LEFT (0x4B | KEY_EXT_FLAG) +#define KEY_RIGHT (0x4D | KEY_EXT_FLAG) + +// ASCII control / printable ranges (non-extended codes). +#define KEY_ESCAPE 0x1B +#define KEY_ASCII_DEL 0x7F // not the same as KEY_DELETE (scancode 0x53) +#define KEY_ASCII_PRINT_FIRST 0x20 // space +#define KEY_ASCII_PRINT_LAST 0x7E // tilde + +// Ctrl-letter codes (A=1, B=2, ..., Z=26) used for clipboard shortcuts. +#define KEY_CTRL_C 0x03 +#define KEY_CTRL_V 0x16 +#define KEY_CTRL_X 0x18 +#define KEY_CTRL_Z 0x1A typedef struct { int32_t key; // key code: ASCII char or KEY_Fxx for extended @@ -487,6 +508,13 @@ typedef struct { #define MIN_WINDOW_H 60 #define WM_MAX_FROM_SCREEN (-1) // use screen dimension as max (for maxW/maxH) +// Default / minimum VESA video mode. Used as the lower bound when +// enumerating available modes and as the fallback mode when the +// preferences file doesn't specify otherwise. +#define DVX_DEFAULT_VIDEO_W 640 +#define DVX_DEFAULT_VIDEO_H 480 +#define DVX_DEFAULT_VIDEO_BPP 16 + // Window paint states (for WindowT.paintNeeded) #define PAINT_NONE 0 // no paint needed #define PAINT_PARTIAL 1 // only dirty widgets (deferred wgtInvalidatePaint) @@ -644,6 +672,14 @@ typedef struct { #define MOUSE_ACCEL_DEFAULT "medium" #define MOUSE_WHEEL_DIR_DEFAULT "normal" +// Mouse acceleration threshold levels. Higher values = less acceleration +// (threshold above which doubling kicks in). MOUSE_ACCEL_OFF effectively +// disables acceleration by setting the threshold above any realistic speed. +#define MOUSE_ACCEL_OFF 10000 +#define MOUSE_ACCEL_LOW 100 +#define MOUSE_ACCEL_MEDIUM 64 +#define MOUSE_ACCEL_HIGH 32 + // ============================================================ // Mouse cursor // ============================================================ diff --git a/src/libs/kpunch/libdvx/dvxWgt.h b/src/libs/kpunch/libdvx/dvxWgt.h index 75cf83e..3b13ef7 100644 --- a/src/libs/kpunch/libdvx/dvxWgt.h +++ b/src/libs/kpunch/libdvx/dvxWgt.h @@ -81,6 +81,11 @@ struct WidgetClassT; #define WGT_SIZE_CHARS 0x40000000 #define WGT_SIZE_PERCENT 0x80000000 +// Standard "fill remaining space" weight for VBox/HBox children. +// A widget with WGT_WEIGHT_FILL gets one share of extra space; use +// 2*, 3* etc. to give siblings proportionally more space. +#define WGT_WEIGHT_FILL 100 + #define wgtPixels(v) ((int32_t)(WGT_SIZE_PIXELS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK))) #define wgtChars(v) ((int32_t)(WGT_SIZE_CHARS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK))) #define wgtPercent(v) ((int32_t)(WGT_SIZE_PERCENT | ((uint32_t)(v) & WGT_SIZE_VAL_MASK))) diff --git a/src/libs/kpunch/libdvx/sysdoc.dhs b/src/libs/kpunch/libdvx/sysdoc.dhs index 33ec7d7..40334c4 100644 --- a/src/libs/kpunch/libdvx/sysdoc.dhs +++ b/src/libs/kpunch/libdvx/sysdoc.dhs @@ -29,4 +29,69 @@ .image help.png center -DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, core API, libraries, and widget toolkit. +DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, the core libdvx API, the widget toolkit, and the platform abstraction layer. + +.h2 What Is libdvx? + +libdvx is the heart of DVX -- a DXE3 library that provides: + +.list +.item A VESA VBE 2.0+ video backend (LFB only, no bank switching). +.item Optimised 2D drawing primitives (rectangle fill, bevels, text, bitmap blits, cursors). +.item A dirty-rectangle compositor that minimises LFB traffic. +.item A window manager with Motif-style chrome, drag/resize, menus, and scrollbars. +.item A retained-mode widget toolkit with automatic layout. +.item Modal dialog helpers, an INI preferences system, a DXE resource system, and per-app memory tracking. +.endlist + +Applications written in C link against libdvx (and the widget DXEs they need) to build native DVX programs. Applications written in DVX BASIC compile to bytecode that runs on top of libdvx via the form runtime. + +.h2 Target Audience + +This document is aimed at developers writing native C code for DVX: + +.list +.item System-level contributors maintaining libdvx itself. +.item Widget authors writing new .wgt DXE modules. +.item Application authors writing .app DXE modules. +.item Tool authors (e.g. the BASIC compiler/runtime) that sit on top of libdvx. +.endlist + +All examples and signatures assume the DJGPP cross-compiler toolchain. + +.h2 What's Covered + +.list +.item Architecture -- Five-layer model, display pipeline, event model, build system. +.item API Reference -- Every public function, struct, enum, and constant documented with parameters and return values. +.endlist + +Use the table of contents on the left to navigate. The API reference is organised by header file; each function has a one-line summary, parameter table, and (where useful) a working example. + +.h2 Conventions + +.list +.item Types end in a capital T (e.g. WindowT, DisplayT, BlitOpsT). +.item Enum types end in a capital E (e.g. ColorIdE, WallpaperModeE, ScrollbarOrientE). +.item Public functions use camelCase and prefixes that identify the subsystem: dvx* (application), wm* (window manager), wgt* (widget), prefs* (preferences), draw* / rect* (drawing), dirtyList* / flush* (compositor), video* / packColor / setClipRect (video), platform* (platform layer). +.item Constants use SCREAMING_SNAKE_CASE (e.g. HIT_CONTENT, MB_OK, CURSOR_ARROW). +.item Every header uses stdint.h types (int32_t, uint8_t) and stdbool.h types (bool). +.endlist + +.h2 Getting Started + +If you're new to DVX, read these topics in order: + +.list +.item Architecture Overview -- The big picture, five-layer model, and the design philosophy. +.item Display Pipeline -- How the backbuffer, dirty list, and compositor work together. +.item Event Model -- How input becomes window/widget callbacks. +.item Widget System -- The retained-mode toolkit layered on the window manager. +.item DXE Module System -- How apps and widgets are loaded dynamically. +.endlist + +Then dip into the API Reference to find specific functions. The public entry point for any application is dvxInit / dvxRun in dvxApp.h. + +.h2 License + +DVX is distributed under the MIT License (see the copyright notice at the top of every source file). Third-party code (stb_image, stb_ds, stb_image_write in thirdparty/) is used under its own permissive license. diff --git a/src/libs/kpunch/libdvx/widgetEvent.c b/src/libs/kpunch/libdvx/widgetEvent.c index ee7d909..b50e54f 100644 --- a/src/libs/kpunch/libdvx/widgetEvent.c +++ b/src/libs/kpunch/libdvx/widgetEvent.c @@ -237,7 +237,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) { wclsOnKey(focus, key, mod); // Fire user callbacks after the widget's internal handler - if (key >= 32 && key < 127 && focus->onKeyPress) { + if (key >= KEY_ASCII_PRINT_FIRST && key <= KEY_ASCII_PRINT_LAST && focus->onKeyPress) { focus->onKeyPress(focus, key); } diff --git a/src/libs/kpunch/libtasks/README.md b/src/libs/kpunch/libtasks/README.md index ee5d411..2386fac 100644 --- a/src/libs/kpunch/libtasks/README.md +++ b/src/libs/kpunch/libtasks/README.md @@ -1,118 +1,26 @@ -# taskswitch -- Cooperative Task Switching Library +# libtasks -- Cooperative Task Switching -Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS -protected mode). Built as `libtasks.lib` -- a DXE3 module loaded by -the DVX loader. +A cooperative (non-preemptive) multitasking library. Tasks yield +voluntarily via `tsYield()`; the scheduler picks the next runnable task +by priority. Used by the DVX shell to run multiple DXE applications +concurrently. +## Documentation -## Why Cooperative +Full reference is in `libtasks.dhs` (this directory), compiled into the +DVX System Reference. Open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -DOS is single-threaded with no kernel scheduler. DPMI provides no -thread or timer-based preemption. The DVX GUI event model is -inherently single-threaded (one compositor, one input queue, one -window stack). Cooperative switching lets each task yield at safe -points, avoiding the need for synchronization primitives entirely. +Topics covered: `TaskT` struct, `tsInit` / `tsShutdown` / `tsSpawn` / +`tsYield` / `tsCurrent` / `tsKill` / `tsPause` / `tsSetPriority`, priority +model, stack allocation, and the exact return-code semantics for each +entry point. +## Source Files -## Scheduling Algorithm - -Credit-based weighted fair-share, round-robin within a credit epoch. - -Each task receives `(priority + 1)` credits per scheduling round. -Tasks run round-robin, consuming one credit per turn. When all -credits are spent, every ready task is refilled. - -| Priority | Constant | Credits/Round | Relative Share | -|----------|----------|---------------|----------------| -| 0 | `TS_PRIORITY_LOW` | 1 | ~4% | -| 5 | `TS_PRIORITY_NORMAL` | 6 | ~22% | -| 10 | `TS_PRIORITY_HIGH` | 11 | ~41% | - -Higher-priority tasks run proportionally more often but never starve -lower ones. A priority-10 task gets 11 turns per round while a -priority-0 task gets 1. - - -## Task States - -| State | Description | -|-------|-------------| -| `TaskStateReady` | Eligible for scheduling | -| `TaskStateRunning` | Currently executing (cosmetic marker) | -| `TaskStatePaused` | Skipped until resumed | -| `TaskStateTerminated` | Slot available for reuse | - - -## API Reference - -| Function | Description | -|----------|-------------| -| `tsInit()` | Initialize task system; calling context becomes task 0 (main) | -| `tsShutdown()` | Shut down and free all resources | -| `tsCreate(name, entry, arg, stackSize, priority)` | Create a new task; returns task ID | -| `tsYield()` | Yield CPU to next eligible task (credit-based round-robin) | -| `tsPause(taskId)` | Pause a task (implicit yield if self) | -| `tsResume(taskId)` | Resume a paused task (refills credits) | -| `tsSetPriority(taskId, priority)` | Set priority (refills credits immediately) | -| `tsGetPriority(taskId)` | Get task priority | -| `tsGetState(taskId)` | Get task state | -| `tsCurrentId()` | Get currently running task's ID | -| `tsGetName(taskId)` | Get task name | -| `tsExit()` | Terminate calling task (never returns) | -| `tsKill(taskId)` | Forcibly terminate another task | -| `tsRecoverToMain()` | Force scheduler back to task 0 after crash longjmp | -| `tsActiveCount()` | Count of non-terminated tasks | - -### Error Codes - -| Constant | Value | Description | -|----------|-------|-------------| -| `TS_OK` | 0 | Success | -| `TS_ERR_INIT` | -1 | Initialization failure | -| `TS_ERR_PARAM` | -2 | Invalid parameter | -| `TS_ERR_FULL` | -3 | No available task slots | -| `TS_ERR_NOMEM` | -4 | Stack allocation failed | -| `TS_ERR_STATE` | -5 | Invalid state transition | - -### Constants - -| Constant | Value | Description | -|----------|-------|-------------| -| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack size (32KB) | -| `TS_NAME_MAX` | 32 | Maximum task name length | - - -## Task 0 (Main Task) - -Task 0 is special: -* Cannot be killed or paused -* Uses the process stack directly (no separate allocation) -* `tsRecoverToMain()` always returns control here after a crash -* The DVX shell runs as task 0 with `TS_PRIORITY_HIGH` - - -## Crash Recovery - -`tsRecoverToMain()` is called after `longjmp` from a signal handler -that fired in a non-main task. It fixes the scheduler's `currentIdx` -to point back to task 0. The crashed task is NOT cleaned up by this -call -- `tsKill()` must be called separately afterward. - - -## Files - -| File | Description | -|------|-------------| -| `taskswitch.h` | Public API header | -| `taskswitch.c` | Complete implementation (context switch, scheduler, stack management) | -| `Makefile` | Builds `bin/libs/libtasks.lib` | - +- `taskSwch.h` -- public API +- `taskswitch.c` -- implementation (context save/restore, scheduler) ## Build -``` -make # builds bin/libs/libtasks.lib -make clean # removes objects and library -``` - -No dependencies. Exports all symbols matching `_ts*`. +`make -C src/libs/kpunch/libtasks` (invoked by the top-level `make`). diff --git a/src/libs/kpunch/libtasks/libtasks.dhs b/src/libs/kpunch/libtasks/libtasks.dhs index 9c33674..fbb031d 100644 --- a/src/libs/kpunch/libtasks/libtasks.dhs +++ b/src/libs/kpunch/libtasks/libtasks.dhs @@ -33,12 +33,32 @@ Credit-based cooperative (non-preemptive) multitasking library for DOS protected mode (DJGPP/DPMI). Each task receives (priority + 1) credits per scheduling round. Tasks run round-robin, consuming one credit per turn. When all credits are exhausted, every ready task is refilled. Higher-priority tasks run proportionally more often but never starve lower ones. -Header: tasks/taskswitch.h +Header: libtasks/taskSwch.h + +Loaded as: bin/libs/libtasks.lib .h2 Why Cooperative DOS is single-threaded with no kernel scheduler. DPMI provides no preemption primitives. The DVX GUI event model is inherently single-threaded: one compositor, one input queue, one window stack. Cooperative switching lets each task yield at safe points, avoiding synchronization primitives entirely. +.h2 Scheduling Semantics + +Task storage is a stb_ds dynamic array. Terminated slots are recycled by tsCreate so the array does not grow unboundedly. The scheduler performs a linear scan for the next Ready task whose credit count is positive; when no task has credits left, every Ready task is refilled with (priority + 1) credits and the scan restarts. + +.table + Priority Credits per round Approximate share + -------- ----------------- ----------------- + TS_PRIORITY_LOW 1 ~4% (1 of 25 possible credits) + TS_PRIORITY_NORMAL 6 ~22% + TS_PRIORITY_HIGH 11 ~41% +.endtable + +Actual share depends on the mix of priorities currently running. What the algorithm guarantees is that no ready task ever goes unscheduled -- even a priority-0 task always earns at least one turn per round. + +.h3 Stack Allocation + +Task 0 uses the caller's stack (no separate allocation). Every other task gets a heap-allocated stack of the size passed to tsCreate (or TS_DEFAULT_STACK_SIZE if 0 is passed). Stacks are freed at task termination, either via tsExit or tsKill. + .h2 Error Codes .table @@ -95,7 +115,7 @@ int32_t tsInit(void); Initialize the task system. The calling context becomes task 0 (the main task). Task 0 is special: it cannot be killed or paused, and tsRecoverToMain() always returns control here. No separate stack is allocated for task 0; it uses the process stack directly. -Returns: TS_OK on success, TS_ERR_INIT if already initialized. +Returns: TS_OK on success, TS_ERR_PARAM if already initialized. .h2 tsShutdown @@ -123,7 +143,7 @@ Create a new task. Terminated task slots are recycled to avoid unbounded array g priority Scheduling priority (0..10; use TS_PRIORITY_* constants) .endtable -Returns: Task ID (>= 0) on success, or a negative error code (TS_ERR_INIT, TS_ERR_PARAM, TS_ERR_NOMEM). +Returns: Task ID (>= 0) on success, or a negative error code (TS_ERR_PARAM if not initialized or entry is NULL, TS_ERR_NOMEM if stack allocation failed). .h2 tsYield @@ -147,7 +167,7 @@ Pause a task, removing it from scheduling. Cannot pause the main task (ID 0). If taskId ID of the task to pause .endtable -Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, TS_ERR_STATE if the task is not in a pausable state. +Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE if the task is the main task or not in a pausable state (not Running or Ready). .h2 tsResume @@ -180,7 +200,7 @@ Set a task's scheduling priority. Also refills credits so the change takes effec priority New priority level (0..10) .endtable -Returns: TS_OK on success, TS_ERR_PARAM on invalid ID or out-of-range priority. +Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE if the task is terminated. .h2 tsGetPriority @@ -262,7 +282,7 @@ Forcibly terminate another task. Cannot kill the main task (ID 0) or the current taskId ID of the task to terminate .endtable -Returns: TS_OK on success, TS_ERR_PARAM on invalid ID or illegal target (main task, self). +Returns: TS_OK on success, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE on illegal target (main task, self, or already terminated). .h2 tsRecoverToMain diff --git a/src/libs/kpunch/listhelp/README.md b/src/libs/kpunch/listhelp/README.md index ab41824..9f1ab6d 100644 --- a/src/libs/kpunch/listhelp/README.md +++ b/src/libs/kpunch/listhelp/README.md @@ -1,101 +1,24 @@ -# listhelp -- Shared List/Dropdown Helper Library +# listhelp -- Shared List / Dropdown Helpers -Shared infrastructure for list and dropdown widgets, built as -`listhelp.lib` (DXE3 module). Provides dropdown arrow drawing, item -measurement, keyboard navigation, popup rectangle calculation, and -popup list painting. +Shared rendering and hit-testing infrastructure used by the ListBox, +ListView, ComboBox, and Dropdown widgets. Centralizes popup-list +painting, dropdown-arrow glyphs, and key-navigation helpers so the +widgets stay consistent and small. -Used by: Dropdown, ComboBox, ListBox, ListView, TreeView. +## Documentation +Full reference is in `listhelp.dhs` (this directory), compiled into the +DVX System Reference. Open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -## API Reference +Topics covered: dropdown arrow drawing, popup-list hit test and paint, +navigation keys (Up/Down/Home/End/PgUp/PgDn). -### Dropdown Arrow Glyph - -```c -void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, - int32_t centerX, int32_t centerY, uint32_t color); -``` - -Draws the small downward-pointing triangle glyph used on dropdown -buttons. - -### Item Measurement - -```c -int32_t widgetMaxItemLen(const char **items, int32_t count); -``` - -Scans an array of item strings and returns the length of the longest -one. Used to size dropdown popups and list columns to fit their -content. - -### Keyboard Navigation - -```c -int32_t widgetNavigateIndex(int32_t key, int32_t current, - int32_t count, int32_t pageSize); -``` - -Maps arrow key presses to index changes for list navigation. Handles -Up, Down, Home, End, PageUp, and PageDown. Returns the new selected -index, clamped to valid range. - -### Popup Rectangle Calculation - -```c -void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, - int32_t contentH, int32_t itemCount, - int32_t *popX, int32_t *popY, - int32_t *popW, int32_t *popH); -``` - -Computes the screen rectangle for a dropdown popup overlay. Positions -the popup below the widget (or above if there is not enough room -below). Limits height to `DROPDOWN_MAX_VISIBLE` items. - -### Popup List Painting - -```c -void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, - const BitmapFontT *font, const ColorSchemeT *colors, - int32_t popX, int32_t popY, int32_t popW, int32_t popH, - const char **items, int32_t itemCount, - int32_t hoverIdx, int32_t scrollPos); -``` - -Renders the popup overlay list with items, selection highlight, scroll -position, and beveled border. Used by Dropdown and ComboBox for their -popup overlays. - -### Constants - -| Name | Value | Description | -|------|-------|-------------| -| `DROPDOWN_BTN_WIDTH` | 16 | Width of dropdown arrow button area | -| `DROPDOWN_MAX_VISIBLE` | 8 | Maximum visible items in popup list | - - -## Exported Symbols - -Matches prefixes: `_widgetDraw*`, `_widgetDropdown*`, `_widgetMax*`, -`_widgetNavigate*`, `_widgetPaint*`. - - -## Files - -| File | Description | -|------|-------------| -| `listHelp.h` | Public API header | -| `listHelp.c` | Complete implementation | -| `Makefile` | Builds `bin/libs/listhelp.lib` + dep file | +## Source Files +- `listHelp.h` -- public API +- `listHelp.c` -- implementation ## Build -``` -make # builds bin/libs/listhelp.lib + listhelp.dep -make clean # removes objects, library, and dep file -``` - -Depends on: `libtasks.lib`, `libdvx.lib` (via listhelp.dep). +`make -C src/libs/kpunch/listhelp` (invoked by the top-level `make`). diff --git a/src/libs/kpunch/listhelp/listHelp.c b/src/libs/kpunch/listhelp/listHelp.c index 6ab774a..6158536 100644 --- a/src/libs/kpunch/listhelp/listHelp.c +++ b/src/libs/kpunch/listhelp/listHelp.c @@ -118,7 +118,7 @@ int32_t widgetMaxItemLen(const char **items, int32_t count) { // Returns -1 for unrecognized keys so callers can check whether the // key was consumed. int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) { - if (key == (0x50 | 0x100)) { + if (key == KEY_DOWN) { // Down arrow if (current < count - 1) { return current + 1; @@ -127,7 +127,7 @@ int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t return current < 0 ? 0 : current; } - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { // Up arrow if (current > 0) { return current - 1; @@ -136,23 +136,23 @@ int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t return current < 0 ? 0 : current; } - if (key == (0x47 | 0x100)) { + if (key == KEY_HOME) { // Home return 0; } - if (key == (0x4F | 0x100)) { + if (key == KEY_END) { // End return count - 1; } - if (key == (0x51 | 0x100)) { + if (key == KEY_PGDN) { // Page Down int32_t n = current + pageSize; return n >= count ? count - 1 : n; } - if (key == (0x49 | 0x100)) { + if (key == KEY_PGUP) { // Page Up int32_t n = current - pageSize; return n < 0 ? 0 : n; diff --git a/src/libs/kpunch/listhelp/listhelp.dhs b/src/libs/kpunch/listhelp/listhelp.dhs index 0c4b32f..f9587bf 100644 --- a/src/libs/kpunch/listhelp/listhelp.dhs +++ b/src/libs/kpunch/listhelp/listhelp.dhs @@ -33,17 +33,20 @@ .h1 List Helper Library -Shared helper routines for dropdown and list-based widget DXEs (ListBox, Dropdown, ComboBox, ListView, TreeView). Provides dropdown arrow rendering, item measurement, keyboard navigation, popup geometry calculation, and popup list painting. +Shared helper routines for dropdown and list-based widget DXEs (ListBox, Dropdown, ComboBox, ListView). Provides dropdown arrow rendering, item measurement, keyboard navigation, popup geometry calculation, popup list painting, type-ahead search, and popup scrollbar hit testing. Header: listhelp/listHelp.h +Loaded as: bin/libs/listhelp.lib + .h2 Constants .table - Constant Value Description - -------- ----- ----------- - DROPDOWN_BTN_WIDTH 16 Width of the dropdown arrow button in pixels. - DROPDOWN_MAX_VISIBLE 8 Maximum number of items visible in a popup list. + Constant Value Description + -------- ----- ----------- + DROPDOWN_BTN_WIDTH 16 Width of the dropdown arrow button in pixels. + DROPDOWN_MAX_VISIBLE 8 Maximum number of items visible in a popup list before scrolling. + POPUP_SCROLLBAR_W SCROLLBAR_WIDTH Popup vertical scrollbar width (mirrors the core window-manager scrollbar width). .endtable .h2 widgetDrawDropdownArrow @@ -100,7 +103,7 @@ int32_t widgetNavigateIndex(int32_t key, int32_t current, pageSize Number of visible items (used for PgUp/PgDn step size). .endtable -Returns the new index, clamped to [0, count-1]. +Returns the new index, clamped to [0, count-1]. Returns -1 for unrecognized keys so callers can detect whether the key was consumed. .h2 widgetDropdownPopupRect @@ -129,7 +132,7 @@ The popup is sized to show up to DROPDOWN_MAX_VISIBLE items. .h2 widgetPaintPopupList -Render a popup item list with highlight, scrolling, and beveled border. +Render a popup item list with highlight, scrolling, and beveled border. When itemCount exceeds DROPDOWN_MAX_VISIBLE, draws a vertical scrollbar on the right edge sized according to scrollPos. .code void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, @@ -155,3 +158,50 @@ void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, hoverIdx Index of the highlighted (hovered/selected) item, or -1 for none. scrollPos Index of the first visible item (scroll offset). .endtable + +.h2 widgetTypeAheadSearch + +Case-insensitive type-ahead search. Scans items forward from currentIdx + 1, wrapping around, for the next item whose first character matches ch. Used by list and dropdown widgets to jump to an item based on a single keystroke. + +.code +int32_t widgetTypeAheadSearch(char ch, const char **items, + int32_t itemCount, int32_t currentIdx); +.endcode + +.table + Parameter Description + --------- ----------- + ch Character to match (case-insensitive). + items Array of null-terminated item strings. + itemCount Number of items in the array. + currentIdx Currently selected index (search starts at currentIdx + 1 and wraps). +.endtable + +Returns the index of the next matching item, or -1 if no match is found or itemCount is not positive. + +.h2 widgetPopupScrollbarClick + +Hit-test a popup scrollbar click. Tests whether the (x, y) coordinate falls on the popup's vertical scrollbar track; if so, updates scrollPos for up/down arrow clicks or page-up/page-down clicks in the trough. No-op on the thumb itself (callers handle drag separately). + +.code +bool widgetPopupScrollbarClick(int32_t x, int32_t y, + int32_t popX, int32_t popY, int32_t popW, int32_t popH, + int32_t itemCount, int32_t visibleItems, + int32_t *scrollPos); +.endcode + +.table + Parameter Description + --------- ----------- + x Click X in the popup's coordinate space. + y Click Y in the popup's coordinate space. + popX Popup X origin. + popY Popup Y origin. + popW Popup width. + popH Popup height. + itemCount Total number of items. + visibleItems Number of visible rows in the popup. + scrollPos [in/out] Current scroll offset; updated in place when the click hits the scrollbar. +.endtable + +Returns true if the click landed on the scrollbar (even if scrollPos was not changed, for example clicking the thumb), false if the click was in the item list area. When itemCount is less than or equal to DROPDOWN_MAX_VISIBLE, always returns false (there is no scrollbar). diff --git a/src/libs/kpunch/serial/packet/README.md b/src/libs/kpunch/serial/packet/README.md index fc04b1f..cd77cac 100644 --- a/src/libs/kpunch/serial/packet/README.md +++ b/src/libs/kpunch/serial/packet/README.md @@ -1,428 +1,27 @@ -# Packet -- Reliable Serial Transport with HDLC Framing +# packet -- Reliable HDLC Transport -Packetized serial transport providing reliable, ordered delivery over an -unreliable serial link. Uses HDLC-style byte-stuffed framing, CRC-16-CCITT -error detection, and a Go-Back-N sliding window protocol for automatic -retransmission. +A reliable, ordered serial-transport layer. HDLC-style byte stuffing +(FLAG=0x7E, ESC=0x7D), CRC-16-CCITT integrity, and Go-Back-N sliding- +window retransmission over any byte-oriented link. Sits on top of the +rs232 driver (or the Linux socket shim used by the proxy). -This layer sits on top of an already-open rs232 COM port. It does not -open or close the serial port itself. +## Documentation +Full reference is in the combined serial stack doc: +`../serial.dhs` (the `packet` section). It compiles into the DVX System +Reference -- open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -## Architecture +Topics covered: connection open/close, send/poll, frame format, window +size, retransmit timer, CRC polynomial and initial value, error codes, +callback contract. -``` -Application - | - | pktSend() queue a packet for reliable delivery - | pktPoll() receive, process ACKs/NAKs, check retransmit timers - | -[Packet Layer] framing, CRC, sequencing, sliding window ARQ - | -[rs232] raw byte I/O via ISR-driven ring buffers - | -UART -``` +## Source Files -The packet layer adds framing, error detection, and reliability to the -raw byte stream provided by rs232. The caller provides a receive callback -that is invoked synchronously from `pktPoll()` for each complete, CRC-verified, -in-order data packet. +- `packet.h` -- public API and error codes +- `packet.c` -- implementation (framing, CRC, sliding window) +## Build -## Frame Format - -Before byte stuffing, each frame has the following layout: - -``` -[0x7E] [SEQ] [TYPE] [LEN] [PAYLOAD...] [CRC_LO] [CRC_HI] -``` - -| Field | Size | Description | -|-----------|-----------|----------------------------------------------| -| `0x7E` | 1 byte | Flag byte -- frame delimiter | -| `SEQ` | 1 byte | Sequence number (wrapping uint8_t, 0-255) | -| `TYPE` | 1 byte | Frame type (DATA, ACK, NAK, RST) | -| `LEN` | 1 byte | Payload length (0-255) | -| Payload | 0-255 | Application data | -| `CRC` | 2 bytes | CRC-16-CCITT, little-endian, over SEQ..payload | - -The header is 3 bytes (SEQ + TYPE + LEN), the CRC is 2 bytes, so the -minimum frame size (no payload) is 5 bytes. The maximum frame size (255-byte -payload) is 260 bytes before byte stuffing. - -### Frame Types - -| Type | Value | Description | -|--------|-------|----------------------------------------------------| -| `DATA` | 0x00 | Carries application payload | -| `ACK` | 0x01 | Cumulative acknowledgment -- next expected sequence | -| `NAK` | 0x02 | Negative ack -- request retransmit from this seq | -| `RST` | 0x03 | Connection reset -- clear all state | - -### Byte Stuffing - -HDLC transparency encoding ensures the flag byte (0x7E) and escape byte -(0x7D) never appear in the frame body. Within the frame data (everything -between flags), these two bytes are escaped by prefixing with 0x7D and -XORing the original byte with 0x20: - -- `0x7E` becomes `0x7D 0x5E` -- `0x7D` becomes `0x7D 0x5D` - -In the worst case, every byte in the frame is escaped, doubling the wire -size. In practice, these byte values are uncommon in typical data and the -overhead is minimal. - -### Why HDLC Framing - -HDLC's flag-byte + byte-stuffing scheme is the simplest way to delimit -variable-length frames on a raw byte stream. The 0x7E flag byte -unambiguously marks frame boundaries. This is proven, lightweight, and -requires zero buffering at the framing layer. - -The alternative -- length-prefixed framing -- is fragile on noisy links -because a corrupted length field permanently desynchronizes the receiver. -With HDLC framing, the receiver can always resynchronize by hunting for -the next flag byte. - - -## CRC-16-CCITT - -Error detection uses CRC-16-CCITT (polynomial 0x1021, initial value -0xFFFF). The CRC covers the SEQ, TYPE, LEN, and payload fields. It is -stored little-endian in the frame (CRC low byte first, then CRC high byte). - -The CRC is computed via a 256-entry lookup table (512 bytes of `.rodata`). -Table-driven CRC is approximately 10x faster than bit-by-bit computation -on a 486 -- a worthwhile trade for a function called on every frame -transmitted and received. - - -## Go-Back-N Sliding Window Protocol - -### Why Go-Back-N - -Go-Back-N ARQ is simpler than Selective Repeat -- the receiver does not -need an out-of-order reassembly buffer and only tracks a single expected -sequence number. This works well for the low bandwidth-delay product of a -serial link. On a 115200 bps local connection, the round-trip time is -negligible, so the window rarely fills. - -Go-Back-N's retransmit-all-from-NAK behavior wastes bandwidth on lossy -links, but serial links are nearly lossless. The CRC check is primarily a -safety net for electrical noise, not a routine error recovery mechanism. - -### Protocol Details - -The sliding window is configurable from 1 to 8 slots (default 4). -Sequence numbers are 8-bit unsigned integers that wrap naturally at 256. -The sequence space (256) is much larger than 2x the maximum window (16), -so there is no ambiguity between old and new frames. - -**Sender behavior:** -- Assigns a monotonically increasing sequence number to each DATA frame -- Retains a copy of each sent frame in a retransmit slot until it is - acknowledged -- When the window is full (`txCount >= windowSize`), blocks or returns - `PKT_ERR_TX_FULL` depending on the `block` parameter - -**Receiver behavior:** -- Accepts frames strictly in order (`seq == rxExpectSeq`) -- On in-order delivery, increments `rxExpectSeq` and sends an ACK - carrying the new expected sequence number (cumulative acknowledgment) -- Out-of-order frames within the window trigger a NAK for the expected - sequence number -- Duplicate and out-of-window frames are silently discarded - -**ACK processing:** -- ACKs carry the next expected sequence number (cumulative) -- On receiving an ACK, the sender frees all retransmit slots with - sequence numbers less than the ACK's sequence number - -**NAK processing:** -- A NAK requests retransmission from a specific sequence number -- The sender retransmits that frame AND all subsequent unacknowledged - frames (the Go-Back-N property) -- Each retransmitted slot has its timer reset - -**RST processing:** -- Resets all sequence numbers and buffers to zero on both sides -- The remote side also sends a RST in response - -### Timer-Based Retransmission - -Each retransmit slot tracks the time it was last (re)transmitted. If -500ms elapses without an ACK, the slot is retransmitted and the timer -is reset. This handles the case where an ACK or NAK was lost on the -wire -- without this safety net, the connection would stall permanently. - -The 500ms timeout is conservative for a local serial link (RTT is under -1ms) but accounts for the remote side being busy processing. On BBS -connections through the Linux proxy, the round-trip includes TCP latency, -making the generous timeout appropriate. - -### Receive State Machine - -Incoming bytes from the serial port are fed through a three-state HDLC -deframing state machine: - -| State | Description | -|----------|------------------------------------------------------| -| `HUNT` | Discarding bytes until a flag (0x7E) is seen | -| `ACTIVE` | Accumulating frame bytes; flag ends frame, ESC escapes | -| `ESCAPE` | Previous byte was 0x7D; XOR this byte with 0x20 | - -The flag byte serves double duty: it ends the current frame AND starts -the next one. Back-to-back frames share a single flag byte, saving -bandwidth. A frame is only processed if it meets the minimum size -requirement (5 bytes), so spurious flags produce harmless zero-length -"frames" that are discarded. - - -## API Reference - -### Types - -```c -// Receive callback -- called for each verified, in-order data packet -typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len); - -// Opaque connection handle -typedef struct PktConnS PktConnT; -``` - -### Constants - -| Name | Value | Description | -|------------------------|-------|--------------------------------------| -| `PKT_MAX_PAYLOAD` | 255 | Maximum payload bytes per packet | -| `PKT_DEFAULT_WINDOW` | 4 | Default sliding window size | -| `PKT_MAX_WINDOW` | 8 | Maximum sliding window size | -| `PKT_SUCCESS` | 0 | Success | -| `PKT_ERR_INVALID_PORT` | -1 | Invalid COM port | -| `PKT_ERR_NOT_OPEN` | -2 | Connection not open | -| `PKT_ERR_ALREADY_OPEN` | -3 | Connection already open | -| `PKT_ERR_WOULD_BLOCK` | -4 | Operation would block | -| `PKT_ERR_OVERFLOW` | -5 | Buffer overflow | -| `PKT_ERR_INVALID_PARAM`| -6 | Invalid parameter | -| `PKT_ERR_TX_FULL` | -7 | Transmit window full (non-blocking) | -| `PKT_ERR_NO_DATA` | -8 | No data available | -| `PKT_ERR_DISCONNECTED` | -9 | Serial port disconnected or error | - -### Functions - -#### pktOpen - -```c -PktConnT *pktOpen(int com, int windowSize, - PktRecvCallbackT callback, void *callbackCtx); -``` - -Creates a packetized connection over an already-open COM port. - -- `com` -- RS232 port index (`RS232_COM1` through `RS232_COM4`) -- `windowSize` -- sliding window size (1-8), or 0 for the default (4) -- `callback` -- called from `pktPoll()` for each received, verified, - in-order data packet. The `data` pointer is valid only during the - callback. -- `callbackCtx` -- user pointer passed through to the callback - -Returns a connection handle, or `NULL` on failure (allocation error). - -#### pktClose - -```c -void pktClose(PktConnT *conn); -``` - -Frees the connection state. Does **not** close the underlying COM port. -The caller is responsible for calling `rs232Close()` separately. - -#### pktSend - -```c -int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block); -``` - -Sends a data packet. `len` must be in the range 1 to `PKT_MAX_PAYLOAD` -(255). The data is copied into a retransmit slot before transmission, so -the caller can reuse its buffer immediately. - -- `block = true` -- If the transmit window is full, polls internally - (calling `pktPoll()` in a tight loop) until an ACK frees a slot. Returns - `PKT_ERR_DISCONNECTED` if the serial port drops during the wait. -- `block = false` -- Returns `PKT_ERR_TX_FULL` immediately if the window - is full. - -Returns `PKT_SUCCESS` on success. - -#### pktPoll - -```c -int pktPoll(PktConnT *conn); -``` - -The main work function. Must be called frequently (every iteration of -your main loop or event loop). It performs three tasks: - -1. **Drain serial RX** -- reads all available bytes from the rs232 port - and feeds them through the HDLC deframing state machine -2. **Process frames** -- verifies CRC, handles DATA/ACK/NAK/RST frames, - delivers data packets to the callback -3. **Check retransmit timers** -- resends any slots that have timed out - -Returns the number of DATA packets delivered to the callback this call, -or `PKT_ERR_DISCONNECTED` if the serial port returned an error, or -`PKT_ERR_INVALID_PARAM` if `conn` is NULL. - -The callback is invoked synchronously, so the caller should be prepared -for re-entrant calls to `pktSend()` from within the callback. - -#### pktReset - -```c -int pktReset(PktConnT *conn); -``` - -Resets all sequence numbers, TX slots, and RX state to zero. Sends a RST -frame to the remote side so it resets as well. Useful for recovering from -a desynchronized state. - -#### pktCanSend - -```c -bool pktCanSend(PktConnT *conn); -``` - -Returns `true` if there is room in the transmit window for another -packet. Useful for non-blocking send loops to avoid calling `pktSend()` -when it would return `PKT_ERR_TX_FULL`. - -#### pktGetPending - -```c -int pktGetPending(PktConnT *conn); -``` - -Returns the number of unacknowledged packets currently in the transmit -window. Ranges from 0 (all sent packets acknowledged) to `windowSize` -(window full). Useful for throttling sends and monitoring link health. - - -## Usage Example - -```c -#include "packet.h" -#include "../rs232/rs232.h" - -void onPacket(void *ctx, const uint8_t *data, int len) { - // process received packet -- data is valid only during this callback -} - -int main(void) { - // Open serial port first (packet layer does not manage it) - rs232Open(RS232_COM1, 115200, 8, 'N', 1, RS232_HANDSHAKE_NONE); - - // Create packet connection with default window size (4) - PktConnT *conn = pktOpen(RS232_COM1, 0, onPacket, NULL); - - // Send a packet (blocking -- waits for window space if needed) - uint8_t msg[] = "Hello, packets!"; - pktSend(conn, msg, sizeof(msg), true); - - // Main loop -- must call pktPoll() frequently - while (1) { - int delivered = pktPoll(conn); - if (delivered == PKT_ERR_DISCONNECTED) { - break; - } - // delivered = number of packets received this iteration - } - - pktClose(conn); - rs232Close(RS232_COM1); - return 0; -} -``` - -### Non-Blocking Send Pattern - -```c -// Send as fast as the window allows, doing other work between sends -while (bytesLeft > 0) { - pktPoll(conn); // process ACKs, free window slots - - if (pktCanSend(conn)) { - int chunk = bytesLeft; - if (chunk > PKT_MAX_PAYLOAD) { - chunk = PKT_MAX_PAYLOAD; - } - - if (pktSend(conn, data + offset, chunk, false) == PKT_SUCCESS) { - offset += chunk; - bytesLeft -= chunk; - } - } - - // do other work here (update UI, check for cancel, etc.) -} - -// Drain remaining ACKs -while (pktGetPending(conn) > 0) { - pktPoll(conn); -} -``` - - -## Internal Data Structures - -### Connection State (PktConnT) - -The connection handle contains: - -- **COM port index** and **window size** (configuration) -- **Callback** function pointer and context -- **TX state**: next sequence to assign, oldest unacked sequence, array - of retransmit slots, count of slots in use -- **RX state**: next expected sequence, deframing state machine state, - frame accumulation buffer - -### Retransmit Slots (TxSlotT) - -Each slot holds a copy of the sent payload, its sequence number, payload -length, and a `clock_t` timestamp of when it was last transmitted. The -retransmit timer compares this timestamp against the current time to -detect timeout. - - -## Building - -``` -make # builds ../lib/libpacket.a -make clean # removes objects and library -``` - -Cross-compiled with the DJGPP toolchain targeting i486+ CPUs. Compiler -flags: `-O2 -Wall -Wextra -march=i486 -mtune=i586`. - -Objects are placed in `../obj/packet/`, the library in `../lib/`. - -Requires `librs232.a` at link time (for `rs232Read()` and `rs232Write()`). - - -## Files - -- `packet.h` -- Public API header (types, constants, function prototypes) -- `packet.c` -- Complete implementation (framing, CRC, ARQ, state machine) -- `Makefile` -- DJGPP cross-compilation build rules - - -## Dependencies - -- `rs232/` -- Serial port I/O (must be linked: `-lrs232`) - -## Used By - -- `seclink/` -- Secure serial link (adds channel multiplexing and encryption) -- `proxy/` -- Linux serial proxy (uses a socket-based adaptation) +`make -C src/libs/kpunch/serial/packet` (invoked by the top-level `make` +via the serial subdirectory). diff --git a/src/libs/kpunch/serial/packet/packet.c b/src/libs/kpunch/serial/packet/packet.c index 1b6f379..3b86deb 100644 --- a/src/libs/kpunch/serial/packet/packet.c +++ b/src/libs/kpunch/serial/packet/packet.c @@ -73,14 +73,21 @@ // Maximum stuffed frame size (worst case: every byte stuffed + flag) #define MAX_STUFFED_SIZE (1 + MAX_FRAME_SIZE * 2) +// Slack beyond one max-size stuffed frame -- absorbs short bursts arriving +// between pktPoll calls so we don't drop bytes while processing the current +// frame. +#define RX_BUF_SLACK 64 // Receive buffer must hold at least one max-size stuffed frame -#define RX_BUF_SIZE (MAX_STUFFED_SIZE + 64) +#define RX_BUF_SIZE (MAX_STUFFED_SIZE + RX_BUF_SLACK) // Retransmit timeout. 500ms is conservative for a local serial link (RTT // is < 1ms) but accounts for the remote side being busy. On a real BBS // connection through the proxy, the round-trip includes TCP latency. #define RETRANSMIT_TIMEOUT_MS 500 +// CRC-16-CCITT initial register value (RFC 1662 / HDLC convention). +#define CRC_INIT_VALUE 0xFFFF + // Receive state machine: three states for HDLC deframing. // HUNT: discarding bytes until a flag (0x7E) is seen -- sync acquisition. // ACTIVE: accumulating frame bytes, watching for flag (end of frame) or @@ -209,7 +216,7 @@ static int txSlotIndex(PktConnT *conn, uint8_t seq); // costs 512 bytes of .rodata -- a worthwhile trade for a function called // on every frame received and transmitted. static uint16_t crcCalc(const uint8_t *data, int len) { - uint16_t crc = 0xFFFF; + uint16_t crc = CRC_INIT_VALUE; for (int i = 0; i < len; i++) { crc = (crc << 8) ^ sCrcTable[(crc >> 8) ^ data[i]]; diff --git a/src/libs/kpunch/serial/rs232/README.md b/src/libs/kpunch/serial/rs232/README.md index 19ec4be..8b211c6 100644 --- a/src/libs/kpunch/serial/rs232/README.md +++ b/src/libs/kpunch/serial/rs232/README.md @@ -1,417 +1,28 @@ -# RS232 -- ISR-Driven Serial Port Library for DJGPP +# rs232 -- Interrupt-Driven UART Driver -Interrupt-driven UART communication library supporting up to 4 simultaneous -COM ports with ring buffers and hardware/software flow control. Targets -486-class DOS hardware running under DJGPP/DPMI. +Low-level serial driver for up to 4 simultaneous COM ports. Handles +UART type detection (8250 / 16450 / 16550 / 16550A), IRQ sharing, ring +buffering, hardware and software flow control, and auto-detection of +port base address and IRQ from BIOS data. This is the bottom layer of +the DVX serial stack. -Ported from the DOS Serial Library 1.4 by Karl Stenerud (MIT License), -stripped to DJGPP-only codepaths and restyled for the DVX project. +## Documentation +Full reference is in the combined serial stack doc: +`../serial.dhs` (the `rs232` section). It compiles into the DVX System +Reference -- open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -## Architecture +Topics covered: open/close, read/write, baud rates, handshake modes, +ring-buffer capacity, flow-control watermarks, UART register map, +error codes, and the DMA / polling performance trade-offs. -The library is built around a single shared ISR (`comGeneralIsr`) that -services all open COM ports. This design is necessary because COM1/COM3 -typically share IRQ4 and COM2/COM4 share IRQ3 -- a single handler that -polls all ports avoids the complexity of per-IRQ dispatch. +## Source Files -``` -Application - | - | rs232Read() non-blocking drain from RX ring buffer - | rs232Write() blocking polled write directly to UART THR - | rs232WriteBuf() non-blocking write into TX ring buffer - | -[Ring Buffers] 2048-byte RX + TX per port, power-of-2 bitmask indexing - | -[ISR] comGeneralIsr -- shared handler for all open ports - | -[UART] 8250 / 16450 / 16550 / 16550A hardware -``` +- `rs232.h` -- public API and error codes +- `rs232.c` -- implementation (ISRs, ring buffers, UART detection) -### ISR Design +## Build -The ISR follows a careful protocol to remain safe under DPMI while -keeping the system responsive: - -1. **Mask** all COM port IRQs on the PIC to prevent ISR re-entry -2. **STI** to allow higher-priority interrupts (timer tick, keyboard) through -3. **Loop** over all open ports, draining each UART's pending interrupt - conditions (data ready, TX hold empty, modem status, line status) -4. **CLI**, send EOI to the PIC, re-enable COM IRQs, **STI** before IRET - -This mask-then-STI pattern is standard for slow device ISRs on PC -hardware. It prevents the same IRQ from re-entering while allowing the -system timer and keyboard to function during UART processing. - -### Ring Buffers - -Both RX and TX buffers are 2048 bytes, sized as a power of 2 so that -head/tail wraparound is a single AND operation (bitmask indexing) rather -than an expensive modulo -- critical for ISR-speed code on a 486. - -The buffers use a one-slot-wasted design to distinguish full from empty: -`head == tail` means empty, `(head + 1) & MASK == tail` means full. - -### Flow Control - -Flow control operates entirely within the ISR using watermark thresholds. -When the RX buffer crosses 80% full, the ISR signals the remote side to -stop sending; when it drops below 20%, the ISR allows the remote to -resume. This prevents buffer overflow without any application involvement. - -Three modes are supported: - -| Mode | Stop Signal | Resume Signal | -|------------|-------------------|-------------------| -| XON/XOFF | Send XOFF (0x13) | Send XON (0x11) | -| RTS/CTS | Deassert RTS | Assert RTS | -| DTR/DSR | Deassert DTR | Assert DTR | - -On the TX side, the ISR monitors incoming XON/XOFF bytes and the CTS/DSR -modem status lines to pause and resume transmission from the TX ring -buffer. - -### DPMI Memory Locking - -The ISR code and all per-port state structures (`sComPorts` array) are -locked in physical memory via `__dpmi_lock_linear_region`. This prevents -page faults during interrupt handling -- a hard requirement for any ISR -running under a DPMI host (DOS extender, Windows 3.x, OS/2 VDM, etc.). -An IRET wrapper is allocated by DPMI to handle the real-mode to -protected-mode transition on hardware interrupt entry. - - -## UART Type Detection - -`rs232GetUartType()` probes the UART hardware to identify the chip: - -1. **Scratch register test** -- Writes two known values (0xAA, 0x55) to - UART register 7 and reads them back. The 8250 lacks this register, so - readback fails. If both values read back correctly, the UART is at - least a 16450. - -2. **FIFO test** -- Enables the FIFO via the FCR (FIFO Control Register), - then reads bits 7:6 of the IIR (Interrupt Identification Register): - - `0b11` = 16550A (working 16-byte FIFO) - - `0b10` = 16550 (broken FIFO -- present in hardware but unusable) - - `0b00` = 16450 (no FIFO at all) - - The original FCR value is restored after probing. - -| Constant | Value | Description | -|---------------------|-------|----------------------------------------| -| `RS232_UART_UNKNOWN`| 0 | Unknown or undetected | -| `RS232_UART_8250` | 1 | Original IBM PC -- no FIFO, no scratch | -| `RS232_UART_16450` | 2 | Scratch register present, no FIFO | -| `RS232_UART_16550` | 3 | Broken FIFO (rare, unusable) | -| `RS232_UART_16550A` | 4 | Working 16-byte FIFO (most common) | - -On 16550A UARTs, the FIFO trigger threshold is configurable via -`rs232SetFifoThreshold()` with levels of 1, 4, 8, or 14 bytes. The -default is 14, which minimizes interrupt overhead at high baud rates. - - -## IRQ Auto-Detection - -When `rs232Open()` is called without a prior `rs232SetIrq()` override, -the library auto-detects the UART's IRQ by: - -1. Saving the current PIC interrupt mask registers (IMR) -2. Enabling all IRQ lines on both PICs -3. Generating a TX Hold Empty interrupt on the UART -4. Reading the PIC's Interrupt Request Register (IRR) to see which line - went high -5. Disabling the interrupt, reading IRR again to mask out persistent bits -6. Re-enabling once more to confirm the detection -7. Restoring the original PIC mask - -If auto-detection fails (common on virtualized hardware that does not -model the IRR accurately), the library falls back to the default IRQ for -the port (IRQ4 for COM1/COM3, IRQ3 for COM2/COM4). - -The BIOS Data Area (at segment 0x0040) is read to determine each port's -I/O base address. Ports not configured in the BDA are unavailable. - - -## COM Port Support - -| Constant | Value | Default IRQ | Default Base | -|--------------|-------|-------------|--------------| -| `RS232_COM1` | 0 | IRQ 4 | 0x3F8 | -| `RS232_COM2` | 1 | IRQ 3 | 0x2F8 | -| `RS232_COM3` | 2 | IRQ 4 | 0x3E8 | -| `RS232_COM4` | 3 | IRQ 3 | 0x2E8 | - -Base addresses are read from the BIOS Data Area at runtime. The default -IRQ values are used only as a fallback when auto-detection fails. Both -the base address and IRQ can be overridden before opening with -`rs232SetBase()` and `rs232SetIrq()`. - - -## Supported Baud Rates - -All standard rates from 50 to 115200 bps are supported. The baud rate -divisor is computed from the standard 1.8432 MHz UART crystal: - -| Rate | Divisor | Rate | Divisor | -|--------|---------|--------|---------| -| 50 | 2304 | 4800 | 24 | -| 75 | 1536 | 7200 | 16 | -| 110 | 1047 | 9600 | 12 | -| 150 | 768 | 19200 | 6 | -| 300 | 384 | 38400 | 3 | -| 600 | 192 | 57600 | 2 | -| 1200 | 96 | 115200 | 1 | -| 1800 | 64 | | | -| 2400 | 48 | | | -| 3800 | 32 | | | - -Data bits (5-8), parity (N/O/E/M/S), and stop bits (1-2) are configured -by writing the appropriate LCR (Line Control Register) bits. - - -## API Reference - -### Error Codes - -| Constant | Value | Description | -|-------------------------------|-------|--------------------------| -| `RS232_SUCCESS` | 0 | Success | -| `RS232_ERR_UNKNOWN` | -1 | Unknown error | -| `RS232_ERR_NOT_OPEN` | -2 | Port not open | -| `RS232_ERR_ALREADY_OPEN` | -3 | Port already open | -| `RS232_ERR_NO_UART` | -4 | No UART detected at base | -| `RS232_ERR_INVALID_PORT` | -5 | Bad port index | -| `RS232_ERR_INVALID_BASE` | -6 | Bad I/O base address | -| `RS232_ERR_INVALID_IRQ` | -7 | Bad IRQ number | -| `RS232_ERR_INVALID_BPS` | -8 | Unsupported baud rate | -| `RS232_ERR_INVALID_DATA` | -9 | Bad data bits (not 5-8) | -| `RS232_ERR_INVALID_PARITY` | -10 | Bad parity character | -| `RS232_ERR_INVALID_STOP` | -11 | Bad stop bits (not 1-2) | -| `RS232_ERR_INVALID_HANDSHAKE` | -12 | Bad handshaking mode | -| `RS232_ERR_INVALID_FIFO` | -13 | Bad FIFO threshold | -| `RS232_ERR_NULL_PTR` | -14 | NULL pointer argument | -| `RS232_ERR_IRQ_NOT_FOUND` | -15 | Could not detect IRQ | -| `RS232_ERR_LOCK_MEM` | -16 | DPMI memory lock failed | - -### Handshaking Modes - -| Constant | Value | Description | -|---------------------------|-------|----------------------| -| `RS232_HANDSHAKE_NONE` | 0 | No flow control | -| `RS232_HANDSHAKE_XONXOFF` | 1 | Software (XON/XOFF) | -| `RS232_HANDSHAKE_RTSCTS` | 2 | Hardware (RTS/CTS) | -| `RS232_HANDSHAKE_DTRDSR` | 3 | Hardware (DTR/DSR) | - -### Open / Close - -```c -int rs232Open(int com, int32_t bps, int dataBits, char parity, - int stopBits, int handshake); -``` - -Opens a COM port. Reads the UART base address from the BIOS data area, -auto-detects the IRQ, locks ISR memory via DPMI, installs the ISR, and -configures the UART for the specified parameters. Returns `RS232_SUCCESS` -or an error code. - -- `com` -- port index (`RS232_COM1` through `RS232_COM4`) -- `bps` -- baud rate (50 through 115200) -- `dataBits` -- 5, 6, 7, or 8 -- `parity` -- `'N'` (none), `'O'` (odd), `'E'` (even), `'M'` (mark), - `'S'` (space) -- `stopBits` -- 1 or 2 -- `handshake` -- `RS232_HANDSHAKE_*` constant - -```c -int rs232Close(int com); -``` - -Closes the port, disables UART interrupts, removes the ISR, restores the -original interrupt vector, and unlocks DPMI memory (when the last port -closes). - -### Read / Write - -```c -int rs232Read(int com, char *data, int len); -``` - -Non-blocking read. Drains up to `len` bytes from the RX ring buffer. -Returns the number of bytes actually read (0 if the buffer is empty). -If flow control is active and the buffer drops below the low-water mark, -the ISR will re-enable receive from the remote side. - -```c -int rs232Write(int com, const char *data, int len); -``` - -Blocking polled write. Sends `len` bytes directly to the UART THR -(Transmit Holding Register), bypassing the TX ring buffer entirely. -Polls LSR for THR empty before each byte. Returns `RS232_SUCCESS` or -an error code. - -```c -int rs232WriteBuf(int com, const char *data, int len); -``` - -Non-blocking buffered write. Copies as many bytes as will fit into the -TX ring buffer. The ISR drains the TX buffer to the UART automatically. -Returns the number of bytes actually queued. If the buffer is full, some -bytes may be dropped. - -### Buffer Management - -```c -int rs232ClearRxBuffer(int com); -int rs232ClearTxBuffer(int com); -``` - -Discard all data in the receive or transmit ring buffer by resetting -head and tail pointers to zero. - -### Getters - -```c -int rs232GetBase(int com); // UART I/O base address -int32_t rs232GetBps(int com); // Current baud rate -int rs232GetCts(int com); // CTS line state (0 or 1) -int rs232GetData(int com); // Data bits setting -int rs232GetDsr(int com); // DSR line state (0 or 1) -int rs232GetDtr(int com); // DTR line state (0 or 1) -int rs232GetHandshake(int com); // Handshaking mode -int rs232GetIrq(int com); // IRQ number -int rs232GetLsr(int com); // Line Status Register -int rs232GetMcr(int com); // Modem Control Register -int rs232GetMsr(int com); // Modem Status Register -char rs232GetParity(int com); // Parity setting ('N','O','E','M','S') -int rs232GetRts(int com); // RTS line state (0 or 1) -int rs232GetRxBuffered(int com); // Bytes waiting in RX buffer -int rs232GetStop(int com); // Stop bits setting -int rs232GetTxBuffered(int com); // Bytes waiting in TX buffer -int rs232GetUartType(int com); // UART type (RS232_UART_* constant) -``` - -Most getters return cached register values from the per-port state -structure, avoiding unnecessary I/O port reads. `rs232GetUartType()` -actively probes the hardware (see UART Type Detection above). - -### Setters - -```c -int rs232Set(int com, int32_t bps, int dataBits, char parity, - int stopBits, int handshake); -``` - -Reconfigure all port parameters at once. The port must already be open. - -```c -int rs232SetBase(int com, int base); // Override I/O base (before open) -int rs232SetBps(int com, int32_t bps); // Change baud rate -int rs232SetData(int com, int dataBits); // Change data bits -int rs232SetDtr(int com, bool dtr); // Assert/deassert DTR -int rs232SetFifoThreshold(int com, int thr); // FIFO trigger (1, 4, 8, 14) -int rs232SetHandshake(int com, int handshake); // Change flow control mode -int rs232SetIrq(int com, int irq); // Override IRQ (before open) -int rs232SetMcr(int com, int mcr); // Write Modem Control Register -int rs232SetParity(int com, char parity); // Change parity -int rs232SetRts(int com, bool rts); // Assert/deassert RTS -int rs232SetStop(int com, int stopBits); // Change stop bits -``` - - -## Usage Example - -```c -#include "rs232.h" - -int main(void) { - // Open COM1 at 115200 8N1, no flow control - int rc = rs232Open(RS232_COM1, 115200, 8, 'N', 1, RS232_HANDSHAKE_NONE); - if (rc != RS232_SUCCESS) { - return 1; - } - - // Identify the UART chip - int uartType = rs232GetUartType(RS232_COM1); - // uartType == RS232_UART_16550A on most 486+ systems - - // Enable 16550A FIFO with trigger at 14 bytes - if (uartType == RS232_UART_16550A) { - rs232SetFifoThreshold(RS232_COM1, 14); - } - - // Blocking send - rs232Write(RS232_COM1, "Hello\r\n", 7); - - // Non-blocking receive loop - char buf[128]; - int n; - while ((n = rs232Read(RS232_COM1, buf, sizeof(buf))) > 0) { - // process buf[0..n-1] - } - - rs232Close(RS232_COM1); - return 0; -} -``` - - -## Implementation Notes - -- The single shared ISR handles all four COM ports. On entry it disables - UART interrupts for all open ports on the PIC, then re-enables CPU - interrupts (STI) so higher-priority devices (timer, keyboard) are - serviced promptly. - -- Ring buffers use power-of-2 sizes (2048 bytes) with bitmask indexing - for zero-branch wraparound. Each port uses 4KB total (2KB RX + 2KB TX). - -- Flow control watermarks are at 80% (assert stop) and 20% (deassert - stop) of buffer capacity. These percentages are defined as compile-time - constants and apply to both RX and TX directions. - -- DPMI `__dpmi_lock_linear_region` is used to pin the ISR code, ring - buffers, and port state in physical memory. The ISR code region is - locked for 2048 bytes starting at the `comGeneralIsr` function address. - -- `rs232Write()` is a blocking polled write that bypasses the TX ring - buffer entirely. It writes directly to the UART THR register, polling - LSR for readiness between each byte. `rs232WriteBuf()` is the - non-blocking alternative that queues into the TX ring buffer for ISR - draining. - -- Per-port state is stored in a static array of `Rs232StateT` structures - (`sComPorts[4]`). This array is locked in physical memory alongside the - ISR code. - -- The BIOS Data Area (real-mode address 0040:0000) is read via DJGPP's - far pointer API (`_farpeekw`) to obtain port base addresses at runtime. - - -## Building - -``` -make # builds ../lib/librs232.a -make clean # removes objects and library -``` - -Cross-compiled with the DJGPP toolchain targeting i486+ CPUs. Compiler -flags: `-O2 -Wall -Wextra -march=i486 -mtune=i586`. - -Objects are placed in `../obj/rs232/`, the library in `../lib/`. - - -## Files - -- `rs232.h` -- Public API header -- `rs232.c` -- Complete implementation (ISR, DPMI, ring buffers, UART I/O) -- `Makefile` -- DJGPP cross-compilation build rules - - -## Used By - -- `packet/` -- Packetized serial transport layer (HDLC framing, CRC, ARQ) -- `seclink/` -- Secure serial link (opens and closes the COM port) -- `proxy/` -- Linux serial proxy (uses a socket-based shim of this API) +`make -C src/libs/kpunch/serial/rs232` (invoked by the top-level `make` +via the serial subdirectory). diff --git a/src/libs/kpunch/serial/rs232/rs232.c b/src/libs/kpunch/serial/rs232/rs232.c index 5fbecd4..dd8633c 100644 --- a/src/libs/kpunch/serial/rs232/rs232.c +++ b/src/libs/kpunch/serial/rs232/rs232.c @@ -103,6 +103,20 @@ #define IRQ_MAX 15 #define IRQ_NONE 0xFF +// Max valid UART port base (12-bit I/O address). +#define UART_MAX_BASE 0xFFF + +// BIOS data area at 0040:0000 holds up to 4 COM port base addresses as +// 16-bit words at offsets 0x400-0x407 (linear, in DOS low memory). +#define BIOS_COM_DATA_AREA 0x400 + +// UART type detection constants. +#define UART_SCRATCH_TEST_A 0xA5 // first value written to UART_SCR +#define UART_SCRATCH_TEST_B 0x5A // second value written to UART_SCR +#define IIR_FIFO_STATUS_MASK 0xC0 // IIR bits 7:6 encode FIFO capability +#define IIR_FIFO_WORKING 0xC0 // 16550A: FIFO enabled and functional +#define IIR_FIFO_PRESENT 0x80 // 16550: FIFO advertised but broken + // PIC registers #define INT_VECTOR_OFFSET 8 #define PIC_MASTER 0x20 @@ -815,7 +829,7 @@ int rs232GetBase(int com) { if (com < COM_MIN || com > COM_MAX) { return RS232_ERR_INVALID_PORT; } - if (port->base < 1 || port->base > 0xFFF) { + if (port->base < 1 || port->base > UART_MAX_BASE) { return RS232_ERR_INVALID_BASE; } @@ -1073,14 +1087,14 @@ int rs232GetUartType(int com) { } // Test scratch register -- 8250 lacks it - outportb(port->base + UART_SCR, 0xA5); + outportb(port->base + UART_SCR, UART_SCRATCH_TEST_A); scratch = inportb(port->base + UART_SCR); - if (scratch != 0xA5) { + if (scratch != UART_SCRATCH_TEST_A) { return RS232_UART_8250; } - outportb(port->base + UART_SCR, 0x5A); + outportb(port->base + UART_SCR, UART_SCRATCH_TEST_B); scratch = inportb(port->base + UART_SCR); - if (scratch != 0x5A) { + if (scratch != UART_SCRATCH_TEST_B) { return RS232_UART_8250; } @@ -1092,10 +1106,10 @@ int rs232GetUartType(int com) { outportb(port->base + UART_FCR, port->fcr); // Check IIR bits 7:6 for FIFO status - switch (iir & 0xC0) { - case 0xC0: + switch (iir & IIR_FIFO_STATUS_MASK) { + case IIR_FIFO_WORKING: return RS232_UART_16550A; - case 0x80: + case IIR_FIFO_PRESENT: return RS232_UART_16550; default: return RS232_UART_16450; @@ -1124,7 +1138,7 @@ int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int // stores up to 4 COM port base addresses as 16-bit words at 40:00-40:07. // This is more reliable than hardcoding 0x3F8/0x2F8 because BIOS setup // may have remapped ports or detected them in a different order. - if (rs232SetBase(com, _farpeekw(_dos_ds, 0x400 + (com << 1))) != RS232_SUCCESS) { + if (rs232SetBase(com, _farpeekw(_dos_ds, BIOS_COM_DATA_AREA + (com << 1))) != RS232_SUCCESS) { return RS232_ERR_NO_UART; } diff --git a/src/libs/kpunch/serial/seclink/README.md b/src/libs/kpunch/serial/seclink/README.md index 146cce4..f8447ae 100644 --- a/src/libs/kpunch/serial/seclink/README.md +++ b/src/libs/kpunch/serial/seclink/README.md @@ -1,450 +1,27 @@ -# SecLink -- Secure Serial Link Library +# seclink -- Secure Serial Link -SecLink is the top-level API for the DVX serial/networking stack. It -composes three lower-level libraries into a single interface for reliable, -optionally encrypted, channel-multiplexed serial communication: +The top-level API for the DVX serial stack. Wraps the packet transport +and the security primitives to provide a multi-channel encrypted byte +link: per-packet encryption flag, up to 128 channels multiplexed on a +single physical port, forward-secret DH handshake, and bulk send with +retransmit handled underneath. -- **rs232** -- ISR-driven UART I/O with ring buffers and flow control -- **packet** -- HDLC framing, CRC-16, Go-Back-N sliding window ARQ -- **security** -- 1024-bit Diffie-Hellman key exchange, XTEA-CTR encryption +## Documentation -SecLink adds channel multiplexing and per-packet encryption control on -top of the packet layer's reliable delivery. +Full reference is in the combined serial stack doc: +`../serial.dhs` (the `seclink` section). It compiles into the DVX +System Reference -- open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. +Topics covered: open/close, handshake state machine, send/poll, per- +channel callbacks, channel mask, error codes, typical end-to-end usage. -## Architecture +## Source Files -``` -Application - | - | secLinkSend() send data on a channel, optionally encrypted - | secLinkPoll() receive, decrypt, deliver to callback - | secLinkHandshake() DH key exchange (blocking) - | -[SecLink] channel header, encrypt/decrypt, key management - | -[Packet] HDLC framing, CRC-16, Go-Back-N ARQ - | -[RS232] ISR-driven UART, 2048-byte ring buffers - | -UART Hardware -``` +- `secLink.h` -- public API and error codes +- `secLink.c` -- implementation (handshake, channel mux, bulk send) -### Channel Multiplexing +## Build -SecLink prepends a one-byte header to every packet's payload before -handing it to the packet layer: - -``` - Bit 7 Bits 6..0 - ----- --------- - Encrypt Channel (0-127) -``` - -This allows up to 128 independent logical channels over a single serial -link. Each channel can carry a different type of traffic (terminal data, -file transfer, control messages, etc.) without needing separate framing -or sequencing per stream. The receive callback includes the channel -number so the application can dispatch accordingly. - -The encrypt flag (bit 7) tells the receiver whether the payload portion -of this packet is encrypted. The channel header byte itself is always -sent in the clear. - -### Mixed Clear and Encrypted Traffic - -Unencrypted packets can be sent before or after the DH handshake. This -enables a startup protocol (version negotiation, capability exchange) -before keys are established. Encrypted packets require a completed -handshake -- attempting to send an encrypted packet before the handshake -returns `SECLINK_ERR_NOT_READY`. - -On the receive side, encrypted packets arriving before the handshake is -complete are silently dropped. Cleartext packets are delivered regardless -of handshake state. - - -## Lifecycle - -``` -secLinkOpen() Open COM port and packet layer -secLinkHandshake() DH key exchange (blocks until both sides complete) -secLinkSend() Send data on a channel (encrypted or cleartext) -secLinkPoll() Receive and deliver packets to callback -secLinkClose() Tear down everything (ciphers, packet, COM port) -``` - -### Handshake Protocol - -The DH key exchange uses the packet layer's reliable delivery, so lost -packets are automatically retransmitted. Both sides can send their public -key simultaneously -- there is no initiator/responder distinction. - -1. Both sides generate a DH keypair (256-bit private, 1024-bit public) -2. Both sides send their 128-byte public key as a single packet -3. On receiving the remote's public key, each side immediately computes - the shared secret (`remote^private mod p`) -4. Each side derives separate TX and RX cipher keys from the master key -5. Cipher contexts are created and the link transitions to READY state -6. The DH context (containing the private key) is destroyed immediately - -**Directional key derivation:** - -The side with the lexicographically lower public key uses -`masterKey XOR 0xAA` for TX and `masterKey XOR 0x55` for RX. The other -side uses the reverse assignment. This is critical for CTR mode security: -if both sides used the same key and counter, they would produce identical -keystreams, and XORing two ciphertexts would reveal the XOR of the -plaintexts. The XOR-derived directional keys ensure each direction has a -unique keystream even though both sides start their counters at zero. - -**Forward secrecy:** - -The DH context (containing the private key and shared secret) is -destroyed immediately after deriving the session cipher keys. Even if -the application's long-term state is compromised later, past session -keys cannot be recovered from memory. - - -## Payload Size - -The maximum payload per `secLinkSend()` call is `SECLINK_MAX_PAYLOAD` -(254 bytes). This is the packet layer's 255-byte maximum minus the -1-byte channel header that SecLink prepends. - -For sending data larger than 254 bytes, use `secLinkSendBuf()` which -automatically splits the data into 254-byte chunks and sends each one -with blocking delivery. - - -## API Reference - -### Types - -```c -// Receive callback -- delivers plaintext with channel number -typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, - uint8_t channel); - -// Opaque connection handle -typedef struct SecLinkS SecLinkT; -``` - -The receive callback is invoked from `secLinkPoll()` for each incoming -packet. Encrypted packets are decrypted before delivery -- the callback -always receives plaintext regardless of whether encryption was used on -the wire. The `data` pointer is valid only during the callback. - -### Constants - -| Name | Value | Description | -|-------------------------|-------|---------------------------------------| -| `SECLINK_MAX_PAYLOAD` | 254 | Max bytes per `secLinkSend()` call | -| `SECLINK_MAX_CHANNEL` | 127 | Highest valid channel number | -| `SECLINK_SUCCESS` | 0 | Operation succeeded | -| `SECLINK_ERR_PARAM` | -1 | Invalid parameter or NULL pointer | -| `SECLINK_ERR_SERIAL` | -2 | Serial port open failed | -| `SECLINK_ERR_ALLOC` | -3 | Memory allocation failed | -| `SECLINK_ERR_HANDSHAKE` | -4 | DH key exchange failed | -| `SECLINK_ERR_NOT_READY` | -5 | Encryption requested before handshake | -| `SECLINK_ERR_SEND` | -6 | Packet layer send failed or window full | - -### Functions - -#### secLinkOpen - -```c -SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, char parity, - int stopBits, int handshake, - SecLinkRecvT callback, void *ctx); -``` - -Opens the COM port via rs232, creates the packet layer with default -window size (4), and returns a link handle. The callback is invoked from -`secLinkPoll()` for each received packet (decrypted if applicable). - -Returns `NULL` on failure (serial port error, packet layer allocation -error, or memory allocation failure). On failure, all partially -initialized resources are cleaned up. - -- `com` -- RS232 port index (`RS232_COM1` through `RS232_COM4`) -- `bps` -- baud rate (50 through 115200) -- `dataBits` -- 5, 6, 7, or 8 -- `parity` -- `'N'`, `'O'`, `'E'`, `'M'`, or `'S'` -- `stopBits` -- 1 or 2 -- `handshake` -- `RS232_HANDSHAKE_*` constant -- `callback` -- receive callback function -- `ctx` -- user pointer passed through to the callback - -#### secLinkClose - -```c -void secLinkClose(SecLinkT *link); -``` - -Full teardown in order: destroys TX and RX cipher contexts (secure zero), -destroys the DH context if still present, closes the packet layer, closes -the COM port, zeroes the link structure, and frees memory. - -#### secLinkHandshake - -```c -int secLinkHandshake(SecLinkT *link); -``` - -Performs a Diffie-Hellman key exchange. Blocks until both sides have -exchanged public keys and derived cipher keys. The RNG must be seeded -(via `secRngSeed()` or `secRngAddEntropy()`) before calling this. - -Internally: -1. Creates a DH context and generates keys -2. Sends the 128-byte public key via the packet layer (blocking) -3. Polls the packet layer in a loop until the remote's public key arrives -4. Computes the shared secret and derives directional cipher keys -5. Destroys the DH context (forward secrecy) -6. Transitions the link to READY state - -Returns `SECLINK_SUCCESS` or `SECLINK_ERR_HANDSHAKE` on failure -(DH key generation failure, send failure, or serial disconnect during -the exchange). - -#### secLinkSend - -```c -int secLinkSend(SecLinkT *link, const uint8_t *data, int len, - uint8_t channel, bool encrypt, bool block); -``` - -Sends up to `SECLINK_MAX_PAYLOAD` (254) bytes on the given channel. - -- `channel` -- logical channel number (0-127) -- `encrypt` -- if `true`, encrypts the payload before sending. Requires - a completed handshake; returns `SECLINK_ERR_NOT_READY` otherwise. -- `block` -- if `true`, waits for transmit window space. If `false`, - returns `SECLINK_ERR_SEND` when the packet layer's window is full. - -**Cipher counter safety:** The function checks transmit window space -BEFORE encrypting the payload. If it encrypted first and then the send -failed, the cipher counter would advance without the data being sent, -permanently desynchronizing the TX cipher state from the remote's RX -cipher. This ordering is critical for correctness. - -The channel header byte is prepended to the data, and only the payload -portion (not the header) is encrypted. - -Returns `SECLINK_SUCCESS` or an error code. - -#### secLinkSendBuf - -```c -int secLinkSendBuf(SecLinkT *link, const uint8_t *data, int len, - uint8_t channel, bool encrypt); -``` - -Sends an arbitrarily large buffer by splitting it into -`SECLINK_MAX_PAYLOAD`-byte (254-byte) chunks. Always blocks until all -data is sent. The receiver sees multiple packets on the same channel and -must reassemble if needed. - -Returns `SECLINK_SUCCESS` or the first error encountered. - -#### secLinkPoll - -```c -int secLinkPoll(SecLinkT *link); -``` - -Delegates to `pktPoll()` to read serial data, process frames, handle -ACKs and retransmits. Received packets are routed through an internal -callback that: - -- During handshake: expects a 128-byte DH public key -- When ready: strips the channel header, decrypts the payload if the - encrypt flag is set, and forwards plaintext to the user callback - -Returns the number of packets delivered, or a negative error code. - -Must be called frequently (every iteration of your main loop). - -#### secLinkGetPending - -```c -int secLinkGetPending(SecLinkT *link); -``` - -Returns the number of unacknowledged packets in the transmit window. -Delegates directly to `pktGetPending()`. Useful for non-blocking send -loops to determine when there is room to send more data. - -#### secLinkIsReady - -```c -bool secLinkIsReady(SecLinkT *link); -``` - -Returns `true` if the DH handshake is complete and the link is ready for -encrypted communication. Cleartext sends do not require the link to be -ready. - - -## Usage Examples - -### Basic Encrypted Link - -```c -#include "secLink.h" -#include "../security/security.h" - -void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) { - // handle received plaintext on 'channel' -} - -int main(void) { - // Seed the RNG before handshake - uint8_t entropy[16]; - secRngGatherEntropy(entropy, sizeof(entropy)); - secRngSeed(entropy, sizeof(entropy)); - - // Open link on COM1 at 115200 8N1, no flow control - SecLinkT *link = secLinkOpen(RS232_COM1, 115200, 8, 'N', 1, - RS232_HANDSHAKE_NONE, onRecv, NULL); - if (!link) { - return 1; - } - - // DH key exchange (blocks until both sides complete) - if (secLinkHandshake(link) != SECLINK_SUCCESS) { - secLinkClose(link); - return 1; - } - - // Send encrypted data on channel 0 - const char *msg = "Hello, secure world!"; - secLinkSend(link, (const uint8_t *)msg, strlen(msg), 0, true, true); - - // Main loop - while (1) { - secLinkPoll(link); - } - - secLinkClose(link); - return 0; -} -``` - -### Mixed Encrypted and Cleartext Channels - -```c -#define CHAN_CONTROL 0 // cleartext control channel -#define CHAN_DATA 1 // encrypted data channel - -// Cleartext status message (no handshake needed) -secLinkSend(link, statusMsg, statusLen, CHAN_CONTROL, false, true); - -// Encrypted payload (requires completed handshake) -secLinkSend(link, payload, payloadLen, CHAN_DATA, true, true); -``` - -### Non-Blocking File Transfer - -```c -int sendFile(SecLinkT *link, const uint8_t *fileData, int fileSize, - uint8_t channel, bool encrypt) { - int offset = 0; - int bytesLeft = fileSize; - - while (bytesLeft > 0) { - secLinkPoll(link); // process ACKs, free window slots - - if (secLinkGetPending(link) < 4) { // window has room - int chunk = bytesLeft; - if (chunk > SECLINK_MAX_PAYLOAD) { - chunk = SECLINK_MAX_PAYLOAD; - } - - int rc = secLinkSend(link, fileData + offset, chunk, - channel, encrypt, false); - if (rc == SECLINK_SUCCESS) { - offset += chunk; - bytesLeft -= chunk; - } - // SECLINK_ERR_SEND means window full, retry next iteration - } - - // Application can do other work here: - // update progress bar, check for cancel, etc. - } - - // Drain remaining ACKs - while (secLinkGetPending(link) > 0) { - secLinkPoll(link); - } - - return SECLINK_SUCCESS; -} -``` - -### Blocking Bulk Transfer - -```c -// Send an entire file in one call (blocks until complete) -secLinkSendBuf(link, fileData, fileSize, CHAN_DATA, true); -``` - - -## Internal State Machine - -SecLink maintains a three-state internal state machine: - -| State | Value | Description | -|--------------|-------|----------------------------------------------| -| `STATE_INIT` | 0 | Link open, no handshake attempted yet | -| `STATE_HANDSHAKE` | 1 | DH key exchange in progress | -| `STATE_READY` | 2 | Handshake complete, ciphers ready | - -Transitions: -- `INIT -> HANDSHAKE`: when `secLinkHandshake()` is called -- `HANDSHAKE -> READY`: when the remote's public key is received and - cipher keys are derived -- Any state -> cleanup: when `secLinkClose()` is called - -Cleartext packets can be sent and received in any state. Encrypted -packets require `STATE_READY`. - - -## Building - -``` -make # builds ../lib/libseclink.a -make clean # removes objects and library -``` - -Cross-compiled with the DJGPP toolchain targeting i486+ CPUs. Compiler -flags: `-O2 -Wall -Wextra -march=i486 -mtune=i586`. - -Objects are placed in `../obj/seclink/`, the library in `../lib/`. - -Link against all four libraries in this order: - -``` --lseclink -lpacket -lsecurity -lrs232 -``` - - -## Files - -- `secLink.h` -- Public API header (types, constants, function prototypes) -- `secLink.c` -- Complete implementation (handshake, send, receive, state - machine) -- `Makefile` -- DJGPP cross-compilation build rules - - -## Dependencies - -SecLink requires these libraries (all built into `../lib/`): - -| Library | Purpose | -|------------------|---------------------------------------------| -| `librs232.a` | Serial port driver (ISR, ring buffers) | -| `libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ | -| `libsecurity.a` | DH key exchange, XTEA-CTR cipher, RNG | +`make -C src/libs/kpunch/serial/seclink` (invoked by the top-level `make` +via the serial subdirectory). diff --git a/src/libs/kpunch/serial/security/README.md b/src/libs/kpunch/serial/security/README.md index d5080bb..02f6941 100644 --- a/src/libs/kpunch/serial/security/README.md +++ b/src/libs/kpunch/serial/security/README.md @@ -1,454 +1,26 @@ -# Security -- Diffie-Hellman Key Exchange and XTEA-CTR Cipher +# security -- Crypto Primitives -Cryptographic library providing Diffie-Hellman key exchange, XTEA -symmetric encryption in CTR mode, and a DRBG-based pseudo-random number -generator. Optimized for 486-class DOS hardware running under DJGPP/DPMI. +Compact cryptographic primitives used by the secLink wrapper: 1024-bit +Diffie-Hellman key exchange (RFC 2409 Group 2), XTEA block cipher in CTR +mode for session encryption, and an XTEA-CTR-based DRBG for session key +and IV generation. No external dependencies. -This library has no dependencies on the serial stack and can be used -independently for any application requiring key exchange, encryption, -or random number generation. +## Documentation +Full reference is in the combined serial stack doc: +`../serial.dhs` (the `security` section). It compiles into the DVX +System Reference -- open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -## Components +Topics covered: DH handshake API, XTEA-CTR encrypt/decrypt, DRBG seeding +and reseeding, remote-key validation range, threat model. -### 1. XTEA Cipher (CTR Mode) +## Source Files -XTEA (eXtended Tiny Encryption Algorithm) is a 64-bit block cipher with a -128-bit key and 32 Feistel rounds. In CTR (counter) mode, it operates as -a stream cipher: an incrementing counter is encrypted with the key to -produce a keystream, which is XOR'd with the plaintext. Because XOR is -its own inverse, the same operation encrypts and decrypts. +- `security.h` -- public API (DH, XTEA-CTR, DRBG) +- `security.c` -- implementation (bignum, Montgomery modexp, XTEA) -**Why XTEA instead of AES or DES:** +## Build -XTEA requires zero lookup tables, no key schedule, and compiles to -approximately 20 instructions per round (shifts, adds, and XORs only). -This makes it ideal for a 486 where the data cache is tiny (8KB) and -AES's 4KB S-boxes would thrash it. DES is similarly table-heavy and has -a complex key schedule. XTEA has no library dependencies -- the entire -cipher fits in about a dozen lines of C. At 32 rounds, XTEA provides -128-bit security with negligible per-byte overhead even on the slowest -target hardware. - -**CTR mode properties:** - -- Encrypt and decrypt are the same function (XOR is symmetric) -- No padding required -- operates on arbitrary-length data -- Random access possible (set the counter to any value) -- CRITICAL: the same counter value must never be reused with the same key. - Reuse reveals the XOR of two plaintexts. The secLink layer prevents this - by deriving separate TX/RX cipher keys for each direction. - -**XTEA block cipher internals:** - -The Feistel network uses the golden-ratio constant (delta = 0x9E3779B9) -as a round key mixer. Each round combines the two 32-bit halves using -shifts, additions, and XORs. The delta ensures each round uses a -different effective subkey, preventing slide attacks. No S-boxes or lookup -tables are involved anywhere in the computation. - -### 2. Diffie-Hellman Key Exchange (1024-bit) - -Uses the RFC 2409 Group 2 safe prime (1024-bit MODP group) with a -generator of 2. Private exponents are 256 bits for fast computation on -486-class hardware. - -**Why 1024-bit DH with 256-bit private exponents:** - -RFC 2409 Group 2 provides a well-audited, interoperable safe prime. -256-bit private exponents (versus full 1024-bit) reduce the modular -exponentiation from approximately 1024 squarings+multiplies to approximately -256 squarings + approximately 128 multiplies (half the exponent bits are 1 on -average). This makes key generation feasible on a 486 in under a second -rather than minutes. The security reduction is negligible -- Pollard's -rho on a 256-bit exponent requires approximately 2^128 operations, matching -XTEA's key strength. - -**Key validation:** - -`secDhComputeSecret()` validates that the remote public key is in the -range [2, p-2] to prevent small-subgroup attacks. Keys of 0, 1, or p-1 -would produce trivially guessable shared secrets. - -**Key derivation:** - -The 128-byte shared secret is reduced to a symmetric key via XOR-folding: -each byte of the secret is XOR'd into the output key at position -`i % keyLen`. For a 16-byte XTEA key, each output byte is the XOR of -8 secret bytes, providing thorough mixing. A proper KDF (HKDF, etc.) -would be more rigorous but adds complexity and code size for marginal -benefit in this use case. - -### 3. Pseudo-Random Number Generator - -XTEA-CTR based DRBG (Deterministic Random Bit Generator). The RNG -encrypts a monotonically increasing 64-bit counter with a 128-bit XTEA -key, producing 8 bytes of pseudorandom output per block. The counter -never repeats (64-bit space is sufficient for any practical session -length), so the output is a pseudorandom stream as long as the key has -sufficient entropy. - -**Hardware entropy sources:** - -- PIT (Programmable Interval Timer) -- runs at 1.193182 MHz. Its LSBs - change rapidly and provide approximately 10 bits of entropy per read, - depending on timing jitter. Two readings with intervening code execution - provide additional jitter. -- BIOS tick count -- 18.2 Hz timer at real-mode address 0040:046C. Adds - a few more bits of entropy. - -Total from hardware: roughly 20 bits of real entropy per call to -`secRngGatherEntropy()`. This is not enough on its own for -cryptographic use but is sufficient to seed the DRBG when supplemented -by user interaction timing (keyboard, mouse jitter). - -**Seeding and mixing:** - -The seed function (`secRngSeed()`) XOR-folds the entropy into the XTEA -key, derives the initial counter from the key bits, and then generates and -discards 64 bytes to advance past any weak initial output. This discard -step is standard DRBG practice -- it ensures the first bytes the caller -receives do not leak information about the seed material. - -Additional entropy can be stirred in at any time via `secRngAddEntropy()` -without resetting the RNG state. This function XOR-folds new entropy into -the key and then re-mixes by encrypting the key with itself, diffusing -the new entropy across all key bits. - -Auto-seeding: if `secRngBytes()` is called before `secRngSeed()`, it -automatically gathers hardware entropy and seeds itself as a safety net. - - -## BigNum Arithmetic - -All modular arithmetic uses a 1024-bit big number type (`BigNumT`) -stored as 32 x `uint32_t` words in little-endian order. Operations: - -| Function | Description | -|----------------|------------------------------------------------------| -| `bnAdd` | Add two bignums, return carry | -| `bnSub` | Subtract two bignums, return borrow | -| `bnCmp` | Compare two bignums (-1, 0, +1) | -| `bnBit` | Test a single bit by index | -| `bnBitLength` | Find the highest set bit position | -| `bnShiftLeft1` | Left-shift by 1, return carry | -| `bnClear` | Zero all words | -| `bnSet` | Set to a 32-bit value (clear upper words) | -| `bnCopy` | Copy from source to destination | -| `bnFromBytes` | Convert big-endian byte array to little-endian words | -| `bnToBytes` | Convert little-endian words to big-endian byte array | -| `bnMontMul` | Montgomery multiplication (CIOS variant) | -| `bnModExp` | Modular exponentiation via Montgomery multiply | - - -## Montgomery Multiplication - -The CIOS (Coarsely Integrated Operand Scanning) variant computes -`a * b * R^(-1) mod m` in a single pass without explicit division by the -modulus. This replaces the expensive modular reduction step (division by a -1024-bit number) with cheaper additions and right-shifts. - -For each of the 32 outer iterations (one per word of operand `a`): -1. Accumulate `a[i] * b` into the temporary product `t` -2. Compute the Montgomery reduction factor `u = t[0] * m0inv mod 2^32` -3. Add `u * mod` to `t` and shift right by 32 bits (implicit division) - -After all iterations, the result is in the range [0, 2m), so a single -conditional subtraction brings it into [0, m). - -**Montgomery constants** (computed once, lazily on first DH use): - -- `R^2 mod p` -- computed via 2048 iterations of shift-left-1 with - conditional subtraction. This is the Montgomery domain conversion - factor. -- `-p[0]^(-1) mod 2^32` -- computed via Newton's method (5 iterations, - doubling precision each step: 1->2->4->8->16->32 correct bits). This - is the Montgomery reduction constant. - -**Modular exponentiation** uses left-to-right binary square-and-multiply -scanning. For a 256-bit private exponent, this requires approximately 256 -squarings plus approximately 128 multiplies (half the bits are 1 on average), -where each operation is a Montgomery multiplication on 32-word numbers. - - -## Secure Zeroing - -Key material (private keys, shared secrets, cipher contexts) is erased -using a volatile-pointer loop: - -```c -static void secureZero(void *ptr, int len) { - volatile uint8_t *p = (volatile uint8_t *)ptr; - for (int i = 0; i < len; i++) { - p[i] = 0; - } -} -``` - -The `volatile` qualifier prevents the compiler from optimizing away the -zeroing as a dead store. Without it, the compiler would see that the -buffer is about to be freed and remove the memset entirely. This is -critical for preventing sensitive key material from lingering in freed -memory where a later `malloc` could expose it. - - -## Performance - -At serial port speeds, XTEA-CTR encryption overhead is minimal: - -| Speed | Blocks/sec | CPU Cycles/sec | % of 33 MHz 486 | -|----------|------------|----------------|------------------| -| 9600 | 120 | ~240K | < 1% | -| 57600 | 720 | ~1.4M | ~4% | -| 115200 | 1440 | ~2.9M | ~9% | - -DH key exchange takes approximately 0.3 seconds at 66 MHz or 0.6 seconds -at 33 MHz (256-bit private exponent, 1024-bit modulus, Montgomery -multiplication). - - -## API Reference - -### Constants - -| Name | Value | Description | -|---------------------|-------|-----------------------------------| -| `SEC_DH_KEY_SIZE` | 128 | DH public key size in bytes | -| `SEC_XTEA_KEY_SIZE` | 16 | XTEA key size in bytes | -| `SEC_SUCCESS` | 0 | Success | -| `SEC_ERR_PARAM` | -1 | Invalid parameter or NULL pointer | -| `SEC_ERR_NOT_READY` | -2 | Keys not yet generated/derived | -| `SEC_ERR_ALLOC` | -3 | Memory allocation failed | - -### Types - -```c -typedef struct SecDhS SecDhT; // Opaque DH context -typedef struct SecCipherS SecCipherT; // Opaque cipher context -``` - -### RNG Functions - -```c -int secRngGatherEntropy(uint8_t *buf, int len); -``` - -Reads hardware entropy from the PIT counter and BIOS tick count. Returns -the number of bytes written (up to 8). Provides roughly 20 bits of true -entropy -- not sufficient alone, but enough to seed the DRBG when -supplemented by user interaction timing. - -```c -void secRngSeed(const uint8_t *entropy, int len); -``` - -Initializes the DRBG with the given entropy. XOR-folds the input into -the XTEA key, derives the initial counter, and generates and discards 64 -bytes to advance past weak initial output. - -```c -void secRngAddEntropy(const uint8_t *data, int len); -``` - -Mixes additional entropy into the running RNG state without resetting it. -XOR-folds data into the key and re-mixes by encrypting the key with -itself. Use this to stir in keyboard timing, mouse jitter, or other -runtime entropy sources. - -```c -void secRngBytes(uint8_t *buf, int len); -``` - -Generates `len` pseudorandom bytes. Auto-seeds from hardware entropy if -not previously seeded. Produces 8 bytes per XTEA block encryption of the -internal counter. - -### Diffie-Hellman Functions - -```c -SecDhT *secDhCreate(void); -``` - -Allocates a new DH context. Returns `NULL` on allocation failure. The -context must be destroyed with `secDhDestroy()` when no longer needed. - -```c -int secDhGenerateKeys(SecDhT *dh); -``` - -Generates a 256-bit random private key and computes the corresponding -1024-bit public key (`g^private mod p`). Lazily initializes Montgomery -constants on first call. The RNG should be seeded before calling this. - -```c -int secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len); -``` - -Exports the public key as a big-endian byte array into `buf`. On entry, -`*len` must be at least `SEC_DH_KEY_SIZE` (128). On return, `*len` is -set to 128. - -```c -int secDhComputeSecret(SecDhT *dh, const uint8_t *remotePub, int len); -``` - -Computes the shared secret from the remote side's public key -(`remote^private mod p`). Validates the remote key is in range [2, p-2]. -Both sides compute this independently and arrive at the same value. - -```c -int secDhDeriveKey(SecDhT *dh, uint8_t *key, int keyLen); -``` - -Derives a symmetric key by XOR-folding the 128-byte shared secret down -to `keyLen` bytes. Each output byte is the XOR of `128/keyLen` input -bytes. - -```c -void secDhDestroy(SecDhT *dh); -``` - -Securely zeroes the entire DH context (private key, shared secret, public -key) and frees the memory. Must be called to prevent key material from -lingering in the heap. - -### Cipher Functions - -```c -SecCipherT *secCipherCreate(const uint8_t *key); -``` - -Creates an XTEA-CTR cipher context with the given 16-byte key. The -internal counter starts at zero. Returns `NULL` on allocation failure or -NULL key. - -```c -void secCipherCrypt(SecCipherT *c, uint8_t *data, int len); -``` - -Encrypts or decrypts `data` in place. CTR mode is symmetric -- the same -function handles both directions. The internal counter advances by one -for every 8 bytes processed (one XTEA block). The counter must never -repeat with the same key; callers are responsible for ensuring this -(secLink handles it by using separate cipher instances per direction). - -```c -void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi); -``` - -Sets the 64-bit nonce/counter to a specific value. Both the nonce -(baseline) and the running counter are set to the same value. Call this -before encrypting if you need a deterministic starting point. - -```c -void secCipherDestroy(SecCipherT *c); -``` - -Securely zeroes the cipher context (key and counter state) and frees the -memory. - - -## Usage Examples - -### Full Key Exchange - -```c -#include "security.h" -#include - -// Seed the RNG -uint8_t entropy[16]; -secRngGatherEntropy(entropy, sizeof(entropy)); -secRngSeed(entropy, sizeof(entropy)); - -// Create DH context and generate keys -SecDhT *dh = secDhCreate(); -secDhGenerateKeys(dh); - -// Export public key to send to remote -uint8_t myPub[SEC_DH_KEY_SIZE]; -int pubLen = SEC_DH_KEY_SIZE; -secDhGetPublicKey(dh, myPub, &pubLen); -// ... send myPub to remote, receive remotePub ... - -// Compute shared secret and derive a 16-byte XTEA key -secDhComputeSecret(dh, remotePub, SEC_DH_KEY_SIZE); - -uint8_t key[SEC_XTEA_KEY_SIZE]; -secDhDeriveKey(dh, key, SEC_XTEA_KEY_SIZE); -secDhDestroy(dh); // private key no longer needed - -// Create cipher and encrypt -SecCipherT *cipher = secCipherCreate(key); -uint8_t message[] = "Secret message"; -secCipherCrypt(cipher, message, sizeof(message)); -// message is now encrypted - -// Decrypt (reset counter first, then apply same operation) -secCipherSetNonce(cipher, 0, 0); -secCipherCrypt(cipher, message, sizeof(message)); -// message is now plaintext again - -secCipherDestroy(cipher); -``` - -### Standalone Encryption (Without DH) - -```c -// XTEA-CTR can be used independently of Diffie-Hellman -uint8_t key[SEC_XTEA_KEY_SIZE] = { /* your key */ }; -SecCipherT *c = secCipherCreate(key); - -uint8_t data[1024]; -// ... fill data ... -secCipherCrypt(c, data, sizeof(data)); // encrypt in place - -secCipherDestroy(c); -``` - -### Random Number Generation - -```c -// Seed from hardware -uint8_t hwEntropy[16]; -secRngGatherEntropy(hwEntropy, sizeof(hwEntropy)); -secRngSeed(hwEntropy, sizeof(hwEntropy)); - -// Stir in user-derived entropy (keyboard timing, etc.) -uint8_t userEntropy[4]; -// ... gather from timing events ... -secRngAddEntropy(userEntropy, sizeof(userEntropy)); - -// Generate random bytes -uint8_t randomBuf[32]; -secRngBytes(randomBuf, sizeof(randomBuf)); -``` - - -## Building - -``` -make # builds ../lib/libsecurity.a -make clean # removes objects and library -``` - -Cross-compiled with the DJGPP toolchain targeting i486+ CPUs. Compiler -flags: `-O2 -Wall -Wextra -march=i486 -mtune=i586`. - -Objects are placed in `../obj/security/`, the library in `../lib/`. - -No external dependencies -- the library is self-contained. It uses only -DJGPP's ``, ``, and `` for hardware entropy -collection (PIT and BIOS tick count access). - - -## Files - -- `security.h` -- Public API header (types, constants, function prototypes) -- `security.c` -- Complete implementation (bignum, Montgomery, DH, XTEA, RNG) -- `Makefile` -- DJGPP cross-compilation build rules - - -## Used By - -- `seclink/` -- Secure serial link (DH handshake, cipher creation, RNG seeding) +`make -C src/libs/kpunch/serial/security` (invoked by the top-level +`make` via the serial subdirectory). diff --git a/src/libs/kpunch/serial/security/security.c b/src/libs/kpunch/serial/security/security.c index 9aa6483..76949ec 100644 --- a/src/libs/kpunch/serial/security/security.c +++ b/src/libs/kpunch/serial/security/security.c @@ -47,6 +47,10 @@ #define BN_WORDS (BN_BITS / 32) #define BN_BYTES (BN_BITS / 8) +// R^2 mod m initialization uses repeated doubling (shift-left-by-1 + mod). +// Starting from 1, 2*BN_BITS doublings take us to 2^(2*BN_BITS) mod m = R^2. +#define R2_DOUBLE_ITERATIONS (2 * BN_BITS) + #define DH_PRIVATE_BITS 256 #define DH_PRIVATE_BYTES (DH_PRIVATE_BITS / 8) @@ -390,7 +394,7 @@ static uint32_t computeM0Inv(uint32_t m0) { static void computeR2(BigNumT *r2, const BigNumT *m) { bnSet(r2, 1); - for (int i = 0; i < 2 * BN_BITS; i++) { + for (int i = 0; i < R2_DOUBLE_ITERATIONS; i++) { int carry = bnShiftLeft1(r2); if (carry || bnCmp(r2, m) >= 0) { bnSub(r2, r2, m); diff --git a/src/libs/kpunch/serial/serial.dhs b/src/libs/kpunch/serial/serial.dhs index e72aa34..a64dc42 100644 --- a/src/libs/kpunch/serial/serial.dhs +++ b/src/libs/kpunch/serial/serial.dhs @@ -44,10 +44,35 @@ The DVX serial/networking stack provides reliable, optionally encrypted communic Loaded as: bin/libs/kpunch/serial/serial.lib +.h2 Layered Architecture + +.code ++--------------------------------------------------+ +| Application | +| | +| secLinkSend() -- send on channel, opt. encrypt | +| secLinkPoll() -- receive + dispatch | ++--------------------------------------------------+ +| secLink (channel multiplex, TX/RX ciphers) | ++--------------------------------------------------+ +| packet (HDLC framing, CRC-16, Go-Back-N ARQ) | ++--------------------------------------------------+ +| security -- DH key exchange, XTEA-CTR, DRBG | +| (used by secLink during handshake) | ++--------------------------------------------------+ +| rs232 (ISR-driven UART, 2048-byte rings) | ++--------------------------------------------------+ +| UART hardware (8250 / 16450 / 16550 / 16550A) | ++--------------------------------------------------+ +.endcode + +The security layer is invoked by secLink during the Diffie-Hellman handshake and for per-packet cipher operations. It has no runtime coupling to rs232 or packet, so it can be used standalone for cryptographic operations independent of the serial stack. + .link lib.serial.rs232 Layer 1: RS-232 UART Driver .link lib.serial.packet Layer 2: Packet Transport .link lib.serial.security Layer 3: Security (DH + XTEA) .link lib.serial.seclink Layer 4: Secure Link +.link lib.serial.example End-to-End Example .topic lib.serial.rs232 .title RS-232 UART Driver @@ -69,12 +94,13 @@ Header: rs232/rs232.h .h2 Port Constants .table - Constant Value Description - -------- ----- ----------- - RS232_COM1 0 First serial port. - RS232_COM2 1 Second serial port. - RS232_COM3 2 Third serial port. - RS232_COM4 3 Fourth serial port. + Constant Value Description + -------- ----- ----------- + RS232_COM1 0 First serial port. + RS232_COM2 1 Second serial port. + RS232_COM3 2 Third serial port. + RS232_COM4 3 Fourth serial port. + RS232_NUM_PORTS 4 Total number of supported COM ports. .endtable .h2 Handshake Modes @@ -176,13 +202,13 @@ Returns the number of bytes actually read (may be less than len). .h2 rs232Write -Blocking polled write directly to UART transmit holding register. Bypasses the TX ring buffer. Use for small, immediate writes. +Blocking polled write directly to UART transmit holding register. Bypasses the TX ring buffer. Polls LSR_TX_HOLD_EMPTY before writing each byte. Use for small, immediate writes where order matters (the packet layer uses this for frame transmission). .code int rs232Write(int com, const char *data, int len); .endcode -Returns RS232_SUCCESS or a negative error code. +Returns the number of bytes actually written (may be less than len if software flow control is asserted), or a negative error code (RS232_ERR_INVALID_PORT, RS232_ERR_NOT_OPEN, RS232_ERR_NULL_PTR). .h2 rs232WriteBuf @@ -196,7 +222,7 @@ Returns the number of bytes actually queued (may be less than len if the buffer .h2 rs232Set -Change all line parameters on an open port. +Change all line parameters on an open port. Internally calls rs232SetBps, rs232SetData, rs232SetParity, rs232SetStop, and rs232SetHandshake in sequence. Returns RS232_SUCCESS on success or the first negative error code encountered. .code int rs232Set(int com, int32_t bps, int dataBits, @@ -209,15 +235,17 @@ int rs232Set(int com, int32_t bps, int dataBits, Function Returns -------- ------- rs232GetBase(com) I/O base address. - rs232GetBps(com) Current baud rate. - rs232GetData(com) Data bits setting. - rs232GetParity(com) Parity character ('N', 'O', 'E', 'M', 'S'). - rs232GetStop(com) Stop bits setting. - rs232GetHandshake(com) Handshake mode. + rs232GetBps(com) Current baud rate (int32_t). + rs232GetData(com) Data bits setting (5-8). + rs232GetParity(com) Parity character: 'n', 'o', 'e', 'm', or 's' (lowercase). + rs232GetStop(com) Stop bits setting (1 or 2). + rs232GetHandshake(com) Handshake mode (RS232_HANDSHAKE_*). rs232GetIrq(com) IRQ number. rs232GetUartType(com) Detected UART type (RS232_UART_*). .endtable +rs232GetParity returns a lowercase character on success. Callers comparing against uppercase constants must use either both cases or tolower(). + .h2 Status Getters .table @@ -299,10 +327,10 @@ Header: packet/packet.h PKT_ERR_NOT_OPEN -2 Connection is not open. PKT_ERR_WOULD_BLOCK -4 Operation would block. PKT_ERR_OVERFLOW -5 Buffer overflow. - PKT_ERR_INVALID_PARAM -6 Invalid parameter. - PKT_ERR_TX_FULL -7 Transmit window is full. + PKT_ERR_INVALID_PARAM -6 Invalid parameter (NULL handle, bad data pointer, length out of range). + PKT_ERR_TX_FULL -7 Transmit window is full (non-blocking send only). PKT_ERR_NO_DATA -8 No data available. - PKT_ERR_DISCONNECTED -9 Remote side disconnected. + PKT_ERR_DISCONNECTED -9 Serial port returned a negative read (disconnect/error). .endtable .h2 PktRecvCallbackT @@ -324,7 +352,7 @@ typedef void (*PktRecvCallbackT)(void *ctx, .h2 pktOpen -Open a packetized connection over an already-open COM port. +Open a packetized connection over an already-open COM port. Only one packet connection may exist on a given COM port at a time; a second pktOpen call on the same port returns NULL to avoid silent framing corruption. .code PktConnT *pktOpen(int com, int windowSize, @@ -335,16 +363,16 @@ PktConnT *pktOpen(int com, int windowSize, Parameter Description --------- ----------- com COM port index (RS232_COM1..RS232_COM4). Must already be open via rs232Open. - windowSize Sliding window size (1..PKT_MAX_WINDOW). Pass 0 for PKT_DEFAULT_WINDOW. - callback Called when a complete, CRC-verified packet is received. + windowSize Sliding window size (1..PKT_MAX_WINDOW). Pass 0 for PKT_DEFAULT_WINDOW. Values greater than PKT_MAX_WINDOW are clamped. + callback Called when a complete, CRC-verified, in-order packet is received. The data pointer is valid only during the callback. callbackCtx User pointer passed to the callback. .endtable -Returns an opaque PktConnT handle, or NULL on failure. +Returns an opaque PktConnT handle, or NULL on failure (invalid port, duplicate open, or allocation failure). .h2 pktClose -Close a packetized connection. Does not close the underlying COM port. +Close a packetized connection and release its registry slot. Does not close the underlying COM port. The caller is responsible for calling rs232Close separately when no longer needed. .code void pktClose(PktConnT *conn); @@ -352,7 +380,7 @@ void pktClose(PktConnT *conn); .h2 pktSend -Send a data packet. +Send a data packet. The payload is copied into an internal retransmit slot before transmission, so the caller may reuse the supplied buffer immediately. .code int pktSend(PktConnT *conn, const uint8_t *data, @@ -363,34 +391,36 @@ int pktSend(PktConnT *conn, const uint8_t *data, Parameter Description --------- ----------- conn Connection handle from pktOpen. - data Payload bytes (up to PKT_MAX_PAYLOAD). + data Payload bytes (1..PKT_MAX_PAYLOAD). len Payload length. - block If true, block until transmit window has space. If false, return PKT_ERR_TX_FULL when the window is full. + block If true, poll pktPoll internally until transmit window has space. If false, return PKT_ERR_TX_FULL immediately when the window is full. .endtable -Returns PKT_SUCCESS or a negative error code. +Returns PKT_SUCCESS on success, PKT_ERR_INVALID_PARAM for NULL/out-of-range input, PKT_ERR_TX_FULL for a non-blocking call with a full window, or PKT_ERR_DISCONNECTED if the serial port drops during a blocking wait. .h2 pktPoll -Poll for incoming data, process received frames, and handle retransmits. Must be called frequently in the main loop. +Poll for incoming data, process received frames, and handle retransmits. Must be called frequently in the main loop. The callback is invoked synchronously for each complete, CRC-verified, in-order data frame, so the caller should be prepared for reentrant pktSend calls from within the callback. .code int pktPoll(PktConnT *conn); .endcode -Returns the number of valid data packets delivered to the callback. +Returns the number of valid data packets delivered to the callback this call, or PKT_ERR_INVALID_PARAM if conn is NULL, or PKT_ERR_DISCONNECTED if the serial layer returned a negative read. .h2 pktReset -Reset the connection state (sequence numbers, buffers) and send a RST frame to the remote side. +Reset the local connection state (sequence numbers, transmit slots, RX state machine) and send a RST frame to the remote side. The remote's RST handler clears its own state and echoes a RST back. Useful for recovering from a desynchronized connection. .code int pktReset(PktConnT *conn); .endcode +Returns PKT_SUCCESS on success or PKT_ERR_INVALID_PARAM if conn is NULL. + .h2 pktCanSend -Check whether there is room in the transmit window for another packet. +Check whether there is room in the transmit window for another packet. Returns false when conn is NULL or the transmit window is full. .code bool pktCanSend(PktConnT *conn); @@ -398,12 +428,14 @@ bool pktCanSend(PktConnT *conn); .h2 pktGetPending -Get the number of unacknowledged packets currently in the transmit window. +Get the number of unacknowledged packets currently in the transmit window. Ranges from 0 (all sent packets acknowledged) to windowSize (window full). .code int pktGetPending(PktConnT *conn); .endcode +Returns the pending count on success, or PKT_ERR_INVALID_PARAM if conn is NULL. + .topic lib.serial.security .title Security (DH + XTEA) .toc 1 Security (DH + XTEA) @@ -507,12 +539,14 @@ void secCipherDestroy(SecCipherT *c); .table Function Description -------- ----------- - secCipherCreate Allocate a cipher context with a 128-bit key. - secCipherSetNonce Set the initial counter value (nonce). Must be unique per session. - secCipherCrypt Encrypt or decrypt data in place. The counter increments automatically. - secCipherDestroy Free the cipher context. + secCipherCreate Allocate a cipher context with a 128-bit key. Returns NULL on allocation failure or NULL key. Initial counter is zero. + secCipherSetNonce Set the initial counter value (nonce). Must be unique per session. Both the stored nonce and the running counter are set to (nonceLo, nonceHi). + secCipherCrypt Encrypt or decrypt data in place. The counter increments by one every 8 bytes (one XTEA block). + secCipherDestroy Securely zero the cipher context (key and counter state) and free its memory. .endtable +secCipherCreate returns NULL if the key pointer is NULL or allocation fails. secCipherCrypt is a no-op when any of c/data is NULL or len <= 0. + .topic lib.serial.seclink .title Secure Link .toc 1 Secure Link @@ -534,10 +568,12 @@ Header: seclink/secLink.h .h2 Constants .table - Constant Value Description - -------- ----- ----------- - SECLINK_MAX_PAYLOAD 254 Maximum plaintext bytes per send (PKT_MAX_PAYLOAD minus 1-byte channel header). - SECLINK_MAX_CHANNEL 127 Highest valid channel number. + Constant Value Description + -------- ----- ----------- + SECLINK_MAX_PAYLOAD 254 Maximum plaintext bytes per send (PKT_MAX_PAYLOAD minus 1-byte channel header). + SECLINK_MAX_CHANNEL 127 Highest valid channel number. + SECLINK_NUM_CHANNELS 128 Array size needed to hold one slot per channel (SECLINK_MAX_CHANNEL + 1). + SECLINK_CHAN_BUF_SIZE 4096 Recommended per-channel receive buffer size for callers that buffer inbound data between the receive callback and an application read. .endtable .h2 Error Codes @@ -546,12 +582,12 @@ Header: seclink/secLink.h Constant Value Description -------- ----- ----------- SECLINK_SUCCESS 0 Operation succeeded. - SECLINK_ERR_PARAM -1 Invalid parameter. + SECLINK_ERR_PARAM -1 Invalid parameter (NULL pointer, bad channel, bad length). SECLINK_ERR_SERIAL -2 RS-232 layer error. SECLINK_ERR_ALLOC -3 Memory allocation failed. - SECLINK_ERR_HANDSHAKE -4 DH handshake failed. + SECLINK_ERR_HANDSHAKE -4 DH handshake failed (key generation, send, or disconnect during exchange). SECLINK_ERR_NOT_READY -5 Handshake not yet completed (encrypted send attempted). - SECLINK_ERR_SEND -6 Packet send failed. + SECLINK_ERR_SEND -6 Packet send failed, or non-blocking send when window is full. .endtable .h2 SecLinkRecvT @@ -565,7 +601,7 @@ typedef void (*SecLinkRecvT)(void *ctx, .h2 secLinkOpen -Open a secure serial link. Opens the COM port and packet layer internally. +Open a secure serial link. Opens the COM port via rs232Open and creates a packet layer (default window size) with an internal receive dispatcher. The handshake must be performed separately via secLinkHandshake before encrypted data can be sent. .code SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, @@ -579,18 +615,18 @@ SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, com COM port index (RS232_COM1..RS232_COM4). bps Baud rate. dataBits Data bits per character (5-8). - parity Parity mode ('N', 'O', 'E', 'M', 'S'). + parity Parity mode ('N', 'O', 'E', 'M', 'S' or lowercase). stopBits Stop bits (1 or 2). handshake Flow control mode (RS232_HANDSHAKE_*). - callback Called when a packet is received (plaintext, with channel). + callback Called when a packet is received, after decryption if applicable. Plaintext data pointer is valid only during the callback. ctx User pointer passed to the callback. .endtable -Returns an opaque SecLinkT handle, or NULL on failure. +Returns an opaque SecLinkT handle, or NULL on failure (allocation failure, rs232Open error, or pktOpen error). On failure all partially initialized resources are cleaned up. .h2 secLinkClose -Close the link, free all resources, and close the COM port. +Close the link and free all associated resources in reverse of open order: securely destroy TX and RX ciphers (which scrubs key material), destroy any remaining DH context, close the packet layer, close the COM port, and finally free the link struct itself. Safe to call with a NULL link (no-op). .code void secLinkClose(SecLinkT *link); @@ -598,17 +634,36 @@ void secLinkClose(SecLinkT *link); .h2 secLinkHandshake -Perform Diffie-Hellman key exchange. Blocks until both sides complete. The RNG must be seeded before calling this. +Perform a Diffie-Hellman key exchange and derive the session cipher keys. Blocks, polling the packet layer in a loop, until both sides have swapped public keys. The RNG must be seeded (via secRngSeed, secRngGatherEntropy + secRngSeed, or secRngAddEntropy) before calling this, or the generated private key will be predictable. .code int secLinkHandshake(SecLinkT *link); .endcode -Returns SECLINK_SUCCESS or SECLINK_ERR_HANDSHAKE. +Internally: + +.list +.item Creates a DH context via secDhCreate, generates 1024-bit keys via secDhGenerateKeys, and exports the 128-byte public key. +.item Sends the public key as a single packet via pktSend (blocking). +.item Polls pktPoll until the remote's public key arrives and the internal callback completes the handshake (computes the shared secret, derives directional TX and RX cipher keys, transitions to READY, destroys the DH context for forward secrecy). +.endlist + +Returns SECLINK_SUCCESS on success, SECLINK_ERR_PARAM on NULL link, SECLINK_ERR_ALLOC on DH context allocation failure, or SECLINK_ERR_HANDSHAKE for DH key generation failure, packet send failure, or serial disconnect during the exchange. + +.h3 Directional Key Derivation + +To prevent CTR-mode keystream collision, the two sides never use the same TX key. After computing the shared secret, each side derives a master XTEA key via secDhDeriveKey and then XORs it with a direction byte: + +.list +.item The side with the lexicographically lower public key uses masterKey XOR 0xAA for TX and masterKey XOR 0x55 for RX. +.item The other side uses the reverse assignment. +.endlist + +Both sides initialize their cipher counters to zero and advance monotonically from there. .h2 secLinkSend -Send data on a channel, optionally encrypted. +Send data on a channel, optionally encrypted. Prepends a one-byte channel header (bit 7 = encrypt flag, bits 6..0 = channel number), then encrypts the payload only (never the header) when encrypt is true. .code int secLinkSend(SecLinkT *link, const uint8_t *data, @@ -620,13 +675,15 @@ int secLinkSend(SecLinkT *link, const uint8_t *data, --------- ----------- link Connection handle from secLinkOpen. data Plaintext payload (1..SECLINK_MAX_PAYLOAD bytes). - len Payload length. + len Payload length. Must be in 1..SECLINK_MAX_PAYLOAD. channel Logical channel number (0..SECLINK_MAX_CHANNEL). encrypt If true, encrypt before sending (requires completed handshake). - block If true, block until the transmit window has space. + block If true, block until the transmit window has space. If false, return SECLINK_ERR_SEND immediately when the window is full. .endtable -Returns SECLINK_SUCCESS or a negative error code. +Returns SECLINK_SUCCESS on success or a negative error code (SECLINK_ERR_PARAM, SECLINK_ERR_NOT_READY, SECLINK_ERR_SEND). + +Cipher counter safety: the function checks transmit window space BEFORE encrypting the payload. Encrypting first and then failing to send would advance the TX cipher counter while the data never reached the wire, permanently desynchronizing the TX cipher state from the remote's RX cipher. This ordering is critical for correctness when non-blocking sends are used. .h2 secLinkSendBuf @@ -641,26 +698,118 @@ Returns SECLINK_SUCCESS or the first error encountered. .h2 secLinkPoll -Poll for incoming data. Decrypts encrypted packets and delivers plaintext to the receive callback. +Poll for incoming data. Delegates to pktPoll, which drains the RS-232 ring buffer, processes received frames, checks retransmit timers, and dispatches each complete packet to an internal callback that strips the channel header, decrypts when the encrypt flag is set, and forwards plaintext to the user callback. .code int secLinkPoll(SecLinkT *link); .endcode -Returns the number of packets delivered, or a negative error code. +Returns the number of packets delivered to the callback this call, SECLINK_ERR_PARAM if link is NULL, or one of the pktPoll error codes (PKT_ERR_DISCONNECTED, PKT_ERR_INVALID_PARAM). Must be called frequently in the main loop. .h2 secLinkIsReady -Check whether the handshake is complete and the link is ready for encrypted data. +Check whether the handshake is complete and the link is ready for encrypted traffic. Cleartext sends do not require the link to be ready. .code bool secLinkIsReady(SecLinkT *link); .endcode +Returns true only when link is non-NULL and the internal state is STATE_READY (that is, secLinkHandshake has completed). Returns false on NULL or before the handshake. + .h2 secLinkGetPending -Get the number of unacknowledged packets in the transmit window. +Get the number of unacknowledged packets in the transmit window. Delegates directly to pktGetPending on the underlying packet connection. Useful for non-blocking send loops to decide when to throttle. .code int secLinkGetPending(SecLinkT *link); .endcode + +Returns the pending count on success or SECLINK_ERR_PARAM when link is NULL. + +.topic lib.serial.example +.title End-to-End Example +.toc 1 End-to-End Example +.index Serial Example +.index BBS Client + +.h1 End-to-End Example + +Below is a complete secure BBS client that opens COM1, exchanges DH keys, and then shuttles terminal traffic over a single encrypted channel. It demonstrates the typical serial stack usage: open the link, seed the RNG, run the handshake, then enter a poll+send loop. + +.code +#include "rs232/rs232.h" +#include "packet/packet.h" +#include "security/security.h" +#include "seclink/secLink.h" +#include +#include + +#define CHAN_TERMINAL 0 + +static void onPacket(void *ctx, const uint8_t *data, + int len, uint8_t channel) +{ + (void)ctx; + if (channel == CHAN_TERMINAL) { + fwrite(data, 1, len, stdout); + fflush(stdout); + } +} + + +int main(void) { + // Seed the RNG from hardware entropy plus any user-derived + // randomness (keyboard timing, etc.). + uint8_t entropy[16]; + int got = secRngGatherEntropy(entropy, sizeof(entropy)); + secRngSeed(entropy, got); + + // Open the link at 115200 8N1 with RTS/CTS flow control. + SecLinkT *link = secLinkOpen( + RS232_COM1, 115200, 8, 'N', 1, + RS232_HANDSHAKE_RTSCTS, + onPacket, NULL); + if (!link) { + fprintf(stderr, "secLinkOpen failed\n"); + return 1; + } + + // Run the DH key exchange. Blocks until both sides + // have swapped public keys and derived cipher keys. + if (secLinkHandshake(link) != SECLINK_SUCCESS) { + fprintf(stderr, "Handshake failed\n"); + secLinkClose(link); + return 1; + } + + // Send an encrypted banner and then relay keyboard input. + const char *hello = "Hello, secure BBS!\r\n"; + secLinkSend(link, (const uint8_t *)hello, + (int)strlen(hello), CHAN_TERMINAL, true, true); + + while (!feof(stdin)) { + // Deliver any incoming packets to onPacket(). + secLinkPoll(link); + + // If the user typed something, forward it encrypted. + int ch = getchar(); + if (ch == EOF) { + break; + } + uint8_t byte = (uint8_t)ch; + secLinkSend(link, &byte, 1, CHAN_TERMINAL, true, true); + } + + secLinkClose(link); + return 0; +} +.endcode + +Notes: + +.list +.item secLinkOpen internally calls rs232Open and pktOpen. secLinkClose tears them down in reverse order. +.item The RNG must be seeded before secLinkHandshake. secRngGatherEntropy provides roughly 20 bits of hardware entropy; supplement with user interaction timing for cryptographic use. +.item secLinkPoll must be called frequently to drain the RX ring buffer, process ACKs, and dispatch received packets to the callback. +.item For bulk transfers larger than SECLINK_MAX_PAYLOAD (254 bytes), use secLinkSendBuf which splits the data into chunks automatically. +.endlist diff --git a/src/libs/kpunch/sql/dvxsql.dhs b/src/libs/kpunch/sql/dvxsql.dhs index dae0e55..5ff5081 100644 --- a/src/libs/kpunch/sql/dvxsql.dhs +++ b/src/libs/kpunch/sql/dvxsql.dhs @@ -36,9 +36,37 @@ High-level wrapper around SQLite3 for DVX applications. Manages database connect Header: sql/dvxSql.h +.h3 SQLite Wrapping + +dvxSql wraps the bundled SQLite3 amalgamation (sql/thirdparty/sqlite/examples/sqlite3.h). It manages two internal dynamic tables keyed by 1-based handles: + +.list +.item Database table: each slot holds a sqlite3 * plus a per-database error string and affected-row count. +.item Cursor table: each slot holds a sqlite3_stmt *, the owning database handle, and EOF tracking. +.endlist + +Growing either table doubles its capacity. Closed slots are recycled by subsequent dvxSqlOpen or dvxSqlQuery calls, keeping handle values stable for the caller. + .h3 Handle Model -Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it. +Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it, so callers do not need to track cursor lifetimes per-database. + +.h3 Connection Lifecycle + +.list +.item dvxSqlOpen allocates a handle and calls sqlite3_open. The database file is created if it does not exist. +.item Use dvxSqlExec for DDL and non-query DML. On success, dvxSqlAffectedRows returns the row count for the last call on this handle. +.item Use dvxSqlQuery to obtain a cursor over a SELECT. Iterate with dvxSqlNext and read columns with dvxSqlFieldText, dvxSqlFieldInt, dvxSqlFieldDbl, or dvxSqlFieldByName. +.item Call dvxSqlFreeResult when the cursor is no longer needed. Call dvxSqlClose when the database is no longer needed; any cursors still open on that database are finalized automatically. +.endlist + +.h3 Common Patterns + +.list +.item Parameterize user input with dvxSqlEscape before interpolating into SQL strings. +.item Check dvxSqlError(db) for the last error on a handle; its message is stable until the next operation on the same database. +.item dvxSqlFieldByName matches column names case-insensitively, suitable for most real-world schemas. +.endlist .link sql.db Database Operations .link sql.cursor Cursor Operations diff --git a/src/libs/kpunch/taskmgr/README.md b/src/libs/kpunch/taskmgr/README.md index 3559b76..6bf7c89 100644 --- a/src/libs/kpunch/taskmgr/README.md +++ b/src/libs/kpunch/taskmgr/README.md @@ -1,75 +1,20 @@ -# DVX Task Manager (taskmgr.lib) +# taskmgr -- Task Manager -A separate DXE3 module that provides the system Task Manager window. -Built as `taskmgr.lib` and loaded alongside the other libs at startup. -The Task Manager is a shell-level component, not tied to any app -- it -persists even if the desktop app (Program Manager) is terminated. +Provides the system Task Manager window that lists running tasks and +lets the user switch to or terminate any task. Invoked from the shell +(usually via a system-menu item). +## Documentation -## Registration +Full reference is in `taskmgr.dhs` (this directory), compiled into the +DVX System Reference. Open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -The module uses a GCC `__attribute__((constructor))` function to -register itself with the shell at load time. The constructor sets the -`shellCtrlEscFn` function pointer (declared in `shellApp.h`) to -`shellTaskMgrOpen`. The shell calls this pointer when Ctrl+Esc is -pressed. If `taskmgr.lib` is not loaded, the pointer remains NULL and -Ctrl+Esc does nothing. - - -## Features - -- **App list**: 6-column ListView showing Name, Title, File, Type, - Memory, and Status for all running apps -- **Memory column**: Per-app memory usage from `dvxMemGetAppUsage()`, - displayed in KB or MB -- **Switch To**: Raises and focuses the selected app's topmost window; - restores it if minimized. Also triggered by double-clicking a row. -- **End Task**: Force-kills the selected app via `shellForceKillApp()` -- **Run...**: Opens a file dialog to browse for and launch a `.app` file -- **Accelerator keys**: `&Switch To` (Alt+S), `&End Task` (Alt+E), - `&Run...` (Alt+R) -- **Status bar**: Shows running app count and system memory usage -- **Live refresh**: Registers a desktop update callback so the list - refreshes automatically when apps are loaded, reaped, or crash - - -## Window Behavior - -- Owned by the shell (appId = 0), not by any app -- Single-instance: if already open, Ctrl+Esc raises and focuses it -- Closing the window unregisters the desktop update callback and - cleans up dynamic arrays - - -## API - -```c -// Open or raise the Task Manager window. -void shellTaskMgrOpen(AppContextT *ctx); - -// Refresh the task list (called by desktop update notification). -void shellTaskMgrRefresh(void); -``` - - -## Dependencies - -Loaded after: `dvxshell`, `libtasks`, `libdvx`, `texthelp`, `listhelp` -(via `taskmgr.dep`). - - -## Files - -| File | Description | -|------|-------------| -| `shellTaskMgr.h` | Public API (open, refresh) | -| `shellTaskMgr.c` | Task Manager window, list, buttons, DXE constructor | -| `Makefile` | Builds `bin/libs/taskmgr.lib` + dep file | +## Source Files +- `shellTaskMgr.h` -- public entry point +- `shellTaskMgr.c` -- Task Manager window implementation ## Build -``` -make # builds bin/libs/taskmgr.lib + taskmgr.dep -make clean # removes objects, library, and dep file -``` +`make -C src/libs/kpunch/taskmgr` (invoked by the top-level `make`). diff --git a/src/libs/kpunch/taskmgr/shellTaskMgr.c b/src/libs/kpunch/taskmgr/shellTaskMgr.c index 3029401..995b273 100644 --- a/src/libs/kpunch/taskmgr/shellTaskMgr.c +++ b/src/libs/kpunch/taskmgr/shellTaskMgr.c @@ -312,7 +312,7 @@ void shellTaskMgrOpen(AppContextT *ctx) { tmCols[5].align = ListViewAlignLeftE; sTmListView = wgtListView(root); - sTmListView->weight = 100; + sTmListView->weight = WGT_WEIGHT_FILL; sTmListView->prefH = wgtPixels(TM_LIST_PREF_H); sTmListView->onDblClick = onTmSwitchTo; wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT); @@ -321,7 +321,7 @@ void shellTaskMgrOpen(AppContextT *ctx) { btnRow->spacing = wgtPixels(TM_BTN_SPACING); sTmStatusLbl = wgtLabel(btnRow, ""); - sTmStatusLbl->weight = 100; + sTmStatusLbl->weight = WGT_WEIGHT_FILL; WidgetT *switchBtn = wgtButton(btnRow, "&Switch To"); switchBtn->onClick = onTmSwitchTo; diff --git a/src/libs/kpunch/taskmgr/taskmgr.dhs b/src/libs/kpunch/taskmgr/taskmgr.dhs index 3ea2719..2194b28 100644 --- a/src/libs/kpunch/taskmgr/taskmgr.dhs +++ b/src/libs/kpunch/taskmgr/taskmgr.dhs @@ -30,12 +30,33 @@ .h1 Task Manager -System-level task manager for DVX. Displays running applications and per-app memory usage. Always accessible via Ctrl+Esc regardless of which application is focused. Persists independently of the desktop app (Program Manager). +System-level task manager for DVX. Displays all running applications with a six-column ListView (Name, Title, File, Type, Memory, Status) and exposes Switch To, End Task, and Run... operations. Always accessible via Ctrl+Esc regardless of which application is focused. Persists independently of the desktop app (Program Manager). Header: taskmgr/shellTaskMgr.h Loaded as: bin/libs/taskmgr.lib +.h2 Integration + +The task manager is a shell-level library, not a regular DXE app. At DXE load time a GCC constructor function registers the Task Manager with the shell by setting the shellCtrlEscFn function pointer (declared extern in dvxshell/shellApp.h). The shell invokes shellCtrlEscFn on every Ctrl+Esc keystroke; if taskmgr.lib is not loaded the pointer stays NULL and Ctrl+Esc does nothing. + +The Task Manager owns its window on behalf of the shell itself (appId = 0), not any running app, which is why it survives when the desktop app terminates. It registers shellTaskMgrRefresh with shellRegisterDesktopUpdate so the list refreshes live as apps load, reap, or crash. + +.h2 Integration with libtasks + +The Task Manager reads from the shell's app slot table (shellGetApp, shellAppSlotCount, shellRunningAppCount) rather than from the libtasks scheduler directly. Each ShellAppT slot carries its cooperative task ID when hasMainLoop is true; End Task calls shellForceKillApp, which in turn calls tsKill on that task ID (for main-loop apps) after destroying the app's windows. + +.h2 Features + +.list +.item Six-column ListView showing Name, Title, File, Type (Task or Callback), Memory (from dvxMemGetAppUsage), and Status for every running app. +.item Switch To button (Alt+S): raises and focuses the selected app's topmost window, restoring it if minimized. Also triggered by double-clicking a row. +.item End Task button (Alt+E): force-kills the selected app via shellForceKillApp. +.item Run... button (Alt+R): opens a file dialog to browse for and launch a .app file. +.item Status bar showing running app count plus total and used system memory. +.item Single-instance: reopening the Task Manager while it is already visible raises and focuses the existing window rather than creating a new one. +.endlist + .h2 shellTaskMgrOpen Open the Task Manager window, or raise it to the front if already open. @@ -47,14 +68,14 @@ void shellTaskMgrOpen(AppContextT *ctx); .table Parameter Description --------- ----------- - ctx Application context (from dvxInit). Required for window creation and event registration. + ctx Application context (from dvxInit). Required for window creation, stack manipulation, and registering the desktop-update callback. .endtable -Called by the shell's global Ctrl+Esc handler. +Called by the shell's global Ctrl+Esc handler via the shellCtrlEscFn hook. .h2 shellTaskMgrRefresh -Refresh the task list display. Call this when applications are launched or terminated so the Task Manager reflects the current state. +Rebuild the task list display from the current shell app slot table and refresh the status bar. Registered with shellRegisterDesktopUpdate at open time so it is invoked whenever the shell detects app state changes. .code void shellTaskMgrRefresh(void); diff --git a/src/libs/kpunch/texthelp/README.md b/src/libs/kpunch/texthelp/README.md index b539cc4..d6ee1c3 100644 --- a/src/libs/kpunch/texthelp/README.md +++ b/src/libs/kpunch/texthelp/README.md @@ -1,133 +1,26 @@ -# texthelp -- Shared Text Editing Helper Library +# texthelp -- Shared Text-Editing Helpers -Shared infrastructure for text editing widgets, built as -`texthelp.lib` (DXE3 module). Provides clipboard operations, cursor -blink management, word boundary detection, cross-widget selection -clearing, and a complete single-line text editing engine. +Shared text-editing infrastructure used by the TextInput, Spinner, +ComboBox, and AnsiTerm widgets. Centralizes cursor blinking, multi-click +detection, word boundary logic, cross-widget selection clearing, and the +single-line editing engine (key dispatch, mouse hit, drag-select, +undo buffer). -Used by: TextInput, TextArea, Spinner, ComboBox, AnsiTerm. +## Documentation +Full reference is in `texthelp.dhs` (this directory), compiled into the +DVX System Reference. Open via the Help Viewer app, or read +`docs/dvx_system_reference.html` after `make`. -## API Reference +Topics covered: cursor-blink update, word boundary functions, single-line +text edit mouse/key/paint entry points, undo snapshot API, and the +cross-widget selection clearing protocol. -### Cursor Blink - -```c -void wgtUpdateCursorBlink(void); -``` - -Toggles the global cursor blink state (`sCursorBlinkOn`) based on -`clock()` timing. Called from the event dispatch layer during paint. -Widgets that show a text cursor check `sCursorBlinkOn` to decide -whether to draw or hide the cursor. - -### Selection Management - -```c -void clearOtherSelections(WidgetT *except); -``` - -Clears text/item selections in all widgets except the specified one. -Called when a widget gains focus or starts a new selection, ensuring -only one widget has an active selection at a time. - -### Word / Character Helpers - -```c -bool isWordChar(char c); -``` - -Returns true if `c` is a word character (alphanumeric or underscore). -Used for double-click word selection and Ctrl+arrow word navigation. - -```c -int32_t wordStart(const char *buf, int32_t pos); -``` - -Scans backward from `pos` to find the start of the current word. - -```c -int32_t wordEnd(const char *buf, int32_t len, int32_t pos); -``` - -Scans forward from `pos` to find the end of the current word. - -### Single-Line Text Editing Engine - -Four functions implement the complete editing behavior shared across -TextInput, Spinner, ComboBox, and other single-line text widgets: - -```c -void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, - char *buf, int32_t bufSize, int32_t *pLen, - int32_t *pCursor, int32_t *pScrollOff, - int32_t *pSelStart, int32_t *pSelEnd, - char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, - int32_t fieldWidth); -``` - -Handles all keyboard input: character insertion, deletion (Backspace, -Delete), cursor movement (arrows, Home, End), word navigation -(Ctrl+arrows), selection (Shift+arrows, Ctrl+Shift+arrows, Ctrl+A), -clipboard (Ctrl+C/X/V), and undo (Ctrl+Z). - -```c -void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, - int32_t textLeftX, const BitmapFontT *font, - const char *buf, int32_t len, int32_t scrollOff, - int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, - bool wordSelect, bool dragSelect); -``` - -Handles mouse click positioning: converts pixel coordinates to cursor -position, handles word selection (double-click), and drag-select. - -```c -void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, - int32_t maxChars, const BitmapFontT *font, int32_t len, - int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd); -``` - -Updates cursor and selection during mouse drag operations. - -```c -void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, - const BitmapFontT *font, const ColorSchemeT *colors, - int32_t textX, int32_t textY, const char *buf, - int32_t visLen, int32_t scrollOff, int32_t cursorPos, - int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, - bool showCursor, int32_t cursorMinX, int32_t cursorMaxX); -``` - -Renders a single line of text with selection highlighting and cursor. - -### Constants - -| Name | Value | Description | -|------|-------|-------------| -| `TEXT_INPUT_PAD` | 3 | Padding inside text input border | - - -## Exported Symbols - -Matches prefixes: `_clipboard*`, `_clearOther*`, `_isWordChar*`, -`_multiClick*`, `_widgetText*`, `_wordStart*`, `_wordEnd*`. - - -## Files - -| File | Description | -|------|-------------| -| `textHelp.h` | Public API header | -| `textHelp.c` | Complete implementation | -| `Makefile` | Builds `bin/libs/texthelp.lib` + dep file | +## Source Files +- `textHelp.h` -- public API +- `textHelp.c` -- implementation ## Build -``` -make # builds bin/libs/texthelp.lib + texthelp.dep -make clean # removes objects, library, and dep file -``` - -Depends on: `libtasks.lib`, `libdvx.lib` (via texthelp.dep). +`make -C src/libs/kpunch/texthelp` (invoked by the top-level `make`). diff --git a/src/libs/kpunch/texthelp/textHelp.c b/src/libs/kpunch/texthelp/textHelp.c index 3054aba..74ae63a 100644 --- a/src/libs/kpunch/texthelp/textHelp.c +++ b/src/libs/kpunch/texthelp/textHelp.c @@ -296,7 +296,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ } // Ctrl+C -- copy - if (key == 3) { + if (key == KEY_CTRL_C) { if (hasSel) { clipboardCopy(buf + selLo, selHi - selLo); } @@ -305,7 +305,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ } // Ctrl+V -- paste - if (key == 22) { + if (key == KEY_CTRL_V) { int32_t cbLen = 0; const char *cb = clipboardGet(&cbLen); @@ -354,7 +354,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ } // Ctrl+X -- cut - if (key == 24) { + if (key == KEY_CTRL_X) { if (hasSel) { clipboardCopy(buf + selLo, selHi - selLo); @@ -373,7 +373,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ } // Ctrl+Z -- undo - if (key == 26 && undoBuf && pUndoLen && pUndoCursor) { + if (key == KEY_CTRL_Z && undoBuf && pUndoLen && pUndoCursor) { // Swap current and undo char tmpBuf[*pLen + 1]; int32_t tmpLen = *pLen; @@ -414,7 +414,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ goto adjustScroll; } - if (key >= 32 && key < 127) { + if (key >= KEY_ASCII_PRINT_FIRST && key <= KEY_ASCII_PRINT_LAST) { // Printable character if (undoBuf) { textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize); @@ -461,7 +461,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ w->onChange(w); } } - } else if (key == (0x4B | 0x100)) { + } else if (key == KEY_LEFT) { // Left arrow if (shift && pSelStart && pSelEnd) { if (*pSelStart < 0) { @@ -487,7 +487,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ (*pCursor)--; } } - } else if (key == (0x4D | 0x100)) { + } else if (key == KEY_RIGHT) { // Right arrow if (shift && pSelStart && pSelEnd) { if (*pSelStart < 0) { @@ -559,7 +559,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ *pCursor = newPos; } - } else if (key == (0x47 | 0x100)) { + } else if (key == KEY_HOME) { // Home if (shift && pSelStart && pSelEnd) { if (*pSelStart < 0) { @@ -580,7 +580,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ *pCursor = 0; } - } else if (key == (0x4F | 0x100)) { + } else if (key == KEY_END) { // End if (shift && pSelStart && pSelEnd) { if (*pSelStart < 0) { @@ -601,7 +601,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ *pCursor = *pLen; } - } else if (key == (0x53 | 0x100)) { + } else if (key == KEY_DELETE) { // Delete if (hasSel) { if (undoBuf) { diff --git a/src/libs/kpunch/texthelp/texthelp.dhs b/src/libs/kpunch/texthelp/texthelp.dhs index 8e9e3ca..824eb80 100644 --- a/src/libs/kpunch/texthelp/texthelp.dhs +++ b/src/libs/kpunch/texthelp/texthelp.dhs @@ -35,6 +35,21 @@ Shared text editing infrastructure library for DVX widget DXEs. Provides cursor Header: texthelp/textHelp.h +Loaded as: bin/libs/texthelp.lib + +.h2 How Widgets Use This Library + +Widget DXEs that implement text editing delegate to textHelp for the four high-cost behaviors: + +.list +.item Cursor blink. The library tracks a 250 ms blink timer in a static global and the focused widget reads sCursorBlinkOn (exposed via libdvx) when repainting. +.item Selection clearing. When a widget gains focus it calls clearOtherSelections(self) so only one widget ever has an active text selection. +.item Word boundaries. isWordChar, wordStart/wordEnd, and wordBoundaryLeft/wordBoundaryRight implement the logic for double-click word selection and Ctrl+Left/Right navigation in a uniform way. +.item Single-line editing engine. widgetTextEditOnKey, widgetTextEditMouseClick, widgetTextEditDragUpdateLine, and widgetTextEditPaintLine form a pointer-parameterized implementation of keyboard, mouse, drag, and paint behaviors. Widgets (TextInput, Spinner, ComboBox, AnsiTerm) hand the library pointers to their internal buffer, cursor, scroll offset, and selection state. +.endlist + +The engine is intentionally pointer-parameterized rather than struct-based so widgets can reuse it without adopting a shared state struct. Each widget owns its own buffer and state and passes pointers in on every call. + .h2 Constants .table @@ -145,6 +160,82 @@ int32_t wordStart(const char *buf, int32_t pos); Returns the index of the first character of the word containing pos. +.topic lib.texthelp.wordboundaryleft +.title wordBoundaryLeft +.toc 1 wordBoundaryLeft +.index wordBoundaryLeft +.index Word Boundary + +.h2 wordBoundaryLeft + +Finds the left word boundary for Ctrl+Left navigation. Skips non-word characters first, then skips word characters, leaving the cursor at the start of the previous word. + +.code +int32_t wordBoundaryLeft(const char *buf, int32_t pos); +.endcode + +.table + Parameter Type Description + --------- ---- ----------- + buf const char * The text buffer to scan. + pos int32_t Starting position (character index). +.endtable + +Returns the index of the previous word boundary. Returns 0 if pos is already at or before the start of the buffer. + +.topic lib.texthelp.wordboundaryright +.title wordBoundaryRight +.toc 1 wordBoundaryRight +.index wordBoundaryRight +.index Word Boundary + +.h2 wordBoundaryRight + +Finds the right word boundary for Ctrl+Right navigation. Skips word characters first, then skips non-word characters, leaving the cursor at the start of the next word. + +.code +int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos); +.endcode + +.table + Parameter Type Description + --------- ---- ----------- + buf const char * The text buffer to scan. + len int32_t Length of the text buffer. + pos int32_t Starting position (character index). +.endtable + +Returns the index of the next word boundary. Returns len if pos is already at or past the end of the buffer. + +.topic lib.texthelp.saveundo +.title textEditSaveUndo +.toc 1 textEditSaveUndo +.index textEditSaveUndo +.index Undo + +.h2 textEditSaveUndo + +Capture a snapshot of the current buffer contents and cursor position into an undo buffer. Called by widgetTextEditOnKey before every destructive operation (insert, delete, paste, cut). Truncates to bufSize - 1 characters so the null terminator always fits. + +.code +void textEditSaveUndo(char *buf, int32_t len, + int32_t cursor, char *undoBuf, + int32_t *pUndoLen, int32_t *pUndoCursor, + int32_t bufSize); +.endcode + +.table + Parameter Type Description + --------- ---- ----------- + buf char * Source buffer to snapshot. + len int32_t Current length of source contents. + cursor int32_t Current cursor position to save. + undoBuf char * Destination undo buffer. If NULL the call is a no-op. + pUndoLen int32_t * [out] Length of saved undo contents. + pUndoCursor int32_t * [out] Saved cursor position. + bufSize int32_t Size of the destination undo buffer including the null terminator. +.endtable + .topic lib.texthelp.dragupdate .title widgetTextEditDragUpdateLine .toc 1 widgetTextEditDragUpdateLine diff --git a/src/loader/README.md b/src/loader/README.md index 9eb5747..b6501c7 100644 --- a/src/loader/README.md +++ b/src/loader/README.md @@ -1,133 +1,190 @@ # DVX Loader -Bootstrap loader for the DVX desktop environment. Builds as `dvx.exe` --- the only native executable in the system. Everything else is a -dynamically loaded DXE3 module. +Bootstrap loader for the DVX desktop environment. Builds as +`bin/dvx.exe` -- the only native executable in the system. Everything +else is a dynamically loaded DXE3 module: libraries (`.lib`), widget +plug-ins (`.wgt`), and applications (`.app`). ## What It Does -1. Changes working directory to the directory containing `dvx.exe` -2. Truncates `dvx.log` and initializes logging -3. Calls `platformInit()` to suppress Ctrl+C and install signal handlers -4. Calls `platformRegisterDxeExports()` to register platform and C - runtime symbols (libc, libm, libgcc) for DXE module resolution -5. Scans and loads all modules in two phases (see below) -6. Finds `shellMain()` via `dlsym` across loaded modules -7. Calls `shellMain()` -- the shell takes over from here -8. On return, closes all module handles in reverse load order +On launch, `dvx.exe` performs these steps in order: + +1. `chdir` into the directory containing the executable. From that + point on, the working directory is the install root and all + relative paths (`LIBS/`, `WIDGETS/`, `APPS/`, `CONFIG/`, `SYSTEM/`) + resolve relative to it. +2. Truncate `dvx.log`, then use append-per-write thereafter. +3. Call `platformInit()` to suppress Ctrl+C, mask the default DOS + break handler, and prepare the signal-handling path for later. +4. Switch the display into VGA mode 13h and paint the splash screen + from `SYSTEM/SPLASH.RAW` (raw palette + pixels, as produced by + `bmp2raw`). +5. Call `platformRegisterDxeExports()` to publish platform entry + points and a curated set of libc / libm / libgcc symbols to the + DXE dynamic symbol resolver. This makes the loader the single + source of truth for these symbols so that every DXE module + resolves them consistently. +6. Scan and load all modules in two phases (see below), updating the + splash progress bar as each module loads. +7. Check whether help files need rebuilding (the module set changed + since the last boot); if so, run the integrated help compiler. +8. Find `shellMain` via `dlsym` across every loaded module and call + it. The shell takes over from here. +9. When `shellMain` returns, close every module handle in reverse + load order and exit. ## Two-Phase Module Loading -### Phase 1: Libraries (libs/*.lib) +### Phase 1: Libraries (`LIBS/*.lib`) -Recursively scans the `LIBS/` directory for `.lib` files. Each module may have a -`.dep` file (same base name, `.dep` extension) listing base names of -modules that must load first. The loader resolves the dependency graph -and loads in topological order. +Recursive scan of the `LIBS/` tree for `.lib` files. Each module may +have a `.dep` file (same base name, `.dep` extension) listing +base names of modules that must load first. The loader resolves the +dependency graph and loads in topological order. + +Typical load order (determined entirely by `.dep` files): -Load order (via dep files): ``` -libtasks.lib (no deps) -libdvx.lib (deps: libtasks) -texthelp.lib (deps: libtasks, libdvx) -listhelp.lib (deps: libtasks, libdvx) -dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp) -taskmgr.lib (deps: dvxshell, libtasks, libdvx, texthelp, listhelp) +libtasks.lib (no deps) +libdvx.lib (deps: libtasks) +texthelp.lib (deps: libtasks, libdvx) +listhelp.lib (deps: libtasks, libdvx) +dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp) +taskmgr.lib (deps: dvxshell, libtasks, libdvx, texthelp, listhelp) +serial.lib (deps: libtasks, libdvx) +dvxsql.lib (deps: libtasks, libdvx) +basrt.lib (deps: libtasks, libdvx) ``` -### Phase 2: Widgets (widgets/*.wgt) +### Phase 2: Widgets (`WIDGETS/*.wgt`) -Recursively scans the `WIDGETS/` directory for `.wgt` files. Widget modules may -also have `.dep` files (e.g., `textinpt.dep` lists `texthelp`). -Loaded in topological order, same as libs. +Same algorithm applied to the `WIDGETS/` tree. Widget modules may +also declare dependencies (for example, `comboBox` depends on +`texthelp` and `listhelp`). -After loading each module, the loader checks for a `wgtRegister` -export. If present, it is called immediately so the widget registers -its class(es) and API with the core. +Immediately after each widget module loads, the loader looks up the +symbol `wgtRegister` and, if present, calls it. That entry point +registers the widget's class and API with the core so the shell and +apps can then create instances of it. ## Dependency File Format -Plain text, one dependency base name per line. Empty lines and lines -starting with `#` are ignored. Names are case-insensitive. +`.dep` files are plain text. Each line is one dependency base name +(case-insensitive, no extension). Blank lines and lines starting +with `#` are ignored. + +Example (`comboBox.dep`): -Example (`combobox.dep`): ``` +# combobox uses the shared text-editing helpers and the shared +# list/dropdown helpers. texthelp listhelp ``` +Missing dependencies are detected before any module is loaded and +abort the boot with a clear fatal error, so a misconfigured +install fails early rather than crashing partway through. + ## Hosted Components -### dvxLog() +### `dvxLog()` The global logging function is defined in `loaderMain.c` and exported -to all DXE modules. It appends a line to `dvx.log`, opening and -closing the file per write so it is never held open. +to every DXE. It appends a single line to `dvx.log`, opening and +closing the file per call. This means the log is never held open and +its contents are safe to read even if the system crashes. ```c void dvxLog(const char *fmt, ...); ``` -### stb_ds +### `stb_ds` -The `STB_DS_IMPLEMENTATION` is compiled into the loader with -`STBDS_REALLOC` and `STBDS_FREE` overridden to use `dvxRealloc` and -`dvxFree`. This means all `arrput`/`hmput`/`arrfree` calls in DXE -code route through the per-app memory tracker, so stb_ds memory is -attributed correctly in the Task Manager's memory column. stb_ds -functions are exported to all DXE modules via the platform export -table. +`STB_DS_IMPLEMENTATION` is compiled into the loader so the +implementation is shared across the loader's translation units +(`loaderMain.o`, `dvxPrefs.o`). The loader uses plain `realloc` / +`free` for stb_ds; per-app memory tracking applies to DXE allocations +via the export table mappings described below. ### Platform Layer -`dvxPlatformDos.c` is compiled into the loader (not into libdvx.lib). -All platform functions are exported to DXE modules. This includes: +`dvxPlatformDos.c` is compiled into the loader (not into +`libdvx.lib`). All platform functions are exported to DXE modules via +the export table. This includes: -* Video: VESA VBE init, LFB mapping, mode enumeration -* Input: INT 33h mouse, INT 16h keyboard, CuteMouse wheel API -* Spans: `rep stosl`/`rep movsd` asm inner loops (8/16/32 bpp) -* DXE: symbol registration, symbol overrides -* Crash: signal handler installation, register dump logging -* System: memory info, directory creation, path utilities +* Video -- VESA VBE init, linear framebuffer mapping, mode + enumeration +* Input -- INT 33h mouse, INT 16h keyboard, CuteMouse wheel API +* Span fills / copies -- `rep stosl` / `rep movsd` asm inner loops at + 8, 16, and 32 bpp +* DXE support -- symbol registration, symbol overrides +* Crash handling -- signal handler installation, register dump + logging +* System utilities -- memory info, directory creation, path helpers ### Per-App Memory Tracking -The DXE export table maps standard C allocation symbols to tracked -wrappers: +The DXE export table maps the standard C allocation symbols to +DVX-tracked wrappers: -| C Symbol | Mapped To | Effect | -|----------|-----------|--------| -| `malloc` | `dvxMalloc` | Allocations attributed to `currentAppId` | -| `calloc` | `dvxCalloc` | Same | -| `realloc` | `dvxRealloc` | Transfers attribution on resize | -| `free` | `dvxFree` | Decrements app's tracked usage | -| `strdup` | `dvxStrdup` | Tracks the duplicated string | +| C symbol | Mapped to | Behaviour | +|------------|-----------------|-----------| +| `malloc` | `dvxMalloc` | Allocation is attributed to the current app ID. | +| `calloc` | `dvxCalloc` | Same. | +| `realloc` | `dvxRealloc` | Attribution transfers with the resized block. | +| `free` | `dvxFree` | Decrements the app's tracked usage. | +| `strdup` | `dvxStrdup` | Tracks the duplicated buffer. | -This is transparent to DXE code -- apps call `malloc` normally and the -tracked wrapper runs instead. The Task Manager reads per-app usage via -`dvxMemGetAppUsage()`. When an app is reaped, `dvxMemResetApp()` zeroes -its counter. +This is transparent to DXE code -- apps call `malloc` normally and +the tracked wrapper runs instead. The Task Manager reads per-app +usage via `dvxMemGetAppUsage()`, and `dvxMemResetApp()` zeros the +counter when an app is reaped. + +### Integrated Help Compilation + +The help compiler (`dvxhlpc.c`) is built a second time as an object +file with `-DHLPC_NO_MAIN` and linked into the loader. On boot, the +loader compares the currently-loaded module set against the last-run +state recorded in `DVX.INI`. If it has changed, the loader +re-compiles the affected help files before starting the shell, so +newly-installed widgets and libraries contribute to the system help +without a manual step. + +### Splash Screen + +`SYSTEM/SPLASH.RAW` is a 320x200 256-colour image in raw +palette-plus-pixels format (as emitted by `bmp2raw`). The loader +drops to VGA mode 13h, paints the splash, and updates a progress bar +as modules load. The splash stays up until the shell switches into +its configured VESA mode. ## Files | File | Description | |------|-------------| -| `loaderMain.c` | Entry point, module scanner, dependency resolver, logger | -| `Makefile` | Builds `bin/dvx.exe` | +| `loaderMain.c` | Entry point, module scanner, dependency resolver, logger, splash, help-recompile hook. | +| `stddclmr.h` | Standard disclaimer. Included by `loaderMain.c`; leave it alone. | +| `Makefile` | Builds `bin/dvx.exe`. | ## Build ``` -make # builds bin/dvx.exe -make clean # removes objects and binary +make # build bin/dvx.exe +make clean # remove the binary and intermediate objects ``` -The loader links `loaderMain.c` + `dvxPlatformDos.c` into a native -DJGPP executable. The CWSDPMI stub is prepended via exe2coff + -CWSDSTUB.EXE for standalone execution. +The loader is cross-compiled with the DJGPP toolchain +(`i586-pc-msdosdjgpp-gcc`). The resulting `a.out` is converted with +`exe2coff`, and the CWSDPMI stub (`CWSDSTUB.EXE`) is prepended so the +final `dvx.exe` runs stand-alone on DOS without a separate DPMI host. + +Linked translation units: `loaderMain.c` + `dvxPlatformDos.c` + +`dvxPlatformUtil.c` + `dvxPrefs.c` + `dvxhlpc.c` (as +`-DHLPC_NO_MAIN`). diff --git a/src/tools/README.md b/src/tools/README.md index a3a36cc..5cc58e3 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -1,17 +1,26 @@ # DVX Tools -Host-native utilities that run on the development machine (Linux or -DOS). These are not DXE modules and do not cross-compile with DJGPP -- -they build with the system GCC. +Host-native utilities that run on the development machine during the +build. These tools are not DXE modules and do not cross-compile; they +build with the system GCC. They produce binaries, icons, resource +blocks, help files, and raw image data that get embedded into DXE +apps, widgets, and libraries. + +All tools build into `bin/host/` with `make`. Two of them (the +resource tool and the help compiler) are also cross-compiled for DOS +and shipped to `bin/system/` as DOS executables so they can run on +the target as well. ## dvxres -- Resource Tool -Command-line tool for managing resource blocks appended to DXE3 files -(`.app`, `.wgt`, `.lib`). Resources are invisible to `dlopen` because -they are appended after the DXE3 content. +Manages resource blocks appended to DXE3 files (`.app`, `.wgt`, +`.lib`). Resources are invisible to `dlopen` because they sit after +the DXE3 image, in a separately indexed block. The runtime reads them +via `dvxResOpen`/`dvxResRead`/`dvxResClose` (see +`src/libs/kpunch/libdvx/dvxResource.c`). -### Commands +### Usage ``` dvxres add @@ -23,105 +32,361 @@ dvxres strip | Command | Description | |---------|-------------| -| `add` | Add or replace a single resource in a DXE file | -| `build` | Add all resources listed in a manifest file (replaces any existing resources) | -| `list` | List all resources in a DXE file | -| `get` | Extract a resource to a file or stdout | -| `strip` | Remove all appended resources, leaving only the DXE content | +| `add` | Add or replace a single resource in a DXE file. Existing entries with the same name are overwritten. | +| `build` | Replace all resources in a DXE file with the entries listed in a manifest. Any previously attached resources are removed first. | +| `list` | Dump a table of every resource in a DXE file (name, type, size). | +| `get` | Extract a named resource to a file, or to stdout if no output path is given. | +| `strip` | Truncate the file to remove the appended resource block. The DXE image itself is left unchanged. | ### Resource Types -| Type | Keyword | Description | -|------|---------|-------------| -| `DVX_RES_ICON` | `icon` or `image` | Image data (BMP icons, etc.) | -| `DVX_RES_TEXT` | `text` | Null-terminated string (author, copyright) | -| `DVX_RES_BINARY` | `binary` | Arbitrary binary data (app-specific) | +| Keyword | Type constant | Description | +|--------------------|--------------------|-------------| +| `icon` or `image` | `DVX_RES_ICON` | Image data (BMP for icons, PNG/JPEG/GIF for images used by apps). | +| `text` | `DVX_RES_TEXT` | Null-terminated string. Used for metadata: `author`, `copyright`, `publisher`, `description`, `version`, `name`. | +| `binary` | `DVX_RES_BINARY` | Arbitrary binary data, application-defined. | -For `add` with type `text`, the data argument is the string value -directly. For `icon` or `binary`, the data argument is a file path. - -### Resource File Format - -Resources are appended after the normal DXE3 content: - -``` -[DXE3 content] -- untouched, loaded by dlopen -[resource data entries] -- sequential, variable length -[resource directory] -- fixed-size 48-byte entries -[footer] -- 16 bytes: magic + dir offset + count -``` - -The footer is at the very end of the file. Reading starts from -`EOF - 16` bytes. The magic value is `0x52585644` ("DVXR" in -little-endian). The directory offset points to the start of the -directory entries, and the entry count gives the number of resources. - -Each directory entry (48 bytes) contains: -- `name[32]` -- resource name (null-terminated) -- `type` (uint32) -- DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY -- `offset` (uint32) -- absolute file offset of data -- `size` (uint32) -- data size in bytes -- `reserved` (uint32) -- padding +For `add` with type `text`, the `` argument is the literal +string value. For `icon` or `binary`, the `` argument is a file +path; a leading `@` is stripped if present. ### Manifest File Format (.res) -Plain text, one resource per line: +Plain-text UTF-8 (ASCII preferred), one resource per line: ``` -# Comment lines start with # -name type data +# Comments start with '#' +# Fields: name type data +name type data # Examples: -icon32 icon icons/myapp32.bmp -icon16 icon icons/myapp16.bmp -author text "John Doe" -appdata binary data/config.bin +icon32 icon icon32.bmp +name text "My App" +author text "Scott Duensing" +copyright text "Copyright 2026 Scott Duensing" +description text "Editor with syntax highlighting" +appdata binary data/config.bin ``` -Each line has three fields: name, type, and data. Text data can be -quoted. Empty lines and lines starting with `#` are ignored. +Each non-empty, non-comment line has three whitespace-delimited +fields: `name`, `type`, `data`. For text values, double quotes around +the data are stripped. For icon/binary values, the data is a file +path relative to the current working directory when `dvxres build` +was invoked. + +Names are limited to 31 characters (32 including the terminator) and +are truncated silently. + +### Examples + +``` +# Attach a single icon to a freshly-built app +dvxres add bin/apps/kpunch/myapp/myapp.app icon32 icon icon32.bmp + +# Rebuild the full resource block from a manifest +dvxres build bin/apps/kpunch/myapp/myapp.app myapp.res + +# List every resource in a module +dvxres list bin/widgets/kpunch/button/button.wgt + +# Extract an icon for inspection +dvxres get bin/apps/kpunch/myapp/myapp.app icon32 recovered.bmp + +# Strip all resources from a file (useful for diffing the raw DXE) +dvxres strip bin/apps/kpunch/myapp/myapp.app +``` + +### File Format + +Resources are appended after the DXE3 image: + +``` +[DXE3 image] <-- untouched, what dlopen sees +[resource data entries] <-- sequential, variable length +[resource directory] <-- fixed-size 48-byte entries +[footer] <-- 16 bytes: magic, dir offset, count +``` + +The footer sits at `EOF - 16` bytes. Magic value is `0x52585644` +(`"DVXR"` little-endian). Each directory entry (48 bytes) holds a +32-byte name, a `uint32_t` type, a `uint32_t` absolute file offset, +a `uint32_t` size, and a `uint32_t` reserved field. + +### Binaries + +* Host: `bin/host/dvxres` +* Target: `bin/system/DVXRES.EXE` (DOS executable, for use inside the + running system) -## mkicon -- Icon Generator +## dvxhlpc -- Help Compiler -Generates simple 32x32 24-bit BMP pixel-art icons for DVX apps. +Compiles hypertext help source files (`.dhs` = "DVX Help Source") +into the binary `.hlp` format consumed by the DVX help viewer. The +compiler also emits an equivalent HTML file so the same content can +be browsed outside the system. + +### Usage + +``` +dvxhlpc -o [-i ] [--html ] [--quiet] [@filelist] ... +``` + +| Flag | Purpose | +|---------------|---------| +| `-o ` | Required. Path of the compiled `.hlp` output. | +| `-i ` | Optional. Directory where referenced images live. Defaults to the working directory. | +| `--html ` | Optional. Also emit a standalone HTML rendering of the help content. | +| `--quiet` | Suppress progress output. | +| `@` | Response file: each line names an additional `.dhs` input file. Lines starting with `#` and blank lines are ignored. | +| `` | One or more input source files. Order matters for topic discovery. | + +### Five-Pass Compilation + +1. Parse each source file; collect topics, table-of-contents entries, + index entries, and image references. +2. Word-wrap text and list-item records to a fixed width. +3. Build a deduplicated string table. +4. Generate a trigram-based search index for full-text search. +5. Serialize the binary `.hlp` file (and, if requested, the HTML). + +### Source Format (.dhs) + +`.dhs` is a line-oriented markup format. Documents are divided into +topics, each with a unique ID and title. Topics can link to one +another, include images, and be grouped into table-of-contents +sections. Widget and library help files use `.bhs` ("BASIC help +source") files with the same format but a BASIC-oriented syntax +vocabulary. See existing sources under +`src/libs/kpunch/libdvx/*.dhs` for examples. + +### Examples + +``` +# Compile the system reference help file +dvxhlpc -o bin/apps/kpunch/progman/dvxhelp.hlp \ + --html docs/dvx_system_reference.html \ + -i src/libs/kpunch/libdvx \ + src/libs/kpunch/libdvx/sysdoc.dhs \ + src/libs/kpunch/libdvx/arch.dhs \ + src/libs/kpunch/libdvx/apiref.dhs + +# Response file (one filename per line) +dvxhlpc -o out.hlp @inputs.txt +``` + +### Binaries + +* Host: `bin/host/dvxhlpc` +* Target: `bin/system/DVXHLPC.EXE` (DOS executable) + +The help compiler is also linked into the loader as an object file so +help files can be rebuilt on first boot if the module set has +changed. + + +## mkicon -- Application Icon Generator + +Generates 32x32 24-bit BMP pixel-art icons for the bundled +applications. Each icon is drawn procedurally by dedicated code so no +external image assets are needed. + +### Usage ``` mkicon ``` -Available icon types: `clock`, `notepad`, `cpanel`, `dvxdemo`, `imgview`, -`noicon`. +Available types: + +| Type | Description | +|-----------|-------------| +| `noicon` | Grey square with a red diagonal X and a question mark. Used as a fallback. | +| `clock` | Clock face with hour and minute hands. | +| `notepad` | Paper with folded corner and text lines. | +| `cpanel` | Gear wheel with eight teeth. | +| `dvxdemo` | Coloured diamond shape. | +| `imgview` | Landscape in a picture frame. | +| `basic` | BASIC "B" glyph flanked by code brackets. | +| `help` | Blue book with a question mark. | +| `iconed` | Pixel grid with a pencil. | +| `resedit` | Document window with stacked resource rows. | + +### Example + +``` +mkicon icon32.bmp clock +``` + +Used by app Makefiles to generate `icon32.bmp` when no hand-drawn +icon exists. + +### Binary + +`bin/host/mkicon` ## mktbicon -- Toolbar Icon Generator -Generates 16x16 24-bit BMP toolbar button icons. +Generates 16x16 24-bit BMP toolbar icons for the DVX BASIC IDE. + +### Usage ``` mktbicon ``` -Used to create toolbar button resources for DVX BASIC and other apps. +Available types: `open`, `save`, `run`, `stop`, `code`, `design`, +`debug`, `stepinto`, `stepover`, `stepout`, `runtocur`. + +### Example + +``` +mktbicon tb_run.bmp run +``` + +### Binary + +`bin/host/mktbicon` -## Files +## mkwgticon -- Widget Toolbox Icon Generator -| File | Description | -|------|-------------| -| `dvxres.c` | Resource tool implementation | -| `mkicon.c` | 32x32 icon generator | -| `mktbicon.c` | 16x16 toolbar icon generator | -| `Makefile` | Builds `bin/dvxres`, `bin/mkicon`, `bin/mktbicon` (host native) | +Generates 24x24 24-bit BMP icons representing each widget type in the +DVX BASIC visual form designer's toolbox. + +### Usage + +``` +mkwgticon +``` + +Supported widget types (one BMP per widget): + +``` +button label textbox checkbox radio dropdown +combobox listbox listview treeview image imgbtn +canvas slider spinner progress timer frame +hbox vbox splitter scrollpane tabctrl toolbar +statusbar separator spacer terminal wrapbox datactrl +dbgrid +``` + +### Example + +``` +mkwgticon wgt_button.bmp button +``` + +### Binary + +`bin/host/mkwgticon` + + +## bmp2raw -- VGA Splash Converter + +Converts a 320x200 256-colour BMP into the raw VGA splash image +format used by the bootstrap loader. The loader displays this raw +image in VGA mode 13h before switching into the configured VESA mode. + +### Usage + +``` +bmp2raw +``` + +Input must be exactly 320x200 at 8 bits per pixel. Output is a flat +64768-byte file: + +* 768 bytes of palette (256 entries of 3 bytes, VGA 6-bit RGB values) +* 64000 bytes of pixel data (320x200 in top-to-bottom scanline order) + +### Example + +``` +bmp2raw assets/splash.bmp bin/system/SPLASH.RAW +``` + +### Binary + +`bin/host/bmp2raw` + + +## bascomp -- DVX BASIC Compiler (host) + +Command-line compiler for DVX BASIC projects. Takes a `.dbp` project +file and produces a standalone `.app` DXE binary. The compiler links +against `basstub.app`, which is the runtime stub that hosts the +compiled BASIC bytecode. + +### Usage + +``` +BASCOMP [-o ] [-release] +``` + +| Flag | Purpose | +|------------------|---------| +| `` | Required. Path to the project file. | +| `-o ` | Output path. Defaults to the project name with `.app` extension. | +| `-release` | Strip debug information and obfuscate form and control identifiers before emitting the binary. | + +The `-release` flag enables two internal passes inside the compiler: + +* **strip** -- removes debug variable info and debug UDT definitions + from the emitted module, mangles procedure names that are not + needed for name-based dispatch. +* **obfuscate** -- renames form and control identifiers to short + generated tokens (`C1`, `C2`, ...) throughout the module and in + the raw `.frm` text resources. + +These are implemented in +`src/apps/kpunch/dvxbasic/compiler/strip.{c,h}` and +`src/apps/kpunch/dvxbasic/compiler/obfuscate.{c,h}` and run +automatically when `-release` is passed. + +### Example + +``` +BASCOMP iconed/iconed.dbp -o bin/apps/kpunch/iconed/iconed.app -release +``` + +The compiler lives under `src/apps/kpunch/dvxbasic/stub/bascomp.c` +(the main-less library form of the BASIC stack is shared with the +IDE). + +### Binaries + +* Host: `bin/host/bascomp` +* Target: `bin/system/BASCOMP.EXE` (DOS executable) + + +## proxy -- SecLink Serial Bridge + +Separate Linux-side program; see `src/tools/proxy/README.md`. + + +## Shared Files + +| File | Description | +|------------------|-------------| +| `bmpDraw.h` / `bmpDraw.c` | Minimal 24-bit BMP canvas and pixel-art primitives shared by mkicon, mktbicon, and mkwgticon. | +| `dvxResWrite.h` | Resource-writing helper used by `dvxres`. | +| `hlpcCompile.h` | Public interface of the help-compiler core, used both by the standalone `dvxhlpc` binary and by the loader (for boot-time recompilation). | ## Build ``` -make # builds bin/dvxres -make clean # removes bin/dvxres +make # build every host tool +make clean # remove bin/host/ and bin/system/ ``` -Uses the system GCC, not the DJGPP cross-compiler. Links against -`core/dvxResource.c` for the runtime resource API (`dvxResOpen`, -`dvxResRead`, `dvxResClose`). +Uses the system GCC with `-O2 -Wall -Wextra -Werror`. The resource +tool, help compiler, and BASIC compiler are additionally cross-compiled +with the DJGPP toolchain (`i586-pc-msdosdjgpp-gcc`) so they can ship as +DOS executables inside `bin/system/`. (`bascomp` is built by the +`src/apps/kpunch/dvxbasic/Makefile`, not this directory's Makefile.) + +The targets are host-native only; they do not need the DJGPP +toolchain for the non-DOS binaries. diff --git a/src/tools/proxy/README.md b/src/tools/proxy/README.md index a43e9e4..05fa825 100644 --- a/src/tools/proxy/README.md +++ b/src/tools/proxy/README.md @@ -1,9 +1,9 @@ # SecLink Proxy -- Linux Serial Bridge -Linux-hosted proxy that bridges an 86Box emulated serial port to a +Linux-hosted proxy that bridges an emulated DOS serial port to a remote telnet BBS. Part of the DVX GUI project. -The 86Box side communicates using the SecLink protocol (HDLC packet +The DOS side communicates using the SecLink protocol (HDLC packet framing, CRC-16, DH key exchange, XTEA-CTR encryption). The BBS side is plain telnet over TCP. All crypto is transparent to the BBS -- it sees a normal telnet client. @@ -12,7 +12,7 @@ sees a normal telnet client. ## Architecture ``` -86Box (DOS terminal) Remote BBS +DOS side (terminal app) Remote BBS | | emulated modem telnet | | @@ -23,11 +23,11 @@ sees a normal telnet client. (encrypted, reliable) ``` -The proxy accepts a single TCP connection from 86Box, performs the -SecLink handshake (Diffie-Hellman key exchange), then connects to the -BBS. All traffic between 86Box and the proxy is encrypted via XTEA-CTR -on channel 0. Traffic between the proxy and the BBS is unencrypted -telnet with IAC negotiation handling. +The proxy accepts a single TCP connection from the DOS side, performs +the SecLink handshake (Diffie-Hellman key exchange), then connects to +the BBS. All traffic between the DOS side and the proxy is encrypted +via XTEA-CTR on channel 0. Traffic between the proxy and the BBS is +unencrypted telnet with IAC negotiation handling. ## Usage @@ -36,11 +36,11 @@ telnet with IAC negotiation handling. secproxy [listen_port] [bbs_host] [bbs_port] ``` -| Argument | Default | Description | -|---------------|--------------|---------------------------------| -| `listen_port` | 2323 | TCP port for 86Box connection | -| `bbs_host` | 10.1.0.244 | BBS hostname or IP | -| `bbs_port` | 2023 | BBS TCP port | +| Argument | Default | Description | +|---------------|--------------|----------------------------------| +| `listen_port` | 2323 | TCP port for the DOS connection | +| `bbs_host` | 10.1.0.244 | BBS hostname or IP | +| `bbs_port` | 2023 | BBS TCP port | Examples: @@ -55,7 +55,7 @@ secproxy --help # show usage ## Startup Sequence 1. Listen on the configured TCP port. -2. Wait for 86Box to connect (polls with Ctrl+C support). +2. Wait for the DOS side to connect (polls with Ctrl+C support). 3. Map the TCP socket to COM0 via the socket shim. 4. Seed the RNG from `/dev/urandom`. 5. Open SecLink and perform the DH handshake (blocks until the DOS @@ -71,13 +71,13 @@ secproxy --help # show usage The main loop uses `poll()` with a 10ms timeout to multiplex between the two TCP connections: -- **86Box -> BBS**: `secLinkPoll()` reads from the 86Box socket via +- **DOS -> BBS**: `secLinkPoll()` reads from the DOS-side socket via the socket shim, decrypts incoming packets, and the receive callback writes plaintext to the BBS socket. -- **BBS -> 86Box**: `read()` from the BBS socket, then the telnet +- **BBS -> DOS**: `read()` from the BBS socket, then the telnet filter strips IAC sequences, then `secLinkSend()` encrypts and sends - to 86Box via the socket shim. If the send window is full, the loop - retries with ACK processing until the data goes through. + to the DOS side via the socket shim. If the send window is full, the + loop retries with ACK processing until the data goes through. - **Maintenance**: `secLinkPoll()` also handles packet-layer retransmit timers on every iteration. @@ -110,12 +110,12 @@ the DOS build. A socket shim (`sockShim.h` / `sockShim.c`) provides rs232-compatible functions backed by TCP sockets instead of UART hardware: -| rs232 function | Socket shim behavior | -|----------------|-----------------------------------------------| +| rs232 function | Socket shim behavior | +|----------------|--------------------------------------------------| | `rs232Open()` | Validates socket assigned; ignores serial params | | `rs232Close()` | Marks port closed (socket lifecycle is caller's) | -| `rs232Read()` | Non-blocking `recv()` with `MSG_DONTWAIT` | -| `rs232Write()` | Blocking `send()` loop with `MSG_NOSIGNAL` | +| `rs232Read()` | Non-blocking `recv()` with `MSG_DONTWAIT` | +| `rs232Write()` | Blocking `send()` loop with `MSG_NOSIGNAL` | The shim maps COM port indices (0-3) to socket file descriptors via `sockShimSetFd()`, which must be called before opening the SecLink @@ -126,30 +126,31 @@ secLink layers, which defines `RS232_H` to prevent the real `rs232.h` from being included. -## DJGPP Stubs +## Platform Stubs DOS-specific headers required by the security library are replaced by minimal stubs in `stubs/`: -| Stub | Replaces DJGPP header | Contents | -|-------------------|------------------------|-------------------| -| `stubs/pc.h` | `` | No-op definitions | -| `stubs/go32.h` | `` | No-op definitions | -| `stubs/sys/farptr.h` | `` | No-op definitions | +| Stub | Replaces | Contents | +|----------------------|------------------------|-------------------| +| `stubs/pc.h` | `` | No-op definitions | +| `stubs/go32.h` | `` | No-op definitions | +| `stubs/sys/farptr.h` | `` | No-op definitions | The security library's hardware entropy function returns zeros on Linux, which is harmless since the proxy seeds the RNG from `/dev/urandom` before the handshake. -## 86Box Configuration +## Emulator Configuration -Configure the 86Box serial port to connect to the proxy: +Configure the emulated DOS machine's serial port to connect to the +proxy: -1. In 86Box settings, set a COM port to TCP client mode pointing at - the proxy's listen port (default 2323). -2. Enable "No telnet negotiation" to send raw bytes. -3. The DOS terminal application running inside 86Box uses SecLink +1. Point a COM port at the proxy's listen port (default 2323) in TCP + client mode. +2. Disable any built-in telnet negotiation so raw bytes pass through. +3. The DOS terminal application running in the emulator uses SecLink over this serial port. @@ -176,8 +177,8 @@ proxy/ sockShim.c socket shim implementation Makefile Linux native build stubs/ - pc.h stub for DJGPP - go32.h stub for DJGPP + pc.h stub for + go32.h stub for sys/ - farptr.h stub for DJGPP + farptr.h stub for ``` diff --git a/src/tools/proxy/proxy.c b/src/tools/proxy/proxy.c index 9249d1e..d3d1d53 100644 --- a/src/tools/proxy/proxy.c +++ b/src/tools/proxy/proxy.c @@ -20,16 +20,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. -// SecLink proxy -- bridges an 86Box serial connection to a telnet BBS +// SecLink proxy -- bridges a DOS serial connection to a telnet BBS // // Architecture: -// 86Box (DOS terminal) <-> TCP <-> proxy <-> TCP <-> BBS -// secLink protocol plain telnet +// DOS terminal <-> TCP <-> proxy <-> TCP <-> BBS +// secLink protocol plain telnet // // The proxy runs on Linux and sits between two TCP connections: -// 1. Left side: 86Box connects via its serial-over-TCP feature. The proxy -// speaks the full secLink protocol (packet framing, DH handshake, -// XTEA encryption) over this connection. +// 1. Left side: the DOS side connects via a serial-over-TCP feature. +// The proxy speaks the full secLink protocol (packet framing, DH +// handshake, XTEA encryption) over this connection. // 2. Right side: plain telnet to a BBS. The proxy handles telnet IAC // negotiation and strips control sequences before forwarding clean // data to the DOS side. @@ -414,7 +414,7 @@ int main(int argc, char *argv[]) { sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); - // Listen for 86Box connection + // Listen for incoming DOS-side connection listenFd = createListenSocket(listenPort); if (listenFd < 0) { fprintf(stderr, "Failed to listen on port %d: %s\n", listenPort, strerror(errno)); @@ -422,7 +422,7 @@ int main(int argc, char *argv[]) { } printf("Listening on port %d...\n", listenPort); - // Accept connection from 86Box (poll so Ctrl+C works) + // Accept connection from the DOS side (poll so Ctrl+C works) struct pollfd listenPoll = {listenFd, POLLIN, 0}; while (sRunning) { int pr = poll(&listenPoll, 1, 500); @@ -439,7 +439,7 @@ int main(int argc, char *argv[]) { } return sRunning ? 1 : 0; } - printf("86Box connected.\n"); + printf("DOS client connected.\n"); sClientFd = clientFd; // Map the TCP socket to "COM0" so the secLink stack (which calls rs232Read/ @@ -501,8 +501,8 @@ int main(int argc, char *argv[]) { // Main proxy loop: poll both sockets with a short timeout so secLinkPoll // runs frequently enough for ACK processing and retransmits. Data flows: - // 86Box -> secLink (decrypt) -> callback -> write to BBS - // BBS -> read -> telnet filter -> secLink (encrypt) -> 86Box + // DOS side -> secLink (decrypt) -> callback -> write to BBS + // BBS -> read -> telnet filter -> secLink (encrypt) -> DOS side fds[0].fd = clientFd; fds[0].events = POLLIN; fds[1].fd = bbsFd; @@ -511,11 +511,11 @@ int main(int argc, char *argv[]) { while (sRunning) { poll(fds, 2, POLL_TIMEOUT_MS); - // Process incoming secLink packets from 86Box + // Process incoming secLink packets from the DOS side // (callback forwards decrypted data to BBS) secLinkPoll(link); - // Read from BBS, filter telnet, send clean data to 86Box + // Read from BBS, filter telnet, send clean data to the DOS side if (fds[1].revents & POLLIN) { uint8_t raw[64]; uint8_t clean[64]; @@ -539,7 +539,7 @@ int main(int argc, char *argv[]) { // Check for disconnects if (fds[0].revents & (POLLERR | POLLHUP)) { - printf("86Box disconnected.\n"); + printf("DOS client disconnected.\n"); break; } if (fds[1].revents & (POLLERR | POLLHUP)) { diff --git a/src/widgets/kpunch/README.md b/src/widgets/kpunch/README.md index 6b8c621..13bbe03 100644 --- a/src/widgets/kpunch/README.md +++ b/src/widgets/kpunch/README.md @@ -1,430 +1,46 @@ # DVX Widget Modules -Individual widget type implementations, each built as a separate `.wgt` -DXE module. The loader recursively scans the `WIDGETS/` directory at startup and -loads every `.wgt` file it finds. Each module exports `wgtRegister()`, -which registers its widget class(es) and API struct with the core. +Individual widget types, each built as a separate plugin module loaded +at shell startup. The set is extensible: adding a new widget means +adding a new subdirectory with its `.c`, `.dhs`, `.bhs`, and Makefile +target -- no core changes required. -Core knows nothing about individual widgets. All per-widget state lives -in `w->data` (allocated by the widget DXE). All per-widget behavior is -dispatched through the `WidgetClassT` vtable. Each widget provides a -public header with its API struct, typed accessor, and convenience -macros. +## Documentation +The overall widget system -- `WidgetT`, `WidgetClassT`, `WgtIfaceT`, the +prop/method/event descriptor tables, registration, and the paint / +layout / event pipeline -- is documented in `wgtsys.dhs` in this +directory. -## ABI-Stable Handler Dispatch +Each widget has its own two-part documentation: -`WidgetClassT` contains a `handlers[WGT_METHOD_MAX]` array of function -pointers. Core dispatches via integer index: -`w->wclass->handlers[WGT_METHOD_PAINT]`, etc. Currently 21 methods are -defined (`WGT_METHOD_COUNT`), with room for 32 (`WGT_METHOD_MAX`). -Adding new methods does not break existing widget DXE binaries -- old -widgets simply have NULL in the new slots. +- `/.dhs` -- SDK C reference (how to create the widget + from C: constructor, API struct, flags, handler functions). +- `/.bhs` -- DVX BASIC reference (properties, methods, + events, example BASIC code). -Key method constants: +Both compile into the DVX System / BASIC References respectively. Open +via the Help Viewer app, or read `docs/dvx_system_reference.html` and +`docs/dvx_basic_reference.html` after `make`. -| Constant | Index | Signature | -|----------|-------|-----------| -| `WGT_METHOD_PAINT` | 0 | `(w, d, ops, font, colors)` | -| `WGT_METHOD_PAINT_OVERLAY` | 1 | `(w, d, ops, font, colors)` | -| `WGT_METHOD_CALC_MIN_SIZE` | 2 | `(w, font)` | -| `WGT_METHOD_LAYOUT` | 3 | `(w, font)` | -| `WGT_METHOD_ON_MOUSE` | 5 | `(w, root, vx, vy)` | -| `WGT_METHOD_ON_KEY` | 6 | `(w, key, mod)` | -| `WGT_METHOD_DESTROY` | 8 | `(w)` | -| `WGT_METHOD_ON_DRAG_UPDATE` | 15 | `(w, root, x, y)` | -| `WGT_METHOD_ON_DRAG_END` | 16 | `(w, root, x, y)` | -| `WGT_METHOD_QUICK_REPAINT` | 19 | `(w, outY, outH)` | +## Widget Directory -## Generic Drag Support +One subdirectory per widget. Each subdirectory contains: -Widgets that need drag behavior implement `WGT_METHOD_ON_DRAG_UPDATE` -and `WGT_METHOD_ON_DRAG_END`. Core tracks the drag widget and calls -these methods on mouse move/release during a drag. This replaces -per-widget drag hacks. Used by Slider, Splitter, ListBox (reorder), -ListView (reorder, column resize), and TreeView (reorder). - -## Dynamic Limits - -All widget child arrays, app slot tables, and callback lists use stb_ds -dynamic arrays. There are no fixed maximums for child count, app count, -or registered callbacks. - - -## Widget Summary - -26 source files produce 26 `.wgt` modules containing 32 widget types: - -| Source File | .wgt File | Types | Public Header | Dependencies | -|-------------|-----------|-------|---------------|--------------| -| `widgetBox.c` | `box.wgt` | VBox, HBox, Frame | `widgetBox.h` | -- | -| `widgetButton.c` | `button.wgt` | Button | `widgetButton.h` | -- | -| `widgetLabel.c` | `label.wgt` | Label | `widgetLabel.h` | -- | -| `widgetTextInput.c` | `textinpt.wgt` | TextInput, TextArea | `widgetTextInput.h` | texthelp | -| `widgetCheckbox.c` | `checkbox.wgt` | Checkbox | `widgetCheckbox.h` | -- | -| `widgetRadio.c` | `radio.wgt` | RadioGroup, Radio | `widgetRadio.h` | -- | -| `widgetDropdown.c` | `dropdown.wgt` | Dropdown | `widgetDropdown.h` | listhelp | -| `widgetComboBox.c` | `combobox.wgt` | ComboBox | `widgetComboBox.h` | texthelp, listhelp | -| `widgetListBox.c` | `listbox.wgt` | ListBox | `widgetListBox.h` | listhelp | -| `widgetListView.c` | `listview.wgt` | ListView | `widgetListView.h` | listhelp | -| `widgetTreeView.c` | `treeview.wgt` | TreeView, TreeItem | `widgetTreeView.h` | listhelp | -| `widgetSpinner.c` | `spinner.wgt` | Spinner | `widgetSpinner.h` | texthelp | -| `widgetSlider.c` | `slider.wgt` | Slider | `widgetSlider.h` | -- | -| `widgetProgressBar.c` | `progress.wgt` | ProgressBar | `widgetProgressBar.h` | -- | -| `widgetImage.c` | `image.wgt` | Image | `widgetImage.h` | -- | -| `widgetImageButton.c` | `imgbtn.wgt` | ImageButton | `widgetImageButton.h` | -- | -| `widgetCanvas.c` | `canvas.wgt` | Canvas | `widgetCanvas.h` | -- | -| `widgetSeparator.c` | `separatr.wgt` | Separator | `widgetSeparator.h` | -- | -| `widgetSpacer.c` | `spacer.wgt` | Spacer | `widgetSpacer.h` | -- | -| `widgetTabControl.c` | `tabctrl.wgt` | TabControl, TabPage | `widgetTabControl.h` | -- | -| `widgetScrollPane.c` | `scrlpane.wgt` | ScrollPane | `widgetScrollPane.h` | -- | -| `widgetSplitter.c` | `splitter.wgt` | Splitter | `widgetSplitter.h` | -- | -| `widgetToolbar.c` | `toolbar.wgt` | Toolbar | `widgetToolbar.h` | -- | -| `widgetStatusBar.c` | `statbar.wgt` | StatusBar | `widgetStatusBar.h` | -- | -| `widgetTimer.c` | `timer.wgt` | Timer | `widgetTimer.h` | -- | -| `widgetAnsiTerm.c` | `terminal.wgt` | AnsiTerm | `widgetAnsiTerm.h` | texthelp | - - -## Dependency Files (.dep) - -Widgets that use shared helper libraries need a `.dep` file so the -loader loads the helper first. Dep files are in `config/` and copied -to `bin/widgets/` during build: - -| Widget | Dep File | Dependencies | -|--------|----------|--------------| -| textinpt | `textinpt.dep` | texthelp | -| combobox | `combobox.dep` | texthelp, listhelp | -| spinner | `spinner.dep` | texthelp | -| terminal | `terminal.dep` | texthelp | -| dropdown | `dropdown.dep` | listhelp | -| listbox | `listbox.dep` | listhelp | -| listview | `listview.dep` | listhelp | -| treeview | `treeview.dep` | listhelp | - - -## Per-Widget API Details - -### Box (VBox, HBox, Frame) - -Container widgets that arrange children vertically or horizontally. -Frame adds a titled border around its children. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `vBox(parent)` | `wgtVBox(parent)` | Create vertical container | -| `hBox(parent)` | `wgtHBox(parent)` | Create horizontal container | -| `frame(parent, title)` | `wgtFrame(parent, title)` | Create titled frame container | - -### Button - -Push button with text label and accelerator key support. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, text)` | `wgtButton(parent, text)` | Create a button | - -### Label - -Static text label with optional alignment. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, text)` | `wgtLabel(parent, text)` | Create a label | -| `setAlign(w, align)` | `wgtLabelSetAlign(w, align)` | Set text alignment | - -### TextInput / TextArea - -Single-line text input, password input, masked input, and multiline -text area. Uses texthelp.lib for editing engine, cursor blink, word -boundaries, clipboard, and selection. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, maxLen)` | `wgtTextInput(parent, maxLen)` | Single-line text input | -| `password(parent, maxLen)` | `wgtPasswordInput(parent, maxLen)` | Password input (masked display) | -| `masked(parent, mask)` | `wgtMaskedInput(parent, mask)` | Masked input (e.g. phone number) | -| `textArea(parent, maxLen)` | `wgtTextArea(parent, maxLen)` | Multiline text editor | - -### Checkbox - -Toggle checkbox with text label. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, text)` | `wgtCheckbox(parent, text)` | Create a checkbox | -| `isChecked(w)` | `wgtCheckboxIsChecked(w)` | Get checked state | -| `setChecked(w, checked)` | `wgtCheckboxSetChecked(w, checked)` | Set checked state | - -### Radio (RadioGroup, Radio) - -Mutually exclusive radio buttons within a group container. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `group(parent)` | `wgtRadioGroup(parent)` | Create radio group container | -| `create(parent, text)` | `wgtRadio(parent, text)` | Create radio button in group | -| `groupSetSelected(w, idx)` | `wgtRadioGroupSetSelected(w, idx)` | Select radio by index | -| `getIndex(w)` | `wgtRadioGetIndex(w)` | Get selected index | - -### Dropdown - -Drop-down selection list with popup overlay. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtDropdown(parent)` | Create a dropdown | -| `setItems(w, items, count)` | `wgtDropdownSetItems(w, items, count)` | Set item list | -| `getSelected(w)` | `wgtDropdownGetSelected(w)` | Get selected index | -| `setSelected(w, idx)` | `wgtDropdownSetSelected(w, idx)` | Set selected index | - -### ComboBox - -Editable text input with dropdown suggestion list. Combines TextInput -and Dropdown behavior. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, maxLen)` | `wgtComboBox(parent, maxLen)` | Create a combo box | -| `setItems(w, items, count)` | `wgtComboBoxSetItems(w, items, count)` | Set suggestion list | -| `getSelected(w)` | `wgtComboBoxGetSelected(w)` | Get selected index (-1 if typed) | -| `setSelected(w, idx)` | `wgtComboBoxSetSelected(w, idx)` | Select item by index | - -### ListBox - -Scrollable list of text items with single or multi-select and optional -drag-reorder. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtListBox(parent)` | Create a list box | -| `setItems(w, items, count)` | `wgtListBoxSetItems(w, items, count)` | Set item list | -| `getSelected(w)` | `wgtListBoxGetSelected(w)` | Get selected index | -| `setSelected(w, idx)` | `wgtListBoxSetSelected(w, idx)` | Set selected index | -| `setMultiSelect(w, multi)` | `wgtListBoxSetMultiSelect(w, multi)` | Enable multi-select | -| `isItemSelected(w, idx)` | `wgtListBoxIsItemSelected(w, idx)` | Check if item is selected | -| `setItemSelected(w, idx, sel)` | `wgtListBoxSetItemSelected(w, idx, sel)` | Select/deselect item | -| `selectAll(w)` | `wgtListBoxSelectAll(w)` | Select all items | -| `clearSelection(w)` | `wgtListBoxClearSelection(w)` | Deselect all items | -| `setReorderable(w, r)` | `wgtListBoxSetReorderable(w, r)` | Enable drag reorder | - -### ListView - -Multi-column list with sortable headers, resizable columns, single or -multi-select, and optional drag-reorder. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtListView(parent)` | Create a list view | -| `setColumns(w, cols, count)` | `wgtListViewSetColumns(w, cols, count)` | Define columns | -| `setData(w, cellData, rowCount)` | `wgtListViewSetData(w, cellData, rowCount)` | Set row data | -| `getSelected(w)` | `wgtListViewGetSelected(w)` | Get selected row | -| `setSelected(w, idx)` | `wgtListViewSetSelected(w, idx)` | Set selected row | -| `setSort(w, col, dir)` | `wgtListViewSetSort(w, col, dir)` | Set sort column/direction | -| `setHeaderClickCallback(w, cb)` | `wgtListViewSetHeaderClickCallback(w, cb)` | Set header click handler | -| `setMultiSelect(w, multi)` | `wgtListViewSetMultiSelect(w, multi)` | Enable multi-select | -| `isItemSelected(w, idx)` | `wgtListViewIsItemSelected(w, idx)` | Check if row is selected | -| `setItemSelected(w, idx, sel)` | `wgtListViewSetItemSelected(w, idx, sel)` | Select/deselect row | -| `selectAll(w)` | `wgtListViewSelectAll(w)` | Select all rows | -| `clearSelection(w)` | `wgtListViewClearSelection(w)` | Deselect all rows | -| `setReorderable(w, r)` | `wgtListViewSetReorderable(w, r)` | Enable drag reorder | - -Column definition uses `ListViewColT` with title, tagged width, and alignment -(`ListViewAlignLeftE`, `ListViewAlignCenterE`, `ListViewAlignRightE`). -Sort direction: `ListViewSortNoneE`, `ListViewSortAscE`, `ListViewSortDescE`. - -### TreeView (TreeView, TreeItem) - -Hierarchical tree with expand/collapse, single or multi-select, and -optional drag-reorder. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtTreeView(parent)` | Create a tree view | -| `getSelected(w)` | `wgtTreeViewGetSelected(w)` | Get selected item widget | -| `setSelected(w, item)` | `wgtTreeViewSetSelected(w, item)` | Set selected item | -| `setMultiSelect(w, multi)` | `wgtTreeViewSetMultiSelect(w, multi)` | Enable multi-select | -| `setReorderable(w, r)` | `wgtTreeViewSetReorderable(w, r)` | Enable drag reorder | -| `item(parent, text)` | `wgtTreeItem(parent, text)` | Create tree item | -| `itemSetExpanded(w, exp)` | `wgtTreeItemSetExpanded(w, exp)` | Expand/collapse item | -| `itemIsExpanded(w)` | `wgtTreeItemIsExpanded(w)` | Check if expanded | -| `itemIsSelected(w)` | `wgtTreeItemIsSelected(w)` | Check if selected | -| `itemSetSelected(w, sel)` | `wgtTreeItemSetSelected(w, sel)` | Select/deselect item | - -### Spinner - -Numeric input with up/down buttons. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, min, max, step)` | `wgtSpinner(parent, min, max, step)` | Create a spinner | -| `setValue(w, value)` | `wgtSpinnerSetValue(w, value)` | Set current value | -| `getValue(w)` | `wgtSpinnerGetValue(w)` | Get current value | -| `setRange(w, min, max)` | `wgtSpinnerSetRange(w, min, max)` | Set value range | -| `setStep(w, step)` | `wgtSpinnerSetStep(w, step)` | Set increment step | - -### Slider - -Horizontal track with draggable thumb for value selection. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, min, max)` | `wgtSlider(parent, min, max)` | Create a slider | -| `setValue(w, value)` | `wgtSliderSetValue(w, value)` | Set current value | -| `getValue(w)` | `wgtSliderGetValue(w)` | Get current value | - -### ProgressBar - -Horizontal or vertical progress indicator (0-100). - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtProgressBar(parent)` | Create horizontal progress bar | -| `createV(parent)` | `wgtProgressBarV(parent)` | Create vertical progress bar | -| `setValue(w, value)` | `wgtProgressBarSetValue(w, value)` | Set value (0-100) | -| `getValue(w)` | `wgtProgressBarGetValue(w)` | Get current value | - -### Image - -Displays a pixel buffer or image file in native display format. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, data, w, h, pitch)` | `wgtImage(parent, data, w, h, pitch)` | Create from pixel data | -| `fromFile(parent, path)` | `wgtImageFromFile(parent, path)` | Create from image file | -| `setData(w, data, imgW, imgH, pitch)` | `wgtImageSetData(w, data, imgW, imgH, pitch)` | Replace image data | - -### ImageButton - -Clickable button displaying an image instead of text. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, data, w, h, pitch)` | `wgtImageButton(parent, data, w, h, pitch)` | Create from pixel data | -| `fromFile(parent, path)` | `wgtImageButtonFromFile(parent, path)` | Create from image file | -| `setData(w, data, imgW, imgH, pitch)` | `wgtImageButtonSetData(w, data, imgW, imgH, pitch)` | Replace image data | - -### Canvas - -Drawable surface with pen tools, shapes, pixel access, and file I/O. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, w, h)` | `wgtCanvas(parent, w, h)` | Create a canvas | -| `clear(w, color)` | `wgtCanvasClear(w, color)` | Fill with solid color | -| `setPenColor(w, color)` | `wgtCanvasSetPenColor(w, color)` | Set drawing color | -| `setPenSize(w, size)` | `wgtCanvasSetPenSize(w, size)` | Set pen width | -| `setMouseCallback(w, cb)` | `wgtCanvasSetMouseCallback(w, cb)` | Set mouse event handler | -| `save(w, path)` | `wgtCanvasSave(w, path)` | Save canvas to file | -| `load(w, path)` | `wgtCanvasLoad(w, path)` | Load image into canvas | -| `drawLine(w, x0, y0, x1, y1)` | `wgtCanvasDrawLine(w, ...)` | Draw line | -| `drawRect(w, x, y, w, h)` | `wgtCanvasDrawRect(w, ...)` | Draw rectangle outline | -| `fillRect(w, x, y, w, h)` | `wgtCanvasFillRect(w, ...)` | Fill rectangle | -| `fillCircle(w, cx, cy, r)` | `wgtCanvasFillCircle(w, ...)` | Fill circle | -| `setPixel(w, x, y, color)` | `wgtCanvasSetPixel(w, x, y, color)` | Set individual pixel | -| `getPixel(w, x, y)` | `wgtCanvasGetPixel(w, x, y)` | Read pixel color | - -### Separator (HSeparator, VSeparator) - -Thin decorative line (horizontal or vertical). - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `hSeparator(parent)` | `wgtHSeparator(parent)` | Create horizontal separator | -| `vSeparator(parent)` | `wgtVSeparator(parent)` | Create vertical separator | - -### Spacer - -Invisible flexible space for layout padding. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtSpacer(parent)` | Create a spacer | - -### TabControl (TabControl, TabPage) - -Tabbed container with clickable tab headers. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtTabControl(parent)` | Create tab control | -| `page(parent, title)` | `wgtTabPage(parent, title)` | Add a tab page | -| `setActive(w, idx)` | `wgtTabControlSetActive(w, idx)` | Switch to tab by index | -| `getActive(w)` | `wgtTabControlGetActive(w)` | Get active tab index | - -### ScrollPane - -Scrollable container with internal scrollbars. Child content can -exceed the visible area; the ScrollPane handles clipping and scroll. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtScrollPane(parent)` | Create a scroll pane | - -### Splitter - -Resizable split between two child regions with a draggable divider. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, vertical)` | `wgtSplitter(parent, vertical)` | Create splitter | -| `setPos(w, pos)` | `wgtSplitterSetPos(w, pos)` | Set divider position (pixels) | -| `getPos(w)` | `wgtSplitterGetPos(w)` | Get divider position | - -### Toolbar - -Horizontal container for toolbar buttons (typically ImageButtons). - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtToolbar(parent)` | Create a toolbar | - -### StatusBar - -Single-line text display at the bottom of a window. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent)` | `wgtStatusBar(parent)` | Create a status bar | - -Set text via `wgtSetText(statusBar, "text")`. - -### Timer - -Non-visual widget that fires callbacks at a specified interval. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, intervalMs, repeat)` | `wgtTimer(parent, intervalMs, repeat)` | Create a timer | -| `start(w)` | `wgtTimerStart(w)` | Start the timer | -| `stop(w)` | `wgtTimerStop(w)` | Stop the timer | -| `setInterval(w, intervalMs)` | `wgtTimerSetInterval(w, intervalMs)` | Change interval | -| `isRunning(w)` | `wgtTimerIsRunning(w)` | Check if running | -| `updateTimers()` | `wgtUpdateTimers()` | Process all pending timers | - -### AnsiTerm - -ANSI terminal emulator widget with scrollback buffer. Supports standard -escape sequences (cursor control, SGR colors, erase, scroll, DEC -private modes). Optionally connects to a comm channel for serial I/O. - -| API Function | Macro | Description | -|--------------|-------|-------------| -| `create(parent, cols, rows)` | `wgtAnsiTerm(parent, cols, rows)` | Create terminal | -| `write(w, data, len)` | `wgtAnsiTermWrite(w, data, len)` | Write data to terminal | -| `clear(w)` | `wgtAnsiTermClear(w)` | Clear terminal screen | -| `setComm(w, ctx, readFn, writeFn)` | `wgtAnsiTermSetComm(w, ctx, readFn, writeFn)` | Set comm channel | -| `setScrollback(w, maxLines)` | `wgtAnsiTermSetScrollback(w, maxLines)` | Set scrollback depth | -| `poll(w)` | `wgtAnsiTermPoll(w)` | Poll comm channel | -| `repaint(w, outY, outH)` | `wgtAnsiTermRepaint(w, outY, outH)` | Fast incremental repaint | +- `widget.c` -- implementation (defines `sApi`, `sIface`, + `sProps`, `sMethods`, `sEvents`, `sClass`). +- `.h` -- public C API (where applicable). +- `.dhs` -- SDK C help source. +- `.bhs` -- BASIC help source. +- Optional icons (`icon.bmp`, `icon32.bmp`) and resource file. +Widgets shipped: ansiTerm, box, button, canvas, checkbox, comboBox, +dataCtrl, dbGrid, dropdown, image, imageButton, label, listBox, +listView, progressBar, radio, scrollPane, separator, slider, spacer, +spinner, splitter, statusBar, tabControl, textInput, timer, toolbar, +treeView, wrapBox. ## Build -``` -make # builds all 26 .wgt modules + dep files -make clean # removes objects, .wgt files, and dep files -``` - -Each widget is compiled to a single `.o` file, then packaged via -`dxe3gen` into a `.wgt` DXE module exporting only `wgtRegister`. +`make -C src/widgets` (invoked by the top-level `make`). Produces one +`.wgt` per subdirectory under `bin/widgets/kpunch/`. diff --git a/src/widgets/kpunch/ansiTerm/ansiTerm.dhs b/src/widgets/kpunch/ansiTerm/ansiTerm.dhs index 9fa68b6..fc9e0e0 100644 --- a/src/widgets/kpunch/ansiTerm/ansiTerm.dhs +++ b/src/widgets/kpunch/ansiTerm/ansiTerm.dhs @@ -31,9 +31,11 @@ .h2 AnsiTerm -A VT100/ANSI-compatible terminal emulator widget designed for connecting to BBS systems over the serial link. Uses a traditional text-mode cell buffer (character + attribute byte pairs) with the CP437 character set and 16-color CGA palette. Supports cursor movement, screen/line erase, scrolling regions, SGR colors, and scrollback history. Communication is abstracted through read/write function pointers, allowing the terminal to work with raw serial ports, the secLink encrypted channel, or any other byte-oriented transport. +A VT100/ANSI-compatible terminal emulator widget designed for connecting to BBS systems over the serial link. Uses a traditional text-mode cell buffer (character + attribute byte pairs) with the CP437 character set and 16-color CGA palette. Supports cursor movement, screen/line erase, scrolling regions, SGR colors, DEC private modes, and a configurable scrollback buffer. Communication is abstracted through read/write function pointers, allowing the terminal to work with raw serial ports, the secLink encrypted channel, or any other byte-oriented transport. -Header: widgets/widgetAnsiTerm.h +The widget renders through two paint paths: a full paint used by the normal widget pipeline, and a fast incremental repaint (wgtAnsiTermRepaint) that pushes dirty rows directly to the window content buffer for low-latency serial echo. + +Header: widgets/ansiTerm.h .h3 Creation @@ -41,7 +43,7 @@ Header: widgets/widgetAnsiTerm.h WidgetT *term = wgtAnsiTerm(parent, 80, 25); .endcode -.h3 Macros +.h3 API Functions .index wgtAnsiTermWrite .index wgtAnsiTermClear @@ -51,15 +53,31 @@ WidgetT *term = wgtAnsiTerm(parent, 80, 25); .index wgtAnsiTermRepaint .table - Macro Description - ----- ----------- - wgtAnsiTerm(parent, cols, rows) Create an ANSI terminal widget with the given column and row dimensions. - wgtAnsiTermWrite(w, data, len) Write raw bytes into the terminal's ANSI parser. data is a const uint8_t * buffer, len is the byte count. - wgtAnsiTermClear(w) Clear the terminal screen and reset the cursor to the home position. - wgtAnsiTermSetComm(w, ctx, readFn, writeFn) Attach a communication channel. readFn and writeFn are I/O callbacks; ctx is passed as their first argument. - wgtAnsiTermSetScrollback(w, maxLines) Set the maximum number of scrollback lines. Lines scrolled off the top are saved in a circular buffer. - wgtAnsiTermPoll(w) Poll the communication channel for incoming data and feed it into the ANSI parser. - wgtAnsiTermRepaint(w, outY, outH) Fast repaint path that renders dirty rows directly into the window's content buffer, bypassing the widget pipeline. Returns the dirty region via outY/outH. + Function Description + -------- ----------- + WidgetT *wgtAnsiTerm(parent, cols, rows) Create an ANSI terminal widget with the given column/row dimensions. + void wgtAnsiTermWrite(w, data, len) Write raw bytes into the terminal's ANSI parser. data is a const uint8_t * buffer. + void wgtAnsiTermClear(w) Clear the terminal screen and reset the cursor to the home position. + void wgtAnsiTermSetComm(w, ctx, readFn, writeFn) Attach a communication channel. readFn and writeFn are I/O callbacks; ctx is passed as their first argument. + void wgtAnsiTermSetScrollback(w, maxLines) Set the maximum number of scrollback lines. + int32_t wgtAnsiTermPoll(w) Poll the communication channel for incoming data and feed it into the parser. Returns number of bytes consumed. + int32_t wgtAnsiTermRepaint(w, outY, outH) Fast repaint path that renders dirty rows directly into the window's content buffer, bypassing the widget pipeline. Returns the number of rows repainted and the dirty region via outY/outH. +.endtable + +.h3 API Struct (wgtRegisterApi "ansiterm") + +The sApi struct exposes these function pointers: + +.table + Slot Function + ---- -------- + create wgtAnsiTerm + write wgtAnsiTermWrite + clear wgtAnsiTermClear + setComm wgtAnsiTermSetComm + setScrollback wgtAnsiTermSetScrollback + poll wgtAnsiTermPoll + repaint wgtAnsiTermRepaint .endtable .h3 Properties (BASIC Interface) @@ -69,7 +87,7 @@ WidgetT *term = wgtAnsiTerm(parent, 80, 25); -------- ---- ------ ----------- Cols Integer Read-only Number of columns. Rows Integer Read-only Number of rows. - Scrollback Integer Write-only Maximum scrollback lines. + Scrollback Integer Write-only Maximum number of scrollback lines. .endtable .h3 Methods (BASIC Interface) @@ -78,9 +96,24 @@ WidgetT *term = wgtAnsiTerm(parent, 80, 25); Method Description ------ ----------- Clear Clear the terminal screen. - Write Write a string into the terminal. + Poll Process any pending bytes on the attached communication channel. + Write Write a string into the terminal (with ANSI escape processing). .endtable .h3 Events AnsiTerm uses the common events only. No widget-specific events are defined. + +.h3 Keyboard Shortcuts + +Within the terminal widget: + +.table + Key Action + --- ------ + Ctrl+C Copy selection to the clipboard (when a selection exists). + Ctrl+V Send the clipboard contents to the attached write function. + Arrows Send VT100 cursor escape sequences. + Home/End Send VT100 Home/End sequences. + PgUp/PgDn/Del Send the corresponding VT100 escape sequences. +.endtable diff --git a/src/widgets/kpunch/ansiTerm/ansiterm.bhs b/src/widgets/kpunch/ansiTerm/ansiterm.bhs index bf92796..930b93d 100644 --- a/src/widgets/kpunch/ansiTerm/ansiterm.bhs +++ b/src/widgets/kpunch/ansiTerm/ansiterm.bhs @@ -30,9 +30,9 @@ .h1 Terminal -DVX Extension -- DVX Widget: ansiterm (ANSI terminal emulator) +DVX Extension -- DVX Widget: ansiterm -A VT100/ANSI terminal emulator widget. Supports ANSI escape sequences, scrollback buffer, and serial communication. Default size is 80 columns by 25 rows. +A VT100/ANSI terminal emulator with a text-mode cell buffer, 16-color CGA palette, and scrollback history. Processes standard ANSI escape sequences (cursor control, erase, scroll, SGR colors, DEC private modes) typically used by DOS BBS software. Default size is 80 columns by 25 rows. Supports text selection with the mouse, Ctrl+C to copy, and Ctrl+V to send clipboard text as input. .h2 Type-Specific Properties @@ -41,23 +41,20 @@ A VT100/ANSI terminal emulator widget. Supports ANSI escape sequences, scrollbac ---------- ------- ------------------------------------------- Cols Integer Number of character columns (read-only). Rows Integer Number of character rows (read-only). - Scrollback Integer Number of scrollback lines (write-only). + Scrollback Integer Maximum number of scrollback lines. Older lines are dropped (write-only; default 500). .endtable -.index CommAttach -.index CommOpen - .h2 Type-Specific Methods .table Method Description ------ ----------- - Clear Clear the terminal screen. - Poll Process pending comm data and update the display. - Write text$ Write text (with ANSI escape processing) to the terminal. + Clear Clear the terminal screen and reset the cursor to the home position. + Poll Poll the attached communication channel for incoming bytes and feed them into the ANSI parser. + Write text$ Write a string to the terminal with full ANSI escape processing. .endtable -No default event. +No default event. Uses common events only. .h2 Serial Communication @@ -72,4 +69,17 @@ CommAttach link, "Terminal1" See comm.bas for the full communications API. +.h2 Example + +.code +Begin Terminal Terminal1 + MinWidth = 640 + MinHeight = 400 +End + +Sub Form_Load () + Terminal1.Write "Welcome!" & Chr$(13) & Chr$(10) +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods diff --git a/src/widgets/kpunch/ansiTerm/widgetAnsiTerm.c b/src/widgets/kpunch/ansiTerm/widgetAnsiTerm.c index 8582102..b5f1bb9 100644 --- a/src/widgets/kpunch/ansiTerm/widgetAnsiTerm.c +++ b/src/widgets/kpunch/ansiTerm/widgetAnsiTerm.c @@ -76,6 +76,15 @@ static int32_t sTypeId = -1; #define BLINK_MS 500 #define CURSOR_MS 250 +// dirtyRows is a 32-bit bitmask, so ANSI_MAX_DIRTY_ROWS is fixed at 32. +// Rows beyond this still render correctly -- they just always repaint +// because we can't track their dirty state in the mask. +#define ANSI_MAX_DIRTY_ROWS 32 +#define ANSI_DIRTY_ALL_ROWS 0xFFFFFFFF + +// Clipboard buffer size for selection copy. +#define ANSI_CLIPBOARD_BUF 4096 + typedef struct { uint8_t *cells; int32_t cols; @@ -109,6 +118,7 @@ typedef struct { int32_t lastCursorCol; uint32_t packedPalette[16]; bool paletteValid; + bool hasBlinkCells; // tracked to skip the per-blink O(rows*cols) scan int32_t selStartLine; int32_t selStartCol; int32_t selEndLine; @@ -202,7 +212,7 @@ static void ansiTermBuildPalette(WidgetT *w, const DisplayT *d) { static void ansiTermClearSelection(WidgetT *w) { AnsiTermDataT *at = (AnsiTermDataT *)w->data; - if (ansiTermHasSelection(w)) { at->dirtyRows = 0xFFFFFFFF; } + if (ansiTermHasSelection(w)) { at->dirtyRows = ANSI_DIRTY_ALL_ROWS; } at->selStartLine = -1; at->selStartCol = -1; at->selEndLine = -1; @@ -217,7 +227,7 @@ static void ansiTermCopySelection(WidgetT *w) { int32_t sLine, sCol, eLine, eCol; ansiTermSelectionRange(w, &sLine, &sCol, &eLine, &eCol); int32_t cols = at->cols; - char buf[4096]; + char buf[ANSI_CLIPBOARD_BUF]; int32_t pos = 0; for (int32_t line = sLine; line <= eLine && pos < 4095; line++) { const uint8_t *lineData = ansiTermGetLine(w, line); @@ -264,7 +274,7 @@ static void ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count) { static void ansiTermDirtyRow(WidgetT *w, int32_t row) { - if (row >= 0 && row < 32) { + if (row >= 0 && row < ANSI_MAX_DIRTY_ROWS) { ((AnsiTermDataT *)w->data)->dirtyRows |= (1U << row); } } @@ -558,7 +568,7 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) { AnsiTermDataT *at = (AnsiTermDataT *)w->data; switch (at->parseState) { case PARSE_NORMAL: - if (ch == 0x1B) { at->parseState = PARSE_ESC; } + if (ch == KEY_ESCAPE) { at->parseState = PARSE_ESC; } else if (ch == '\r') { at->cursorCol = 0; } else if (ch == '\n') { ansiTermNewline(w); } else if (ch == '\b') { if (at->cursorCol > 0) { at->cursorCol--; } } @@ -634,6 +644,9 @@ static void ansiTermPutChar(WidgetT *w, uint8_t ch) { int32_t idx = (row * cols + col) * 2; at->cells[idx] = ch; at->cells[idx + 1] = at->curAttr; + if (at->curAttr & ATTR_BLINK_BIT) { + at->hasBlinkCells = true; + } ansiTermDirtyRow(w, row); } at->cursorCol++; @@ -652,7 +665,7 @@ static void ansiTermScrollDown(WidgetT *w) { int32_t bytesPerRow = cols * 2; if (bot > top) { memmove(at->cells + (top + 1) * bytesPerRow, at->cells + top * bytesPerRow, (bot - top) * bytesPerRow); } ansiTermFillCells(w, top * cols, cols); - for (int32_t r = top; r <= bot && r < 32; r++) { at->dirtyRows |= (1U << r); } + for (int32_t r = top; r <= bot && r < ANSI_MAX_DIRTY_ROWS; r++) { at->dirtyRows |= (1U << r); } } @@ -669,7 +682,7 @@ static void ansiTermScrollUp(WidgetT *w) { } if (bot > top) { memmove(at->cells + top * bytesPerRow, at->cells + (top + 1) * bytesPerRow, (bot - top) * bytesPerRow); } ansiTermFillCells(w, bot * cols, cols); - for (int32_t r = top; r <= bot && r < 32; r++) { at->dirtyRows |= (1U << r); } + for (int32_t r = top; r <= bot && r < ANSI_MAX_DIRTY_ROWS; r++) { at->dirtyRows |= (1U << r); } } @@ -705,7 +718,7 @@ WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows) { at->selEndLine = -1; at->selEndCol = -1; at->blinkVisible = true; at->blinkTime = clock(); at->cursorOn = true; at->cursorTime = clock(); - at->dirtyRows = 0xFFFFFFFF; at->lastCursorRow = -1; at->lastCursorCol = -1; + at->dirtyRows = ANSI_DIRTY_ALL_ROWS; at->lastCursorRow = -1; at->lastCursorCol = -1; for (int32_t i = 0; i < cellCount; i++) { at->cells[i * 2] = ' '; at->cells[i * 2 + 1] = ANSI_DEFAULT_ATTR; } return w; } @@ -755,17 +768,29 @@ int32_t wgtAnsiTermPoll(WidgetT *w) { clock_t curInterval = (clock_t)CURSOR_MS * CLOCKS_PER_SEC / 1000; if ((now - at->blinkTime) >= blinkInterval) { at->blinkTime = now; at->blinkVisible = !at->blinkVisible; - int32_t cols = at->cols; int32_t rows = at->rows; - for (int32_t row = 0; row < rows && row < 32; row++) { - for (int32_t col = 0; col < cols; col++) { - if (at->cells[(row * cols + col) * 2 + 1] & ATTR_BLINK_BIT) { at->dirtyRows |= (1U << row); break; } + if (at->hasBlinkCells) { + int32_t cols = at->cols; int32_t rows = at->rows; + bool anyBlink = false; + for (int32_t row = 0; row < rows && row < ANSI_MAX_DIRTY_ROWS; row++) { + for (int32_t col = 0; col < cols; col++) { + if (at->cells[(row * cols + col) * 2 + 1] & ATTR_BLINK_BIT) { + at->dirtyRows |= (1U << row); + anyBlink = true; + break; + } + } + } + // Clear flag if a full scan found no blink cells (e.g. after a + // terminal clear). This keeps subsequent blink ticks free. + if (!anyBlink) { + at->hasBlinkCells = false; } } } if ((now - at->cursorTime) >= curInterval) { at->cursorTime = now; at->cursorOn = !at->cursorOn; int32_t cRow = at->cursorRow; - if (cRow >= 0 && cRow < 32) { at->dirtyRows |= (1U << cRow); } + if (cRow >= 0 && cRow < ANSI_MAX_DIRTY_ROWS) { at->dirtyRows |= (1U << cRow); } } if (!at->commRead) { return 0; } uint8_t buf[256]; @@ -872,7 +897,7 @@ static bool widgetAnsiTermClearSelection(WidgetT *w) { at->selEndLine = -1; at->selEndCol = -1; at->selecting = false; - at->dirtyRows = 0xFFFFFFFF; + at->dirtyRows = ANSI_DIRTY_ALL_ROWS; return true; } @@ -916,7 +941,7 @@ static void widgetAnsiTermOnDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, in int32_t lineIndex = at->scrollPos + row; at->selEndLine = lineIndex; at->selEndCol = col; - at->dirtyRows = 0xFFFFFFFF; + at->dirtyRows = ANSI_DIRTY_ALL_ROWS; } @@ -930,20 +955,20 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) { if (ansiTermHasSelection(w)) { ansiTermClearSelection(w); } uint8_t buf[8]; int32_t len = 0; - if (key >= 32 && key < 127) { buf[0] = (uint8_t)key; len = 1; } - else if (key == 0x1B) { buf[0] = 0x1B; len = 1; } + if (key >= KEY_ASCII_PRINT_FIRST && key <= KEY_ASCII_PRINT_LAST) { buf[0] = (uint8_t)key; len = 1; } + else if (key == KEY_ESCAPE) { buf[0] = 0x1B; len = 1; } else if (key == 0x09) { buf[0] = 0x09; len = 1; } else if (key == 13 || key == 10) { buf[0] = '\r'; len = 1; } else if (key == 8) { buf[0] = 0x08; len = 1; } - else if (key == (0x48 | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'A'; len = 3; } - else if (key == (0x50 | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'B'; len = 3; } - else if (key == (0x4D | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'C'; len = 3; } - else if (key == (0x4B | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'D'; len = 3; } - else if (key == (0x47 | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'H'; len = 3; } - else if (key == (0x4F | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'F'; len = 3; } - else if (key == (0x49 | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = '5'; buf[3] = '~'; len = 4; } - else if (key == (0x51 | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = '6'; buf[3] = '~'; len = 4; } - else if (key == (0x53 | 0x100)) { buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~'; len = 4; } + else if (key == KEY_UP) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'A'; len = 3; } + else if (key == KEY_DOWN) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'B'; len = 3; } + else if (key == KEY_RIGHT) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'C'; len = 3; } + else if (key == KEY_LEFT) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'D'; len = 3; } + else if (key == KEY_HOME) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'H'; len = 3; } + else if (key == KEY_END) { buf[0] = 0x1B; buf[1] = '['; buf[2] = 'F'; len = 3; } + else if (key == KEY_PGUP) { buf[0] = 0x1B; buf[1] = '['; buf[2] = '5'; buf[3] = '~'; len = 4; } + else if (key == KEY_PGDN) { buf[0] = 0x1B; buf[1] = '['; buf[2] = '6'; buf[3] = '~'; len = 4; } + else if (key == KEY_DELETE) { buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~'; len = 4; } else if (key >= 1 && key < 32) { buf[0] = (uint8_t)key; len = 1; } if (len > 0) { at->commWrite(at->commCtx, buf, len); } wgtInvalidatePaint(w); @@ -991,7 +1016,7 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) at->selEndLine = lineIndex; at->selEndCol = clickCol; at->selecting = true; sDragWidget = hit; } - at->dirtyRows = 0xFFFFFFFF; + at->dirtyRows = ANSI_DIRTY_ALL_ROWS; return; } @@ -1031,9 +1056,9 @@ void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit int32_t sbCount = at->scrollbackCount; bool viewingLive = (at->scrollPos == sbCount); uint32_t dirty = at->dirtyRows; - if (dirty == 0) { dirty = 0xFFFFFFFF; } + if (dirty == 0) { dirty = ANSI_DIRTY_ALL_ROWS; } for (int32_t row = 0; row < rows; row++) { - if (row < 32 && !(dirty & (1U << row))) { continue; } + if (row < ANSI_MAX_DIRTY_ROWS && !(dirty & (1U << row))) { continue; } int32_t lineIndex = at->scrollPos + row; const uint8_t *lineData = ansiTermGetLine(w, lineIndex); int32_t curCol = -1; diff --git a/src/widgets/kpunch/box/box.bhs b/src/widgets/kpunch/box/box.bhs index ffddd76..bdde542 100644 --- a/src/widgets/kpunch/box/box.bhs +++ b/src/widgets/kpunch/box/box.bhs @@ -28,7 +28,7 @@ .h1 Frame -VB Equivalent: Frame -- DVX Widget: frame (titled VBox container) +VB Equivalent: Frame -- DVX Widget: frame (titled container) A container with a titled border. Child controls are placed inside the frame using VBox layout. In the .frm file, nest Begin/End blocks inside the Frame block. @@ -40,6 +40,8 @@ A container with a titled border. Child controls are placed inside the frame usi Caption String The title displayed in the frame border. .endtable +No type-specific methods or events. + Container: Yes Default Event: Click @@ -69,13 +71,27 @@ End DVX Extension -- DVX Widget: hbox (horizontal layout container) -A container that arranges its children horizontally, left to right. Use Weight on children to distribute extra space. +A container that arranges its children horizontally, left to right. No visible border. Use Weight on children to distribute extra space proportionally. + +No type-specific properties, methods, or events. Container: Yes Default Event: Click -No type-specific properties. +.h2 Example + +.code +Begin HBox ButtonRow + Spacing = 4 + Begin CommandButton Command1 + Caption = "OK" + End + Begin CommandButton Command2 + Caption = "Cancel" + End +End +.endcode .link ctrl.common.props Common Properties, Events, and Methods .link ctrl.vbox VBox @@ -89,13 +105,26 @@ No type-specific properties. DVX Extension -- DVX Widget: vbox (vertical layout container) -A container that arranges its children vertically, top to bottom. No title or border. Use Weight on children to distribute extra space. +A container that arranges its children vertically, top to bottom. No title or border. Use Weight on children to distribute extra space proportionally. + +No type-specific properties, methods, or events. Container: Yes Default Event: Click -No type-specific properties. +.h2 Example + +.code +Begin VBox Column1 + Spacing = 4 + Begin Label Label1 + Caption = "Name:" + End + Begin TextBox Text1 + End +End +.endcode .link ctrl.common.props Common Properties, Events, and Methods .link ctrl.hbox HBox diff --git a/src/widgets/kpunch/box/box.dhs b/src/widgets/kpunch/box/box.dhs index 5ea4839..146d46a 100644 --- a/src/widgets/kpunch/box/box.dhs +++ b/src/widgets/kpunch/box/box.dhs @@ -35,33 +35,63 @@ .h2 Box (VBox / HBox / Frame) -Container widgets that arrange their children in a vertical column (VBox), horizontal row (HBox), or a titled group box (Frame). These are the primary layout building blocks. Children are laid out using a flexbox-like algorithm with weight-based extra-space distribution. +Container widgets that arrange their children in a vertical column (VBox), horizontal row (HBox), or a titled group box (Frame). These are the primary layout building blocks. Children are laid out using a weight-based algorithm with configurable spacing, padding, and alignment. -Header: widgets/widgetBox.h +Frame is a labelled grouping container with a Motif-style beveled border. Its title text sits centered vertically on the top border line with a small background-filled gap behind the title, giving the classic Windows 3.1 / Motif group box appearance. Internally, Frame behaves like a VBox for layout purposes. + +The widget DXE registers three separate interface entries ("vbox", "hbox", "frame") so the form designer can create each type independently. + +Header: widgets/box.h .h3 Creation +.code +WidgetT *row = wgtHBox(parent); +WidgetT *col = wgtVBox(parent); +WidgetT *group = wgtFrame(parent, "Options"); +.endcode + +.h3 API Functions + .table - Macro Description - ----- ----------- - wgtVBox(parent) Create a vertical box container. Children are stacked top to bottom. - wgtHBox(parent) Create a horizontal box container. Children are placed left to right. - wgtFrame(parent, title) Create a titled group box (a VBox with a border and label). + Function Description + -------- ----------- + WidgetT *wgtVBox(parent) Create a vertical box container. Children stack top-to-bottom. + WidgetT *wgtHBox(parent) Create a horizontal box container. Children stack left-to-right. + WidgetT *wgtFrame(parent, title) Create a titled group box. Children stack vertically inside the bordered frame. The title string may include a '&' prefix for an accelerator key. .endtable +.h3 API Struct (wgtRegisterApi "box") + +.table + Slot Function + ---- -------- + vBox wgtVBox + hBox wgtHBox + frame wgtFrame +.endtable + +The designer also registers per-type APIs: "vbox", "hbox", and "frame" each expose a single create slot. + .h3 Properties -Box containers use the common WidgetT fields for layout control: +Box containers use the common WidgetT fields for layout control. There are no widget-specific properties registered with the interface system. .table - Property Description - -------- ----------- - align Main-axis alignment of children. HBox: Start=left, Center=center, End=right. VBox: Start=top, Center=center, End=bottom. - spacing Gap between children (tagged size). - padding Internal padding around children (tagged size). - weight Controls how the box itself stretches within its parent. + Field Description + ----- ----------- + align Main-axis alignment of children. HBox: Start=left, Center=center, End=right. VBox: Start=top, Center=center, End=bottom. + spacing Gap between children (tagged size). + padding Internal padding around children (tagged size). + weight Controls how the box itself stretches within its parent. .endtable +Frame text is managed via the standard wgtSetText() / wgtGetText() interface (the widget has WCLASS_HAS_TEXT). BASIC code can set its title via the generic "Caption" or "Text" property. + .h3 Events -Containers use the common events only. No widget-specific events. +Containers use the common events only. No widget-specific events or methods are registered. + +.h3 Default Event + +"Click" on all three types. diff --git a/src/widgets/kpunch/button/button.bhs b/src/widgets/kpunch/button/button.bhs index 8feffdc..d954836 100644 --- a/src/widgets/kpunch/button/button.bhs +++ b/src/widgets/kpunch/button/button.bhs @@ -31,17 +31,17 @@ VB Equivalent: CommandButton -- DVX Widget: button | Name Prefix: Command -A push button that triggers an action when clicked. Created with wgtButton(parent, text). +A push button that triggers an action when clicked. The Caption may include a '&' before a character to mark an accelerator key (e.g. "&OK" underlines 'O', and Alt+O activates the button). .h2 Type-Specific Properties .table Property Type Description -------- ------ ------------------------------------------- - Caption String The text displayed on the button. Use & for accelerator keys (e.g. "&OK"). + Caption String The text displayed on the button. Use '&' for an accelerator key. .endtable -No additional type-specific properties beyond common properties and Caption. +No type-specific methods or events. Default Event: Click diff --git a/src/widgets/kpunch/button/button.dhs b/src/widgets/kpunch/button/button.dhs index cc396fa..312aeda 100644 --- a/src/widgets/kpunch/button/button.dhs +++ b/src/widgets/kpunch/button/button.dhs @@ -29,32 +29,53 @@ .h2 Button -A push button with a text label. Fires onClick when pressed and released. Supports keyboard activation via accelerator keys. +A push button with a text label. Fires onClick when pressed and released. Uses a two-phase press model: the button visually depresses on mouse-down and fires onClick only when the mouse is released while still inside the button bounds -- dragging the mouse off cancels the press. Supports accelerator keys via '&' prefix in the text (e.g. "&OK" underlines 'O' and binds Alt+O). -Header: widgets/widgetButton.h +Disabled buttons use the classic "embossed" rendering (highlight text offset by +1,+1 and shadow text at 0,0) for a chiseled appearance. + +Header: widgets/button.h .h3 Creation .code -WidgetT *btn = wgtButton(parent, "OK"); +WidgetT *btn = wgtButton(parent, "&OK"); +btn->onClick = onOkClicked; .endcode -.h3 Macro +.h3 API Functions .table - Macro Description - ----- ----------- - wgtButton(parent, text) Create a push button with the given label text. + Function Description + -------- ----------- + WidgetT *wgtButton(parent, text) Create a push button with the given label text. Pass NULL for no text. Text is copied into the widget. +.endtable + +.h3 API Struct (wgtRegisterApi "button") + +.table + Slot Function + ---- -------- + create wgtButton .endtable .h3 Properties -Uses common WidgetT properties. Set accelKey for keyboard shortcut. Use wgtSetText() / wgtGetText() to change the label. +Uses common WidgetT properties. Label text is managed via wgtSetText() / wgtGetText(). Set accelKey for keyboard shortcut (automatically parsed from '&' prefix in the text). The button is focusable (WCLASS_FOCUSABLE) and draws a focus rectangle when it holds keyboard focus. + +No widget-specific properties are registered with the interface system. BASIC code sets the label via the generic "Caption" or "Text" property. + +.h3 Methods + +No widget-specific methods. .h3 Events .table Callback Description -------- ----------- - onClick Fires when the button is clicked (press + release). + onClick Fires when the button is clicked (mouse press and release inside, or Space/Enter when focused). .endtable + +.h3 Default Event + +"Click" (VB basName: CommandButton, namePrefix: Command). diff --git a/src/widgets/kpunch/canvas/canvas.dhs b/src/widgets/kpunch/canvas/canvas.dhs index 759e8d2..21df8b5 100644 --- a/src/widgets/kpunch/canvas/canvas.dhs +++ b/src/widgets/kpunch/canvas/canvas.dhs @@ -36,12 +36,15 @@ .index wgtCanvasDrawText .index wgtCanvasSave .index wgtCanvasLoad +.index wgtCanvasResize .h2 Canvas -A freeform drawing surface with a fixed-size pixel buffer. Provides drawing primitives (lines, rectangles, circles, text, individual pixels) and supports save/load to BMP files. Mouse interaction is available via a callback. +A freeform drawing surface with a fixed-size pixel buffer stored in the display's native pixel format. Provides drawing primitives (lines, rectangles, circles, text, individual pixels) and supports save/load to image files (BMP/PNG/etc. via dvxLoadImage/dvxSaveImage). Mouse interaction is available via a callback. The buffer is rendered to screen as a straight blit with no per-pixel conversion. -Header: widgets/widgetCanvas.h +Canvas coordinates are independent of widget position: (0,0) is the top-left of the canvas content, not the widget. The widget frames the buffer with a 2-pixel sunken bevel. + +Header: widgets/canvas.h .h3 Creation @@ -49,44 +52,87 @@ Header: widgets/widgetCanvas.h WidgetT *cv = wgtCanvas(parent, 320, 200); .endcode -.h3 Macros +.h3 API Functions .index wgtCanvasSetPenColor .index wgtCanvasSetPenSize .index wgtCanvasSetMouseCallback .table - Macro Description - ----- ----------- - wgtCanvas(parent, w, h) Create a canvas with the given pixel dimensions. - wgtCanvasClear(w, color) Fill the entire canvas with a solid color. - wgtCanvasSetPenColor(w, color) Set the drawing pen color. - wgtCanvasSetPenSize(w, size) Set the drawing pen size in pixels. - wgtCanvasDrawLine(w, x0, y0, x1, y1) Draw a line from (x0,y0) to (x1,y1). - wgtCanvasDrawRect(w, x, y, width, height) Draw a rectangle outline. - wgtCanvasFillRect(w, x, y, width, height) Draw a filled rectangle. - wgtCanvasFillCircle(w, cx, cy, radius) Draw a filled circle. - wgtCanvasSetPixel(w, x, y, color) Set a single pixel to the given color. - wgtCanvasGetPixel(w, x, y) Get the color of a single pixel. - wgtCanvasDrawText(w, x, y, text) Draw text at the given position using the current pen color. - wgtCanvasSetMouseCallback(w, cb) Set a mouse interaction callback. Signature: void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag). Receives canvas-relative coordinates and whether the mouse is being dragged. - wgtCanvasSave(w, path) Save the canvas contents to a BMP file. - wgtCanvasLoad(w, path) Load a BMP file into the canvas. + Function Description + -------- ----------- + WidgetT *wgtCanvas(parent, w, h) Create a canvas with the given pixel dimensions. Buffer is initialized to white. + void wgtCanvasClear(w, color) Fill the entire canvas with a packed color. + void wgtCanvasSetPenColor(w, color) Set the drawing pen color (packed display color). + void wgtCanvasSetPenSize(w, size) Set the drawing pen size in pixels (affects DrawLine dot thickness). + void wgtCanvasDrawLine(w, x0, y0, x1, y1) Draw a line using Bresenham's algorithm with the current pen color and size. + void wgtCanvasDrawRect(w, x, y, width, height) Draw a 1-pixel rectangle outline using the current pen color. + void wgtCanvasFillRect(w, x, y, width, height) Fill a rectangle using the current pen color. + void wgtCanvasFillCircle(w, cx, cy, radius) Fill a circle using the current pen color (integer sqrt; no FPU needed). + void wgtCanvasSetPixel(w, x, y, color) Set a single pixel to the given packed color. + uint32_t wgtCanvasGetPixel(w, x, y) Read a single pixel. Returns packed color. + void wgtCanvasDrawText(w, x, y, text) Draw text at the given canvas coordinates using the current pen color. + void wgtCanvasSetMouseCallback(w, cb) Set a mouse callback. Signature: void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag). cx/cy are canvas-relative, drag=true on mouse-move during press. + int32_t wgtCanvasSave(w, path) Save the canvas buffer to an image file. Returns 0 on success, -1 on error. + int32_t wgtCanvasLoad(w, path) Load an image file into the canvas (resizes buffer to match). Returns 0 on success, -1 on error. + void wgtCanvasResize(w, newW, newH) Resize the canvas buffer. New pixels are filled with white. +.endtable + +.h3 API Struct (wgtRegisterApi "canvas") + +.table + Slot Function + ---- -------- + create wgtCanvas + clear wgtCanvasClear + setPenColor wgtCanvasSetPenColor + setPenSize wgtCanvasSetPenSize + setMouseCallback wgtCanvasSetMouseCallback + save wgtCanvasSave + load wgtCanvasLoad + drawLine wgtCanvasDrawLine + drawRect wgtCanvasDrawRect + fillRect wgtCanvasFillRect + fillCircle wgtCanvasFillCircle + setPixel wgtCanvasSetPixel + getPixel wgtCanvasGetPixel + drawText wgtCanvasDrawText + resize wgtCanvasResize +.endtable + +.h3 Properties + +Canvas has no widget-specific properties registered with the interface system. Drawing state (pen color, pen size) is managed by the API functions above. + +.h3 Methods (BASIC Interface) + +.table + Method Description + ------ ----------- + Clear color% Fill the entire canvas with a 0x00RRGGBB color. + DrawLine x0%, y0%, x1%, y1%, color% Draw a line between two points. + DrawRect x%, y%, w%, h%, color% Draw a rectangle outline. + DrawText x%, y%, text$ Draw text at the given position. + FillCircle cx%, cy%, radius%, color% Fill a circle. + FillRect x%, y%, w%, h%, color% Fill a rectangle. + GetPixel(x%, y%) Returns the 0x00RRGGBB color at a pixel. + Load path$ Load an image file onto the canvas. + Resize w%, h% Resize the canvas (fills new area with white). + Save path$ Save the canvas to an image file. + SetPenColor color% Set the drawing color (0x00RRGGBB). + SetPenSize size% Set the pen size in pixels. + SetPixel x%, y%, color% Set a single pixel. .endtable .h3 Events .table - Callback Description - -------- ----------- - onClick Fires when the canvas is clicked. - Mouse callback (via wgtCanvasSetMouseCallback) Fires on mouse down and drag with canvas-local coordinates. + Callback Description + -------- ----------- + onClick Fires when the canvas is clicked. + Mouse callback (via wgtCanvasSetMouseCallback) Fires on mouse down and drag with canvas-local coordinates. .endtable -.h3 Methods (BASIC Interface) +.h3 Default Event -.table - Method Description - ------ ----------- - Clear Clear the canvas to a given color. -.endtable +"Click" (VB basName: PictureBox, namePrefix: Picture). diff --git a/src/widgets/kpunch/checkbox/checkbox.bhs b/src/widgets/kpunch/checkbox/checkbox.bhs index 8cca6bf..7fd92a6 100644 --- a/src/widgets/kpunch/checkbox/checkbox.bhs +++ b/src/widgets/kpunch/checkbox/checkbox.bhs @@ -30,7 +30,7 @@ VB Equivalent: CheckBox -- DVX Widget: checkbox -A toggle control with a label. Checked state is exposed as a Boolean. +A toggle control with a label. The checked state is exposed as a Boolean. Supports accelerator keys (put '&' before a character in the Caption). Press Space or Enter to toggle when focused. .h2 Type-Specific Properties @@ -41,6 +41,8 @@ A toggle control with a label. Checked state is exposed as a Boolean. Value Boolean True if checked, False if unchecked. .endtable +No type-specific methods or events. + Default Event: Click .h2 Example diff --git a/src/widgets/kpunch/checkbox/checkbox.dhs b/src/widgets/kpunch/checkbox/checkbox.dhs index 433dea8..d9e78e2 100644 --- a/src/widgets/kpunch/checkbox/checkbox.dhs +++ b/src/widgets/kpunch/checkbox/checkbox.dhs @@ -31,33 +31,36 @@ .h2 Checkbox -A toggle control with a text label. Clicking toggles between checked and unchecked states. +A toggle control with a text label. Clicking toggles between checked and unchecked states. Fires onChange each time the state flips. Supports accelerator keys (via '&' in the text) and keyboard toggle via Space or Enter when focused. -Header: widgets/widgetCheckbox.h +The check mark is drawn as an "X" pattern rather than a traditional checkmark glyph. The focus rectangle wraps the label text (not the box), matching the Windows 3.1 convention. + +Header: widgets/checkbox.h .h3 Creation .code -WidgetT *cb = wgtCheckbox(parent, "Enable logging"); +WidgetT *cb = wgtCheckbox(parent, "Enable &logging"); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtCheckbox(parent, text) Create a checkbox with the given label text. - wgtCheckboxIsChecked(w) Returns true if the checkbox is checked. - wgtCheckboxSetChecked(w, checked) Set the checked state programmatically. + Function Description + -------- ----------- + WidgetT *wgtCheckbox(parent, text) Create a checkbox with the given label text. + bool wgtCheckboxIsChecked(w) Returns true if the checkbox is checked. + void wgtCheckboxSetChecked(w, checked) Set the checked state programmatically. Triggers a repaint but does NOT fire onChange. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "checkbox") .table - Callback Description - -------- ----------- - onClick Fires when clicked (after toggle). - onChange Fires when the checked state changes. + Slot Function + ---- -------- + create wgtCheckbox + isChecked wgtCheckboxIsChecked + setChecked wgtCheckboxSetChecked .endtable .h3 Properties (BASIC Interface) @@ -67,3 +70,22 @@ WidgetT *cb = wgtCheckbox(parent, "Enable logging"); -------- ---- ------ ----------- Value Boolean Read/Write Whether the checkbox is checked. .endtable + +Label text is managed via the standard wgtSetText() / wgtGetText() interface. BASIC code uses the generic "Caption" or "Text" property. + +.h3 Methods + +No widget-specific methods. + +.h3 Events + +.table + Callback Description + -------- ----------- + onClick Fires when clicked (after the toggle has already applied). + onChange Fires when the checked state changes (mouse click or keyboard toggle). +.endtable + +.h3 Default Event + +"Click" (VB basName: CheckBox). diff --git a/src/widgets/kpunch/checkbox/widgetCheckbox.c b/src/widgets/kpunch/checkbox/widgetCheckbox.c index 12d7e91..35d884e 100644 --- a/src/widgets/kpunch/checkbox/widgetCheckbox.c +++ b/src/widgets/kpunch/checkbox/widgetCheckbox.c @@ -39,6 +39,9 @@ #define CHECKBOX_BOX_SIZE 12 #define CHECKBOX_GAP 4 +// Check-glyph inset from the box edge on each side. Drawing the X this +// far inside leaves a consistent border between the mark and the bevel. +#define CHECKBOX_CHECK_INSET 3 static int32_t sTypeId = -1; @@ -164,9 +167,9 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit // for what's always a small fixed-size glyph (6x6 pixels). The 3px // inset from the box edge keeps the mark visually centered. if (cd->checked) { - int32_t cx = w->x + 3; - int32_t cy = boxY + 3; - int32_t cs = CHECKBOX_BOX_SIZE - 6; + int32_t cx = w->x + CHECKBOX_CHECK_INSET; + int32_t cy = boxY + CHECKBOX_CHECK_INSET; + int32_t cs = CHECKBOX_BOX_SIZE - 2 * CHECKBOX_CHECK_INSET; uint32_t checkFg = w->enabled ? fg : colors->windowShadow; for (int32_t i = 0; i < cs; i++) { diff --git a/src/widgets/kpunch/comboBox/comboBox.dhs b/src/widgets/kpunch/comboBox/comboBox.dhs index 9e308a7..ed2d123 100644 --- a/src/widgets/kpunch/comboBox/comboBox.dhs +++ b/src/widgets/kpunch/comboBox/comboBox.dhs @@ -27,40 +27,59 @@ .index ComboBox .index wgtComboBox .index wgtComboBoxSetItems +.index wgtComboBoxAddItem +.index wgtComboBoxRemoveItem +.index wgtComboBoxClear .index wgtComboBoxGetSelected .index wgtComboBoxSetSelected .h2 ComboBox -A combination of a text input and a dropdown list. The user can either type a value or select from a list of predefined options. Unlike Dropdown, the text field is editable. +A combination of a single-line text input and a drop-down list. The user can type a value or select one from the list. When the user picks a list item, its text is copied into the edit buffer. Supports full text editing (cursor movement, selection, clipboard, undo) via the texthelp library, and a popup overlay list via the listhelp library. -Header: widgets/widgetComboBox.h +Depends on "texthelp" and "listhelp" helper libraries (declared in combobox.dep). + +Header: widgets/comboBox.h .h3 Creation .code WidgetT *cb = wgtComboBox(parent, 128); -const char *items[] = { "Arial", "Courier", "Times" }; -wgtComboBoxSetItems(cb, items, 3); +wgtComboBoxAddItem(cb, "Arial"); +wgtComboBoxAddItem(cb, "Courier"); +wgtComboBoxAddItem(cb, "Times"); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtComboBox(parent, maxLen) Create a combo box. maxLen is the maximum text input length. - wgtComboBoxSetItems(w, items, count) Set the dropdown items. - wgtComboBoxGetSelected(w) Get the index of the selected item (-1 if the text does not match any item). - wgtComboBoxSetSelected(w, idx) Set the selected item by index. + Function Description + -------- ----------- + WidgetT *wgtComboBox(parent, maxLen) Create a combo box. maxLen is the maximum editable text length (0 => default 256). + void wgtComboBoxSetItems(w, items, count) Set the dropdown items from a const char ** array. Items are not copied -- caller owns them. + int32_t wgtComboBoxGetSelected(w) Get the index of the last selected item (-1 if the text was typed freely). + void wgtComboBoxSetSelected(w, idx) Select an item by index. Copies its text into the edit buffer. + void wgtComboBoxAddItem(w, text) Append an item to the owned list (strdup'd). + void wgtComboBoxRemoveItem(w, idx) Remove an owned item by index. + void wgtComboBoxClear(w) Remove all owned items and reset the selection. + const char *wgtComboBoxGetItem(w, idx) Get the text of an item by index. + int32_t wgtComboBoxGetItemCount(w) Get the total number of items. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "combobox") .table - Callback Description - -------- ----------- - onChange Fires when the text or selection changes. + Slot Function + ---- -------- + create wgtComboBox + setItems wgtComboBoxSetItems + getSelected wgtComboBoxGetSelected + setSelected wgtComboBoxSetSelected + addItem wgtComboBoxAddItem + removeItem wgtComboBoxRemoveItem + clear wgtComboBoxClear + getItem wgtComboBoxGetItem + getItemCount wgtComboBoxGetItemCount .endtable .h3 Properties (BASIC Interface) @@ -68,5 +87,31 @@ wgtComboBoxSetItems(cb, items, 3); .table Property Type Access Description -------- ---- ------ ----------- - ListIndex Integer Read/Write Index of the currently selected item. + ListIndex Integer Read/Write Index of the currently selected item (-1 if none). .endtable + +Editable text is accessible via the generic "Text" property. "ListCount" is available through the ListCount method. + +.h3 Methods (BASIC Interface) + +.table + Method Description + ------ ----------- + AddItem text$ Append an item to the dropdown list. + Clear Remove all items and clear the selection. + List(index%) Return the text of the item at the given index. + ListCount() Return the total number of items. + RemoveItem index% Remove the item at the given index. +.endtable + +.h3 Events + +.table + Callback Description + -------- ----------- + onChange Fires when the selection or text changes. +.endtable + +.h3 Default Event + +"Click" (VB basName: ComboBox). diff --git a/src/widgets/kpunch/comboBox/combobox.bhs b/src/widgets/kpunch/comboBox/combobox.bhs index 843d510..30a83fd 100644 --- a/src/widgets/kpunch/comboBox/combobox.bhs +++ b/src/widgets/kpunch/comboBox/combobox.bhs @@ -27,9 +27,9 @@ .h1 ComboBox -VB Equivalent: ComboBox -- DVX Widget: combobox (editable text field + drop-down list, max 256 chars) +VB Equivalent: ComboBox -- DVX Widget: combobox (editable text field + drop-down list, default maxLen 256) -A combination of a text input and a drop-down list. The user can type text or select from the list. Supports the same AddItem/RemoveItem/Clear/List methods as ListBox. +A combination of a single-line text input and a drop-down list. The user can either type a value or pick one from the list. Selecting a list item copies its text into the edit field. Supports the same AddItem / RemoveItem / Clear / List methods as ListBox. .h2 Type-Specific Properties @@ -37,16 +37,39 @@ A combination of a text input and a drop-down list. The user can type text or se Property Type Description --------- ------- ------------------------------------------- Text String The text in the editable field. - ListIndex Integer Index of the currently selected list item (-1 = none). - ListCount Integer Number of items in the drop-down list (read-only). + ListIndex Integer Index of the currently selected list item (-1 = user typed something not in the list). .endtable .h2 Type-Specific Methods -Same as ListBox: AddItem, RemoveItem, Clear, List. - -.link ctrl.listbox See ListBox for details +.table + Method Description + ------ ----------- + AddItem text$ Append an item to the drop-down list. + Clear Remove all items. + List(index%) Return the text of the item at the given index. + ListCount() Return the number of items in the list. + RemoveItem index% Remove the item at the given index. +.endtable Default Event: Click +.h2 Example + +.code +Begin ComboBox Combo1 +End + +Sub Form_Load () + Combo1.AddItem "Small" + Combo1.AddItem "Medium" + Combo1.AddItem "Large" +End Sub + +Sub Combo1_Change () + Label1.Caption = "Selected: " & Combo1.Text +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods +.link ctrl.listbox See ListBox for details diff --git a/src/widgets/kpunch/comboBox/widgetComboBox.c b/src/widgets/kpunch/comboBox/widgetComboBox.c index 929628a..eebfcea 100644 --- a/src/widgets/kpunch/comboBox/widgetComboBox.c +++ b/src/widgets/kpunch/comboBox/widgetComboBox.c @@ -142,7 +142,7 @@ WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) { d->selStart = -1; d->selEnd = -1; d->selectedIdx = -1; - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -337,7 +337,7 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) { ComboBoxDataT *d = (ComboBoxDataT *)w->data; if (d->open) { - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { if (d->hoverIdx > 0) { d->hoverIdx--; @@ -350,7 +350,7 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) { return; } - if (key == (0x50 | 0x100)) { + if (key == KEY_DOWN) { if (d->hoverIdx < d->itemCount - 1) { d->hoverIdx++; @@ -410,7 +410,7 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Down arrow on closed combobox opens the popup - if (!d->open && key == (0x50 | 0x100)) { + if (!d->open && key == KEY_DOWN) { d->open = true; d->hoverIdx = d->selectedIdx; sOpenPopup = w; diff --git a/src/widgets/kpunch/dataCtrl/dataCtrl.dhs b/src/widgets/kpunch/dataCtrl/dataCtrl.dhs index 66770c3..d8f19a9 100644 --- a/src/widgets/kpunch/dataCtrl/dataCtrl.dhs +++ b/src/widgets/kpunch/dataCtrl/dataCtrl.dhs @@ -32,9 +32,11 @@ .h2 DataCtrl -A VB3-style Data control for database binding. Displays a visible navigation bar that connects to a SQLite database via dvxSql* functions. Reads all rows from the RecordSource query into an in-memory cache for bidirectional navigation. Fires Reposition events when the cursor moves so bound controls can update. Supports master-detail linking between Data controls. +A VB3-style Data control for database binding. Displays a visible navigation bar (|< < > >|) that connects to a SQLite database via dvxSql* functions. Reads all rows from the RecordSource query into an in-memory cache for bidirectional navigation. Fires a Reposition event each time the current row changes so bound controls can update. Supports master-detail linking between Data controls (via MasterSource / MasterField / DetailField), INSERT via AddNew, and DELETE via Delete. UPDATEs use the KeyColumn to identify the row. -Header: widgets/widgetDataCtrl.h +The widget depends on the "dvxsql" library DXE; the navigation logic caches the full result set so there is no round-trip for MoveNext/MovePrev. + +Header: widgets/dataCtrl.h .h3 Creation @@ -42,38 +44,65 @@ Header: widgets/widgetDataCtrl.h WidgetT *data = wgtDataCtrl(parent); .endcode -.h3 Macros +Set properties (DatabaseName, RecordSource, KeyColumn, etc.) and then call dataCtrlRefresh to populate. -.index wgtDataCtrlRefresh -.index wgtDataCtrlMoveFirst -.index wgtDataCtrlMoveNext -.index wgtDataCtrlGetField -.index wgtDataCtrlSetField -.index wgtDataCtrlUpdate -.index wgtDataCtrlAddNew -.index wgtDataCtrlDelete +.h3 API Functions + +.index dataCtrlRefresh +.index dataCtrlMoveFirst +.index dataCtrlMoveNext +.index dataCtrlGetField +.index dataCtrlSetField +.index dataCtrlUpdate +.index dataCtrlAddNew +.index dataCtrlDelete .table - Macro Description - ----- ----------- - wgtDataCtrl(parent) Create a Data control. - wgtDataCtrlRefresh(w) Re-execute the RecordSource query and rebuild the row cache. - wgtDataCtrlMoveFirst(w) Move the cursor to the first row. - wgtDataCtrlMovePrev(w) Move the cursor to the previous row. - wgtDataCtrlMoveNext(w) Move the cursor to the next row. - wgtDataCtrlMoveLast(w) Move the cursor to the last row. - wgtDataCtrlGetField(w, colName) Get the value of a column in the current row. Returns const char *. - wgtDataCtrlSetField(w, colName, value) Set the value of a column in the current row (marks the row dirty). - wgtDataCtrlUpdateRow(w) Write the current row's pending changes back to the database. - wgtDataCtrlUpdate(w) Flush all pending changes to the database. - wgtDataCtrlAddNew(w) Begin a new row. Sets dirty state; call Update to commit. - wgtDataCtrlDelete(w) Delete the current row from the database. - wgtDataCtrlSetMasterValue(w, val) Set the master-detail filter value for this control. - wgtDataCtrlGetRowCount(w) Get the total number of cached rows. - wgtDataCtrlGetColCount(w) Get the number of columns in the result set. - wgtDataCtrlGetColName(w, col) Get the name of a column by index. Returns const char *. - wgtDataCtrlGetCellText(w, row, col) Get the text of a specific cell by row and column index. Returns const char *. - wgtDataCtrlSetCurrentRow(w, row) Set the current row by index (0-based). + Function Description + -------- ----------- + WidgetT *dataCtrlCreate(parent) Create a Data control. Exposed as create in the API struct. + void dataCtrlRefresh(w) Re-execute the RecordSource query and rebuild the row cache. + void dataCtrlMoveFirst(w) Move the cursor to the first row. + void dataCtrlMovePrev(w) Move the cursor to the previous row. + void dataCtrlMoveNext(w) Move the cursor to the next row. + void dataCtrlMoveLast(w) Move the cursor to the last row. + const char *dataCtrlGetField(w, colName) Get the value of a column in the current row (case-insensitive). + void dataCtrlSetField(w, colName, value) Set the value of a column in the current row. Marks the row dirty. + void dataCtrlUpdate(w) Flush pending changes (INSERT or UPDATE) to the database. + void dataCtrlUpdateRow(w) Legacy wrapper around dataCtrlUpdate. + void dataCtrlAddNew(w) Append a blank row and move the cursor to it. The row is dirty and new until Update is called. + void dataCtrlDelete(w) Delete the current row from the cache and the database. + void dataCtrlSetMasterValue(w, val) Set the master-detail filter value. Normally managed by the form runtime. + int32_t dataCtrlGetRowCount(w) Get the total number of cached rows. + int32_t dataCtrlGetColCount(w) Get the number of columns in the result set. + const char *dataCtrlGetColName(w, col) Get the name of a column by index. + const char *dataCtrlGetCellText(w, row, col) Get the text of a specific cell. + void dataCtrlSetCurrentRow(w, row) Set the current row by index. Auto-saves the previous row if dirty. +.endtable + +.h3 API Struct (wgtRegisterApi "data") + +.table + Slot Function + ---- -------- + create dataCtrlCreate + refresh dataCtrlRefresh + moveFirst dataCtrlMoveFirst + movePrev dataCtrlMovePrev + moveNext dataCtrlMoveNext + moveLast dataCtrlMoveLast + getField dataCtrlGetField + setField dataCtrlSetField + updateRow dataCtrlUpdateRow + update dataCtrlUpdate + addNew dataCtrlAddNew + delete dataCtrlDelete + setMasterValue dataCtrlSetMasterValue + getRowCount dataCtrlGetRowCount + getColCount dataCtrlGetColCount + getColName dataCtrlGetColName + getCellText dataCtrlGetCellText + setCurrentRow dataCtrlSetCurrentRow .endtable .h3 Properties (BASIC Interface) @@ -87,9 +116,9 @@ WidgetT *data = wgtDataCtrl(parent); MasterSource String Read/Write Name of the master Data control for master-detail linking. MasterField String Read/Write Column in the master control to read for the filter value. DetailField String Read/Write Column in this table to filter by the master value. - Caption String Read/Write Text displayed on the navigation bar. - BOF Boolean Read-only True when the cursor is before the first row. - EOF Boolean Read-only True when the cursor is past the last row. + Caption String Read/Write Text displayed on the navigator bar. + BOF Boolean Read-only True when the cursor is at the first row (or no rows). + EOF Boolean Read-only True when the cursor is past the last row (or no rows). .endtable .h3 Methods (BASIC Interface) @@ -97,12 +126,12 @@ WidgetT *data = wgtDataCtrl(parent); .table Method Description ------ ----------- - AddNew Begin a new row. + AddNew Append a blank row and move to it. Delete Delete the current row. - MoveFirst Move to the first row. - MoveLast Move to the last row. - MoveNext Move to the next row. - MovePrevious Move to the previous row. + MoveFirst Move the cursor to the first row. + MoveLast Move the cursor to the last row. + MoveNext Move the cursor to the next row. + MovePrevious Move the cursor to the previous row. Refresh Re-execute the query and rebuild the cache. Update Write pending changes to the database. .endtable @@ -112,6 +141,6 @@ WidgetT *data = wgtDataCtrl(parent); .table Event Description ----- ----------- - Reposition Fires when the current row changes (navigation, refresh, etc.). Default event. - Validate Fires before writing changes. Return false to cancel. + Reposition Fires when the current row changes. Default event. + Validate Fires before writing. Return false to cancel the write. .endtable diff --git a/src/widgets/kpunch/dataCtrl/datactrl.bhs b/src/widgets/kpunch/dataCtrl/datactrl.bhs index 1aa59f6..3169f17 100644 --- a/src/widgets/kpunch/dataCtrl/datactrl.bhs +++ b/src/widgets/kpunch/dataCtrl/datactrl.bhs @@ -37,26 +37,26 @@ .h1 Data -VB Equivalent: Data -- DVX Widget: data (database record navigator) +VB Equivalent: Data -- DVX Widget: data | Name Prefix: Data -A data access control that connects to a SQLite database and provides record navigation. Other controls can bind to a Data control via their DataSource and DataField properties. +A data access control that connects to a SQLite database and provides record navigation. Other controls bind to a Data control via their DataSource and DataField properties. The Data control cache is populated by calling Refresh. Move operations auto-save the current row if dirty. .link ctrl.databinding See Data Binding for details .h2 Type-Specific Properties .table - Property Type R/W Description - ------------ ------- --- ------------------------------------------- - DatabaseName String R/W Path to the SQLite database file. - RecordSource String R/W Table name or SQL SELECT query for the recordset. - KeyColumn String R/W Primary key column name (used for UPDATE/DELETE operations). - Caption String R/W Text displayed on the navigator bar. - BOF Boolean R True if the current position is before the first record (read-only). - EOF Boolean R True if the current position is past the last record (read-only). - MasterSource String R/W Name of a master Data control (for master-detail binding). - MasterField String R/W Column in the master recordset to filter by. - DetailField String R/W Column in this recordset that matches the master field. + Property Type Description + ------------ ------- ------------------------------------------- + DatabaseName String Path to the SQLite database file. + RecordSource String Table name or SQL SELECT query for the recordset. + KeyColumn String Primary key column name (used for UPDATE/DELETE operations). + Caption String Text displayed on the navigator bar. + BOF Boolean True if the current position is the first record or there are no records (read-only). + EOF Boolean True if the current position is past the last record or there are no records (read-only). + MasterSource String Name of a master Data control (for master-detail binding). + MasterField String Column in the master recordset to read for the filter value. + DetailField String Column in this recordset that matches the master field. .endtable .h2 Type-Specific Methods @@ -68,7 +68,7 @@ A data access control that connects to a SQLite database and provides record nav MoveLast (none) Navigate to the last record. MoveNext (none) Navigate to the next record. MovePrevious (none) Navigate to the previous record. - AddNew (none) Add a new blank record. + AddNew (none) Append a blank record and move to it. Delete (none) Delete the current record. Refresh (none) Re-query the database and reload records. Update (none) Write pending changes to the database. @@ -79,10 +79,31 @@ A data access control that connects to a SQLite database and provides record nav .table Event Parameters Description ---------- ----------------- ------------------------------------------- - Reposition (none) Fires after the current record changes (navigation). This is the default event. - Validate Cancel As Integer Fires before writing a record. Set Cancel = 1 to abort. + Reposition (none) Fires after the current record changes. Default event. + Validate Cancel As Integer Fires before writing. Set Cancel = 1 to abort. .endtable +Default Event: Reposition + +.h2 Example + +.code +Begin Data Data1 + DatabaseName = "books.db" + RecordSource = "titles" + KeyColumn = "id" +End + +Begin TextBox Text1 + DataSource = "Data1" + DataField = "title" +End + +Sub Form_Load () + Data1.Refresh +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods .link ctrl.databinding Data Binding .link ctrl.dbgrid DBGrid diff --git a/src/widgets/kpunch/dbGrid/dbGrid.dhs b/src/widgets/kpunch/dbGrid/dbGrid.dhs index 244db4e..2bc9407 100644 --- a/src/widgets/kpunch/dbGrid/dbGrid.dhs +++ b/src/widgets/kpunch/dbGrid/dbGrid.dhs @@ -26,40 +26,55 @@ .toc 0 DbGrid .index DbGrid .index wgtDbGrid -.index wgtDbGridSetDataWidget .index Database Grid .h2 DbGrid -A database grid widget that displays all records from a Data control in a scrollable, sortable table. Columns auto-populate from the Data control's column names and can be hidden, resized, and renamed by the application. Clicking a column header sorts the display. Selecting a row syncs the Data control's cursor position. The grid reads directly from the Data control's cached rows, so there is no separate copy of the data. +A database grid widget that displays all records from a Data control in a scrollable, sortable table. Columns auto-populate from the Data control's column names and can be hidden, resized (by dragging a column border), and renamed by the application. Clicking a column header sorts the display ascending/descending. Selecting a row syncs the Data control's cursor position. The grid reads directly from the Data control's cached rows, so there is no separate copy of the data. -Header: widgets/widgetDbGrid.h +The grid supports alternating row shading, vertical and horizontal scrollbars, keyboard navigation (arrows, Page Up/Down, Home, End), and double-click activation (fires onDblClick). + +Header: widgets/dbGrid.h .h3 Creation .code WidgetT *grid = wgtDbGrid(parent); -wgtDbGridSetDataWidget(grid, dataCtrl); +dbGridSetDataWidget(grid, dataCtrl); .endcode -.h3 Macros +.h3 API Functions -.index wgtDbGridRefresh -.index wgtDbGridSetColumnVisible -.index wgtDbGridSetColumnHeader -.index wgtDbGridSetColumnWidth -.index wgtDbGridGetSelectedRow +.index dbGridRefresh +.index dbGridSetColumnVisible +.index dbGridSetColumnHeader +.index dbGridSetColumnWidth +.index dbGridGetSelectedRow .table - Macro Description - ----- ----------- - wgtDbGrid(parent) Create a database grid widget. - wgtDbGridSetDataWidget(w, dataWidget) Bind the grid to a Data control. The grid reads rows from this widget. - wgtDbGridRefresh(w) Re-read the Data control's state and repaint the grid. - wgtDbGridSetColumnVisible(w, fieldName, visible) Show or hide a column by field name. - wgtDbGridSetColumnHeader(w, fieldName, header) Set a display header for a column (overrides the field name). - wgtDbGridSetColumnWidth(w, fieldName, width) Set the width of a column by field name (tagged size, 0 = auto). - wgtDbGridGetSelectedRow(w) Get the index of the currently selected data row (-1 if none). + Function Description + -------- ----------- + WidgetT *dbGridCreate(parent) Create a database grid widget. Exposed as create in the API struct. + void dbGridSetDataWidget(w, dataWidget) Bind the grid to a Data control. Auto-populates columns from the Data control's column names. + void dbGridRefresh(w) Re-read the Data control's state and repaint the grid. Rebuilds the sort index if sorting is active. + void dbGridSetColumnVisible(w, fieldName, visible) Show or hide a column by field name. + void dbGridSetColumnHeader(w, fieldName, header) Set a display header for a column (overrides the field name). + void dbGridSetColumnWidth(w, fieldName, width) Set a column's width (tagged size, 0 = auto). + int32_t dbGridGetSelectedRow(w) Get the index of the currently selected data row (-1 if none). +.endtable + +.h3 API Struct (wgtRegisterApi "dbgrid") + +.table + Slot Function + ---- -------- + create dbGridCreate + setDataWidget dbGridSetDataWidget + refresh dbGridRefresh + setColumnVisible dbGridSetColumnVisible + setColumnHeader dbGridSetColumnHeader + setColumnWidth dbGridSetColumnWidth + getSelectedRow dbGridGetSelectedRow .endtable .h3 Properties (BASIC Interface) @@ -67,7 +82,7 @@ wgtDbGridSetDataWidget(grid, dataCtrl); .table Property Type Access Description -------- ---- ------ ----------- - GridLines Boolean Read/Write Whether to draw grid lines between cells. + GridLines Boolean Read/Write Whether to draw grid lines between cells (default: true). .endtable .h3 Methods (BASIC Interface) @@ -83,6 +98,10 @@ wgtDbGridSetDataWidget(grid, dataCtrl); .table Event Description ----- ----------- - Click Fires when a row is clicked. + Click Fires when a row is clicked (selection change). DblClick Fires when a row is double-clicked. Default event. .endtable + +.h3 Default Event + +"DblClick" (VB basName: DBGrid, namePrefix: DBGrid). diff --git a/src/widgets/kpunch/dbGrid/dbgrid.bhs b/src/widgets/kpunch/dbGrid/dbgrid.bhs index c8fa49a..c257dac 100644 --- a/src/widgets/kpunch/dbGrid/dbgrid.bhs +++ b/src/widgets/kpunch/dbGrid/dbgrid.bhs @@ -28,9 +28,9 @@ .h1 DBGrid -VB Equivalent: DBGrid -- DVX Widget: dbgrid +VB Equivalent: DBGrid -- DVX Widget: dbgrid | Name Prefix: DBGrid -A data-bound grid that displays records from a Data control in a tabular format. Columns are auto-generated from the query results. Bind it using the DataSource property. +A data-bound grid that displays records from a Data control in a tabular format. Columns auto-populate from the query results. Bind the grid using the DataSource property. Click a column header to sort; drag a column border to resize. Selecting a row moves the bound Data control's cursor to that row. .h2 Type-Specific Properties @@ -38,7 +38,7 @@ A data-bound grid that displays records from a Data control in a tabular format. Property Type Description ---------- ------- ------------------------------------------- DataSource String Name of the Data control that supplies records. - GridLines Boolean Show or hide grid lines between cells. + GridLines Boolean Show or hide grid lines between cells (default: True). .endtable .h2 Type-Specific Methods @@ -54,10 +54,34 @@ A data-bound grid that displays records from a Data control in a tabular format. .table Event Parameters Description -------- ---------- ------------------------------------------- - Click (none) Fires when a cell is clicked. - DblClick (none) Fires when a cell is double-clicked. This is the default event. + Click (none) Fires when a row is clicked. + DblClick (none) Fires when a row is double-clicked. Default event. .endtable +Default Event: DblClick + +.h2 Example + +.code +Begin Data Data1 + DatabaseName = "books.db" + RecordSource = "titles" +End + +Begin DBGrid DBGrid1 + DataSource = "Data1" + GridLines = True +End + +Sub Form_Load () + Data1.Refresh +End Sub + +Sub DBGrid1_DblClick () + MsgBox "Opening row " & Str$(Data1.BOF) +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods .link ctrl.data Data .link ctrl.databinding Data Binding diff --git a/src/widgets/kpunch/dbGrid/widgetDbGrid.c b/src/widgets/kpunch/dbGrid/widgetDbGrid.c index 0458dab..d1eb76e 100644 --- a/src/widgets/kpunch/dbGrid/widgetDbGrid.c +++ b/src/widgets/kpunch/dbGrid/widgetDbGrid.c @@ -57,6 +57,19 @@ #define DBGRID_MAX_NAME 64 #define DBGRID_RESIZE_ZONE 3 +// Vertical padding added to font->charHeight for header and rows. +#define DBGRID_HEADER_VPAD 4 +#define DBGRID_ROW_VPAD 2 + +// Alternating-row background shade: darken the content bg by this per- +// channel amount so every other row is subtly distinct. +#define DBGRID_ALT_ROW_SHADE 0x080808 + +// Horizontal cull margin: columns whose rect is within this many pixels +// of the visible area are still rendered. Gives scroll movement a bit +// of pre-draw buffer so columns fade in instead of popping. +#define DBGRID_HORIZ_CULL_MARGIN 20 + // ============================================================ // Sort direction (matches ListView for consistency) // ============================================================ @@ -177,10 +190,10 @@ static int32_t colBorderHit(WidgetT *w, int32_t vx, int32_t vy) { static void computeLayout(WidgetT *w, const BitmapFontT *font, int32_t *outInnerW, int32_t *outInnerH, int32_t *outHeaderH, int32_t *outVisRows, bool *outNeedV, bool *outNeedH) { DbGridDataT *d = (DbGridDataT *)w->data; - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + DBGRID_HEADER_VPAD; int32_t innerW = w->w - DBGRID_BORDER * 2; int32_t innerH = w->h - DBGRID_BORDER * 2 - headerH; - int32_t rowH = font->charHeight + 2; + int32_t rowH = font->charHeight + DBGRID_ROW_VPAD; int32_t rowCount = getDataRowCount(d); int32_t visRows = innerH / rowH; @@ -294,7 +307,7 @@ static void dbGridBuildSortIndex(WidgetT *w) { static void dbGridCalcMinSize(WidgetT *w, const BitmapFontT *font) { w->calcMinW = font->charWidth * 20 + DBGRID_BORDER * 2; - w->calcMinH = (font->charHeight + 2) * (DBGRID_MIN_ROWS + 1) + DBGRID_BORDER * 2; + w->calcMinH = (font->charHeight + DBGRID_ROW_VPAD) * (DBGRID_MIN_ROWS + 1) + DBGRID_BORDER * 2; } @@ -502,17 +515,17 @@ static void dbGridOnKey(WidgetT *w, int32_t key, int32_t mod) { int32_t newDisp = dispRow; // Up arrow - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { newDisp = dispRow > 0 ? dispRow - 1 : 0; } // Down arrow - if (key == (0x50 | 0x100)) { + if (key == KEY_DOWN) { newDisp = dispRow < rowCount - 1 ? dispRow + 1 : rowCount - 1; } // Page Up - if (key == (0x49 | 0x100)) { + if (key == KEY_PGUP) { newDisp = dispRow - visRows; if (newDisp < 0) { @@ -521,7 +534,7 @@ static void dbGridOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Page Down - if (key == (0x51 | 0x100)) { + if (key == KEY_PGDN) { newDisp = dispRow + visRows; if (newDisp >= rowCount) { @@ -530,12 +543,12 @@ static void dbGridOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Home - if (key == (0x47 | 0x100)) { + if (key == KEY_HOME) { newDisp = 0; } // End - if (key == (0x4F | 0x100)) { + if (key == KEY_END) { newDisp = rowCount - 1; } @@ -749,7 +762,7 @@ static void dbGridPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const B bool needV, needH; computeLayout(w, font, &innerW, &innerH, &headerH, &visRows, &needV, &needH); - int32_t rowH = font->charHeight + 2; + int32_t rowH = font->charHeight + DBGRID_ROW_VPAD; int32_t rowCount = getDataRowCount(d); // Outer sunken border @@ -777,7 +790,7 @@ static void dbGridPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const B int32_t cw = d->resolvedW[c]; - if (hx + cw > contentX - 20 && hx < contentX + innerW + 20) { + if (hx + cw > contentX - DBGRID_HORIZ_CULL_MARGIN && hx < contentX + innerW + DBGRID_HORIZ_CULL_MARGIN) { BevelStyleT hdrBevel = BEVEL_RAISED(colors, 1); drawBevel(disp, ops, hx, contentY, cw, headerH, &hdrBevel); @@ -828,7 +841,7 @@ static void dbGridPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const B // Alternating row background if (i % 2 == 1) { - uint32_t altBg = colors->contentBg - 0x080808; + uint32_t altBg = colors->contentBg - DBGRID_ALT_ROW_SHADE; rectFill(disp, ops, contentX, ry, innerW, rowH, altBg); } @@ -840,7 +853,7 @@ static void dbGridPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const B } uint32_t fg = selected ? colors->menuHighlightFg : colors->contentFg; - uint32_t bg = selected ? colors->menuHighlightBg : (i % 2 == 1 ? colors->contentBg - 0x080808 : colors->contentBg); + uint32_t bg = selected ? colors->menuHighlightBg : (i % 2 == 1 ? colors->contentBg - DBGRID_ALT_ROW_SHADE : colors->contentBg); // Draw cells int32_t cx = contentX - d->scrollPosH; diff --git a/src/widgets/kpunch/dropdown/dropdown.bhs b/src/widgets/kpunch/dropdown/dropdown.bhs index 202545c..a796ba3 100644 --- a/src/widgets/kpunch/dropdown/dropdown.bhs +++ b/src/widgets/kpunch/dropdown/dropdown.bhs @@ -29,23 +29,47 @@ DVX Extension -- DVX Widget: dropdown (non-editable drop-down list) -A read-only drop-down list. Unlike ComboBox, the user cannot type free text; they can only select from the provided items. Supports AddItem/RemoveItem/Clear/List. +A read-only drop-down list. Unlike ComboBox, the user cannot type free text; they can only select from the provided items. Supports AddItem / RemoveItem / Clear / List. Use the arrow keys to navigate and Enter to commit when the list is open. .h2 Type-Specific Properties .table Property Type Description --------- ------- ------------------------------------------- - ListIndex Integer Index of the currently selected item. - ListCount Integer Number of items (read-only). + ListIndex Integer Index of the currently selected item (-1 = none). .endtable .h2 Type-Specific Methods -Same as ListBox: AddItem, RemoveItem, Clear, List. - -.link ctrl.listbox See ListBox for details +.table + Method Description + ------ ----------- + AddItem text$ Append an item to the drop-down list. + Clear Remove all items. + List(index%) Return the text of the item at the given index. + ListCount() Return the number of items in the list. + RemoveItem index% Remove the item at the given index. +.endtable Default Event: Click +.h2 Example + +.code +Begin DropDown DropDown1 +End + +Sub Form_Load () + DropDown1.AddItem "Small" + DropDown1.AddItem "Medium" + DropDown1.AddItem "Large" + DropDown1.ListIndex = 1 +End Sub + +Sub DropDown1_Click () + Label1.Caption = "Picked: " & DropDown1.List(DropDown1.ListIndex) +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods +.link ctrl.listbox See ListBox for details diff --git a/src/widgets/kpunch/dropdown/dropdown.dhs b/src/widgets/kpunch/dropdown/dropdown.dhs index 98bfdd1..5877fd4 100644 --- a/src/widgets/kpunch/dropdown/dropdown.dhs +++ b/src/widgets/kpunch/dropdown/dropdown.dhs @@ -27,32 +27,79 @@ .index Dropdown .index wgtDropdown .index wgtDropdownSetItems +.index wgtDropdownAddItem +.index wgtDropdownRemoveItem +.index wgtDropdownClear .index wgtDropdownGetSelected .index wgtDropdownSetSelected .h2 Dropdown -A drop-down list that displays a single selected item and expands to show all options when clicked. Read-only selection (the user cannot type into it). +A drop-down list that displays a single selected item and expands to a popup list when clicked. Unlike ComboBox, the user cannot type free text -- only list items can be selected. Keyboard navigation: Down/Up to move, Enter to select, type-ahead search on printable keys. The popup overlay is rendered above all other widgets via the widget paint overlay mechanism. -Header: widgets/widgetDropdown.h +Depends on the "listhelp" helper library (declared in dropdown.dep). + +Header: widgets/dropdown.h .h3 Creation .code WidgetT *dd = wgtDropdown(parent); -const char *items[] = { "Red", "Green", "Blue" }; -wgtDropdownSetItems(dd, items, 3); +wgtDropdownAddItem(dd, "Red"); +wgtDropdownAddItem(dd, "Green"); +wgtDropdownAddItem(dd, "Blue"); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtDropdown(parent) Create a dropdown list. - wgtDropdownSetItems(w, items, count) Set the list of items. items is a const char ** array. - wgtDropdownGetSelected(w) Get the index of the selected item (-1 if none). - wgtDropdownSetSelected(w, idx) Set the selected item by index. + Function Description + -------- ----------- + WidgetT *wgtDropdown(parent) Create a dropdown list. + void wgtDropdownSetItems(w, items, count) Set the list from a caller-owned const char ** array. + int32_t wgtDropdownGetSelected(w) Get the index of the selected item (-1 if none). + void wgtDropdownSetSelected(w, idx) Set the selected item by index. + void wgtDropdownAddItem(w, text) Append an owned item (strdup'd). + void wgtDropdownRemoveItem(w, idx) Remove an owned item by index. + void wgtDropdownClear(w) Remove all owned items. + const char *wgtDropdownGetItem(w, idx) Get the text of an item by index. + int32_t wgtDropdownGetItemCount(w) Get the total number of items. +.endtable + +.h3 API Struct (wgtRegisterApi "dropdown") + +.table + Slot Function + ---- -------- + create wgtDropdown + setItems wgtDropdownSetItems + getSelected wgtDropdownGetSelected + setSelected wgtDropdownSetSelected + addItem wgtDropdownAddItem + removeItem wgtDropdownRemoveItem + clear wgtDropdownClear + getItem wgtDropdownGetItem + getItemCount wgtDropdownGetItemCount +.endtable + +.h3 Properties (BASIC Interface) + +.table + Property Type Access Description + -------- ---- ------ ----------- + ListIndex Integer Read/Write Index of the currently selected item (-1 if none). +.endtable + +.h3 Methods (BASIC Interface) + +.table + Method Description + ------ ----------- + AddItem text$ Append an item to the drop-down list. + Clear Remove all items. + List(index%) Return the text of the item at the given index. + ListCount() Return the total number of items. + RemoveItem index% Remove the item at the given index. .endtable .h3 Events @@ -63,10 +110,6 @@ wgtDropdownSetItems(dd, items, 3); onChange Fires when the selected item changes. .endtable -.h3 Properties (BASIC Interface) +.h3 Default Event -.table - Property Type Access Description - -------- ---- ------ ----------- - ListIndex Integer Read/Write Index of the currently selected item. -.endtable +"Click" (VB basName: DropDown). diff --git a/src/widgets/kpunch/dropdown/widgetDropdown.c b/src/widgets/kpunch/dropdown/widgetDropdown.c index 9ba705c..8ef8d70 100644 --- a/src/widgets/kpunch/dropdown/widgetDropdown.c +++ b/src/widgets/kpunch/dropdown/widgetDropdown.c @@ -49,6 +49,9 @@ #include #include "stb_ds_wrap.h" +// Focus-rect inset inside the dropdown's text area (pixels on each side). +#define DROPDOWN_FOCUS_INSET 3 + static int32_t sTypeId = -1; typedef struct { @@ -278,7 +281,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) { if (d->open) { // Popup is open -- navigate items - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { if (d->hoverIdx > 0) { d->hoverIdx--; @@ -286,7 +289,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) { d->scrollPos = d->hoverIdx; } } - } else if (key == (0x50 | 0x100)) { + } else if (key == KEY_DOWN) { if (d->hoverIdx < d->itemCount - 1) { d->hoverIdx++; @@ -331,7 +334,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) { if (d->hoverIdx < d->scrollPos) { d->scrollPos = d->hoverIdx; } - } else if (key == (0x50 | 0x100)) { + } else if (key == KEY_DOWN) { // Down arrow: cycle selection forward (wheel-friendly) if (d->selectedIdx < d->itemCount - 1) { d->selectedIdx++; @@ -340,7 +343,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) { w->onChange(w); } } - } else if (key == (0x48 | 0x100)) { + } else if (key == KEY_UP) { if (d->selectedIdx > 0) { d->selectedIdx--; @@ -464,7 +467,7 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg); if (w == sFocusedWidget) { - drawFocusRect(disp, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg); + drawFocusRect(disp, ops, w->x + DROPDOWN_FOCUS_INSET, w->y + DROPDOWN_FOCUS_INSET, textAreaW - 2 * DROPDOWN_FOCUS_INSET, w->h - 2 * DROPDOWN_FOCUS_INSET, fg); } } diff --git a/src/widgets/kpunch/image/image.bhs b/src/widgets/kpunch/image/image.bhs index e5a5ffd..99d0bb4 100644 --- a/src/widgets/kpunch/image/image.bhs +++ b/src/widgets/kpunch/image/image.bhs @@ -31,18 +31,34 @@ VB Equivalent: Image -- DVX Widget: image -A static image display control. Loads BMP images from file. Cannot be placed via the designer toolbox (requires pixel data at creation time); typically created in code or loaded via the Picture property. +A static image display control. Loads bitmap images from file. Set the Picture property at design-time (in the .frm file) or at runtime to load a file. .h2 Type-Specific Properties .table Property Type Description ----------- ------- ------------------------------------------- - Picture String Path to a BMP file to load (write-only). + Picture String Path to an image file to load. + Stretch Boolean When True, the image stretches to fill the widget; when False, shown at natural size. ImageWidth Integer Width of the loaded image in pixels (read-only). ImageHeight Integer Height of the loaded image in pixels (read-only). .endtable +No type-specific methods or events. + Default Event: Click +.h2 Example + +.code +Begin Image Image1 + Picture = "assets/logo.bmp" + Stretch = True +End + +Sub Image1_Click () + MsgBox "Size: " & Str$(Image1.ImageWidth) & "x" & Str$(Image1.ImageHeight) +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods diff --git a/src/widgets/kpunch/image/image.dhs b/src/widgets/kpunch/image/image.dhs index f9c9e1c..072fd6b 100644 --- a/src/widgets/kpunch/image/image.dhs +++ b/src/widgets/kpunch/image/image.dhs @@ -30,30 +30,60 @@ .index wgtImageFromFile .index wgtImageSetData .index wgtImageLoadFile +.index wgtImageSetTransparent .h2 Image -Displays a bitmap image. Can be created from raw pixel data or loaded from a BMP file on disk. The image is rendered at its natural size within the widget bounds. +Displays a bitmap image. Can be created from raw pixel data or loaded from a file on disk (BMP/PNG/etc. via dvxLoadImage). Supports optional stretching to fill the widget and transparent color keys for chroma-key style masking. -Header: widgets/widgetImage.h +Header: widgets/image.h .h3 Creation +.code +WidgetT *img = wgtImageFromFile(parent, "logo.bmp"); +// or from raw pixels: +// WidgetT *img = wgtImage(parent, pixelData, w, h, pitch); +.endcode + +.h3 API Functions + .table - Macro Description - ----- ----------- - wgtImage(parent, data, w, h, pitch) Create an image widget from raw pixel data. data is a uint8_t * pixel buffer, w/h are dimensions, pitch is bytes per row. - wgtImageFromFile(parent, path) Create an image widget by loading a BMP file from disk. + Function Description + -------- ----------- + WidgetT *wgtImage(parent, data, w, h, pitch) Create from raw pixel data (in display's native format). + WidgetT *wgtImageFromFile(parent, path) Create by loading an image file. + void wgtImageSetData(w, data, imgW, imgH, pitch) Replace the displayed image with new raw pixel data. + void wgtImageLoadFile(w, path) Replace by loading a new file. Static but exposed via the API struct. + void wgtImageSetTransparent(w, hasTransparency, keyColor) Set chroma-key color. Pixels matching keyColor are not drawn. +.endtable + +.h3 API Struct (wgtRegisterApi "image") + +.table + Slot Function + ---- -------- + create wgtImage + fromFile wgtImageFromFile + setData wgtImageSetData + loadFile wgtImageLoadFile + setTransparent wgtImageSetTransparent +.endtable + +.h3 Properties (BASIC Interface) + +.table + Property Type Access Description + -------- ---- ------ ----------- + Picture String Read/Write Setting writes a file path (loads image). Reading returns the current path. + Stretch Boolean Read/Write When true, the image stretches to fill the widget; when false, shown at natural size. + ImageWidth Integer Read-only Width of the currently loaded image in pixels. + ImageHeight Integer Read-only Height of the currently loaded image in pixels. .endtable .h3 Methods -.table - Macro Description - ----- ----------- - wgtImageSetData(w, data, imgW, imgH, pitch) Replace the displayed image with new raw pixel data. - wgtImageLoadFile(w, path) Replace the displayed image by loading a new file. -.endtable +No widget-specific BASIC methods. Setting Picture loads a new image. .h3 Events @@ -63,12 +93,6 @@ Header: widgets/widgetImage.h onClick Fires when the image is clicked. .endtable -.h3 Properties (BASIC Interface) +.h3 Default Event -.table - Property Type Access Description - -------- ---- ------ ----------- - Picture String Write-only Load an image from a file path. - ImageWidth Integer Read-only Width of the currently loaded image in pixels. - ImageHeight Integer Read-only Height of the currently loaded image in pixels. -.endtable +"Click" (VB basName: Image). diff --git a/src/widgets/kpunch/imageButton/imgbtn.bhs b/src/widgets/kpunch/imageButton/imgbtn.bhs index 597885b..41ab8f4 100644 --- a/src/widgets/kpunch/imageButton/imgbtn.bhs +++ b/src/widgets/kpunch/imageButton/imgbtn.bhs @@ -29,18 +29,37 @@ DVX Extension -- DVX Widget: imagebutton -A button that displays an image instead of text. Like Image, it requires pixel data at creation time and is typically loaded via the Picture property. +A button that displays an image instead of text. Has the same press/release behavior as CommandButton, but shows a bitmap. Typically used inside a Toolbar. .h2 Type-Specific Properties .table Property Type Description ----------- ------- ------------------------------------------- - Picture String Path to a BMP file to load (write-only). + Picture String Path to an image file to load. ImageWidth Integer Width of the loaded image in pixels (read-only). ImageHeight Integer Height of the loaded image in pixels (read-only). .endtable +No type-specific methods or events. + Default Event: Click +.h2 Example + +.code +Begin Toolbar Toolbar1 + Begin ImageButton ImageButton1 + Picture = "icons/save.bmp" + End + Begin ImageButton ImageButton2 + Picture = "icons/open.bmp" + End +End + +Sub ImageButton1_Click () + MsgBox "Save clicked" +End Sub +.endcode + .link ctrl.common.props Common Properties, Events, and Methods diff --git a/src/widgets/kpunch/imageButton/imgbtn.dhs b/src/widgets/kpunch/imageButton/imgbtn.dhs index 70eb7a7..11fcf40 100644 --- a/src/widgets/kpunch/imageButton/imgbtn.dhs +++ b/src/widgets/kpunch/imageButton/imgbtn.dhs @@ -32,34 +32,37 @@ .h2 ImageButton -A clickable button that displays an image instead of text. Has press/release visual feedback like a regular button. Can be created from raw pixel data or a BMP file. +A clickable button that displays an image instead of text. Has the same press/release visual feedback and focus handling as a regular Button. Can be created from raw pixel data or loaded from an image file. Typically used to populate a Toolbar. -Header: widgets/widgetImageButton.h +Header: widgets/imgBtn.h .h3 Creation +.code +WidgetT *btn = wgtImageButtonFromFile(parent, "icons/save.bmp"); +btn->onClick = onSaveClicked; +.endcode + +.h3 API Functions + .table - Macro Description - ----- ----------- - wgtImageButton(parent, data, w, h, pitch) Create an image button from raw pixel data. - wgtImageButtonFromFile(parent, path) Create an image button by loading a BMP file. + Function Description + -------- ----------- + WidgetT *wgtImageButton(parent, data, w, h, pitch) Create from raw pixel data (in display's native format). + WidgetT *wgtImageButtonFromFile(parent, path) Create by loading an image file. + void wgtImageButtonSetData(w, data, imgW, imgH, pitch) Replace the image with new raw pixel data. + void wgtImageButtonLoadFile(w, path) Replace the image by loading a new file. .endtable -.h3 Methods +.h3 API Struct (wgtRegisterApi "imagebutton") .table - Macro Description - ----- ----------- - wgtImageButtonSetData(w, data, imgW, imgH, pitch) Replace the image with new raw pixel data. - wgtImageButtonLoadFile(w, path) Replace the image by loading a new file. -.endtable - -.h3 Events - -.table - Callback Description - -------- ----------- - onClick Fires when the image button is clicked (press + release). + Slot Function + ---- -------- + create wgtImageButton + fromFile wgtImageButtonFromFile + setData wgtImageButtonSetData + loadFile wgtImageButtonLoadFile .endtable .h3 Properties (BASIC Interface) @@ -67,9 +70,23 @@ Header: widgets/widgetImageButton.h .table Property Type Access Description -------- ---- ------ ----------- - Picture String Write-only Load an image from a file path. + Picture String Read/Write Setting writes a file path (loads image). Reading returns the current path. ImageWidth Integer Read-only Width of the currently loaded image in pixels. ImageHeight Integer Read-only Height of the currently loaded image in pixels. .endtable -.hr +.h3 Methods + +No widget-specific BASIC methods. Setting Picture loads a new image. + +.h3 Events + +.table + Callback Description + -------- ----------- + onClick Fires when the image button is clicked (press and release inside). +.endtable + +.h3 Default Event + +"Click" (VB basName: ImageButton). diff --git a/src/widgets/kpunch/imageButton/widgetImageButton.c b/src/widgets/kpunch/imageButton/widgetImageButton.c index 0fe3d37..8284351 100644 --- a/src/widgets/kpunch/imageButton/widgetImageButton.c +++ b/src/widgets/kpunch/imageButton/widgetImageButton.c @@ -42,6 +42,9 @@ #include "dvxWgtP.h" +// 2px bevel on every side -- min size and draw inset both derive from this. +#define IMAGEBUTTON_BEVEL_W 2 + static int32_t sTypeId = -1; typedef struct { @@ -185,8 +188,8 @@ void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) { ImageButtonDataT *d = (ImageButtonDataT *)w->data; // Bevel border only, no extra padding - w->calcMinW = d->imgW + 4; - w->calcMinH = d->imgH + 4; + w->calcMinW = d->imgW + 2 * IMAGEBUTTON_BEVEL_W; + w->calcMinH = d->imgH + 2 * IMAGEBUTTON_BEVEL_W; } @@ -212,7 +215,7 @@ void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight; bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow; bevel.face = bgFace; - bevel.width = 2; + bevel.width = IMAGEBUTTON_BEVEL_W; drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel); if (d->pixelData) { diff --git a/src/widgets/kpunch/label/label.bhs b/src/widgets/kpunch/label/label.bhs index 3afa212..35d6a06 100644 --- a/src/widgets/kpunch/label/label.bhs +++ b/src/widgets/kpunch/label/label.bhs @@ -31,7 +31,7 @@ VB Equivalent: Label -- DVX Widget: label -A static text label. Supports left, center, and right alignment. +A static text label. Supports left, center, and right alignment. Put '&' before a character in the Caption to define an accelerator that moves focus to the next focusable control. .h2 Type-Specific Properties @@ -42,6 +42,8 @@ A static text label. Supports left, center, and right alignment. Alignment Enum Text alignment: Left (default), Center, or Right. .endtable +No type-specific methods or events. + Default Event: Click .h2 Example diff --git a/src/widgets/kpunch/label/label.dhs b/src/widgets/kpunch/label/label.dhs index 9e9592c..92a5bed 100644 --- a/src/widgets/kpunch/label/label.dhs +++ b/src/widgets/kpunch/label/label.dhs @@ -27,35 +27,38 @@ .index Label .index wgtLabel .index wgtLabelSetAlign +.index wgtLabelGetAlign .h2 Label -A static text label. Does not accept keyboard focus. Typically used to describe other widgets. Supports text alignment and accelerator keys (with WCLASS_FOCUS_FORWARD, the accelerator moves focus to the next focusable sibling). +A static text label. Does not accept keyboard focus directly, but supports accelerator keys that forward focus to the next focusable sibling (WCLASS_FOCUS_FORWARD). Useful for describing other widgets. Supports left, center, and right text alignment. -Header: widgets/widgetLabel.h +Header: widgets/label.h .h3 Creation .code -WidgetT *lbl = wgtLabel(parent, "Name:"); +WidgetT *lbl = wgtLabel(parent, "&Name:"); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtLabel(parent, text) Create a text label. - wgtLabelSetAlign(w, align) Set the text alignment (AlignStartE, AlignCenterE, AlignEndE). + Function Description + -------- ----------- + WidgetT *wgtLabel(parent, text) Create a text label. + void wgtLabelSetAlign(w, align) Set the text alignment (AlignStartE = left, AlignCenterE = center, AlignEndE = right). + WidgetAlignE wgtLabelGetAlign(w) Get the current text alignment. .endtable -.h3 Properties +.h3 API Struct (wgtRegisterApi "label") -Use wgtSetText() / wgtGetText() to change the text. Set accelKey for accelerator support (focus forwards to next focusable widget). - -.h3 Events - -Labels use the common events only. Typically no callbacks are set on labels. +.table + Slot Function + ---- -------- + create wgtLabel + setAlign wgtLabelSetAlign +.endtable .h3 Properties (BASIC Interface) @@ -65,4 +68,16 @@ Labels use the common events only. Typically no callbacks are set on labels. Alignment Enum (Left, Center, Right) Read/Write Text alignment within the label. .endtable -.hr +Label text is managed via the standard wgtSetText() / wgtGetText() interface. BASIC code uses the generic "Caption" or "Text" property. Place an '&' before a character in the caption to mark an accelerator key. + +.h3 Methods + +No widget-specific methods. + +.h3 Events + +Labels are not focusable, so they typically have no event callbacks. The common onClick callback will fire if the label is clicked. + +.h3 Default Event + +"Click" (VB basName: Label). diff --git a/src/widgets/kpunch/listBox/listBox.dhs b/src/widgets/kpunch/listBox/listBox.dhs index 8b70ba5..28da8c6 100644 --- a/src/widgets/kpunch/listBox/listBox.dhs +++ b/src/widgets/kpunch/listBox/listBox.dhs @@ -26,26 +26,31 @@ .toc 0 ListBox .index ListBox .index wgtListBox -.index wgtListBoxSetItems +.index wgtListBoxAddItem +.index wgtListBoxRemoveItem +.index wgtListBoxClear .index wgtListBoxGetSelected +.index wgtListBoxSetSelected .h2 ListBox -A scrollable list of text items. Supports single and multi-selection modes and drag-to-reorder. +A scrollable list of text items. Supports single and multi-select modes (Shift-click for range, Ctrl-click to toggle), drag-to-reorder, mouse wheel scroll, and keyboard navigation (Up/Down/PgUp/PgDn/Home/End with type-ahead search on printable keys). Depends on the "listhelp" helper library (declared in listbox.dep). -Header: widgets/widgetListBox.h +Items can either be set from a caller-owned array (wgtListBoxSetItems) or managed internally via AddItem/RemoveItem/Clear. The two modes are mutually exclusive per widget. + +Header: widgets/listBox.h .h3 Creation .code WidgetT *lb = wgtListBox(parent); -const char *items[] = { "Alpha", "Beta", "Gamma" }; -wgtListBoxSetItems(lb, items, 3); +wgtListBoxAddItem(lb, "Alpha"); +wgtListBoxAddItem(lb, "Beta"); +wgtListBoxAddItem(lb, "Gamma"); .endcode -.h3 Macros +.h3 API Functions -.index wgtListBoxSetSelected .index wgtListBoxSetMultiSelect .index wgtListBoxIsItemSelected .index wgtListBoxSetReorderable @@ -53,28 +58,45 @@ wgtListBoxSetItems(lb, items, 3); .index wgtListBoxClearSelection .table - Macro Description - ----- ----------- - wgtListBox(parent) Create a list box. - wgtListBoxSetItems(w, items, count) Set the list items. - wgtListBoxGetSelected(w) Get the index of the selected item (-1 if none). - wgtListBoxSetSelected(w, idx) Set the selected item by index. - wgtListBoxSetMultiSelect(w, multi) Enable or disable multi-selection mode. - wgtListBoxIsItemSelected(w, idx) Check if a specific item is selected (multi-select mode). - wgtListBoxSetItemSelected(w, idx, selected) Select or deselect a specific item. - wgtListBoxSelectAll(w) Select all items (multi-select mode). - wgtListBoxClearSelection(w) Deselect all items. - wgtListBoxSetReorderable(w, reorderable) Enable drag-to-reorder. + Function Description + -------- ----------- + WidgetT *wgtListBox(parent) Create a list box. + void wgtListBoxSetItems(w, items, count) Set items from a caller-owned const char ** array. + int32_t wgtListBoxGetSelected(w) Get the primary selected index (-1 if none). + void wgtListBoxSetSelected(w, idx) Set the selected index. Scrolls the item into view. + void wgtListBoxSetMultiSelect(w, multi) Enable or disable multi-select mode. + bool wgtListBoxIsItemSelected(w, idx) Returns true if the item is selected (multi-select mode). + void wgtListBoxSetItemSelected(w, idx, selected) Select or deselect a specific item (multi-select mode). + void wgtListBoxSelectAll(w) Select every item (multi-select mode). + void wgtListBoxClearSelection(w) Deselect all items. + void wgtListBoxSetReorderable(w, r) Enable drag-to-reorder of items. + void wgtListBoxAddItem(w, text) Append an owned item (strdup'd). + void wgtListBoxRemoveItem(w, idx) Remove an owned item by index. + void wgtListBoxClear(w) Remove all owned items. + const char *wgtListBoxGetItem(w, idx) Get the text of an item by index. + int32_t wgtListBoxGetItemCount(w) Get the total number of items. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "listbox") .table - Callback Description - -------- ----------- - onClick Fires when an item is clicked. - onDblClick Fires when an item is double-clicked. - onChange Fires when the selection changes. + Slot Function + ---- -------- + create wgtListBox + setItems wgtListBoxSetItems + getSelected wgtListBoxGetSelected + setSelected wgtListBoxSetSelected + setMultiSelect wgtListBoxSetMultiSelect + isItemSelected wgtListBoxIsItemSelected + setItemSelected wgtListBoxSetItemSelected + selectAll wgtListBoxSelectAll + clearSelection wgtListBoxClearSelection + setReorderable wgtListBoxSetReorderable + addItem wgtListBoxAddItem + removeItem wgtListBoxRemoveItem + clear wgtListBoxClear + getItem wgtListBoxGetItem + getItemCount wgtListBoxGetItemCount .endtable .h3 Properties (BASIC Interface) @@ -88,14 +110,32 @@ wgtListBoxSetItems(lb, items, 3); .h3 Methods (BASIC Interface) .table - Method Description - ------ ----------- - SelectAll Select all items. - ClearSelection Deselect all items. - SetMultiSelect Enable or disable multi-selection. - SetReorderable Enable or disable drag-to-reorder. - IsItemSelected Check if a specific item is selected by index. - SetItemSelected Select or deselect a specific item by index. + Method Description + ------ ----------- + AddItem text$ Append an item. + Clear Remove all items. + ClearSelection Deselect all items. + IsItemSelected(index%) Returns True if the item is selected (multi-select mode). + List(index%) Return the text of the item at the given index. + ListCount() Return the total number of items. + RemoveItem index% Remove the item at the given index. + SelectAll Select all items. + SetItemSelected index%, sel Select or deselect a specific item. + SetItems spec$ Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue"); replaces existing items. + SetMultiSelect multi Enable or disable multi-select mode. + SetReorderable r Enable or disable drag-to-reorder. .endtable -.hr +.h3 Events + +.table + Callback Description + -------- ----------- + onClick Fires when an item is clicked. + onDblClick Fires when an item is double-clicked. + onChange Fires when the selection changes. +.endtable + +.h3 Default Event + +"Click" (VB basName: ListBox). diff --git a/src/widgets/kpunch/listBox/listbox.bhs b/src/widgets/kpunch/listBox/listbox.bhs index 145d3a9..0fa423a 100644 --- a/src/widgets/kpunch/listBox/listbox.bhs +++ b/src/widgets/kpunch/listBox/listbox.bhs @@ -33,7 +33,7 @@ VB Equivalent: ListBox -- DVX Widget: listbox -A scrollable list of selectable items. Items are managed via methods (AddItem, RemoveItem, Clear). Supports single and multi-select modes. +A scrollable list of selectable items. Items are managed via methods (AddItem, RemoveItem, Clear). Supports single and multi-select modes, drag-to-reorder, and keyboard navigation with type-ahead search. .h2 Type-Specific Properties @@ -41,7 +41,6 @@ A scrollable list of selectable items. Items are managed via methods (AddItem, R Property Type Description --------- ------- ------------------------------------------- ListIndex Integer Index of the currently selected item (-1 = no selection). - ListCount Integer Number of items in the list (read-only). .endtable .h2 Type-Specific Methods @@ -50,16 +49,17 @@ A scrollable list of selectable items. Items are managed via methods (AddItem, R Method Parameters Description --------------- --------------------------------------- ------------------------------------------- AddItem Text As String Add an item to the end of the list. - RemoveItem Index As Integer Remove the item at the given index. Clear (none) Remove all items from the list. - List Index As Integer Return the text of the item at the given index. - SelectAll (none) Select all items (multi-select mode). ClearSelection (none) Deselect all items. + IsItemSelected Index As Integer Returns True if the item at Index is selected. + List Index As Integer Return the text of the item at the given index. + ListCount (none) Return the number of items in the list. + RemoveItem Index As Integer Remove the item at the given index. + SelectAll (none) Select all items (multi-select mode). + SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific item. + SetItems Items As String Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue"). Replaces existing items. SetMultiSelect Multi As Boolean Enable or disable multi-select mode. SetReorderable Reorderable As Boolean Enable or disable drag-to-reorder. - IsItemSelected Index As Integer Returns True if the item at Index is selected. - SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific item. - SetItems Items As String Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue"). .endtable Default Event: Click diff --git a/src/widgets/kpunch/listView/listView.dhs b/src/widgets/kpunch/listView/listView.dhs index 25b2df3..63b80e4 100644 --- a/src/widgets/kpunch/listView/listView.dhs +++ b/src/widgets/kpunch/listView/listView.dhs @@ -35,7 +35,7 @@ A multi-column list with sortable headers. Supports single and multi-selection, column alignment, header click sorting, and drag-to-reorder. Data is provided as a flat array of strings (row-major order, one string per cell). -Header: widgets/widgetListView.h +Header: widgets/listView.h .h3 Creation @@ -120,14 +120,24 @@ typedef enum { .h3 Methods (BASIC Interface) .table - Method Description - ------ ----------- - SelectAll Select all rows. - ClearSelection Deselect all rows. - SetMultiSelect Enable or disable multi-selection. - SetReorderable Enable or disable drag-to-reorder. - IsItemSelected Check if a specific row is selected by index. - SetItemSelected Select or deselect a specific row by index. + Method Description + ------ ----------- + AddItem text$ Add a row (sets first column text). + Clear Remove all rows. + ClearSelection Deselect all rows. + GetCell(row%, col%) Returns the text of a cell. + IsItemSelected(index%) Check if a specific row is selected. + RemoveItem index% Remove a row by index. + RowCount() Returns the number of rows. + SelectAll Select all rows. + SetCell row%, col%, text$ Set the text of a cell. + SetColumns spec$ Define columns from a pipe-delimited spec string ("Name,Width|Name,Width"). + SetItemSelected idx%, sel Select or deselect a specific row by index. + SetMultiSelect multi Enable or disable multi-selection. + SetReorderable reorderable Enable or disable drag-to-reorder. + SetSort col%, dir% Set the sort column and direction (0=none, 1=ascending, 2=descending). .endtable -.hr +.h3 Default Event + +"Click" (VB basName: ListView). diff --git a/src/widgets/kpunch/listView/widgetListView.c b/src/widgets/kpunch/listView/widgetListView.c index 9f86cd0..ca772f9 100644 --- a/src/widgets/kpunch/listView/widgetListView.c +++ b/src/widgets/kpunch/listView/widgetListView.c @@ -83,10 +83,21 @@ static int32_t sTypeId = -1; #include #include "stb_ds_wrap.h" -#define LISTVIEW_PAD 3 -#define LISTVIEW_MIN_ROWS 4 -#define LISTVIEW_COL_PAD 6 -#define LISTVIEW_SORT_W 10 +#define LISTVIEW_PAD 3 +#define LISTVIEW_MIN_ROWS 4 +#define LISTVIEW_COL_PAD 6 +#define LISTVIEW_SORT_W 10 + +// Header row vertical padding: font->charHeight + LISTVIEW_HEADER_VPAD. +#define LISTVIEW_HEADER_VPAD 4 +// Column-border drag hit-zone radius (pixels to either side of the border). +#define LISTVIEW_RESIZE_ZONE 3 +// Header-text vertical offset inside a header cell. +#define LISTVIEW_HEADER_TEXT_YOFF 2 +// Sort-indicator triangle glyph (centered on column header). +#define LISTVIEW_SORT_TRI_HALF_W 3 // half-width of the widest row +#define LISTVIEW_SORT_TRI_HEIGHT 4 // rows in triangle glyph +#define LISTVIEW_SORT_TRI_BASE 7 // base-row width (2*HALF_W + 1) // ListView private data -- heap-allocated per widget, stored in w->data. // This replaces the old as.listView union pointer. The struct includes @@ -461,7 +472,7 @@ WidgetT *wgtListView(WidgetT *parent) { lv->dropIdx = -1; lv->resizeCol = -1; w->data = lv; - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; return w; } @@ -816,7 +827,7 @@ void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir) { void widgetListViewCalcMinSize(WidgetT *w, const BitmapFontT *font) { - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t minW = font->charWidth * 12 + LISTVIEW_BORDER * 2 + WGT_SB_W; w->calcMinW = minW; @@ -841,7 +852,7 @@ bool widgetListViewColBorderHit(const WidgetT *w, int32_t vx, int32_t vy) { AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t headerTop = w->y + LISTVIEW_BORDER; if (vy < headerTop || vy >= headerTop + headerH) { @@ -853,7 +864,7 @@ bool widgetListViewColBorderHit(const WidgetT *w, int32_t vx, int32_t vy) { for (int32_t c = 0; c < lv->colCount; c++) { int32_t border = colX + lv->resolvedColW[c]; - if (vx >= border - 3 && vx <= border + 3) { + if (vx >= border - LISTVIEW_RESIZE_ZONE && vx <= border + LISTVIEW_RESIZE_ZONE) { return true; } @@ -1003,7 +1014,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) { // Compute visible rows for page up/down AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH; int32_t visibleRows = innerH / font->charHeight; @@ -1138,7 +1149,7 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) resolveColumnWidths(hit, font); } - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t innerH = hit->h - LISTVIEW_BORDER * 2 - headerH; int32_t innerW = hit->w - LISTVIEW_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; @@ -1282,7 +1293,7 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) int32_t cw = lv->resolvedColW[c]; int32_t border = colX + cw; - if (vx >= border - 3 && vx <= border + 3 && c < lv->colCount) { + if (vx >= border - LISTVIEW_RESIZE_ZONE && vx <= border + LISTVIEW_RESIZE_ZONE && c < lv->colCount) { if (multiClickDetect(vx, vy) >= 2) { // Double-click on column border: auto-size to fit content int32_t maxLen = (int32_t)strlen(lv->cols[c].title); @@ -1486,7 +1497,7 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit resolveColumnWidths(w, font); } - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH; int32_t innerW = w->w - LISTVIEW_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; @@ -1556,7 +1567,7 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit // Header text int32_t textX = hdrX + LISTVIEW_PAD; - int32_t textY = baseY + 2; + int32_t textY = baseY + LISTVIEW_HEADER_TEXT_YOFF; int32_t availTextW = cw - LISTVIEW_PAD * 2; // Reserve space for sort indicator if this is the sort column @@ -1586,14 +1597,16 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit int32_t cy = baseY + headerH / 2; if (lv->sortDir == ListViewSortAscE) { - // Up triangle (ascending) - for (int32_t i = 0; i < 4; i++) { - drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, colors->contentFg); + // Up triangle (ascending): narrow row at top, growing + // wider toward the bottom. Row i has width 2*i + 1. + for (int32_t i = 0; i < LISTVIEW_SORT_TRI_HEIGHT; i++) { + drawHLine(d, ops, cx - i, cy - (LISTVIEW_SORT_TRI_HEIGHT / 2) + i, 1 + i * 2, colors->contentFg); } } else { - // Down triangle (descending) - for (int32_t i = 0; i < 4; i++) { - drawHLine(d, ops, cx - 3 + i, cy - 1 + i, 7 - i * 2, colors->contentFg); + // Down triangle (descending): wide row at top, shrinking + // toward the bottom. Row i has width BASE - 2*i. + for (int32_t i = 0; i < LISTVIEW_SORT_TRI_HEIGHT; i++) { + drawHLine(d, ops, cx - LISTVIEW_SORT_TRI_HALF_W + i, cy - (LISTVIEW_SORT_TRI_HEIGHT / 2 - 1) + i, LISTVIEW_SORT_TRI_BASE - i * 2, colors->contentFg); } } } @@ -1836,7 +1849,7 @@ static void widgetListViewReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, in ListViewDataT *lv = (ListViewDataT *)w->data; AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t dataY = w->y + LISTVIEW_BORDER + headerH; int32_t relY = y - dataY; int32_t dropIdx = lv->scrollPos + relY / font->charHeight; @@ -1905,7 +1918,7 @@ static void widgetListViewScrollDragUpdate(WidgetT *w, int32_t orient, int32_t d AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; - int32_t headerH = font->charHeight + 4; + int32_t headerH = font->charHeight + LISTVIEW_HEADER_VPAD; int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH; int32_t innerW = w->w - LISTVIEW_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; diff --git a/src/widgets/kpunch/progressBar/progress.dhs b/src/widgets/kpunch/progressBar/progress.dhs index 7a09028..43dfb07 100644 --- a/src/widgets/kpunch/progressBar/progress.dhs +++ b/src/widgets/kpunch/progressBar/progress.dhs @@ -34,7 +34,7 @@ A visual indicator of progress, displayed as a filled bar. Supports both horizontal and vertical orientations. Value ranges from 0 to 100. -Header: widgets/widgetProgressBar.h +Header: widgets/progress.h .h3 Creation @@ -45,18 +45,27 @@ Header: widgets/widgetProgressBar.h wgtProgressBarV(parent) Create a vertical progress bar. .endtable -.h3 Methods +.h3 API Functions .table - Macro Description - ----- ----------- - wgtProgressBarSetValue(w, value) Set the progress value (0-100). - wgtProgressBarGetValue(w) Get the current progress value. + Function Description + -------- ----------- + WidgetT *wgtProgressBar(parent) Create a horizontal progress bar. + WidgetT *wgtProgressBarV(parent) Create a vertical progress bar. + void wgtProgressBarSetValue(w, val) Set the progress value (0-100). Clamped to 0..maxValue. + int32_t wgtProgressBarGetValue(w) Get the current progress value. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "progressbar") -ProgressBar is a display-only widget. Typically no callbacks are set. +.table + Slot Function + ---- -------- + create wgtProgressBar + createV wgtProgressBarV + setValue wgtProgressBarSetValue + getValue wgtProgressBarGetValue +.endtable .h3 Properties (BASIC Interface) @@ -66,4 +75,14 @@ ProgressBar is a display-only widget. Typically no callbacks are set. Value Integer Read/Write Current progress value (0-100). .endtable -.hr +.h3 Methods + +No widget-specific methods. + +.h3 Events + +ProgressBar is a display-only widget. Typically no callbacks are set. + +.h3 Default Event + +None. (VB basName: ProgressBar.) diff --git a/src/widgets/kpunch/progressBar/widgetProgressBar.c b/src/widgets/kpunch/progressBar/widgetProgressBar.c index 4be6dbc..7176933 100644 --- a/src/widgets/kpunch/progressBar/widgetProgressBar.c +++ b/src/widgets/kpunch/progressBar/widgetProgressBar.c @@ -39,6 +39,10 @@ #include "dvxWgtP.h" +// 2px sunken bevel on every side -- the content area is inset by this +// much on each axis, so 2*PROGRESSBAR_BEVEL_W total. +#define PROGRESSBAR_BEVEL_W 2 + static int32_t sTypeId = -1; typedef struct { @@ -201,7 +205,7 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con bevel.highlight = colors->windowShadow; bevel.shadow = colors->windowHighlight; bevel.face = bg; - bevel.width = 2; + bevel.width = PROGRESSBAR_BEVEL_W; drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel); // Fill bar @@ -211,8 +215,8 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con maxVal = 100; } - int32_t innerW = w->w - 4; - int32_t innerH = w->h - 4; + int32_t innerW = w->w - 2 * PROGRESSBAR_BEVEL_W; + int32_t innerH = w->h - 2 * PROGRESSBAR_BEVEL_W; if (d->vertical) { int32_t fillH = (innerH * d->value) / maxVal; @@ -222,7 +226,7 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con } if (fillH > 0) { - rectFill(disp, ops, w->x + 2, w->y + 2 + innerH - fillH, innerW, fillH, fg); + rectFill(disp, ops, w->x + PROGRESSBAR_BEVEL_W, w->y + PROGRESSBAR_BEVEL_W + innerH - fillH, innerW, fillH, fg); } } else { int32_t fillW = (innerW * d->value) / maxVal; @@ -232,7 +236,7 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, con } if (fillW > 0) { - rectFill(disp, ops, w->x + 2, w->y + 2, fillW, innerH, fg); + rectFill(disp, ops, w->x + PROGRESSBAR_BEVEL_W, w->y + PROGRESSBAR_BEVEL_W, fillW, innerH, fg); } } } diff --git a/src/widgets/kpunch/radio/radio.dhs b/src/widgets/kpunch/radio/radio.dhs index 76c3900..a58a306 100644 --- a/src/widgets/kpunch/radio/radio.dhs +++ b/src/widgets/kpunch/radio/radio.dhs @@ -35,7 +35,7 @@ A mutually exclusive selection control. Radio buttons must be placed inside a radio group container. Only one radio button within a group can be selected at a time. -Header: widgets/widgetRadio.h +Header: widgets/radio.h .h3 Creation @@ -45,15 +45,44 @@ WidgetT *r1 = wgtRadio(grp, "Option A"); WidgetT *r2 = wgtRadio(grp, "Option B"); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtRadioGroup(parent) Create a radio group container. - wgtRadio(parent, text) Create a radio button inside a group. - wgtRadioGroupSetSelected(w, idx) Set the selected radio button by index within the group. - wgtRadioGetIndex(w) Get the index of the currently selected radio button. + Function Description + -------- ----------- + WidgetT *wgtRadioGroup(parent) Create a radio group container. + WidgetT *wgtRadio(parent, text) Create a radio button inside a group. + void wgtRadioGroupSetSelected(group, idx) Set the selected radio button by index within the group. + int32_t wgtRadioGetIndex(w) Get the index of this radio button within its group. +.endtable + +.h3 API Struct (wgtRegisterApi "radio") + +.table + Slot Function + ---- -------- + group wgtRadioGroup + create wgtRadio + groupSetSelected wgtRadioGroupSetSelected + getIndex wgtRadioGetIndex +.endtable + +.h3 Properties (BASIC Interface) + +.table + Property Type Access Description + -------- ---- ------ ----------- + Value Integer Read-only Index of this radio button within its group. +.endtable + +Caption is managed via the standard wgtSetText() / wgtGetText() interface (WCLASS_HAS_TEXT). + +.h3 Methods (BASIC Interface) + +.table + Method Description + ------ ----------- + SetSelected idx% Set the selected radio button by index within the group. .endtable .h3 Events @@ -65,20 +94,6 @@ WidgetT *r2 = wgtRadio(grp, "Option B"); onChange Fires when the selection changes. .endtable -.h3 Properties (BASIC Interface) +.h3 Default Event -.table - Property Type Access Description - -------- ---- ------ ----------- - Value Integer Read-only Index of the currently selected radio button in the group. -.endtable - -.h3 Methods (BASIC Interface) - -.table - Method Description - ------ ----------- - SetSelected Set the selected radio button by index. -.endtable - -.hr +"Click" (VB basName: OptionButton, namePrefix: Option). diff --git a/src/widgets/kpunch/radio/widgetRadio.c b/src/widgets/kpunch/radio/widgetRadio.c index 7eb3cb6..0a56b34 100644 --- a/src/widgets/kpunch/radio/widgetRadio.c +++ b/src/widgets/kpunch/radio/widgetRadio.c @@ -259,7 +259,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) { } wgtInvalidatePaint(w); - } else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) { + } else if (key == KEY_DOWN || key == KEY_RIGHT) { // Down or Right -- next radio in group if (w->parent && w->parent->type == sRadioGroupTypeId) { WidgetT *next = NULL; @@ -285,7 +285,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) { wgtInvalidatePaint(next); } } - } else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) { + } else if (key == KEY_UP || key == KEY_LEFT) { // Up or Left -- previous radio in group if (w->parent && w->parent->type == sRadioGroupTypeId) { WidgetT *prev = NULL; diff --git a/src/widgets/kpunch/scrollPane/scrlpane.dhs b/src/widgets/kpunch/scrollPane/scrlpane.dhs index 082c7ce..aabba6a 100644 --- a/src/widgets/kpunch/scrollPane/scrlpane.dhs +++ b/src/widgets/kpunch/scrollPane/scrlpane.dhs @@ -33,7 +33,7 @@ A scrollable container that provides vertical and/or horizontal scrollbars when its content exceeds the visible area. Place a single child (typically a VBox or HBox) inside the scroll pane. -Header: widgets/widgetScrollPane.h +Header: widgets/scrlPane.h .h3 Creation @@ -43,22 +43,26 @@ WidgetT *content = wgtVBox(sp); // add children to content... .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtScrollPane(parent) Create a scroll pane container. - wgtScrollPaneScrollToChild(sp, child) Scroll so that the given child widget is visible. - wgtScrollPaneSetNoBorder(w, noBorder) When true, removes the border around the scroll pane. + Function Description + -------- ----------- + WidgetT *wgtScrollPane(parent) Create a scroll pane container. + void wgtScrollPaneScrollToChild(sp, child) Scroll so that the given child widget is visible. + void wgtScrollPaneScrollToTop(w) Scroll back to the top-left of the content. + void wgtScrollPaneSetNoBorder(w, noBorder) When true, removes the border around the scroll pane. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "scrollpane") .table - Callback Description - -------- ----------- - onScroll Fires when the scroll position changes. + Slot Function + ---- -------- + create wgtScrollPane + scrollToChild wgtScrollPaneScrollToChild + setNoBorder wgtScrollPaneSetNoBorder + scrollToTop wgtScrollPaneScrollToTop .endtable .h3 Properties (BASIC Interface) @@ -69,4 +73,18 @@ WidgetT *content = wgtVBox(sp); NoBorder Boolean Read/Write Whether the border around the scroll pane is hidden. .endtable -.hr +.h3 Methods + +No widget-specific methods. + +.h3 Events + +.table + Callback Description + -------- ----------- + onScroll Fires when the scroll position changes. +.endtable + +.h3 Default Event + +None. (VB basName: ScrollPane, namePrefix: Scroll.) diff --git a/src/widgets/kpunch/scrollPane/widgetScrollPane.c b/src/widgets/kpunch/scrollPane/widgetScrollPane.c index 0a99423..74797cd 100644 --- a/src/widgets/kpunch/scrollPane/widgetScrollPane.c +++ b/src/widgets/kpunch/scrollPane/widgetScrollPane.c @@ -232,7 +232,7 @@ WidgetT *wgtScrollPane(WidgetT *parent) { w->data = sp; } - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -578,28 +578,28 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) { int32_t step = font->charHeight; - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { // Up sp->scrollPosV -= step; - } else if (key == (0x50 | 0x100)) { + } else if (key == KEY_DOWN) { // Down sp->scrollPosV += step; - } else if (key == (0x49 | 0x100)) { + } else if (key == KEY_PGUP) { // Page Up sp->scrollPosV -= innerH; - } else if (key == (0x51 | 0x100)) { + } else if (key == KEY_PGDN) { // Page Down sp->scrollPosV += innerH; - } else if (key == (0x47 | 0x100)) { + } else if (key == KEY_HOME) { // Home sp->scrollPosV = 0; - } else if (key == (0x4F | 0x100)) { + } else if (key == KEY_END) { // End sp->scrollPosV = maxScrollV; - } else if (key == (0x4B | 0x100)) { + } else if (key == KEY_LEFT) { // Left sp->scrollPosH -= font->charWidth; - } else if (key == (0x4D | 0x100)) { + } else if (key == KEY_RIGHT) { // Right sp->scrollPosH += font->charWidth; } else { diff --git a/src/widgets/kpunch/separatr/separatr.dhs b/src/widgets/kpunch/separatr/separatr.dhs index 82f0fff..70767f5 100644 --- a/src/widgets/kpunch/separatr/separatr.dhs +++ b/src/widgets/kpunch/separatr/separatr.dhs @@ -32,19 +32,38 @@ A visual dividing line used to separate groups of widgets. Available in horizontal and vertical orientations. -Header: widgets/widgetSeparator.h +Header: widgets/separatr.h -.h3 Creation +.h3 API Functions .table - Macro Description - ----- ----------- - wgtHSeparator(parent) Create a horizontal separator line. - wgtVSeparator(parent) Create a vertical separator line. + Function Description + -------- ----------- + WidgetT *wgtHSeparator(parent) Create a horizontal separator line. + WidgetT *wgtVSeparator(parent) Create a vertical separator line. .endtable +.h3 API Struct (wgtRegisterApi "separator") + +.table + Slot Function + ---- -------- + hSeparator wgtHSeparator + vSeparator wgtVSeparator +.endtable + +.h3 Properties + +No widget-specific properties. + +.h3 Methods + +No widget-specific methods. + .h3 Events Separator is a visual-only widget. No events. -.hr +.h3 Default Event + +None. (VB basName: Line.) diff --git a/src/widgets/kpunch/slider/slider.dhs b/src/widgets/kpunch/slider/slider.dhs index 3f64b47..4e30a94 100644 --- a/src/widgets/kpunch/slider/slider.dhs +++ b/src/widgets/kpunch/slider/slider.dhs @@ -33,7 +33,7 @@ A horizontal slider (track bar) for selecting an integer value within a range. The user drags the thumb or clicks the track to change the value. -Header: widgets/widgetSlider.h +Header: widgets/slider.h .h3 Creation @@ -41,22 +41,24 @@ Header: widgets/widgetSlider.h WidgetT *sl = wgtSlider(parent, 0, 100); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtSlider(parent, minVal, maxVal) Create a slider with the given integer range. - wgtSliderSetValue(w, value) Set the slider value programmatically. - wgtSliderGetValue(w) Get the current slider value. + Function Description + -------- ----------- + WidgetT *wgtSlider(parent, minVal, maxVal) Create a slider with the given integer range. + void wgtSliderSetValue(w, value) Set the slider value programmatically. + int32_t wgtSliderGetValue(w) Get the current slider value. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "slider") .table - Callback Description - -------- ----------- - onChange Fires when the slider value changes. + Slot Function + ---- -------- + create wgtSlider + setValue wgtSliderSetValue + getValue wgtSliderGetValue .endtable .h3 Properties (BASIC Interface) @@ -67,4 +69,18 @@ WidgetT *sl = wgtSlider(parent, 0, 100); Value Integer Read/Write Current slider value. .endtable -.hr +.h3 Methods + +No widget-specific methods. + +.h3 Events + +.table + Callback Description + -------- ----------- + onChange Fires when the slider value changes. +.endtable + +.h3 Default Event + +"Change" (VB basName: HScrollBar, namePrefix: HScroll). diff --git a/src/widgets/kpunch/slider/widgetSlider.c b/src/widgets/kpunch/slider/widgetSlider.c index 01ea820..a6b6997 100644 --- a/src/widgets/kpunch/slider/widgetSlider.c +++ b/src/widgets/kpunch/slider/widgetSlider.c @@ -47,6 +47,9 @@ #define SLIDER_TRACK_H 4 #define SLIDER_THUMB_W 11 +// Tick-line inset from the widget's edge (each side). Leaves a margin +// between the thumb's center-tick and where the track ends. +#define SLIDER_TICK_INSET 3 static int32_t sTypeId = -1; @@ -135,7 +138,7 @@ WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) { d->minValue = minVal; d->maxValue = maxVal; w->data = d; - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -233,33 +236,33 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) { // Arrow keys: step by 1. Page Up/Down: step by 10% of range. // Home/End: jump to min/max. if (d->vertical) { - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { d->value -= 1; - } else if (key == (0x50 | 0x100)) { + } else if (key == KEY_DOWN) { d->value += 1; - } else if (key == (0x49 | 0x100)) { + } else if (key == KEY_PGUP) { d->value -= pageStep; - } else if (key == (0x51 | 0x100)) { + } else if (key == KEY_PGDN) { d->value += pageStep; - } else if (key == (0x47 | 0x100)) { + } else if (key == KEY_HOME) { d->value = d->minValue; - } else if (key == (0x4F | 0x100)) { + } else if (key == KEY_END) { d->value = d->maxValue; } else { return; } } else { - if (key == (0x4B | 0x100) || key == (0x48 | 0x100)) { + if (key == KEY_LEFT || key == KEY_UP) { d->value -= 1; - } else if (key == (0x4D | 0x100) || key == (0x50 | 0x100)) { + } else if (key == KEY_RIGHT || key == KEY_DOWN) { d->value += 1; - } else if (key == (0x49 | 0x100)) { + } else if (key == KEY_PGUP) { d->value -= pageStep; - } else if (key == (0x51 | 0x100)) { + } else if (key == KEY_PGDN) { d->value += pageStep; - } else if (key == (0x47 | 0x100)) { + } else if (key == KEY_HOME) { d->value = d->minValue; - } else if (key == (0x4F | 0x100)) { + } else if (key == KEY_END) { d->value = d->maxValue; } else { return; @@ -386,7 +389,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma drawBevel(d, ops, w->x, thumbY, w->w, SLIDER_THUMB_W, &thumb); // Center tick on thumb - drawHLine(d, ops, w->x + 3, thumbY + SLIDER_THUMB_W / 2, w->w - 6, tickFg); + drawHLine(d, ops, w->x + SLIDER_TICK_INSET, thumbY + SLIDER_THUMB_W / 2, w->w - 2 * SLIDER_TICK_INSET, tickFg); } else { // Track groove int32_t trackY = w->y + (w->h - SLIDER_TRACK_H) / 2; @@ -409,7 +412,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma drawBevel(d, ops, thumbX, w->y, SLIDER_THUMB_W, w->h, &thumb); // Center tick on thumb - drawVLine(d, ops, thumbX + SLIDER_THUMB_W / 2, w->y + 3, w->h - 6, tickFg); + drawVLine(d, ops, thumbX + SLIDER_THUMB_W / 2, w->y + SLIDER_TICK_INSET, w->h - 2 * SLIDER_TICK_INSET, tickFg); } if (w == sFocusedWidget) { diff --git a/src/widgets/kpunch/spacer/spacer.dhs b/src/widgets/kpunch/spacer/spacer.dhs index c372376..6afd239 100644 --- a/src/widgets/kpunch/spacer/spacer.dhs +++ b/src/widgets/kpunch/spacer/spacer.dhs @@ -31,7 +31,7 @@ An invisible widget used for layout purposes. By default it has weight=100, so it absorbs available extra space. Useful for pushing other widgets apart or aligning them to edges. -Header: widgets/widgetSpacer.h +Header: widgets/spacer.h .h3 Creation @@ -42,16 +42,26 @@ wgtSpacer(row); // pushes Cancel to the right wgtButton(row, "Cancel"); .endcode -.h3 Macro +.h3 API Functions .table - Macro Description - ----- ----------- - wgtSpacer(parent) Create an invisible spacer widget. + Function Description + -------- ----------- + WidgetT *wgtSpacer(parent) Create an invisible spacer widget. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "spacer") -Spacer is an invisible layout widget. No events. +.table + Slot Function + ---- -------- + create wgtSpacer +.endtable -.hr +.h3 Properties, Methods, Events + +Spacer is an invisible layout widget. No type-specific properties, methods, or events. + +.h3 Default Event + +None. (VB basName: Spacer.) diff --git a/src/widgets/kpunch/spacer/widgetSpacer.c b/src/widgets/kpunch/spacer/widgetSpacer.c index 282b8d2..2a0183f 100644 --- a/src/widgets/kpunch/spacer/widgetSpacer.c +++ b/src/widgets/kpunch/spacer/widgetSpacer.c @@ -90,7 +90,7 @@ WidgetT *wgtSpacer(WidgetT *parent) { WidgetT *w = widgetAlloc(parent, sTypeId); if (w) { - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; diff --git a/src/widgets/kpunch/spinner/spinner.dhs b/src/widgets/kpunch/spinner/spinner.dhs index 6f9c11c..236a94a 100644 --- a/src/widgets/kpunch/spinner/spinner.dhs +++ b/src/widgets/kpunch/spinner/spinner.dhs @@ -34,7 +34,7 @@ A numeric input with up/down buttons for incrementing and decrementing a value within a range. Supports both integer and floating-point (real) modes. -Header: widgets/widgetSpinner.h +Header: widgets/spinner.h .h3 Creation @@ -42,7 +42,7 @@ Header: widgets/widgetSpinner.h WidgetT *sp = wgtSpinner(parent, 0, 100, 1); .endcode -.h3 Macros +.h3 API Functions .index wgtSpinnerSetRange .index wgtSpinnerSetStep @@ -53,27 +53,37 @@ WidgetT *sp = wgtSpinner(parent, 0, 100, 1); .index wgtSpinnerSetDecimals .table - Macro Description - ----- ----------- - wgtSpinner(parent, minVal, maxVal, step) Create a spinner with the given integer range and step size. - wgtSpinnerSetValue(w, value) Set the integer value. - wgtSpinnerGetValue(w) Get the current integer value. - wgtSpinnerSetRange(w, minVal, maxVal) Set the integer range. - wgtSpinnerSetStep(w, step) Set the integer step size. - wgtSpinnerSetRealMode(w, enable) Switch to floating-point mode. - wgtSpinnerGetRealValue(w) Get the current floating-point value. - wgtSpinnerSetRealValue(w, value) Set the floating-point value. - wgtSpinnerSetRealRange(w, minVal, maxVal) Set the floating-point range. - wgtSpinnerSetRealStep(w, step) Set the floating-point step size. - wgtSpinnerSetDecimals(w, decimals) Set the number of decimal places displayed in real mode. + Function Description + -------- ----------- + WidgetT *wgtSpinner(parent, minVal, maxVal, step) Create a spinner with the given integer range and step size. + void wgtSpinnerSetValue(w, value) Set the integer value. + int32_t wgtSpinnerGetValue(w) Get the current integer value. + void wgtSpinnerSetRange(w, minVal, maxVal) Set the integer range. + void wgtSpinnerSetStep(w, step) Set the integer step size. + void wgtSpinnerSetRealMode(w, enable) Switch to floating-point mode. + double wgtSpinnerGetRealValue(w) Get the current floating-point value. + void wgtSpinnerSetRealValue(w, value) Set the floating-point value. + void wgtSpinnerSetRealRange(w, minVal, maxVal) Set the floating-point range. + void wgtSpinnerSetRealStep(w, step) Set the floating-point step size. + void wgtSpinnerSetDecimals(w, decimals) Set the number of decimal places displayed in real mode. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "spinner") .table - Callback Description - -------- ----------- - onChange Fires when the value changes. + Slot Function + ---- -------- + create wgtSpinner + setValue wgtSpinnerSetValue + getValue wgtSpinnerGetValue + setRange wgtSpinnerSetRange + setStep wgtSpinnerSetStep + setRealMode wgtSpinnerSetRealMode + getRealValue wgtSpinnerGetRealValue + setRealValue wgtSpinnerSetRealValue + setRealRange wgtSpinnerSetRealRange + setRealStep wgtSpinnerSetRealStep + setDecimals wgtSpinnerSetDecimals .endtable .h3 Properties (BASIC Interface) @@ -89,10 +99,20 @@ WidgetT *sp = wgtSpinner(parent, 0, 100, 1); .h3 Methods (BASIC Interface) .table - Method Description - ------ ----------- - SetRange Set the integer range (min, max). - SetStep Set the integer step size. + Method Description + ------ ----------- + SetRange min%, max% Set the integer range. + SetStep step% Set the integer step size. .endtable -.hr +.h3 Events + +.table + Callback Description + -------- ----------- + onChange Fires when the value changes. +.endtable + +.h3 Default Event + +"Change" (VB basName: SpinButton, namePrefix: Spin). diff --git a/src/widgets/kpunch/spinner/widgetSpinner.c b/src/widgets/kpunch/spinner/widgetSpinner.c index 83a5367..fb6893c 100644 --- a/src/widgets/kpunch/spinner/widgetSpinner.c +++ b/src/widgets/kpunch/spinner/widgetSpinner.c @@ -527,7 +527,7 @@ static void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) { SpinnerDataT *d = (SpinnerDataT *)w->data; // Up arrow -- increment - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { spinnerCommitEdit(d); spinnerApplyStep(d, 1, 1); @@ -540,7 +540,7 @@ static void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Down arrow -- decrement - if (key == (0x50 | 0x100)) { + if (key == KEY_DOWN) { spinnerCommitEdit(d); spinnerApplyStep(d, -1, 1); @@ -553,7 +553,7 @@ static void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Page Up -- increment by step * 10 - if (key == (0x49 | 0x100)) { + if (key == KEY_PGUP) { spinnerCommitEdit(d); spinnerApplyStep(d, 1, 10); @@ -566,7 +566,7 @@ static void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Page Down -- decrement by step * 10 - if (key == (0x51 | 0x100)) { + if (key == KEY_PGDN) { spinnerCommitEdit(d); spinnerApplyStep(d, -1, 10); @@ -629,7 +629,7 @@ static void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Enter edit mode on first text-modifying key - if (isDigit || isMinus || isDot || key == 8 || key == 127 || key == (0x53 | 0x100)) { + if (isDigit || isMinus || isDot || key == '\b' || key == KEY_ASCII_DEL || key == KEY_DELETE) { spinnerStartEdit(d); } diff --git a/src/widgets/kpunch/splitter/splitter.bhs b/src/widgets/kpunch/splitter/splitter.bhs index dcdfc31..7088e24 100644 --- a/src/widgets/kpunch/splitter/splitter.bhs +++ b/src/widgets/kpunch/splitter/splitter.bhs @@ -30,7 +30,7 @@ DVX Extension -- DVX Widget: splitter -A resizable split pane. Holds exactly two child widgets separated by a draggable divider. Default orientation is vertical (top/bottom split). +A resizable split pane. Holds exactly two child widgets separated by a draggable divider. Default orientation is vertical (vertical divider, panes side-by-side). .h2 Type-Specific Properties diff --git a/src/widgets/kpunch/splitter/splitter.dhs b/src/widgets/kpunch/splitter/splitter.dhs index 5c38522..9087ea5 100644 --- a/src/widgets/kpunch/splitter/splitter.dhs +++ b/src/widgets/kpunch/splitter/splitter.dhs @@ -33,7 +33,7 @@ A two-pane container with a draggable divider. The user drags the splitter bar to resize the two panes. Can be oriented vertically (left/right panes) or horizontally (top/bottom panes). Add exactly two children. -Header: widgets/widgetSplitter.h +Header: widgets/splitter.h .h3 Creation @@ -43,22 +43,24 @@ WidgetT *left = wgtVBox(sp); WidgetT *right = wgtVBox(sp); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtSplitter(parent, vertical) Create a splitter. vertical=true for left/right panes, false for top/bottom. - wgtSplitterSetPos(w, pos) Set the divider position in pixels. - wgtSplitterGetPos(w) Get the current divider position in pixels. + Function Description + -------- ----------- + WidgetT *wgtSplitter(parent, vertical) Create a splitter. vertical=true for left/right panes (vertical divider), false for top/bottom. + void wgtSplitterSetPos(w, pos) Set the divider position in pixels from the leading edge. + int32_t wgtSplitterGetPos(w) Get the current divider position in pixels. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "splitter") .table - Callback Description - -------- ----------- - onChange Fires when the divider position changes. + Slot Function + ---- -------- + create wgtSplitter + setPos wgtSplitterSetPos + getPos wgtSplitterGetPos .endtable .h3 Properties (BASIC Interface) @@ -69,4 +71,14 @@ WidgetT *right = wgtVBox(sp); Position Integer Read/Write Divider position in pixels. .endtable -.hr +.h3 Methods + +No widget-specific methods. + +.h3 Events + +Splitter has no widget-specific events. Common events apply. + +.h3 Default Event + +None. (VB basName: Splitter.) diff --git a/src/widgets/kpunch/splitter/widgetSplitter.c b/src/widgets/kpunch/splitter/widgetSplitter.c index dcebb11..c4d6c58 100644 --- a/src/widgets/kpunch/splitter/widgetSplitter.c +++ b/src/widgets/kpunch/splitter/widgetSplitter.c @@ -175,7 +175,7 @@ WidgetT *wgtSplitter(WidgetT *parent, bool vertical) { d->vertical = vertical; d->dividerPos = 0; w->data = d; - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; diff --git a/src/widgets/kpunch/statbar/statbar.bhs b/src/widgets/kpunch/statbar/statbar.bhs index 5344c6b..ae665a5 100644 --- a/src/widgets/kpunch/statbar/statbar.bhs +++ b/src/widgets/kpunch/statbar/statbar.bhs @@ -29,7 +29,7 @@ VB Equivalent: StatusBar -- DVX Widget: statusbar -A horizontal container styled as a status bar, typically placed at the bottom of a form. At the C API level it accepts child widgets, but it is not registered as a container in the form runtime, so child controls cannot be nested inside it in .frm files. Set its Caption property to display status text. +A horizontal container styled as a status bar, typically placed at the bottom of a form. At the C API level it holds child widgets (such as labels), each drawn with a sunken-panel border for the classic segmented status-bar look. It is not registered as a container in the form runtime, so child controls cannot be nested inside it in .frm files. No type-specific properties, events, or methods. diff --git a/src/widgets/kpunch/statbar/statbar.dhs b/src/widgets/kpunch/statbar/statbar.dhs index bce3f88..e6384e9 100644 --- a/src/widgets/kpunch/statbar/statbar.dhs +++ b/src/widgets/kpunch/statbar/statbar.dhs @@ -31,7 +31,7 @@ A horizontal bar typically placed at the bottom of a window for displaying status text and informational widgets. Children are laid out horizontally. -Header: widgets/widgetStatusBar.h +Header: widgets/statBar.h .h3 Creation @@ -40,20 +40,26 @@ WidgetT *sb = wgtStatusBar(parent); wgtLabel(sb, "Ready"); .endcode -.h3 Macro +.h3 API Functions .table - Macro Description - ----- ----------- - wgtStatusBar(parent) Create a status bar container. + Function Description + -------- ----------- + WidgetT *wgtStatusBar(parent) Create a status bar container. .endtable -.h3 Properties +.h3 API Struct (wgtRegisterApi "statusbar") -Uses common container properties. Add child widgets (labels, progress bars, etc.) to populate. +.table + Slot Function + ---- -------- + create wgtStatusBar +.endtable -.h3 Events +.h3 Properties, Methods, Events -StatusBar itself has no widget-specific events. Events fire on the child widgets. +StatusBar has no widget-specific properties, methods, or events. Add child widgets (labels, progress bars, etc.) to populate. -.hr +.h3 Default Event + +None. (VB basName: StatusBar.) diff --git a/src/widgets/kpunch/tabControl/tabctrl.dhs b/src/widgets/kpunch/tabControl/tabctrl.dhs index d4949bf..6536cd1 100644 --- a/src/widgets/kpunch/tabControl/tabctrl.dhs +++ b/src/widgets/kpunch/tabControl/tabctrl.dhs @@ -35,7 +35,7 @@ A tabbed container that displays one page at a time with clickable tabs along the top. Each tab page is a container that holds its own child widgets. -Header: widgets/widgetTabControl.h +Header: widgets/tabCtrl.h .h3 Creation @@ -46,23 +46,26 @@ WidgetT *page2 = wgtTabPage(tabs, "Advanced"); // add children to page1, page2... .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtTabControl(parent) Create a tab control. - wgtTabPage(parent, title) Add a tab page with the given title. Returns the page container widget. - wgtTabControlSetActive(w, idx) Set the active tab by index (0-based). - wgtTabControlGetActive(w) Get the index of the active tab. + Function Description + -------- ----------- + WidgetT *wgtTabControl(parent) Create a tab control. + WidgetT *wgtTabPage(parent, title) Add a tab page with the given title. Returns the page container widget. + void wgtTabControlSetActive(w, idx) Set the active tab by index (0-based). + int32_t wgtTabControlGetActive(w) Get the index of the active tab. .endtable -.h3 Events +.h3 API Struct (wgtRegisterApi "tabcontrol") .table - Callback Description - -------- ----------- - onChange Fires when the active tab changes. + Slot Function + ---- -------- + create wgtTabControl + page wgtTabPage + setActive wgtTabControlSetActive + getActive wgtTabControlGetActive .endtable .h3 Properties (BASIC Interface) @@ -76,9 +79,21 @@ WidgetT *page2 = wgtTabPage(tabs, "Advanced"); .h3 Methods (BASIC Interface) .table - Method Description - ------ ----------- - SetActive Set the active tab by index. + Method Description + ------ ----------- + AddPage title$ Add a new tab page with the given title. + GetActive() Returns the index of the active tab. + SetActive idx% Set the active tab by index (0-based). .endtable -.hr +.h3 Events + +.table + Callback Description + -------- ----------- + onChange Fires when the active tab changes. +.endtable + +.h3 Default Event + +"Click" (VB basName: TabStrip). diff --git a/src/widgets/kpunch/tabControl/widgetTabControl.c b/src/widgets/kpunch/tabControl/widgetTabControl.c index 613bcc6..2518b81 100644 --- a/src/widgets/kpunch/tabControl/widgetTabControl.c +++ b/src/widgets/kpunch/tabControl/widgetTabControl.c @@ -58,6 +58,13 @@ #define TAB_PAD_H 8 #define TAB_PAD_V 4 #define TAB_BORDER 2 +// Visual "lift" of the active tab (pixels). Inactive tabs are drawn +// TAB_ACTIVE_LIFT pixels lower so the active tab appears foremost. +#define TAB_ACTIVE_LIFT 2 +// Tab interior inset used by rectFill/drawVLine when painting tab body. +#define TAB_EDGE_INSET 2 +// Focus-rect inset inside the active tab. +#define TAB_FOCUS_INSET 3 static int32_t sTabControlTypeId = -1; static int32_t sTabPageTypeId = -1; @@ -189,7 +196,7 @@ WidgetT *wgtTabControl(WidgetT *parent) { d->activeTab = 0; d->scrollOffset = 0; w->data = d; - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -310,13 +317,13 @@ void widgetTabControlOnKey(WidgetT *w, int32_t key, int32_t mod) { int32_t active = d->activeTab; - if (key == (0x4D | 0x100)) { + if (key == KEY_RIGHT) { active = (active + 1) % tabCount; - } else if (key == (0x4B | 0x100)) { + } else if (key == KEY_LEFT) { active = (active - 1 + tabCount) % tabCount; - } else if (key == (0x47 | 0x100)) { + } else if (key == KEY_HOME) { active = 0; - } else if (key == (0x4F | 0x100)) { + } else if (key == KEY_END) { active = tabCount - 1; } else { return; @@ -490,15 +497,15 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B } } - // Tab headers -- clip to header area (tabH + 2 for the active tab's extra height) - int32_t headerLeft = w->x + 2 + (scroll ? TAB_ARROW_W : 0); + // Tab headers -- clip to header area (tabH + TAB_ACTIVE_LIFT for the active tab's extra height) + int32_t headerLeft = w->x + TAB_EDGE_INSET + (scroll ? TAB_ARROW_W : 0); int32_t headerRight = scroll ? (w->x + w->w - TAB_ARROW_W) : (w->x + w->w); int32_t oldClipX = d->clipX; int32_t oldClipY = d->clipY; int32_t oldClipW = d->clipW; int32_t oldClipH = d->clipH; - setClipRect(d, headerLeft, w->y, headerRight - headerLeft, tabH + 2); + setClipRect(d, headerLeft, w->y, headerRight - headerLeft, tabH + TAB_ACTIVE_LIFT); // Clear the header strip so scrolled-away tab pixels are erased. // Only clear above the content panel border (tabH, not tabH+2). @@ -520,33 +527,33 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B TabPageDataT *pd = (TabPageDataT *)c->data; int32_t tw = textWidthAccel(font, pd->title) + TAB_PAD_H * 2; bool isActive = (tabIdx == td->activeTab); - int32_t ty = isActive ? w->y : w->y + 2; - int32_t th = isActive ? tabH + 2 : tabH; + int32_t ty = isActive ? w->y : w->y + TAB_ACTIVE_LIFT; + int32_t th = isActive ? tabH + TAB_ACTIVE_LIFT : tabH; uint32_t tabFace = isActive ? colors->contentBg : colors->windowFace; // Only draw tabs that are at least partially visible if (tabX + tw > headerLeft && tabX < headerRight) { // Fill tab background - rectFill(d, ops, tabX + 2, ty + 2, tw - 4, th - 2, tabFace); + rectFill(d, ops, tabX + TAB_EDGE_INSET, ty + TAB_EDGE_INSET, tw - 2 * TAB_EDGE_INSET, th - TAB_EDGE_INSET, tabFace); // Top edge - drawHLine(d, ops, tabX + 2, ty, tw - 4, colors->windowHighlight); - drawHLine(d, ops, tabX + 2, ty + 1, tw - 4, colors->windowHighlight); + drawHLine(d, ops, tabX + TAB_EDGE_INSET, ty, tw - 2 * TAB_EDGE_INSET, colors->windowHighlight); + drawHLine(d, ops, tabX + TAB_EDGE_INSET, ty + 1, tw - 2 * TAB_EDGE_INSET, colors->windowHighlight); // Left edge - drawVLine(d, ops, tabX, ty + 2, th - 2, colors->windowHighlight); - drawVLine(d, ops, tabX + 1, ty + 2, th - 2, colors->windowHighlight); + drawVLine(d, ops, tabX, ty + TAB_EDGE_INSET, th - TAB_EDGE_INSET, colors->windowHighlight); + drawVLine(d, ops, tabX + 1, ty + TAB_EDGE_INSET, th - TAB_EDGE_INSET, colors->windowHighlight); // Right edge - drawVLine(d, ops, tabX + tw - 1, ty + 2, th - 2, colors->windowShadow); - drawVLine(d, ops, tabX + tw - 2, ty + 2, th - 2, colors->windowShadow); + drawVLine(d, ops, tabX + tw - 1, ty + TAB_EDGE_INSET, th - TAB_EDGE_INSET, colors->windowShadow); + drawVLine(d, ops, tabX + tw - 2, ty + TAB_EDGE_INSET, th - TAB_EDGE_INSET, colors->windowShadow); if (isActive) { // Erase panel top border under active tab - rectFill(d, ops, tabX + 2, w->y + tabH, tw - 4, 2, colors->contentBg); + rectFill(d, ops, tabX + TAB_EDGE_INSET, w->y + tabH, tw - 2 * TAB_EDGE_INSET, TAB_EDGE_INSET, colors->contentBg); } else { // Bottom edge for inactive tab - drawHLine(d, ops, tabX, ty + th - 1, tw, colors->windowShadow); + drawHLine(d, ops, tabX, ty + th - 1, tw, colors->windowShadow); drawHLine(d, ops, tabX + 1, ty + th - 2, tw - 2, colors->windowShadow); } @@ -560,7 +567,7 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B drawTextAccel(d, ops, font, tabX + TAB_PAD_H, labelY, pd->title, colors->contentFg, tabFace, true); if (isActive && w == sFocusedWidget) { - drawFocusRect(d, ops, tabX + 3, ty + 3, tw - 6, th - 4, colors->contentFg); + drawFocusRect(d, ops, tabX + TAB_FOCUS_INSET, ty + TAB_FOCUS_INSET, tw - 2 * TAB_FOCUS_INSET, th - TAB_FOCUS_INSET - 1, colors->contentFg); } } diff --git a/src/widgets/kpunch/textInput/textinpt.bhs b/src/widgets/kpunch/textInput/textinpt.bhs index 1494c6d..c887b03 100644 --- a/src/widgets/kpunch/textInput/textinpt.bhs +++ b/src/widgets/kpunch/textInput/textinpt.bhs @@ -73,7 +73,7 @@ End Sub .h1 TextArea -VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, max 4096 chars) +VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, default max 65536 chars) A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports line numbers, auto-indent, find/replace, and tab configuration. diff --git a/src/widgets/kpunch/textInput/textinpt.dhs b/src/widgets/kpunch/textInput/textinpt.dhs index 275535c..f9cc772 100644 --- a/src/widgets/kpunch/textInput/textinpt.dhs +++ b/src/widgets/kpunch/textInput/textinpt.dhs @@ -36,7 +36,7 @@ Single-line text input, password input, masked input, and multi-line text area with optional syntax colorization, line numbers, find/replace, and gutter decorators. -Header: widgets/widgetTextInput.h +Header: widgets/textInpt.h .h3 Creation @@ -49,7 +49,7 @@ Header: widgets/widgetTextInput.h wgtTextArea(parent, maxLen) Create a multi-line text area. .endtable -.h3 Methods (TextArea-specific) +.h3 API Functions (TextArea-specific) .index wgtTextAreaSetColorize .index wgtTextAreaGoToLine @@ -59,20 +59,53 @@ Header: widgets/widgetTextInput.h .index wgtTextAreaReplaceAll .table - Macro Description - ----- ----------- - wgtTextAreaSetColorize(w, fn, ctx) Set a syntax colorization callback. The callback receives each line and fills a color index array. Color indices: 0=default, 1=keyword, 2=string, 3=comment, 4=number, 5=operator, 6=type/builtin, 7=reserved. - wgtTextAreaGoToLine(w, line) Scroll to and place the cursor on the given line number. - wgtTextAreaSetAutoIndent(w, enable) Enable or disable automatic indentation on newline. - wgtTextAreaSetShowLineNumbers(w, show) Show or hide line numbers in the gutter. - wgtTextAreaSetCaptureTabs(w, capture) When true, Tab key inserts a tab/spaces instead of moving focus. - wgtTextAreaSetTabWidth(w, width) Set the tab stop width in characters. - wgtTextAreaSetUseTabChar(w, useChar) When true, insert literal tab characters; when false, insert spaces. - wgtTextAreaFindNext(w, needle, caseSens, fwd) Search for the next occurrence. Returns true if found. - wgtTextAreaReplaceAll(w, needle, repl, caseSens) Replace all occurrences. Returns the number of replacements made. - wgtTextAreaSetLineDecorator(w, fn, ctx) Set a gutter line decorator callback. The callback returns a color and receives the line number, a color output pointer, and the user context. - wgtTextAreaGetCursorLine(w) Get the current cursor line number. - wgtTextAreaSetGutterClick(w, fn) Set a callback for gutter clicks (e.g. for breakpoint toggling). Callback receives the widget and line number. + Function Description + -------- ----------- + void wgtTextAreaSetColorize(w, fn, ctx) Set a syntax colorization callback. The callback receives each line and fills a color index array. + void wgtTextAreaGoToLine(w, line) Scroll to and place the cursor on the given line number. + void wgtTextAreaSetAutoIndent(w, enable) Enable or disable automatic indentation on newline. + void wgtTextAreaSetShowLineNumbers(w, show) Show or hide line numbers in the gutter. + void wgtTextAreaSetCaptureTabs(w, capture) When true, Tab key inserts a tab/spaces instead of moving focus. + void wgtTextAreaSetTabWidth(w, width) Set the tab stop width in characters. + void wgtTextAreaSetUseTabChar(w, useChar) When true, insert literal tab characters; when false, insert spaces. + bool wgtTextAreaFindNext(w, needle, caseSens, fwd) Search for the next occurrence. Returns true if found. + int32_t wgtTextAreaReplaceAll(w, needle, repl, caseSens) Replace all occurrences. Returns the number of replacements made. + void wgtTextAreaSetLineDecorator(w, fn, ctx) Set a gutter line decorator callback. Returns a color and receives the line number. + int32_t wgtTextAreaGetCursorLine(w) Get the current cursor line number. + void wgtTextAreaSetGutterClickCallback(w, fn) Set a callback for gutter clicks (e.g. for breakpoint toggling). + int32_t wgtTextAreaGetWordAtCursor(w, buf, bufSize) Copy the word under the cursor into buf. Returns its length. + void wgtTextAreaSetSyntaxColors(w, colors, count) Set the color palette used by the colorizer. +.endtable + +.h3 API Struct (wgtRegisterApi "textinput") + +The combined "textinput" API exposes all four constructors plus the TextArea functions. The designer uses per-type APIs registered under the names "textbox" (single-line) and "textarea" (multi-line), each exposing only a create slot. + +.h3 Properties (BASIC Interface) + +TextBox has no widget-specific properties. Text is managed via the generic "Text" property. TextArea exposes: + +.table + Property Type Access Description + -------- ---- ------ ----------- + CursorLine Integer Read-only Current cursor line number (0-based). +.endtable + +.h3 Methods (BASIC Interface, TextArea) + +.table + Method Description + ------ ----------- + FindNext needle$, caseSensitive, forward Search for the next occurrence. Returns True if found. + GetWordAtCursor() Returns the word under the cursor. + GoToLine line% Scroll to and position the cursor on a line. + ReplaceAll needle$, replacement$, caseSensitive Replace all occurrences. Returns the count. + SetAutoIndent enabled Enable or disable automatic indentation on newline. + SetCaptureTabs enabled When True, Tab inserts whitespace instead of moving focus. + SetShowLineNumbers show Show or hide line numbers in the gutter. + SetSyntaxMode mode$ Activate built-in syntax highlighting (e.g. "dhs", "bas"; "" to disable). + SetTabWidth width% Set the tab stop width in characters. + SetUseTabChar useChar When True, Tab inserts a literal tab; when False, spaces. .endtable .h3 Events @@ -85,4 +118,6 @@ Header: widgets/widgetTextInput.h onValidate Called before committing a change. Return false to cancel. .endtable -.hr +.h3 Default Event + +"Change" for both TextBox (basName: TextBox) and TextArea (basName: TextArea). diff --git a/src/widgets/kpunch/textInput/widgetTextInput.c b/src/widgets/kpunch/textInput/widgetTextInput.c index e96222d..4d1b7b0 100644 --- a/src/widgets/kpunch/textInput/widgetTextInput.c +++ b/src/widgets/kpunch/textInput/widgetTextInput.c @@ -191,6 +191,11 @@ typedef struct { #define TEXTAREA_SB_W 14 #define TEXTAREA_MIN_ROWS 4 #define TEXTAREA_MIN_COLS 20 + +// Slack added to lineOffsets/lineVisLens capacity when growing. Bigger +// values = fewer reallocs, smaller = less wasted space. 256 empirically +// keeps realloc traffic low on edits to medium-sized files. +#define TEXTAREA_LINE_CAP_GROWTH 256 #define MAX_COLORIZE_LEN 1024 // Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c) #define CURSOR_BLINK_MS 250 @@ -599,7 +604,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+C -- copy formatted text - if (key == 3) { + if (key == KEY_CTRL_C) { if (ti->selStart >= 0 && ti->selEnd >= 0 && ti->selStart != ti->selEnd) { int32_t selLo = ti->selStart < ti->selEnd ? ti->selStart : ti->selEnd; int32_t selHi = ti->selStart < ti->selEnd ? ti->selEnd : ti->selStart; @@ -621,7 +626,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+V -- paste valid chars into slots - if (key == 22) { + if (key == KEY_CTRL_V) { int32_t clipLen; const char *clip = clipboardGet(&clipLen); @@ -666,7 +671,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+X -- copy and clear selected slots - if (key == 24) { + if (key == KEY_CTRL_X) { if (ti->selStart >= 0 && ti->selEnd >= 0 && ti->selStart != ti->selEnd) { int32_t selLo = ti->selStart < ti->selEnd ? ti->selStart : ti->selEnd; int32_t selHi = ti->selStart < ti->selEnd ? ti->selEnd : ti->selStart; @@ -704,7 +709,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+Z -- undo - if (key == 26 && ti->undoBuf) { + if (key == KEY_CTRL_Z && ti->undoBuf) { char tmpBuf[256]; int32_t tmpLen = maskLen + 1 < (int32_t)sizeof(tmpBuf) ? maskLen + 1 : (int32_t)sizeof(tmpBuf); int32_t tmpCursor = *pCur; @@ -726,7 +731,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { goto done; } - if (key >= 32 && key < 127) { + if (key >= KEY_ASCII_PRINT_FIRST && key <= KEY_ASCII_PRINT_LAST) { // Printable character -- place at current slot if valid if (*pCur < maskLen && maskIsSlot(mask[*pCur]) && maskCharValid(mask[*pCur], (char)key)) { if (ti->undoBuf) { @@ -762,7 +767,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { w->onChange(w); } } - } else if (key == (0x53 | 0x100)) { + } else if (key == KEY_DELETE) { // Delete -- clear current slot if (*pCur < maskLen && maskIsSlot(mask[*pCur])) { if (ti->undoBuf) { @@ -778,7 +783,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { w->onChange(w); } } - } else if (key == (0x4B | 0x100)) { + } else if (key == KEY_LEFT) { // Left arrow -- move to previous slot int32_t prev = maskPrevSlot(mask, *pCur); @@ -795,7 +800,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { ti->selEnd = -1; *pCur = prev; } - } else if (key == (0x4D | 0x100)) { + } else if (key == KEY_RIGHT) { // Right arrow -- move to next slot int32_t next = maskNextSlot(mask, *pCur); @@ -812,7 +817,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { ti->selEnd = -1; *pCur = next; } - } else if (key == (0x47 | 0x100)) { + } else if (key == KEY_HOME) { // Home -- first slot if (shift) { if (ti->selStart < 0) { @@ -827,7 +832,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { ti->selEnd = -1; *pCur = maskFirstSlot(mask); } - } else if (key == (0x4F | 0x100)) { + } else if (key == KEY_END) { // End -- past last slot int32_t last = maskLen; @@ -1320,14 +1325,14 @@ static void textAreaRebuildCache(WidgetT *w) { int32_t needed = lineCount + 1; if (needed > ta->lineOffsetCap) { - int32_t newCap = needed + 256; + int32_t newCap = needed + TEXTAREA_LINE_CAP_GROWTH; ta->lineOffsets = (int32_t *)realloc(ta->lineOffsets, newCap * sizeof(int32_t)); ta->lineOffsetCap = newCap; } // Grow visual length array if needed if (lineCount > ta->lineVisLenCap) { - int32_t newCap = lineCount + 256; + int32_t newCap = lineCount + TEXTAREA_LINE_CAP_GROWTH; ta->lineVisLens = (int32_t *)realloc(ta->lineVisLens, newCap * sizeof(int32_t)); ta->lineVisLenCap = newCap; } @@ -1506,7 +1511,7 @@ WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen) { ta->expandBuf = (char *)malloc(MAX_COLORIZE_LEN); ta->syntaxBuf = (uint8_t *)malloc(MAX_COLORIZE_LEN); - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -1903,7 +1908,7 @@ WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen) { ti->undoBuf = (char *)malloc(bufSize); ti->selStart = -1; ti->selEnd = -1; - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -2112,7 +2117,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+C -- copy - if (key == 3) { + if (key == KEY_CTRL_C) { if (HAS_SEL()) { clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO()); } @@ -2126,7 +2131,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+V -- paste - if (key == 22) { + if (key == KEY_CTRL_V) { int32_t clipLen = 0; const char *clip = clipboardGet(&clipLen); @@ -2168,7 +2173,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+X -- cut - if (key == 24) { + if (key == KEY_CTRL_X) { if (HAS_SEL()) { clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO()); textEditSaveUndo(buf, *pLen, CUR_OFF(), ta->undoBuf, &ta->undoLen, &ta->undoCursor, bufSize); @@ -2195,7 +2200,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Ctrl+Z -- undo - if (key == 26) { + if (key == KEY_CTRL_Z) { if (ta->undoBuf && ta->undoLen >= 0) { // Swap current and undo char tmpBuf[*pLen + 1]; @@ -2336,7 +2341,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Delete - if (key == (0x53 | 0x100)) { + if (key == KEY_DELETE) { if (HAS_SEL()) { textEditSaveUndo(buf, *pLen, CUR_OFF(), ta->undoBuf, &ta->undoLen, &ta->undoCursor, bufSize); int32_t lo = SEL_LO(); @@ -2380,7 +2385,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { navigation: // Left arrow - if (key == (0x4B | 0x100)) { + if (key == KEY_LEFT) { SEL_BEGIN(); int32_t off = CUR_OFF(); @@ -2396,7 +2401,7 @@ navigation: } // Right arrow - if (key == (0x4D | 0x100)) { + if (key == KEY_RIGHT) { SEL_BEGIN(); int32_t off = CUR_OFF(); @@ -2438,7 +2443,7 @@ navigation: } // Up arrow - if (key == (0x48 | 0x100)) { + if (key == KEY_UP) { SEL_BEGIN(); if (*pRow > 0) { @@ -2454,7 +2459,7 @@ navigation: } // Down arrow - if (key == (0x50 | 0x100)) { + if (key == KEY_DOWN) { SEL_BEGIN(); if (*pRow < totalLines - 1) { @@ -2470,7 +2475,7 @@ navigation: } // Home - if (key == (0x47 | 0x100)) { + if (key == KEY_HOME) { SEL_BEGIN(); *pCol = 0; ta->desiredCol = 0; @@ -2481,7 +2486,7 @@ navigation: } // End - if (key == (0x4F | 0x100)) { + if (key == KEY_END) { SEL_BEGIN(); *pCol = textAreaLineLen(buf, *pLen, *pRow); ta->desiredCol = *pCol; @@ -2492,7 +2497,7 @@ navigation: } // Page Up - if (key == (0x49 | 0x100)) { + if (key == KEY_PGUP) { SEL_BEGIN(); *pRow -= visRows; @@ -2509,7 +2514,7 @@ navigation: } // Page Down - if (key == (0x51 | 0x100)) { + if (key == KEY_PGDN) { SEL_BEGIN(); *pRow += visRows; @@ -2599,7 +2604,7 @@ navigation: } // Printable character (blocked in read-only mode) - if (key >= 32 && key < 127 && !w->readOnly) { + if (key >= KEY_ASCII_PRINT_FIRST && key <= KEY_ASCII_PRINT_LAST && !w->readOnly) { if (*pLen < bufSize - 1) { textEditSaveUndo(buf, *pLen, CUR_OFF(), ta->undoBuf, &ta->undoLen, &ta->undoCursor, bufSize); @@ -3422,7 +3427,7 @@ void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod) { // Password mode: block copy (Ctrl+C) and cut (Ctrl+X) if (ti->inputMode == InputPasswordE) { - if (key == 3 || key == 24) { + if (key == KEY_CTRL_C || key == KEY_CTRL_X) { return; } } diff --git a/src/widgets/kpunch/timer/timer.bhs b/src/widgets/kpunch/timer/timer.bhs index dcef31d..f095215 100644 --- a/src/widgets/kpunch/timer/timer.bhs +++ b/src/widgets/kpunch/timer/timer.bhs @@ -40,7 +40,7 @@ A non-visual control that fires its event at a regular interval. The Timer widge Property Type Description -------- ------- ------------------------------------------- Enabled Boolean True to start the timer, False to stop it. - Interval Integer Timer interval in milliseconds (write-only from BASIC). + Interval Integer Timer interval in milliseconds. .endtable .h2 Type-Specific Methods diff --git a/src/widgets/kpunch/timer/timer.dhs b/src/widgets/kpunch/timer/timer.dhs index 6858778..7f68ed0 100644 --- a/src/widgets/kpunch/timer/timer.dhs +++ b/src/widgets/kpunch/timer/timer.dhs @@ -37,7 +37,7 @@ A non-visual widget that fires its onClick callback at a regular interval. Useful for animations, polling, and periodic updates. Must be explicitly started after creation. -Header: widgets/widgetTimer.h +Header: widgets/timer.h .h3 Creation @@ -47,17 +47,32 @@ tmr->onClick = onTimerTick; wgtTimerStart(tmr); .endcode -.h3 Macros +.h3 API Functions .table - Macro Description - ----- ----------- - wgtTimer(parent, intervalMs, repeat) Create a timer. intervalMs is the interval in milliseconds. repeat: true for repeating, false for one-shot. - wgtTimerStart(w) Start the timer. - wgtTimerStop(w) Stop the timer. - wgtTimerSetInterval(w, intervalMs) Change the timer interval. - wgtTimerIsRunning(w) Returns true if the timer is currently running. - wgtUpdateTimers() Global tick function. Called by the event loop to advance all active timers. + Function Description + -------- ----------- + WidgetT *wgtTimer(parent, intervalMs, repeat) Create a timer. intervalMs is the interval in milliseconds. repeat: true for repeating, false for one-shot. + void wgtTimerStart(w) Start the timer. + void wgtTimerStop(w) Stop the timer. + void wgtTimerSetInterval(w, intervalMs) Change the timer interval. + int32_t wgtTimerGetInterval(w) Returns the current interval in milliseconds. + bool wgtTimerIsRunning(w) Returns true if the timer is currently running. + void wgtTimerSetEnabled(w, enabled) Start or stop the timer based on enabled flag. + void wgtUpdateTimers(void) Global tick function. Called by the event loop to advance all active timers. +.endtable + +.h3 API Struct (wgtRegisterApi "timer") + +.table + Slot Function + ---- -------- + create wgtTimer + start wgtTimerStart + stop wgtTimerStop + setInterval wgtTimerSetInterval + isRunning wgtTimerIsRunning + updateTimers wgtUpdateTimers .endtable .h3 Events @@ -65,7 +80,7 @@ wgtTimerStart(tmr); .table Callback Description -------- ----------- - onClick Fires each time the timer elapses. + onClick Fires each time the timer elapses (the BASIC layer surfaces this as the "Timer" event). .endtable .h3 Properties (BASIC Interface) @@ -74,7 +89,7 @@ wgtTimerStart(tmr); Property Type Access Description -------- ---- ------ ----------- Enabled Boolean Read/Write Whether the timer is running. Reading returns the running state; writing starts or stops it. - Interval Integer Write-only Timer interval in milliseconds. + Interval Integer Read/Write Timer interval in milliseconds. .endtable .h3 Methods (BASIC Interface) diff --git a/src/widgets/kpunch/toolbar/toolbar.dhs b/src/widgets/kpunch/toolbar/toolbar.dhs index c7f884b..e53d274 100644 --- a/src/widgets/kpunch/toolbar/toolbar.dhs +++ b/src/widgets/kpunch/toolbar/toolbar.dhs @@ -32,7 +32,7 @@ A horizontal container for toolbar buttons and controls. Typically placed at the top of a window. Children (usually ImageButtons or Buttons) are laid out horizontally with toolbar-appropriate spacing. -Header: widgets/widgetToolbar.h +Header: widgets/toolbar.h .h3 Creation @@ -42,20 +42,26 @@ wgtImageButtonFromFile(tb, "icons/save.bmp"); wgtImageButtonFromFile(tb, "icons/open.bmp"); .endcode -.h3 Macro +.h3 API Functions .table - Macro Description - ----- ----------- - wgtToolbar(parent) Create a toolbar container. + Function Description + -------- ----------- + WidgetT *wgtToolbar(parent) Create a toolbar container. .endtable -.h3 Properties +.h3 API Struct (wgtRegisterApi "toolbar") -Uses common container properties. Add children (buttons, separators, etc.) to populate the toolbar. +.table + Slot Function + ---- -------- + create wgtToolbar +.endtable -.h3 Events +.h3 Properties, Methods, Events -Toolbar itself has no widget-specific events. Events fire on the child widgets. +Toolbar has no widget-specific properties, methods, or events. Add children (buttons, separators, etc.) to populate. -.hr +.h3 Default Event + +None. (VB basName: Toolbar.) diff --git a/src/widgets/kpunch/treeView/treeView.dhs b/src/widgets/kpunch/treeView/treeView.dhs index e371df2..2be6009 100644 --- a/src/widgets/kpunch/treeView/treeView.dhs +++ b/src/widgets/kpunch/treeView/treeView.dhs @@ -34,7 +34,7 @@ A hierarchical tree control with expandable/collapsible nodes. Supports single and multi-selection and drag-to-reorder. Tree items are added as children of the TreeView or of other tree items to create nested hierarchies. -Header: widgets/widgetTreeView.h +Header: widgets/treeView.h .h3 Creation @@ -44,7 +44,7 @@ WidgetT *root = wgtTreeItem(tv, "Root"); WidgetT *child = wgtTreeItem(root, "Child"); .endcode -.h3 Macros +.h3 API Functions .index wgtTreeViewSetSelected .index wgtTreeViewSetMultiSelect @@ -55,18 +55,57 @@ WidgetT *child = wgtTreeItem(root, "Child"); .index wgtTreeItemSetSelected .table - Macro Description - ----- ----------- - wgtTreeView(parent) Create a tree view control. - wgtTreeItem(parent, text) Add a tree item as a child of the tree view or another tree item. - wgtTreeViewGetSelected(w) Get the currently selected tree item (returns WidgetT *, NULL if none). - wgtTreeViewSetSelected(w, item) Set the selected tree item. - wgtTreeViewSetMultiSelect(w, multi) Enable or disable multi-selection. - wgtTreeViewSetReorderable(w, reorderable) Enable drag-to-reorder of items. - wgtTreeItemSetExpanded(w, expanded) Expand or collapse a tree item. - wgtTreeItemIsExpanded(w) Check if a tree item is expanded. - wgtTreeItemIsSelected(w) Check if a tree item is selected (multi-select mode). - wgtTreeItemSetSelected(w, selected) Select or deselect a tree item. + Function Description + -------- ----------- + WidgetT *wgtTreeView(parent) Create a tree view control. + WidgetT *wgtTreeItem(parent, text) Add a tree item as a child of the tree view or another tree item. + WidgetT *wgtTreeViewGetSelected(w) Get the currently selected tree item (NULL if none). + void wgtTreeViewSetSelected(w, item) Set the selected tree item. + void wgtTreeViewSetMultiSelect(w, multi) Enable or disable multi-selection. + void wgtTreeViewSetReorderable(w, reorderable) Enable drag-to-reorder of items. + void wgtTreeItemSetExpanded(w, expanded) Expand or collapse a tree item. + bool wgtTreeItemIsExpanded(w) Check if a tree item is expanded. + bool wgtTreeItemIsSelected(w) Check if a tree item is selected (multi-select mode). + void wgtTreeItemSetSelected(w, selected) Select or deselect a tree item. +.endtable + +.h3 API Struct (wgtRegisterApi "treeview") + +.table + Slot Function + ---- -------- + create wgtTreeView + getSelected wgtTreeViewGetSelected + setSelected wgtTreeViewSetSelected + setMultiSelect wgtTreeViewSetMultiSelect + setReorderable wgtTreeViewSetReorderable + item wgtTreeItem + itemSetExpanded wgtTreeItemSetExpanded + itemIsExpanded wgtTreeItemIsExpanded + itemIsSelected wgtTreeItemIsSelected + itemSetSelected wgtTreeItemSetSelected +.endtable + +.h3 Properties + +No widget-specific properties. + +.h3 Methods (BASIC Interface) + +.table + Method Description + ------ ----------- + AddChildItem parentIdx%, text$ Add a child node under the node at the given index. + AddItem text$ Add a root-level node. + Clear Remove all nodes. + GetItemText(index%) Returns the text of the node at the given depth-first index. + IsExpanded(index%) Returns True if the node is expanded. + IsItemSelected(index%) Returns True if the node is selected. + ItemCount() Returns the total number of nodes. + SetExpanded index%, expanded Expand or collapse the node. + SetItemSelected index%, selected Select or deselect the node. + SetMultiSelect multi Enable or disable multi-selection. + SetReorderable reorderable Enable or disable drag-to-reorder. .endtable .h3 Events @@ -79,13 +118,6 @@ WidgetT *child = wgtTreeItem(root, "Child"); onChange Fires when the selection changes. .endtable -.h3 Methods (BASIC Interface) +.h3 Default Event -.table - Method Description - ------ ----------- - SetMultiSelect Enable or disable multi-selection. - SetReorderable Enable or disable drag-to-reorder. -.endtable - -.hr +"Click" (VB basName: TreeView). diff --git a/src/widgets/kpunch/treeView/widgetTreeView.c b/src/widgets/kpunch/treeView/widgetTreeView.c index 203d955..cca59df 100644 --- a/src/widgets/kpunch/treeView/widgetTreeView.c +++ b/src/widgets/kpunch/treeView/widgetTreeView.c @@ -887,7 +887,7 @@ WidgetT *wgtTreeView(WidgetT *parent) { w->data = tv; } - w->weight = 100; + w->weight = WGT_WEIGHT_FILL; } return w; @@ -1087,7 +1087,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { bool shift = (mod & KEY_MOD_SHIFT) != 0; WidgetT *sel = tv->selectedItem; - if (key == (0x50 | 0x100)) { + if (key == KEY_DOWN) { // Down arrow -- next visible item if (!sel) { setSelectedItem(w, firstVisibleItem(w)); @@ -1098,7 +1098,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { setSelectedItem(w, next); } } - } else if (key == (0x48 | 0x100)) { + } else if (key == KEY_UP) { // Up arrow -- previous visible item if (!sel) { setSelectedItem(w, firstVisibleItem(w)); @@ -1109,7 +1109,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { setSelectedItem(w, prev); } } - } else if (key == (0x4D | 0x100)) { + } else if (key == KEY_RIGHT) { // Right arrow -- expand if collapsed, else move to first child if (sel) { TreeItemDataT *selTi = (TreeItemDataT *)sel->data; @@ -1139,7 +1139,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) { } } } - } else if (key == (0x4B | 0x100)) { + } else if (key == KEY_LEFT) { // Left arrow -- collapse if expanded, else move to parent if (sel) { TreeItemDataT *selTi = (TreeItemDataT *)sel->data; diff --git a/src/widgets/kpunch/wgtsys.dhs b/src/widgets/kpunch/wgtsys.dhs index 8b7153f..4d9d92a 100644 --- a/src/widgets/kpunch/wgtsys.dhs +++ b/src/widgets/kpunch/wgtsys.dhs @@ -32,13 +32,13 @@ .h1 DVX Widget System -Complete reference for the DVX GUI widget toolkit. All widgets are implemented as dynamically loaded DXE modules. They are created via convenience macros that wrap the per-widget API function tables. The base WidgetT structure is defined in core/dvxWidget.h; individual widget headers live in widgets/. +Complete reference for the DVX GUI widget toolkit. All widgets are implemented as dynamically loaded DXE modules. They are created via convenience macros that wrap the per-widget API function tables. The base WidgetT structure is defined in libdvx/dvxWgt.h; individual widget headers live in widgets/. Individual widgets are documented in their own sections. See the table of contents for the full list. .h2 Base WidgetT (Common Properties, Events, and Operations) -Every widget inherits from the WidgetT structure defined in core/dvxWidget.h. The fields and callbacks listed here are available on all widget types. +Every widget inherits from the WidgetT structure defined in libdvx/dvxWgt.h. The fields and callbacks listed here are available on all widget types. .h3 Common Properties diff --git a/src/widgets/kpunch/wrapBox/wrapBox.dhs b/src/widgets/kpunch/wrapBox/wrapBox.dhs index 359ee10..d44a6c7 100644 --- a/src/widgets/kpunch/wrapBox/wrapBox.dhs +++ b/src/widgets/kpunch/wrapBox/wrapBox.dhs @@ -31,7 +31,7 @@ A flow/wrap layout container that arranges children left-to-right, wrapping to the next row when the available width is exceeded. Each row's height is the maximum child height in that row. Supports configurable spacing between items and rows, and per-row alignment for short rows. -Header: widgets/widgetWrapBox.h +Header: widgets/wrapBox.h .h3 Creation @@ -42,15 +42,23 @@ wgtButton(wrap, "Tag 2"); wgtButton(wrap, "Tag 3"); .endcode -.h3 Macro +.h3 API Functions .table - Macro Description - ----- ----------- - wgtWrapBox(parent) Create a wrap box container. + Function Description + -------- ----------- + wgtWrapBox(parent) Create a wrap box container. Macro that calls the registered "wrapbox" create slot. .endtable -.h3 Properties +.h3 API Struct (wgtRegisterApi "wrapbox") + +.table + Slot Function + ---- -------- + create wrapBoxCreate +.endtable + +.h3 Properties (Common Container Fields) WrapBox uses the common WidgetT container fields for layout control: @@ -69,6 +77,14 @@ WrapBox uses the common WidgetT container fields for layout control: Alignment Enum (Left, Center, Right) Read/Write Row alignment for rows that do not fill the full width. .endtable +.h3 Methods + +No widget-specific methods. + .h3 Events WrapBox is a container widget. It uses the common events only. No widget-specific events are defined. + +.h3 Default Event + +"Click" (VB basName: WrapBox, namePrefix: WrapBox). diff --git a/src/widgets/kpunch/wrapBox/wrapbox.bhs b/src/widgets/kpunch/wrapBox/wrapbox.bhs index 131248c..3b25f5b 100644 --- a/src/widgets/kpunch/wrapBox/wrapbox.bhs +++ b/src/widgets/kpunch/wrapBox/wrapbox.bhs @@ -28,7 +28,7 @@ .h1 WrapBox -DVX Extension -- DVX Widget: wrapbox +DVX Extension -- DVX Widget: wrapbox | Name Prefix: WrapBox A container that arranges children in a flowing layout, wrapping to the next row when the available width is exceeded. Similar to CSS flexbox with flex-wrap: wrap.