From: Svjatoslav Agejenko Date: Thu, 27 Aug 2026 22:19:07 +0000 (+0300) Subject: refactor(mouse-driver): slim qbext TSR, ungate programs, refresh media X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=4f65e286c54d8428d9020a00a02d57baf6e7034d;p=qbasicapps.git refactor(mouse-driver): slim qbext TSR, ungate programs, refresh media - qbext: 506 to 234 bytes — drop the unused update counter, inline the startup banner into the source, add build.sh; index.org rewritten to document the minimal 8-byte shared-memory protocol - Galaxy/Universe/Maze explorer and Swapping 3D engine gated all mouse reads on the removed counter byte (getByte(8)) and silently froze with the new driver — drop the gate, read-and-reset every frame - recapture showcase media as CRT Basic screencasts/screenshots (mp4/png replacing webm/png); rename Windowing system.bas to wsystem.bas --- diff --git a/3D GFX/Miscellaneous/3D text.webm b/3D GFX/Miscellaneous/3D text.webm deleted file mode 100644 index 0846d14..0000000 Binary files a/3D GFX/Miscellaneous/3D text.webm and /dev/null differ diff --git a/3D GFX/Miscellaneous/Maze explorer - Screencast 0.mp4 b/3D GFX/Miscellaneous/Maze explorer - Screencast 0.mp4 new file mode 100644 index 0000000..4d7925c Binary files /dev/null and b/3D GFX/Miscellaneous/Maze explorer - Screencast 0.mp4 differ diff --git a/3D GFX/Miscellaneous/Maze explorer - Screenshot 0.png b/3D GFX/Miscellaneous/Maze explorer - Screenshot 0.png new file mode 100644 index 0000000..63d668b Binary files /dev/null and b/3D GFX/Miscellaneous/Maze explorer - Screenshot 0.png differ diff --git a/3D GFX/Miscellaneous/Maze explorer.bas b/3D GFX/Miscellaneous/Maze explorer.bas index 015d32f..2ebb8fb 100755 --- a/3D GFX/Miscellaneous/Maze explorer.bas +++ b/3D GFX/Miscellaneous/Maze explorer.bas @@ -160,42 +160,39 @@ END SUB SUB handleInput -' Read mouse data -IF getByte(8) <> 0 THEN - putByte 8, 0 - ' Read mouse translation along x axis - mouseXDelta = getWord(2) - putWord 2, 0 - ' Read mouse translation along y axis - mouseYDelta = getWord(4) - putWord 4, 0 - ' Read pressed mouse buttons - button = getWord(6) - putWord 6, 0 - - ' Detect if left, right or both mouse buttons were pressed - leftMouseButton = 0 - rightMouseButton = 0 - IF button = 1 THEN leftMouseButton = 1 - IF button = 2 THEN rightMouseButton = 1 - IF button = 3 THEN leftMouseButton = 1: rightMouseButton = 1 - - IF rightMouseButton = 1 THEN - IF leftMouseButton = 1 THEN - ' If mouse left and right buttons are pressed at the same time, - ' use mouse translation to move avatar around X and Z axis. - cameraXSpeed = cameraXSpeed + SIN(angleY) * mouseYDelta / 4 - cameraZSpeed = cameraZSpeed - COS(angleY) * mouseYDelta / 4 - GOTO 3 - END IF - ' If only right button is pressed, move around Y axis - cameraYSpeed = cameraYSpeed + mouseYDelta / 4 -3 - mouseYDelta = 0 +' Read mouse data from the driver and reset its counters (every frame) +' Read mouse translation along x axis +mouseXDelta = getWord(2) +putWord 2, 0 +' Read mouse translation along y axis +mouseYDelta = getWord(4) +putWord 4, 0 +' Read pressed mouse buttons +button = getWord(6) +putWord 6, 0 + +' Detect if left, right or both mouse buttons were pressed +leftMouseButton = 0 +rightMouseButton = 0 +IF button = 1 THEN leftMouseButton = 1 +IF button = 2 THEN rightMouseButton = 1 +IF button = 3 THEN leftMouseButton = 1: rightMouseButton = 1 + +IF rightMouseButton = 1 THEN + IF leftMouseButton = 1 THEN + ' If mouse left and right buttons are pressed at the same time, + ' use mouse translation to move avatar around X and Z axis. + cameraXSpeed = cameraXSpeed + SIN(angleY) * mouseYDelta / 4 + cameraZSpeed = cameraZSpeed - COS(angleY) * mouseYDelta / 4 + GOTO 3 END IF - + ' If only right button is pressed, move around Y axis + cameraYSpeed = cameraYSpeed + mouseYDelta / 4 +3 + mouseYDelta = 0 END IF + ' Limit mouse movement to maxMove IF mouseXDelta < -maxMove THEN mouseXDelta = -maxMove IF mouseXDelta > maxMove THEN mouseXDelta = maxMove diff --git a/3D GFX/Miscellaneous/Maze explorer.png b/3D GFX/Miscellaneous/Maze explorer.png deleted file mode 100644 index 96348a3..0000000 Binary files a/3D GFX/Miscellaneous/Maze explorer.png and /dev/null differ diff --git a/3D GFX/Space/Galaxy explorer - Screencast 0.mp4 b/3D GFX/Space/Galaxy explorer - Screencast 0.mp4 new file mode 100644 index 0000000..135fd23 Binary files /dev/null and b/3D GFX/Space/Galaxy explorer - Screencast 0.mp4 differ diff --git a/3D GFX/Space/Galaxy explorer - Screenshot 0.png b/3D GFX/Space/Galaxy explorer - Screenshot 0.png new file mode 100644 index 0000000..db337b2 Binary files /dev/null and b/3D GFX/Space/Galaxy explorer - Screenshot 0.png differ diff --git a/3D GFX/Space/Galaxy explorer.bas b/3D GFX/Space/Galaxy explorer.bas index ecd8abd..ab2f8c9 100755 --- a/3D GFX/Space/Galaxy explorer.bas +++ b/3D GFX/Space/Galaxy explorer.bas @@ -98,37 +98,33 @@ GOTO 1 SUB control -' Check for mouse input -IF getByte(8) <> 0 THEN - putByte 8, 0 - xPosition = getWord(2) - putWord 2, 0 - yPosition = getWord(4) - putWord 4, 0 - button = getWord(6) - putWord 6, 0 - - ' Reset mouse buttons - buttonLeft = 0 - buttonRight = 0 - - ' Handle mouse button states - IF button = 1 THEN buttonLeft = 1 - IF button = 2 THEN buttonRight = 1 - IF button = 3 THEN buttonLeft = 1: buttonRight = 1 - - ' Handle mouse movements - IF buttonRight = 1 THEN - IF buttonLeft = 1 THEN - speedX = speedX + SIN(angle1) * yPosition / 4 - speedZ = speedZ - COS(angle1) * yPosition / 4 - GOTO 3 - END IF - speedY = speedY + yPosition / 4 - 3 - yPosition = 0 +' Read mouse input from the driver, resetting its counters every frame +xPosition = getWord(2) +putWord 2, 0 +yPosition = getWord(4) +putWord 4, 0 +button = getWord(6) +putWord 6, 0 + +' Reset mouse buttons +buttonLeft = 0 +buttonRight = 0 + +' Handle mouse button states +IF button = 1 THEN buttonLeft = 1 +IF button = 2 THEN buttonRight = 1 +IF button = 3 THEN buttonLeft = 1: buttonRight = 1 + +' Handle mouse movements +IF buttonRight = 1 THEN + IF buttonLeft = 1 THEN + speedX = speedX + SIN(angle1) * yPosition / 4 + speedZ = speedZ - COS(angle1) * yPosition / 4 + GOTO 3 END IF - + speedY = speedY + yPosition / 4 +3 + yPosition = 0 END IF ' Limit the position values to prevent overflow diff --git a/3D GFX/Space/Galaxy explorer.png b/3D GFX/Space/Galaxy explorer.png deleted file mode 100644 index 28550e6..0000000 Binary files a/3D GFX/Space/Galaxy explorer.png and /dev/null differ diff --git a/3D GFX/Space/Universe explorer/Universe explorer.bas b/3D GFX/Space/Universe explorer/Universe explorer.bas index 37b44ff..634c4d6 100755 --- a/3D GFX/Space/Universe explorer/Universe explorer.bas +++ b/3D GFX/Space/Universe explorer/Universe explorer.bas @@ -143,38 +143,34 @@ GOTO 1 SUB control -' Handle mouse input -IF getbyte(8) <> 0 THEN - putbyte 8, 0 - xp = getword(2) - putword 2, 0 - yp = getword(4) - putword 4, 0 - butt = getword(6) - putword 6, 0 - - ' Determine which mouse buttons are pressed - buttL = 0 - buttR = 0 - IF butt = 1 THEN buttL = 1 - IF butt = 2 THEN buttR = 1 - IF butt = 3 THEN buttL = 1: buttR = 1 - - ' Handle right mouse button for up/down movement - IF buttR = 1 THEN - ' Handle both buttons pressed for back/front movement - IF buttL = 1 THEN - myxs = myxs + SIN(an1) * yp / 4 - myzs = myzs - COS(an1) * yp / 4 - GOTO 3 - END IF - - ' Handle right button for up/down movement - myys = myys + yp / 4 -3 - yp = 0 +' Handle mouse input: read the driver and reset its counters every frame +xp = getword(2) +putword 2, 0 +yp = getword(4) +putword 4, 0 +butt = getword(6) +putword 6, 0 + +' Determine which mouse buttons are pressed +buttL = 0 +buttR = 0 +IF butt = 1 THEN buttL = 1 +IF butt = 2 THEN buttR = 1 +IF butt = 3 THEN buttL = 1: buttR = 1 + +' Handle right mouse button for up/down movement +IF buttR = 1 THEN + ' Handle both buttons pressed for back/front movement + IF buttL = 1 THEN + myxs = myxs + SIN(an1) * yp / 4 + myzs = myzs - COS(an1) * yp / 4 + GOTO 3 END IF + ' Handle right button for up/down movement + myys = myys + yp / 4 +3 + yp = 0 END IF ' Clamp user input to maximum movement speed diff --git a/3D GFX/Swapping 3D engine/engine.bas b/3D GFX/Swapping 3D engine/engine.bas index 52e2132..2af65d6 100755 --- a/3D GFX/Swapping 3D engine/engine.bas +++ b/3D GFX/Swapping 3D engine/engine.bas @@ -274,37 +274,34 @@ END SUB SUB control -IF getbyte(8) <> 0 THEN - putbyte 8, 0 - xp = getword(2) - putword 2, 0 - yp = getword(4) - putword 4, 0 - butt = getword(6) - putword 6, 0 - buttL = 0 - buttR = 0 - IF butt = 1 THEN buttL = 1 - IF butt = 2 THEN buttR = 1 - IF butt = 3 THEN buttL = 1: buttR = 1 - - - IF buttR = 1 THEN - IF buttL = 1 THEN - myxs = myxs + SIN(an1) * yp / 4 - myzs = myzs - COS(an1) * yp / 4 - GOTO 3 - END IF - myys = myys + yp / 4 -3 - yp = 0 +xp = getword(2) +putword 2, 0 +yp = getword(4) +putword 4, 0 +butt = getword(6) +putword 6, 0 +buttL = 0 +buttR = 0 +IF butt = 1 THEN buttL = 1 +IF butt = 2 THEN buttR = 1 +IF butt = 3 THEN buttL = 1: buttR = 1 + + +IF buttR = 1 THEN + IF buttL = 1 THEN + myxs = myxs + SIN(an1) * yp / 4 + myzs = myzs - COS(an1) * yp / 4 + GOTO 3 END IF - + myys = myys + yp / 4 +3 + yp = 0 END IF + IF xp < -maxmove THEN xp = -maxmove IF xp > maxmove THEN xp = maxmove an1 = an1 - xp / 150 diff --git a/Math/Game of life in 3D - Screencast 0.mp4 b/Math/Game of life in 3D - Screencast 0.mp4 new file mode 100644 index 0000000..14b5a73 Binary files /dev/null and b/Math/Game of life in 3D - Screencast 0.mp4 differ diff --git a/Math/Game of life in 3D.bas b/Math/Game of life in 3D.bas index 70652ec..29536a1 100755 --- a/Math/Game of life in 3D.bas +++ b/Math/Game of life in 3D.bas @@ -36,6 +36,8 @@ InitializeGame rotationAngle1 = 1.5 10 + +SOUND 0, 1 currentFrameCount = currentFrameCount + 1 Generate3DScene DrawScene diff --git a/Math/Game of life in 3D.webm b/Math/Game of life in 3D.webm deleted file mode 100644 index 2b7e1ed..0000000 Binary files a/Math/Game of life in 3D.webm and /dev/null differ diff --git a/Math/Multiplication trainer - Screencast 0.mp4 b/Math/Multiplication trainer - Screencast 0.mp4 new file mode 100644 index 0000000..0fef991 Binary files /dev/null and b/Math/Multiplication trainer - Screencast 0.mp4 differ diff --git a/Math/Multiplication trainer - Screenshot 0.png b/Math/Multiplication trainer - Screenshot 0.png new file mode 100644 index 0000000..2dc69ae Binary files /dev/null and b/Math/Multiplication trainer - Screenshot 0.png differ diff --git a/Math/Multiplication trainer.bas b/Math/Multiplication trainer.bas index 1be28bf..a9f2d5a 100644 --- a/Math/Multiplication trainer.bas +++ b/Math/Multiplication trainer.bas @@ -112,10 +112,10 @@ NEXT y ' Get user input for their name LOCATE 5, 1 COLOR 7 -INPUT "Enter your name ", userName$ +INPUT "Enter your name: ", userName$ LOCATE 5, 1 COLOR 8 -PRINT "Enter your name " + userName$ +PRINT "Enter your name: " + userName$ ' Greet the user LOCATE 6, 1 @@ -185,4 +185,3 @@ GOTO 2 3 CLS END SUB - diff --git a/Math/Multiplication trainer.png b/Math/Multiplication trainer.png deleted file mode 100644 index 7ea027a..0000000 Binary files a/Math/Multiplication trainer.png and /dev/null differ diff --git a/Math/Sine computation - Screenshot 0.png b/Math/Sine computation - Screenshot 0.png new file mode 100644 index 0000000..bb6016f Binary files /dev/null and b/Math/Sine computation - Screenshot 0.png differ diff --git a/Math/Sine computation.png b/Math/Sine computation.png deleted file mode 100644 index 86fc66c..0000000 Binary files a/Math/Sine computation.png and /dev/null differ diff --git a/Miscellaneous/Mouse driver/build.sh b/Miscellaneous/Mouse driver/build.sh new file mode 100755 index 0000000..4dc29f5 --- /dev/null +++ b/Miscellaneous/Mouse driver/build.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Assemble qbext.asm into qbext.com using FASM. +# Requires: fasm (Debian package: fasm) +set -euo pipefail + +cd "$(dirname "$0")" + +fasm qbext.asm qbext.com diff --git a/Miscellaneous/Mouse driver/index.org b/Miscellaneous/Mouse driver/index.org index 02011a6..41a9bec 100644 --- a/Miscellaneous/Mouse driver/index.org +++ b/Miscellaneous/Mouse driver/index.org @@ -14,83 +14,122 @@ :CUSTOM_ID: overview :END: -QBasic, a popular programming language in the DOS era, lacks native -mouse support. This limitation can be a hurdle for developers looking -to create interactive applications. To bridge this gap, I developed a -workaround that allows QBasic to use mouse input. +QBasic, a popular programming language of the DOS era, lacks native +mouse support. This limitation is a hurdle for developers looking to +create interactive applications. To bridge this gap, I developed a +workaround that allows QBasic programs to use mouse input. * High-level idea :PROPERTIES: :CUSTOM_ID: high-level-idea :END: -Workaround to access mouse involves a Terminate and Stay Resident -(TSR) program written in x86 assembly. This TSR program must be -started before running QBasic program that depends on mouse. This TSR -program hooks into the system's interrupt mechanism, specifically the -timer interrupt (IRQ 0), allowing it to regularly check for mouse -activity several times per second. - -When this timer interrupt triggers, the TSR reads the latest mouse's -horizontal and vertical movements and button states using mouse -interrupts. This data is then stored in a dedicated memory location — -a data table within the TSR's memory space. The TSR uses interrupt 79h -as a pointer to this data table, making it accessible to other -programs, including the QBasic application. - -While QBasic originally is not able to read mouse, it is able to read -(and write) arbitrary location in system RAM. The QBasic demonstration -program begins by retrieving the address of the TSR mouse data table -from the interrupt vector table using interrupt 79h. By checking a -predefined magic number (1983) in the data table, the program confirms -that the mouse driver is loaded. Once verified, the QBasic program -continuously reads mouse data from this shared memory location, while -TSR keeps updating it with latest mouse state simultaneously. +The workaround is a Terminate and Stay Resident (TSR) program written +in x86 assembly. It must be started before running any QBasic program +that depends on the mouse. The TSR hooks into the system timer +interrupt (IRQ 0), which fires about 18.2 times per second, so it can +poll the mouse at that rate. + +On every timer tick, the TSR reads the mouse motion counters and +button states through the mouse driver interrupt (INT 33h) and +accumulates them into a small data table in its own memory. It then +stores the address of this table in the interrupt vector table slot +of interrupt 79h, making the table discoverable by other programs. +Interrupt 79h is never actually invoked — its vector slot is simply +reused as a well-known storage location for the pointer. This assumes +no other software occupies interrupt 79h. + +While QBasic cannot access the mouse directly, it can read (and +write) arbitrary RAM locations using DEF SEG, PEEK and POKE. The +QBasic program retrieves the data table address from the interrupt +79h vector slot and verifies a predefined magic number (1983) at the +start of the table to confirm the driver is loaded. Once verified, +the QBasic program continuously reads mouse data from this shared +memory while the TSR keeps it updated in the background. * Terminate and Stay Resident module :PROPERTIES: :CUSTOM_ID: terminate-and-stay-resident-module :END: -A DOS TSR program that hooks into the system's interrupt mechanism to -regularly read mouse input and store it in a dedicated memory -location. +A DOS TSR program that hooks the timer interrupt to regularly read +mouse input and store it in a dedicated memory table. Files: -- [[file:qbext.asm][qbext.asm - x86 Assembly source code]] +- [[file:qbext.asm][qbext.asm - x86 assembly source code (FASM)]] - [[file:qbext.com][qbext.com - binary COM executable for DOS]] +- [[file:build.sh][build.sh - build script that assembles qbext.asm into qbext.com]] +** Data table -Here is the detailed technical specification for an in-memory table -used to exchange mouse coordinates between a TSR program and a QBasic -program. +In-memory table used to exchange mouse state between the TSR and a +QBasic program: -| Offset | Size (bytes) | Description | -|--------+--------------+-------------------------| -| 0x00 | 2 | Magic Number (1983) | -| 0x02 | 2 | Horizontal Movement (X) | -| 0x04 | 2 | Vertical Movement (Y) | -| 0x06 | 2 | Button Status | -| 0x08 | 1 | Update counter | +| Offset | Size (bytes) | Description | +|--------+--------------+--------------------------------------| +| 0x00 | 2 | Magic Number (1983) | +| 0x02 | 2 | Horizontal Movement (X), accumulated | +| 0x04 | 2 | Vertical Movement (Y), accumulated | +| 0x06 | 2 | Button Status, latched | -- Update counter :: Signals to the QBasic program that new mouse data - is available in the shared memory table. It ensures that the QBasic - program only reads fresh data and avoids processing outdated or - repeated data. When the TSR updates the mouse data, it increments - this flag by 1 to signal the QBasic program that new data is - available. QBasic can compare this number against last retrieved - value. If value has been increased, then there had been update - meanwhile. +** Transfer protocol + +The TSR /accumulates/: on every timer tick it adds the latest motion +deltas to the X and Y fields and ORs the current button state into the +Button Status field. + +The consumer therefore uses a read-and-reset cycle: it reads a field +and then writes 0 back to it (see the =getword= / =putword= routines +in mousedrv.bas). A field that reads as 0 simply means "no new +activity since the last read". + +Button Status bits (as reported by INT 33h function 03h): +- bit 0 :: left button +- bit 1 :: right button +- bit 2 :: middle button + +Bits stay set (latched) until the consumer clears the field. + +* Building +:PROPERTIES: +:CUSTOM_ID: building +:END: + +The TSR is written for the flat assembler (FASM). To produce +=qbext.com=, run: + +#+begin_src sh +./build.sh +#+end_src + +* Usage +:PROPERTIES: +:CUSTOM_ID: usage +:END: + +1. Boot DOS (or start DOSBox). +2. Load the TSR by running =QBEXT.COM=. A startup banner is printed + and the program stays resident in memory. +3. Start QBasic and run a program that uses the driver, for example + =mousedrv.bas=. + +If the TSR is not loaded, the demo detects the missing magic number +and exits with: + +: FATAL ERROR: you must load +: QBasic extension TSR first! * QBasic demonstration program :PROPERTIES: :CUSTOM_ID: qbasic-demonstration-program :END: -A QBasic program that reads mouse data from the memory location +A QBasic program that reads mouse data from the memory table populated by the TSR and demonstrates mouse movement and button -clicks. - +clicks. It switches to 320x200 256-color graphics mode (SCREEN 13) +and draws a circle that follows the mouse. Current coordinates and a +frame counter are displayed at the top of the screen; button presses +are printed as they occur. Press any key to exit. #+attr_html: :class responsive-img #+attr_latex: :width 1000px diff --git a/Miscellaneous/Mouse driver/qbext.asm b/Miscellaneous/Mouse driver/qbext.asm index 89fb13f..d7b246c 100644 --- a/Miscellaneous/Mouse driver/qbext.asm +++ b/Miscellaneous/Mouse driver/qbext.asm @@ -8,6 +8,7 @@ ; Changelog: ; 2004.01, Initial version ; 2025, Improved program readability +; 2026, Inlined startup message into source, removed unused update counter @@ -74,7 +75,6 @@ custom: mov ax, 3 ; AH = 3, function to read mouse buttons int 33h ; Call mouse interrupt or [CS:mouseButtons], bx ; Update mouse button states - inc byte [CS:updated] ; Increment update flag. So that QBasic registers mouse update event. sti ; Re-enable interrupts ; Restore interrupted program state and return control to it @@ -92,10 +92,10 @@ dataTable: mouseHorisontal dw 0 ; Horizontal mouse movement counter mouseVertical dw 0 ; Vertical mouse movement counter mouseButtons dw 0 ; Mouse button states - updated db 0 ; Flag indicating data was updated endPointer: ; End of resident code pointer msg: ; Message to display at start -file 'readme.txt' ; Include content of readme.txt + db 'Mouse adapter/driver for QBasic programs.', 0Dh, 0Ah + db 'Made by Svjatoslav Agejenko: http://svjatoslav.eu', 0Dh, 0Ah db '$' ; End of string marker diff --git a/Miscellaneous/Mouse driver/qbext.com b/Miscellaneous/Mouse driver/qbext.com index ae54fc4..2a9fc20 100755 Binary files a/Miscellaneous/Mouse driver/qbext.com and b/Miscellaneous/Mouse driver/qbext.com differ diff --git a/Miscellaneous/Windowing system.bas b/Miscellaneous/Windowing system.bas deleted file mode 100755 index ff0d067..0000000 --- a/Miscellaneous/Windowing system.bas +++ /dev/null @@ -1,274 +0,0 @@ -' Text mode windowing system. Each window can display text file. -' Window content can be scrolled horizontally and vertically. -' Window can have arbitrary size and location on the screen. -' -' This program is free software: released under Creative Commons Zero (CC0) license -' by Svjatoslav Agejenko. -' Email: svjatoslav@svjatoslav.eu -' Homepage: http://www.svjatoslav.eu - -' Changelog: -' 2003, Initial version -' 2024-2025, Improved program readability - -DECLARE SUB demo () -DECLARE FUNCTION GetLineFromWindow$ (windowNum%, lineNum%) -DECLARE SUB LoadFileIntoWindow (fileName$, windowNum%) -DECLARE SUB SendLineToWindow (windowNum%, lineNum%, newString$) -DECLARE FUNCTION GetFreeLineIndex% () -DECLARE SUB RefreshAllWindows () -DECLARE FUNCTION AddWindow% (x%, y%, widt%, haigh%, title$) -DECLARE SUB DrawBox (x%, y%, widt%, haigh%, edgeStyle$) -DEFINT A-Z -DECLARE SUB ShowWindow (windowNum) -DECLARE SUB InitializeSystem () - -' Global variables for text storage -DIM SHARED maxStorage% -maxStorage% = 5000 -DIM SHARED textStorage$(1 TO maxStorage%) -DIM SHARED storagePointer% - -' Window-related global arrays -DIM SHARED windowData(1 TO 10, 1 TO 1000) ' Stores line indices for each window -DIM SHARED windowX(1 TO 10), windowY(1 TO 10) ' Position of each window -DIM SHARED windowWidth(1 TO 10), windowHeight(1 TO 10) ' Size of each window -DIM SHARED windowActiveStatus(1 TO 10) ' Whether window is active -DIM SHARED windowTitle$(1 TO 10) ' Title of each window - -' Window scrolling/shifting -DIM SHARED windowShiftX(1 TO 10) ' Horizontal shift -DIM SHARED windowShiftY(1 TO 10) ' Vertical shift - -DIM SHARED currentActiveWindow% ' Currently active window for display - -InitializeSystem - -demo - -FUNCTION AddWindow% (x%, y%, widt%, heigh%, title$) - ' Adds a new window to the system - - ' Find an empty window slot - FOR windowNum% = 1 TO 10 - IF windowActiveStatus(windowNum%) = 0 THEN - foundWindow% = windowNum% - GOTO FoundEmptySlot - END IF - NEXT windowNum% - -FoundEmptySlot: - ' Initialize the window properties - windowActiveStatus(foundWindow%) = 1 - windowX(foundWindow%) = x% - windowY(foundWindow%) = y% - windowWidth(foundWindow%) = widt% - windowHeight(foundWindow%) = heigh% - windowTitle$(foundWindow%) = title$ - - ' Return the window number - AddWindow% = foundWindow% -END FUNCTION - -SUB ClearWindowContent (windowNum%) - ' Clears all content from a window - - FOR lineNum% = 1 TO 1000 - IF windowData(windowNum%, lineNum%) > 0 THEN - textStorage$(windowData(windowNum%, lineNum%)) = "" - windowData(windowNum%, lineNum%) = 0 - END IF - NEXT lineNum% -END SUB - -SUB demo - ' Create three windows with different sizes and titles - window1% = AddWindow%(1, 1, 30, 10, "window 1.") - window2% = AddWindow%(1, 12, 80, 30, "second window") - window3% = AddWindow%(31, 2, 30, 10, "last window") - - ' Load the same file into all windows - LoadFileIntoWindow "wsystem.bas", window2% - LoadFileIntoWindow "wsystem.bas", window1% - LoadFileIntoWindow "wsystem.bas", window3% - -AnimateWindows: - ' Randomly select an active window to animate - currentActiveWindow% = INT(RND * 3) + 1 - RefreshAllWindows - - ' Animate the windows by shifting their content - FOR animationFrame% = 1 TO 100 - windowShiftX(currentActiveWindow%) = SIN(animationFrame% / 10) * 10 + 10 - windowShiftY(currentActiveWindow%) = animationFrame% - ShowWindow currentActiveWindow% - ' split-second delay - SOUND 0, 1 - IF INKEY$ <> "" THEN SYSTEM - NEXT animationFrame% - - GOTO AnimateWindows -END SUB - -FUNCTION GetFreeLineIndex% - ' Finds a free line in text storage - -FindNextLine: - IF storagePointer% > 1000 THEN - storagePointer% = 1 - END IF - - IF textStorage$(storagePointer%) = "" THEN - GetFreeLineIndex% = storagePointer% - storagePointer% = storagePointer% + 1 - ELSE - storagePointer% = storagePointer% + 1 - GOTO FindNextLine - END IF -END FUNCTION - -FUNCTION GetLineFromWindow$ (windowNum%, lineNum%) - ' Retrieve a line from a window's memory - - IF windowData(windowNum%, lineNum%) = 0 THEN - GetLineFromWindow$ = "" - ELSE - GetLineFromWindow$ = textStorage$(windowData(windowNum%, lineNum%)) - END IF -END FUNCTION - -SUB LoadFileIntoWindow (fileName$, windowNum%) - ' Load a file into a window's memory - - OPEN fileName$ FOR INPUT AS #1 - FOR lineNum% = 1 TO 1000 - IF EOF(1) <> 0 THEN - GOTO FileLoaded - END IF - LINE INPUT #1, lineContent$ - SendLineToWindow windowNum%, lineNum%, lineContent$ - NEXT lineNum% - -FileLoaded: - CLOSE #1 - - ' Fill the remaining lines with empty strings - FOR blankLineNum% = lineNum% TO 1000 - SendLineToWindow windowNum%, blankLineNum%, "" - NEXT blankLineNum% -END SUB - -SUB RefreshAllWindows - ' Redraw all active windows - - CLS - FOR windowNum% = 1 TO 10 - IF windowActiveStatus(windowNum%) > 0 THEN - ShowWindow windowNum% - END IF - NEXT windowNum% -END SUB - -SUB SendLineToWindow (windowNum%, lineNum%, newString$) - ' Stores a string in a window's memory - - lineContent$ = newString$ - - ' Remove trailing spaces from the string - IF lineContent$ = SPACE$(LEN(lineContent$)) THEN - lineContent$ = "" - END IF - - IF LEN(lineContent$) > 0 THEN -TrimRight: - IF RIGHT$(lineContent$, 1) = " " THEN - lineContent$ = LEFT$(lineContent$, LEN(lineContent$) - 1) - GOTO TrimRight - END IF - END IF - - ' Update the window memory with the new string - IF lineContent$ = "" THEN - IF windowData(windowNum%, lineNum%) > 0 THEN - textStorage$(windowData(windowNum%, lineNum%)) = "": windowData(windowNum%, lineNum%) = 0 - END IF - ELSE - IF windowData(windowNum%, lineNum%) = 0 THEN - windowData(windowNum%, lineNum%) = GetFreeLineIndex% - END IF - textStorage$(windowData(windowNum%, lineNum%)) = lineContent$ - END IF -END SUB - -SUB ShowWindow (windowNum%) - ' Draws a window on the screen - - ' Determine background color based on active window - IF windowNum% = currentActiveWindow% THEN - bgColor% = 1 - ELSE - bgColor% = 0 - END IF - - x% = windowX(windowNum%) - y% = windowY(windowNum%) - widt% = windowWidth(windowNum%) - heigh% = windowHeight(windowNum%) - title$ = windowTitle$(windowNum%) - - COLOR 11, bgColor% - - ' Create border components - FOR borderSegment% = 1 TO widt% - 2 - topBottom$ = topBottom$ + CHR$(205) - NEXT borderSegment% - borderTop$ = CHR$(201) + topBottom$ + CHR$(187) - borderBottom$ = CHR$(200) + topBottom$ + CHR$(188) - - ' Draw top border - LOCATE y%, x% - PRINT borderTop$ - - ' Draw bottom border - LOCATE y% + heigh% - 1, x% - PRINT borderBottom$ - - ' Draw window content - FOR lineNum% = 1 TO heigh% - 2 - LOCATE y% + lineNum%, x% - lineContent$ = GetLineFromWindow$(windowNum%, lineNum% + windowShiftY(windowNum%)) - lineContent$ = lineContent$ + SPACE$(300) - lineContent$ = RIGHT$(lineContent$, LEN(lineContent$) - windowShiftX(windowNum%)) - lineContent$ = LEFT$(lineContent$, widt% - 2) - PRINT CHR$(186) + lineContent$ + CHR$(186) - NEXT lineNum% - - ' Draw window title - titleX% = INT(x% + (widt% / 2) - (LEN(title$) / 2) - 2) - LOCATE y%, titleX% - PRINT "[ " - titleX% = titleX% + 2 - - COLOR 10 - LOCATE y%, titleX% - PRINT title$ - - titleX% = titleX% + LEN(title$) - COLOR 11 - LOCATE y%, titleX% - PRINT " ]" - COLOR 7, 0 -END SUB - -SUB InitializeSystem - ' Initialize the screen and shared memory - - WIDTH 80, 50 - VIEW PRINT 1 TO 50 - - FOR storageIndex% = 1 TO maxStorage% - textStorage$(storageIndex%) = "" - NEXT storageIndex% - - storagePointer% = 1 -END SUB \ No newline at end of file diff --git a/Miscellaneous/Windowing system.webm b/Miscellaneous/Windowing system.webm deleted file mode 100644 index 1a456bb..0000000 Binary files a/Miscellaneous/Windowing system.webm and /dev/null differ diff --git a/Miscellaneous/wsystem - Screencast 0.mp4 b/Miscellaneous/wsystem - Screencast 0.mp4 new file mode 100644 index 0000000..03fcdca Binary files /dev/null and b/Miscellaneous/wsystem - Screencast 0.mp4 differ diff --git a/Miscellaneous/wsystem.bas b/Miscellaneous/wsystem.bas new file mode 100755 index 0000000..ff0d067 --- /dev/null +++ b/Miscellaneous/wsystem.bas @@ -0,0 +1,274 @@ +' Text mode windowing system. Each window can display text file. +' Window content can be scrolled horizontally and vertically. +' Window can have arbitrary size and location on the screen. +' +' This program is free software: released under Creative Commons Zero (CC0) license +' by Svjatoslav Agejenko. +' Email: svjatoslav@svjatoslav.eu +' Homepage: http://www.svjatoslav.eu + +' Changelog: +' 2003, Initial version +' 2024-2025, Improved program readability + +DECLARE SUB demo () +DECLARE FUNCTION GetLineFromWindow$ (windowNum%, lineNum%) +DECLARE SUB LoadFileIntoWindow (fileName$, windowNum%) +DECLARE SUB SendLineToWindow (windowNum%, lineNum%, newString$) +DECLARE FUNCTION GetFreeLineIndex% () +DECLARE SUB RefreshAllWindows () +DECLARE FUNCTION AddWindow% (x%, y%, widt%, haigh%, title$) +DECLARE SUB DrawBox (x%, y%, widt%, haigh%, edgeStyle$) +DEFINT A-Z +DECLARE SUB ShowWindow (windowNum) +DECLARE SUB InitializeSystem () + +' Global variables for text storage +DIM SHARED maxStorage% +maxStorage% = 5000 +DIM SHARED textStorage$(1 TO maxStorage%) +DIM SHARED storagePointer% + +' Window-related global arrays +DIM SHARED windowData(1 TO 10, 1 TO 1000) ' Stores line indices for each window +DIM SHARED windowX(1 TO 10), windowY(1 TO 10) ' Position of each window +DIM SHARED windowWidth(1 TO 10), windowHeight(1 TO 10) ' Size of each window +DIM SHARED windowActiveStatus(1 TO 10) ' Whether window is active +DIM SHARED windowTitle$(1 TO 10) ' Title of each window + +' Window scrolling/shifting +DIM SHARED windowShiftX(1 TO 10) ' Horizontal shift +DIM SHARED windowShiftY(1 TO 10) ' Vertical shift + +DIM SHARED currentActiveWindow% ' Currently active window for display + +InitializeSystem + +demo + +FUNCTION AddWindow% (x%, y%, widt%, heigh%, title$) + ' Adds a new window to the system + + ' Find an empty window slot + FOR windowNum% = 1 TO 10 + IF windowActiveStatus(windowNum%) = 0 THEN + foundWindow% = windowNum% + GOTO FoundEmptySlot + END IF + NEXT windowNum% + +FoundEmptySlot: + ' Initialize the window properties + windowActiveStatus(foundWindow%) = 1 + windowX(foundWindow%) = x% + windowY(foundWindow%) = y% + windowWidth(foundWindow%) = widt% + windowHeight(foundWindow%) = heigh% + windowTitle$(foundWindow%) = title$ + + ' Return the window number + AddWindow% = foundWindow% +END FUNCTION + +SUB ClearWindowContent (windowNum%) + ' Clears all content from a window + + FOR lineNum% = 1 TO 1000 + IF windowData(windowNum%, lineNum%) > 0 THEN + textStorage$(windowData(windowNum%, lineNum%)) = "" + windowData(windowNum%, lineNum%) = 0 + END IF + NEXT lineNum% +END SUB + +SUB demo + ' Create three windows with different sizes and titles + window1% = AddWindow%(1, 1, 30, 10, "window 1.") + window2% = AddWindow%(1, 12, 80, 30, "second window") + window3% = AddWindow%(31, 2, 30, 10, "last window") + + ' Load the same file into all windows + LoadFileIntoWindow "wsystem.bas", window2% + LoadFileIntoWindow "wsystem.bas", window1% + LoadFileIntoWindow "wsystem.bas", window3% + +AnimateWindows: + ' Randomly select an active window to animate + currentActiveWindow% = INT(RND * 3) + 1 + RefreshAllWindows + + ' Animate the windows by shifting their content + FOR animationFrame% = 1 TO 100 + windowShiftX(currentActiveWindow%) = SIN(animationFrame% / 10) * 10 + 10 + windowShiftY(currentActiveWindow%) = animationFrame% + ShowWindow currentActiveWindow% + ' split-second delay + SOUND 0, 1 + IF INKEY$ <> "" THEN SYSTEM + NEXT animationFrame% + + GOTO AnimateWindows +END SUB + +FUNCTION GetFreeLineIndex% + ' Finds a free line in text storage + +FindNextLine: + IF storagePointer% > 1000 THEN + storagePointer% = 1 + END IF + + IF textStorage$(storagePointer%) = "" THEN + GetFreeLineIndex% = storagePointer% + storagePointer% = storagePointer% + 1 + ELSE + storagePointer% = storagePointer% + 1 + GOTO FindNextLine + END IF +END FUNCTION + +FUNCTION GetLineFromWindow$ (windowNum%, lineNum%) + ' Retrieve a line from a window's memory + + IF windowData(windowNum%, lineNum%) = 0 THEN + GetLineFromWindow$ = "" + ELSE + GetLineFromWindow$ = textStorage$(windowData(windowNum%, lineNum%)) + END IF +END FUNCTION + +SUB LoadFileIntoWindow (fileName$, windowNum%) + ' Load a file into a window's memory + + OPEN fileName$ FOR INPUT AS #1 + FOR lineNum% = 1 TO 1000 + IF EOF(1) <> 0 THEN + GOTO FileLoaded + END IF + LINE INPUT #1, lineContent$ + SendLineToWindow windowNum%, lineNum%, lineContent$ + NEXT lineNum% + +FileLoaded: + CLOSE #1 + + ' Fill the remaining lines with empty strings + FOR blankLineNum% = lineNum% TO 1000 + SendLineToWindow windowNum%, blankLineNum%, "" + NEXT blankLineNum% +END SUB + +SUB RefreshAllWindows + ' Redraw all active windows + + CLS + FOR windowNum% = 1 TO 10 + IF windowActiveStatus(windowNum%) > 0 THEN + ShowWindow windowNum% + END IF + NEXT windowNum% +END SUB + +SUB SendLineToWindow (windowNum%, lineNum%, newString$) + ' Stores a string in a window's memory + + lineContent$ = newString$ + + ' Remove trailing spaces from the string + IF lineContent$ = SPACE$(LEN(lineContent$)) THEN + lineContent$ = "" + END IF + + IF LEN(lineContent$) > 0 THEN +TrimRight: + IF RIGHT$(lineContent$, 1) = " " THEN + lineContent$ = LEFT$(lineContent$, LEN(lineContent$) - 1) + GOTO TrimRight + END IF + END IF + + ' Update the window memory with the new string + IF lineContent$ = "" THEN + IF windowData(windowNum%, lineNum%) > 0 THEN + textStorage$(windowData(windowNum%, lineNum%)) = "": windowData(windowNum%, lineNum%) = 0 + END IF + ELSE + IF windowData(windowNum%, lineNum%) = 0 THEN + windowData(windowNum%, lineNum%) = GetFreeLineIndex% + END IF + textStorage$(windowData(windowNum%, lineNum%)) = lineContent$ + END IF +END SUB + +SUB ShowWindow (windowNum%) + ' Draws a window on the screen + + ' Determine background color based on active window + IF windowNum% = currentActiveWindow% THEN + bgColor% = 1 + ELSE + bgColor% = 0 + END IF + + x% = windowX(windowNum%) + y% = windowY(windowNum%) + widt% = windowWidth(windowNum%) + heigh% = windowHeight(windowNum%) + title$ = windowTitle$(windowNum%) + + COLOR 11, bgColor% + + ' Create border components + FOR borderSegment% = 1 TO widt% - 2 + topBottom$ = topBottom$ + CHR$(205) + NEXT borderSegment% + borderTop$ = CHR$(201) + topBottom$ + CHR$(187) + borderBottom$ = CHR$(200) + topBottom$ + CHR$(188) + + ' Draw top border + LOCATE y%, x% + PRINT borderTop$ + + ' Draw bottom border + LOCATE y% + heigh% - 1, x% + PRINT borderBottom$ + + ' Draw window content + FOR lineNum% = 1 TO heigh% - 2 + LOCATE y% + lineNum%, x% + lineContent$ = GetLineFromWindow$(windowNum%, lineNum% + windowShiftY(windowNum%)) + lineContent$ = lineContent$ + SPACE$(300) + lineContent$ = RIGHT$(lineContent$, LEN(lineContent$) - windowShiftX(windowNum%)) + lineContent$ = LEFT$(lineContent$, widt% - 2) + PRINT CHR$(186) + lineContent$ + CHR$(186) + NEXT lineNum% + + ' Draw window title + titleX% = INT(x% + (widt% / 2) - (LEN(title$) / 2) - 2) + LOCATE y%, titleX% + PRINT "[ " + titleX% = titleX% + 2 + + COLOR 10 + LOCATE y%, titleX% + PRINT title$ + + titleX% = titleX% + LEN(title$) + COLOR 11 + LOCATE y%, titleX% + PRINT " ]" + COLOR 7, 0 +END SUB + +SUB InitializeSystem + ' Initialize the screen and shared memory + + WIDTH 80, 50 + VIEW PRINT 1 TO 50 + + FOR storageIndex% = 1 TO maxStorage% + textStorage$(storageIndex%) = "" + NEXT storageIndex% + + storagePointer% = 1 +END SUB \ No newline at end of file