layoutUtils (library for text layout) - Herve - 07-14-2025
Hi,
Here’s a small library that lets me define layouts for displaying text screens containing variable data.
Thank you for your feedback.
layoutUtils.bi
Code: (Select All)
$INCLUDEONCE
type layoutUtils_layoutType
id as integer
layoutName as string
fullpath as string
content as string
resolved as string
prepared as integer
end type
type layoutUtils_elementType
index as integer
position as integer
length as integer
reference as string
end type
redim shared layoutUtils_layoutList(0) as layoutUtils_layoutType
redim shared layoutUtils_elementList(0) as layoutUtils_elementType
layoutUtils.bm
Code: (Select All)
$INCLUDEONCE
'------------------------------------------------------------------------------
' layoutUtils Library
'
' Overview:
' The layoutUtils library provides a simple framework for creating, loading,
' parsing, and manipulating layout data structures represented as strings.
' Layouts can be built directly from in-memory strings, loaded from external
' files, or defined in DATA statements. The library supports placeholder
' references within layouts that can be dynamically resolved or replaced, and
' allows retrieval of the final resolved content.
'
' Key Features:
' 1. Layout Creation & Loading
' - layoutUtils_build%: Create a new layout directly from a string, assign
' it a name, and prepare it for reference resolution.
' - layoutUtils_load%: Load layout content from an external file.
' - layoutUtils_loadData%: Load layout content from embedded DATA statements.
' - Internally manages all layouts in layoutUtils_layoutList.
'
' 2. Reference Extraction & Tracking
' - Scan layout content for placeholder patterns "<&reference>".
' - Record each placeholder’s layout index, position, length, and name in
' layoutUtils_elementList for subsequent processing.
'
' 3. Reference Resolution & Replacement
' - layoutUtils_populateIndex: Replace a named reference in a layout by index.
' - layoutUtils_populate: Replace a named reference in a layout by name.
' - Automatically preserves field width by padding or truncating the replacement.
'
' 4. Layout Retrieval
' - layoutUtils_getPopulatedIndex$: Return the fully resolved content
' of a layout by its numeric index.
' - layoutUtils_getPopulated$: Return the fully resolved content of a
' layout by its name.
'
' 5. Utility Functions
' - layoutUtils_find: Locate a layout by name and return its index.
' - layoutUtils_reset / layoutUtils_resetIndex: Restore a layout’s
' resolved content back to its original raw content.
' - layoutUtils_getNext$: Extract the next placeholder substring from layout
' content between the markers "<&" and ">".
'
' Usage Workflow:
' 1. Create or load a layout:
' idx1 = layoutUtils_build("intro", rawStringContent, "")
' idx2 = layoutUtils_load("level1", "C:\layouts\level1.layout")
' idx3 = layoutUtils_loadData("level2", 5)
'
' 2. Layout preparation (automatic during build/load):
' success = layoutUtils_prepare(idx1)
'
' 3. Populate references:
' layoutUtils_populate("level1", "playerStart", "X:10,Y:20")
'
' 4. Retrieve the final resolved layout content:
' final$. = layoutUtils_getPopulated("level1")
' final$. = layoutUtils_getPopulatedIndex(idx1)
'
' 5. Reset to original content (if needed):
' layoutUtils_reset("level1")
'
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
' Function: layoutUtils_build%
' Purpose : Adds a new layout to the layoutUtils_layoutList array and prepares it.
'
' Parameters:
' layoutName - (string) The name of the layout to be added.
' content - (string) The content/data of the layout.
' fullpath - (string) The full file path associated with the layout.
'
' Returns:
' (integer) The index of the newly added layout in the layoutUtils_layoutList array.
'
' Description:
' This function creates a new layoutUtils_layoutType object, assigns it an ID,
' sets its properties (layoutName, fullpath, content), and appends it to the
' layoutUtils_layoutList array. It then prepares the layout using layoutUtils_prepare
' and stores the result in the 'prepared' field. The function returns the index
' of the newly added layout.
'------------------------------------------------------------------------------
function layoutUtils_build% (layoutName as string, content as string, fullpath as string)
dim res as layoutUtils_layoutType
dim last%
last% = ubound(layoutUtils_layoutList) + 1
redim _preserve layoutUtils_layoutList(last%) as layoutUtils_layoutType
dim layout as layoutUtils_layoutType
layout.id = last%
layout.layoutName = layoutName
layout.fullpath = fullpath
layout.content = content$
layoutUtils_layoutList(last%) = layout
layoutUtils_layoutList(last%).prepared = layoutUtils_prepare(last%)
layoutUtils_build = last%
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_load%
' Purpose : Loads a layout from a file and adds it to the layoutUtils_layoutList array.
'
' Parameters:
' layoutName - (string) The name to assign to the loaded layout.
' fullpath - (string) The full file path to load the layout content from.
'
' Returns:
' (integer) The index of the newly added layout in the layoutUtils_layoutList array.
'
' Description:
' This function opens the specified file in binary mode, reads its content,
' and creates a new layout entry using layoutUtils_build. The layout is then
' prepared and stored in the layoutUtils_layoutList array.
'------------------------------------------------------------------------------
function layoutUtils_load% (layoutName as string, fullpath as string)
open fullpath for binary as #1
dim content$, c as _unsigned _byte
do until eof(1)
get #1,,c
content$ = content$ + chr$(c)
loop
close #1
layoutUtils_load = layoutUtils_build (layoutName, content$, fullpath)
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_loadData%
' Purpose : Loads a layout from DATA statements and adds it to the layoutUtils_layoutList array.
'
' Parameters:
' layoutName - (string) The name to assign to the loaded layout.
' length - (integer) The number of DATA lines to read for the layout content.
'
' Returns:
' (integer) The index of the newly added layout in the layoutUtils_layoutList array.
'
' Description:
' This function reads the specified number of lines from DATA statements,
' concatenates them with line breaks, and creates a new layout entry using
' layoutUtils_build. The layout is then prepared and stored in the
' layoutUtils_layoutList array.
'------------------------------------------------------------------------------
function layoutUtils_loadData% (layoutName as string, length as integer)
dim content$, i%, c$
for i% = 1 to length
read c$
content$ = content$ + c$ + _STR_NAT_EOL
next i%
layoutUtils_loadData = layoutUtils_build (layoutName, content$, "")
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_prepare%
' Purpose : Scans the layout content for references and populates the layoutUtils_elementList array.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList to prepare.
'
' Returns:
' (integer) 1 if preparation is successful.
'
' Description:
' This function searches the layout content for reference patterns (e.g., <&reference>)
' and records their positions, lengths, and reference names in the layoutUtils_elementList array.
' Each found reference is stored as a layoutUtils_elementType object.
'------------------------------------------------------------------------------
function layoutUtils_prepare% (index as integer)
layoutUtils_prepare = 0
dim position%
position% = 1
dim ref as string
do
ref = layoutUtils_getNext (index, position%)
if ref = "" then exit do
dim length%
length% = len(ref) + 3
dim n%
n% = instr(,ref,".")
dim reference$
if n% > 0 then
reference$ = left$(ref,n%-1)
else
reference$ = ref
end if
dim res as layoutUtils_elementType
res.index = index
res.position = position% - 2
res.length = length%
res.reference = reference$
dim last%
last% = ubound(layoutUtils_elementList) + 10
redim _preserve layoutUtils_elementList(last%) as layoutUtils_elementType
layoutUtils_elementList(last%).index = res.index
layoutUtils_elementList(last%).position = res.position
layoutUtils_elementList(last%).length = res.length
layoutUtils_elementList(last%).reference = res.reference
loop
layoutUtils_prepare = 1
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_find
' Purpose : Finds the index of a layout in the layoutUtils_layoutList array by its name.
'
' Parameters:
' layoutName - (string) The name of the layout to search for.
'
' Returns:
' (integer) The index of the layout if found, otherwise 0.
'
' Description:
' This function iterates through the layoutUtils_layoutList array and compares
' each layout's name to the provided name. If a match is found, it returns
' the index of the layout. If no match is found, it returns 0.
'------------------------------------------------------------------------------
function layoutUtils_find (layoutName as string)
layoutUtils_find = 0
dim i%, maxi%
maxi% = ubound(layoutUtils_layoutList)
for i% = 1 to maxi% + 1
if i% > maxi% then exit sub
if layoutUtils_layoutList(i%).layoutName = layoutName then exit for
next i%
layoutUtils_find = i%
end function
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_resetIndex
' Purpose : Resets the resolved content of a layout to its original content.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList to reset.
'
' Description:
' This subroutine sets the 'resolved' field of the specified layout to its
' original 'content', effectively undoing any replacements or modifications.
'------------------------------------------------------------------------------
sub layoutUtils_resetIndex (index as integer)
layoutUtils_layoutList(index).resolved = layoutUtils_layoutList(index).content
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_reset
' Purpose : Resets the resolved content of a layout (by name) to its original content.
'
' Parameters:
' layoutName - (string) The name of the layout to reset.
'
' Description:
' This subroutine finds the layout index by name and calls layoutUtils_resetIndex
' to restore the 'resolved' field to its original 'content'.
'------------------------------------------------------------------------------
sub layoutUtils_reset (layoutName as string)
dim i%
i% = layoutUtils_find(layoutName)
if i% > 0 then
layoutUtils_resetIndex(i%)
end if
end sub
'------------------------------------------------------------------------------
' Function: layoutUtils_findReference
' Purpose : Finds the index of a reference in the layoutUtils_elementList array for a given layout.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList.
' reference - (string) The reference name to search for.
'
' Returns:
' (integer) The index of the reference in layoutUtils_elementList if found, otherwise 0.
'
' Description:
' This function iterates through the layoutUtils_elementList array and compares
' each element's index and reference name to the provided values. If a match
' is found, it returns the index of the reference. If no match is found, it returns 0.
'------------------------------------------------------------------------------
function layoutUtils_findReference (index as integer, reference as string)
layoutUtils_findReference = 0
dim i%, maxi%
maxi% = ubound(layoutUtils_elementList)
for i% = 1 to maxi% + 1
if i% > maxi% then exit sub
if layoutUtils_elementList(i%).index = index and layoutUtils_elementList(i%).reference = reference then exit for
next i%
layoutUtils_findReference = i%
end function
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_populateIndex
' Purpose : Replaces a reference in the resolved layout content with a replacement string.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList.
' reference - (string) The reference name to replace.
' replacementString- (string) The string to insert in place of the reference.
'
' Description:
' This subroutine finds the reference in the layoutUtils_elementList for the given layout.
' It then replaces the corresponding substring in the 'resolved' field of the layout with
' the replacement string, padded or truncated to match the reference length.
'------------------------------------------------------------------------------
sub layoutUtils_populateIndex (index as integer, reference as string, replacementString as string)
dim refIndex%
refIndex% = layoutUtils_findReference(index%, reference)
mid$( _
layoutUtils_layoutList(index%).resolved, _
layoutUtils_elementList(refIndex%).position _
) = _
left$( _
replacementString$ + space$( layoutUtils_elementList(refIndex%).length ), _
layoutUtils_elementList(refIndex%).length _
)
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_populate
' Purpose : Replaces a reference in the resolved layout content (by layout name) with a replacement string.
'
' Parameters:
' layoutName - (string) The name of the layout in layoutUtils_layoutList.
' reference - (string) The reference name to replace.
' replacementString- (string) The string to insert in place of the reference.
'
' Description:
' This subroutine finds the layout index by name and calls layoutUtils_populateIndex
' to replace the specified reference in the resolved content with the replacement string.
'------------------------------------------------------------------------------
sub layoutUtils_populate (layoutName as string, reference as string, replacementString as string)
dim index%
index% = layoutUtils_find(layoutName)
layoutUtils_populateIndex index%, reference, replacementString
end sub
'------------------------------------------------------------------------------
' Function: layoutUtils_getPopulatedIndex$
' Purpose : Returns the resolved (populated) content of a layout by its index.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList.
'
' Returns:
' (string) The resolved content of the layout if the index is valid, otherwise an empty string.
'
' Description:
' This function checks if the provided index is within the valid range of layoutUtils_layoutList.
' If valid, it returns the 'resolved' field of the layout, which contains the content with all
' references replaced. If the index is invalid, it returns an empty string.
'------------------------------------------------------------------------------
function layoutUtils_getPopulatedIndex$ (index as integer)
if index > 0 and index <= ubound(layoutUtils_layoutList) then
layoutUtils_getPopulatedIndex = layoutUtils_layoutList(index).resolved
else
layoutUtils_getPopulatedIndex = ""
end if
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_getPopulated$
' Purpose : Returns the resolved (populated) content of a layout by its name.
'
' Parameters:
' layoutName - (string) The name of the layout in layoutUtils_layoutList.
'
' Returns:
' (string) The resolved content of the layout if found, otherwise an empty string.
'
' Description:
' This function finds the layout index by name and returns the 'resolved' field,
' which contains the content with all references replaced. If the layout is not
' found, it returns an empty string.
'------------------------------------------------------------------------------
function layoutUtils_getPopulated$ (layoutName as string)
dim index%
index% = layoutUtils_find(layoutName)
layoutUtils_getPopulated = layoutUtils_getPopulatedIndex(index%)
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_getNext$ (layoutNumber%, position%)
'
' Description:
' Extracts the next substring found between the markers "<&" and ">" from the
' content of a layout identified by layoutNumber%, starting the search at position%.
'
' Parameters:
' layoutNumber% - The index of the layout in layoutUtils_layoutList to search within.
' position% - The position in the content string to start searching from.
'
' Returns:
' A string containing the substring found between "<&" and ">" markers.
' Returns an empty string if no such substring is found.
'
' Example Usage:
' nextValue$ = layoutUtils_getNext$(1, 10)
'
' Notes:
' - The function updates position% to the start of the found substring.
' - If no marker is found, the function returns an empty string.
'------------------------------------------------------------------------------
function layoutUtils_getNext$ (layoutNumber%, position%)
dim beginning$, middle$, ending$
dim l%, b%, e%, m%
dim content$
content$ = layoutUtils_layoutList(layoutNumber%).content
beginning$ = "<&"
middle$ = ""
ending$ = ">"
l% = len(beginning$)
b% = instr(position%,content$,beginning$)
if b% = 0 then
layoutUtils_getNext = ""
exit sub
end if
e% = instr(b%+l%,content$,ending$)
position% = b%+l%
middle$ = mid$(content$,position%,e%-b%-l%)
layoutUtils_getNext = middle$
end function
And also a test program to demonstrate how it works and why it’s useful.
layoutUtilsTesterFunctional.bas
Code: (Select All)
Option _Explicit
' Include layout utility declarations
'$include: '../library/layoutUtils.bi'
' Create two off‐screen buffers (800×600, 32‐bit) for text and graphics
screen _NewImage(800, 600, 32)
dim as single textDest, grafDest
textDest = _NewImage(800, 600, 32)
grafDest = _NewImage(800, 600, 32)
'=====================================================================
' Define first screen layout (User Information) using DATA statements
'=====================================================================
LAYOUT1:
DATA " "
DATA " +-----------------------------------------------------------------------+"
DATA " | User Information <&pg> |"
DATA " +-----------------------------------------------------------------------+"
DATA " | |"
DATA " | Last Name <&lastName.................................> |"
DATA " | First Name <&firstName................................> |"
DATA " | |"
DATA " | Address: |"
DATA " | |"
DATA " | Street Address <&streetAddress......................> |"
DATA " | Apartment / Suite <&apartmentSuite...................> |"
DATA " | City <&city...............................> |"
DATA " | State / Province <&stateProvince.....................> |"
DATA " | ZIP / Postal <&zipPostal.........................> |"
DATA " | Country <&country...........................> |"
DATA " | |"
DATA " +-----------------------------------------------------------------------+"
DATA " [L:list] [N:next] [P:prev] [Q:quit] "
'==================================================================
' Define second screen layout (User List) using DATA statements
'==================================================================
LAYOUT2:
DATA " "
DATA " +----------------------------------------------------------------------------+"
DATA " | User List |"
DATA " +------------------+------------------+--------------------+-----------------+"
DATA " | Last Name | First Name | City | State |"
DATA " +------------------+------------------+--------------------+-----------------+"
DATA " | <&last1........> | <&first1.......> | <&city1..........> | <&state1......> |"
DATA " | <&last2........> | <&first2.......> | <&city2..........> | <&state2......> |"
DATA " | <&last3........> | <&first3.......> | <&city3..........> | <&state3......> |"
DATA " +------------------+------------------+--------------------+-----------------+"
DATA " [R:return] [Q:quit] "
'==================================================================
' Load screen layouts into memory using layoutUtils library
'==================================================================
dim m1%, m2%
restore layout1
m1% = layoutUtils_loadData ("user", 19) ' Load 19 lines for layout1 under name "user"
restore layout2
m2% = layoutUtils_loadData ("list", 11) ' Load 11 lines for layout2 under name "list"
'==================================================================
' Define sample database entries (3 users, each with 9 fields)
'==================================================================
TEST1:
DATA "1/3","Smith","John","123 Maple Street","Apt. 4B","Springfield","Illinois","62704","United States"
DATA "2/3","Doe","Jane","456 Oak Avenue","Suite 12","Madison","Wisconsin","53703","United States"
DATA "3/3","Brown","Charlie","789 Pine Road","Unit 5","Portland","Oregon","97205","United States"
' Initialize the in‐memory database array and fill it from TEST1
dim db$(1 To 3, 0 To 8)
restore TEST1
dim i%, j%
for i% = 1 to 3
for j% = 0 to 8
read db$(i%, j%)
next j%
next i%
'==================================================================
' Main loop showing the User Information screen
'==================================================================
dim k$, user%
user% = 1 ' Start with the first user
SCREEN1:
' Populate the layout placeholders for the current user
populateLayout1 m1%, db$(), user%
' Render text layer into off‐screen buffer
refreshDestWithLayout "user", textDest
do
_limit 60 ' Cap frame rate to ~60 FPS
_dest grafDest ' Draw graphics into grafDest
cls ' Clear graphics buffer
drawCircle ' Draw animated circle behind text
_putimage , textDest, grafDest ' Blit text buffer over graphics
_dest 0 ' Set drawing back to actual screen
_putimage , grafDest, 0 ' Present final composed image
select case _keyhit ' Handle key input
case 81 or 113 ' Q or q to quit
system
case 78 or 110 ' N or n to go to next user
if user% < 3 then
user% = user% + 1
exit do
end if
case 80 or 112 ' P or p to go to previous user
if user% > 1 then
user% = user% - 1
exit do
end if
case 76 or 108 ' L or l to show user list
gosub screen2
exit do
end select
loop
goto SCREEN1
'==================================================================
' Subroutine to display the User List screen
'==================================================================
SCREEN2:
' Fill in the list layout with all database entries
populateLayout2 "list", db$()
refreshDestWithLayout "list", textDest
do
_limit 60
_dest grafDest
cls
drawTriangle ' Draw rotating triangle graphic
_putimage , textDest, grafDest
_dest 0
_putimage , grafDest, 0
select case _keyhit
case 81 or 113 ' Q or q to quit
system
case 82 or 114 ' R or r to return to info screen
exit do
end select
loop
return ' Return back to SCREEN1 loop
system ' Exit the program
'==================================================================
' Subroutine: populateLayout1
' Fills placeholders in the User Information layout
'==================================================================
sub populateLayout1 (layoutId as integer, database() as string, user as integer)
layoutUtils_resetIndex layoutId
layoutUtils_populateIndex layoutId, "pg", database(user, 0)
layoutUtils_populateIndex layoutId, "lastName", database(user, 1)
layoutUtils_populateIndex layoutId, "firstName", database(user, 2)
layoutUtils_populateIndex layoutId, "streetAddress", database(user, 3)
layoutUtils_populateIndex layoutId, "apartmentSuite", database(user, 4)
layoutUtils_populateIndex layoutId, "city", database(user, 5)
layoutUtils_populateIndex layoutId, "stateProvince", database(user, 6)
layoutUtils_populateIndex layoutId, "zipPostal", database(user, 7)
layoutUtils_populateIndex layoutId, "country", database(user, 8)
end sub
'==================================================================
' Subroutine: populateLayout2
' Fills placeholders in the User List layout
'==================================================================
sub populateLayout2 (layoutName as string, database() as string)
layoutUtils_reset layoutName
layoutUtils_populate layoutName, "last1", database(1, 1)
layoutUtils_populate layoutName, "first1", database(1, 2)
layoutUtils_populate layoutName, "city1", database(1, 5)
layoutUtils_populate layoutName, "state1", database(1, 6)
layoutUtils_populate layoutName, "last2", database(2, 1)
layoutUtils_populate layoutName, "first2", database(2, 2)
layoutUtils_populate layoutName, "city2", database(2, 5)
layoutUtils_populate layoutName, "state2", database(2, 6)
layoutUtils_populate layoutName, "last3", database(3, 1)
layoutUtils_populate layoutName, "first3", database(3, 2)
layoutUtils_populate layoutName, "city3", database(3, 5)
layoutUtils_populate layoutName, "state3", database(3, 6)
end sub
'==================================================================
' Subroutine: refreshDestWithLayout
' Renders a populated layout into the specified destination buffer
'==================================================================
sub refreshDestWithLayout (layoutName as string, destination as double)
dim prevDestination
prevDestination = _dest ' Remember previous drawing target
_dest destination ' Switch to the off‐screen buffer
cls , _rgba(0,0,0,0) ' Clear with fully transparent background
_printmode _KEEPBACKGROUND ' Preserve any background pixels
print layoutUtils_getPopulated(layoutName) ' Print layout text
_dest prevDestination ' Restore the original drawing target
end sub
'==================================================================
' Subroutine: drawCircle
' Draws an animated pulsing circle at the center of the screen
'==================================================================
sub drawCircle ()
static growing as integer
static rFactor as single
dim cx%, cy%, r%
cx% = _width / 2
cy% = _height / 2
' Initialize animation parameters on first run
if rFactor = 0 then
rFactor = 0.10
growing = 1
end if
' Pulse the radius factor up and down
if growing then
rFactor = rFactor + 0.001
if rFactor >= 0.48 then growing = 0
else
rFactor = rFactor - 0.001
if rFactor <= 0.10 then growing = 1
end if
r% = rFactor * _min(_width, _height)
circle (cx%, cy%), r%, _rgba(255,255,0,100)
end sub
'==================================================================
' Subroutine: drawTriangle
' Draws a continuously rotating semi‐transparent triangle
'==================================================================
sub drawTriangle ()
static angleOffset#
angleOffset# = angleOffset# - _pi / 180 ' Decrease rotation by 1° each frame
dim cx%, cy%, r%
cx% = _width / 2
cy% = _height / 2
r% = 0.48 * _min(_width, _height)
' Calculate the three vertex angles
dim angle1#, angle2#, angle3#
angle1# = angleOffset#
angle2# = angleOffset# + 2 * _pi / 3
angle3# = angleOffset# + 4 * _pi / 3
' Compute vertex coordinates
dim x1%, y1%, x2%, y2%, x3%, y3%
x1% = cx% + r% * cos(angle1#)
y1% = cy% - r% * sin(angle1#)
x2% = cx% + r% * cos(angle2#)
y2% = cy% - r% * sin(angle2#)
x3% = cx% + r% * cos(angle3#)
y3% = cy% - r% * sin(angle3#)
' Draw triangle edges
line (x1%, y1%)-(x2%, y2%), _rgba(0,255,0,100)
line (x2%, y2%)-(x3%, y3%), _rgba(0,255,0,100)
line (x3%, y3%)-(x1%, y1%), _rgba(0,255,0,100)
end sub
' Include the binary module for layoutUtils implementation
'$include: '../library/layoutUtils.bm'
RE: layoutUtils (library for text layout) - grymmjack - 07-16-2025
Very cool! Like info forms in the old BBS days.
Elegant approach!
Sharing this link because you might find it interesting / useful. There were a bunch of these kinds of parsers back in the day of BBSes. I like how yours has input length and stuff in it. I haven't tried your code yet./
Check out the old Renegade BBS Info Form docs here:
https://renegadebbs.info/docs/
Search for "Chapter 16 - Infoform" - there is no hypertext, this is an ASCII doc file.
Code: (Select All) ─────────────────────────────────────────────────────────────────────────────
Chapter 16 - Infoform Questionnaire System
─────────────────────────────────────────────────────────────────────────────
──────────────────────────────
Chapter 16─A: How They're Made
──────────────────────────────
Infoform questionnaires are text files with certain commands that only
have meaning to the BBS. The questionnaire is stored in the MISC
directory, in a file with the extension .INF, .INA or .INV (No Emulation,
ANSI Emulation, or Avatar Emulation, respectively.) When users answer
the question(s), their answers are also stored in the MISC directory, in
a file with the extension ".ASW". Answers from other users are appended
to the end of the answer file.
───────────────────────────────────
Chapter 16─B: New User Questionnaire
───────────────────────────────────
A special Infoform questionnaire called NEWUSER is always given to a new
user during the new user logon process. An example is listed below:
─────────────────────────────────────────────────────────────────────────────
Alright! Only a few more questions to go before you can logon. Please
answer the questions, as they are essential for Validation and just as a
security measure. Don't worry though.... It's not a test. (it's a quiz..)
;YAre you the SysOp of a BBS (Y/N)? : *
;INO,NOSYSOP
What is the Phone # of the BBS?
###─###─####
;A ? *
;AWhat BBS software do you run? *
;AHow long has it been running for? *
;GNOSYSOP1
:NOSYSOP
Oh well! Someone's gotta do it, eh?
:NOSYSOP1
Do you know of any of the current users on this BBS, or any other good
users that could recommend you? (2 lines)
;A 1) *
;A 2) *
Ok! Thanks for answering the questions, and I'm sure you'll be validated
very soon!
─────────────────────────────────────────────────────────────────────────────
───────────────────────────────────
Chapter 16─C: Questionnaire Commands
───────────────────────────────────
Below are listed all the Infoform questionnaire commands currently
supported by Renegade. All commands must start on a separate line,
beginning with a ";" character. Labels also start on a separate line,
but beginning with a ":" character, and are followed by a string of
characters which define the label name. Everything else is treated as
straight text. A "; <string>" line is treated as a comment.
Command: A<string>*
Function: Displays <string> then inputs the string at the "*"
character.
Command: B<string>*
Function: Similar to the "A" command, except input is uppercase only.
Command: C"chars"<string>*
Function: Displays string, then inputs a single character at the "*"
character. Only accepted answers are the characters
between the quote marks ("").
Command: Dx<string>
Function: Outputs a doorfile (See Door in Menu Commands for x)
then executes <string>. All rules for standard door
commands apply.
Command: F<Flag Toggles>
Function: Will toggle the AR flags. Refer to the Commandkey "OF" in
Chapter 11─D─12 for information on the <Flag Toggles>.
Command: G<label>
Function: Goes to label <label>. If <label> doesn't exist, the
questionnaire will terminate.
Command: H
Function: Terminates questionnaire and hangs up on user.
Command: I<string>,<label>
Function: Branches to <label> if <string> was the last input
value from a "A", "B", "C", or "Y" command.
Command: K
Function: Terminate questionnaire without saving answers.
Command: L<string>
Function: Outputs <string> to the questionnaire answer file only.
Command: N<String>*
Function: Displays <string>, then accepts either a "Y", or "N"
at the "*" character. Defaults to No.
Command: Q
Function: Terminates questionnaire and saves answers in answer file.
Command: R<Flag Toggles>
Function: Will toggle the AC flags. Refer to the Commandkey "OG" in
Chapter 11─D─12 for information on the <Flag Toggles>.
Command: T<filename>
Function: Displays the file <filename>.
Command: V<level>
Function: Will validate a user up to level <level>.
Command: Y<string>*
Function: Same as N<String>, but defaults to Yes instead of No.
Command: S<ACS>,LABEL
Function: If the user matches the ACS specified between quotes,
execution will branch to LABEL, like the G command.
Another topic that might be interesting for you and your library is the concept of MCI codes:
https://renegadebbs.info/docs/mci.html
TL;DR: it's a variable that is found/replaced (like your references) but with specific data for the BBS - in this case not useful for your library unless the programmer defined them in advance, or you had one for date/time, current directory or I don't know, other things that QB64PE can introspect on
RE: layoutUtils (library for text layout) - grymmjack - 07-16-2025
Also, very cool demo. Text overlaying the animated shape is a nice touch 
Very nice work and it worked as expected. Thanks for the built in documentation too.
What else do you have in ../library/
RE: layoutUtils (library for text layout) - Herve - 07-17-2025
Hi grymmjack,
Thank you for your feedback and for the link to RenegadeBBS, which I didn’t know at all.
I was mainly inspired by the ISPF panels that I’ve used professionally. I’ll look into expanding my library—first by adding color support, then input fields, and if all goes well, best user interactions like those available in CUA mode.
REXX ISPF Panels : https://www.ibmmainframer.com/rexx-tutorial/rexx-ispf-panels-with-examples/
CUA mode : https://en.wikipedia.org/wiki/IBM_Common_User_Access
QB64 is completely new to me (1 month 1/2)—my last QBasic program dates back to the ’90s! Everything QB64 brings to the table is really cool, and the compiled programs run perfectly on my modest setup https://www.raspberrypi.com/products/raspberry-pi-500/
And in my ../library/ folder there are indeed a few little things, but they’re not stable yet and—above all—not as well documented as layoutUtils; I’ll share them once they are.
RE: layoutUtils (library for text layout) - grymmjack - 07-17-2025
Ah cool! REXX is interesting. Did you make mainframe stuff in your day?
You're doing great with QB64 ! Keep up the great work 
If you are ever looking for more libraries/things to make QB64PE easier let us know - the people here are very generous and helpful.
Take it easy
RE: layoutUtils (library for text layout) - Herve - 07-19-2025
Hi,
New version with colorization; I let you discover
There might be a few bugs, please let me know if you find any!
The next step is to add verification code to raise errors if the layout declaration contains mistakes.
layoutUtils.bi
Code: (Select All)
$INCLUDEONCE
type layoutUtils_layoutType
id as integer
layoutName as string
fullpath as string
content as string
resolved as string
prepared as integer
end type
type layoutUtils_elementType
layoutIndex as integer
position as integer
length as integer
reference as string
end type
type layoutUtils_colorType
layoutIndex as integer
value as long
activatorChar as string * 1
end type
type layoutUtils_activatorType
layoutIndex as integer
position as integer
activatorChar as string * 1
colorValue as long
row as integer
column as integer
end type
redim shared layoutUtils_layoutList(0) as layoutUtils_layoutType
redim shared layoutUtils_elementList(0) as layoutUtils_elementType
redim shared layoutUtils_colorList(-1) as layoutUtils_colorType
redim shared layoutUtils_activatorList(-1) as layoutUtils_activatorType
layoutUtils.bm
Code: (Select All)
$INCLUDEONCE
'------------------------------------------------------------------------------
' layoutUtils Library
'
' Author : Herve Heritier
' Date : July 18 2025
' Version: 0.0.2
'
' Overview:
' The layoutUtils library provides a simple framework for creating, loading,
' parsing, and manipulating layout data structures represented as strings.
' Layouts can be built directly from in-memory strings, loaded from external
' files, or defined in DATA statements. The library supports placeholder
' references within layouts that can be dynamically resolved or replaced, and
' allows retrieval of the final resolved content.
'
' Key Features:
' 1. Layout Creation & Loading
' - layoutUtils_build%: Create a new layout directly from a string, assign
' it a name, and prepare it for reference resolution.
' - layoutUtils_load%: Load layout content from an external file.
' - layoutUtils_loadData%: Load layout content from embedded DATA statements.
' - Internally manages all layouts in layoutUtils_layoutList.
'
' 2. Reference Extraction & Tracking
' - Scan layout content for placeholder patterns "<&reference>".
' - Record each placeholder’s layout index, position, length, and name in
' layoutUtils_elementList for subsequent processing.
'
' 3. Reference Resolution & Replacement
' - layoutUtils_populateIndex: Replace a named reference in a layout by index.
' - layoutUtils_populate: Replace a named reference in a layout by name.
' - Automatically preserves field width by padding or truncating the replacement.
'
' 4. Layout Retrieval
' - layoutUtils_getPopulatedIndex$: Return the fully resolved content
' of a layout by its numeric index.
' - layoutUtils_getPopulated$: Return the fully resolved content of a
' layout by its name.
'
' 5. Utility Functions
' - layoutUtils_find: Locate a layout by name and return its index.
' - layoutUtils_reset / layoutUtils_resetIndex: Restore a layout’s
' resolved content back to its original raw content.
' - layoutUtils_getNext$: Extract the next placeholder substring from layout
' content between the markers "<&" and ">".
'
' Usage Workflow:
' 1. Create or load a layout:
' idx1 = layoutUtils_build("intro", rawStringContent, "")
' idx2 = layoutUtils_load("level1", "C:\layouts\level1.layout")
' idx3 = layoutUtils_loadData("level2", 5)
'
' 2. Layout preparation (automatic during build/load):
' success = layoutUtils_prepare(idx1)
'
' 3. Populate references:
' layoutUtils_populate("level1", "playerStart", "X:10,Y:20")
'
' 4. Retrieve the final resolved layout content:
' final$. = layoutUtils_getPopulated("level1")
' final$. = layoutUtils_getPopulatedIndex(idx1)
'
' 5. Reset to original content (if needed):
' layoutUtils_reset("level1")
'
'------------------------------------------------------------------------------
'------------------------------------------------------------------------------
' Function: layoutUtils_build%
' Purpose : Adds a new layout to the layoutUtils_layoutList array and prepares it.
'
' Parameters:
' layoutName - (string) The name of the layout to be added.
' content - (string) The content/data of the layout.
' fullpath - (string) The full file path associated with the layout.
'
' Returns:
' (integer) The index of the newly added layout in the layoutUtils_layoutList array.
'
' Description:
' This function creates a new layoutUtils_layoutType object, assigns it an ID,
' sets its properties (layoutName, fullpath, content), and appends it to the
' layoutUtils_layoutList array. It then prepares the layout using layoutUtils_prepare
' and stores the result in the 'prepared' field. The function returns the index
' of the newly added layout.
'------------------------------------------------------------------------------
function layoutUtils_build% (layoutName as string, content as string, fullpath as string)
dim res as layoutUtils_layoutType
dim last%
last% = ubound(layoutUtils_layoutList) + 1
redim _preserve layoutUtils_layoutList(last%) as layoutUtils_layoutType
dim layout as layoutUtils_layoutType
layout.id = last%
layout.layoutName = layoutName
layout.fullpath = fullpath
dim colors(0) as layoutUtils_colorType
dim activators(0) as layoutUtils_activatorType
layoutUtils_extractColors last%, content
layout.content = content
layoutUtils_layoutList(last%) = layout
layoutUtils_layoutList(last%).prepared = layoutUtils_prepare(last%)
layoutUtils_build = last%
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_load%
' Purpose : Loads a layout from a file and adds it to the layoutUtils_layoutList array.
'
' Parameters:
' layoutName - (string) The name to assign to the loaded layout.
' fullpath - (string) The full file path to load the layout content from.
'
' Returns:
' (integer) The index of the newly added layout in the layoutUtils_layoutList array.
'
' Description:
' This function opens the specified file in binary mode, reads its content,
' and creates a new layout entry using layoutUtils_build. The layout is then
' prepared and stored in the layoutUtils_layoutList array.
'------------------------------------------------------------------------------
function layoutUtils_load% (layoutName as string, fullpath as string)
open fullpath for binary as #1
dim content$, c as _unsigned _byte
do until eof(1)
get #1,,c
content$ = content$ + chr$(c)
loop
close #1
layoutUtils_load = layoutUtils_build (layoutName, content$, fullpath)
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_loadData%
' Purpose : Loads a layout from DATA statements and adds it to the layoutUtils_layoutList array.
'
' Parameters:
' layoutName - (string) The name to assign to the loaded layout.
' length - (integer) The number of DATA lines to read for the layout content.
'
' Returns:
' (integer) The index of the newly added layout in the layoutUtils_layoutList array.
'
' Description:
' This function reads the specified number of lines from DATA statements,
' concatenates them with line breaks, and creates a new layout entry using
' layoutUtils_build. The layout is then prepared and stored in the
' layoutUtils_layoutList array.
'------------------------------------------------------------------------------
function layoutUtils_loadData% (layoutName as string, length as integer)
dim content$, i%, c$
for i% = 1 to length
read c$
content$ = content$ + c$ + _STR_NAT_EOL
next i%
layoutUtils_loadData = layoutUtils_build (layoutName, content$, "")
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_prepare%
' Purpose : Scans the layout content for references and populates the layoutUtils_elementList array.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList to prepare.
'
' Returns:
' (integer) 1 if preparation is successful.
'
' Description:
' This function searches the layout content for reference patterns (e.g., <&reference>)
' and records their positions, lengths, and reference names in the layoutUtils_elementList array.
' Each found reference is stored as a layoutUtils_elementType object.
'------------------------------------------------------------------------------
function layoutUtils_prepare% (layoutIndex as integer)
layoutUtils_prepare = 0
dim position%
position% = 1
dim ref as string
do
ref = layoutUtils_getNext (layoutIndex, position%)
if ref = "" then exit do
dim length%
length% = len(ref) + 3
dim n%
n% = instr(,ref,".")
dim reference$
if n% > 0 then
reference$ = left$(ref,n%-1)
else
reference$ = ref
end if
dim res as layoutUtils_elementType
res.layoutIndex = layoutIndex
res.position = position% - 2
res.length = length%
res.reference = reference$
dim last%
last% = ubound(layoutUtils_elementList) + 1
redim _preserve layoutUtils_elementList(last%) as layoutUtils_elementType
layoutUtils_elementList(last%).layoutIndex = res.layoutIndex
layoutUtils_elementList(last%).position = res.position
layoutUtils_elementList(last%).length = res.length
layoutUtils_elementList(last%).reference = res.reference
loop
layoutUtils_prepare = 1
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_find
' Purpose : Finds the index of a layout in the layoutUtils_layoutList array by its name.
'
' Parameters:
' layoutName - (string) The name of the layout to search for.
'
' Returns:
' (integer) The index of the layout if found, otherwise 0.
'
' Description:
' This function iterates through the layoutUtils_layoutList array and compares
' each layout's name to the provided name. If a match is found, it returns
' the index of the layout. If no match is found, it returns 0.
'------------------------------------------------------------------------------
function layoutUtils_find (layoutName as string)
layoutUtils_find = 0
dim i%, maxi%
maxi% = ubound(layoutUtils_layoutList)
for i% = 1 to maxi% + 1
if i% > maxi% then exit sub
if layoutUtils_layoutList(i%).layoutName = layoutName then exit for
next i%
layoutUtils_find = i%
end function
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_resetIndex
' Purpose : Resets the resolved content of a layout to its original content.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList to reset.
'
' Description:
' This subroutine sets the 'resolved' field of the specified layout to its
' original 'content', effectively undoing any replacements or modifications.
'------------------------------------------------------------------------------
sub layoutUtils_resetIndex (index as integer)
layoutUtils_layoutList(index).resolved = layoutUtils_layoutList(index).content
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_reset
' Purpose : Resets the resolved content of a layout (by name) to its original content.
'
' Parameters:
' layoutName - (string) The name of the layout to reset.
'
' Description:
' This subroutine finds the layout index by name and calls layoutUtils_resetIndex
' to restore the 'resolved' field to its original 'content'.
'------------------------------------------------------------------------------
sub layoutUtils_reset (layoutName as string)
dim i%
i% = layoutUtils_find(layoutName)
if i% > 0 then
layoutUtils_resetIndex(i%)
end if
end sub
'------------------------------------------------------------------------------
' Function: layoutUtils_findReference
' Purpose : Finds the index of a reference in the layoutUtils_elementList array for a given layout.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList.
' reference - (string) The reference name to search for.
'
' Returns:
' (integer) The index of the reference in layoutUtils_elementList if found, otherwise 0.
'
' Description:
' This function iterates through the layoutUtils_elementList array and compares
' each element's index and reference name to the provided values. If a match
' is found, it returns the index of the reference. If no match is found, it returns 0.
'------------------------------------------------------------------------------
function layoutUtils_findReference (layoutIndex as integer, reference as string)
layoutUtils_findReference = 0
dim i%, maxi%
maxi% = ubound(layoutUtils_elementList)
for i% = 1 to maxi% + 1
if i% > maxi% then exit sub
if layoutUtils_elementList(i%).layoutIndex = layoutIndex and layoutUtils_elementList(i%).reference = reference then exit for
next i%
layoutUtils_findReference = i%
end function
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_populateIndex
' Purpose : Replaces a reference in the resolved layout content with a replacement string.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList.
' reference - (string) The reference name to replace.
' replacementString- (string) The string to insert in place of the reference.
'
' Description:
' This subroutine finds the reference in the layoutUtils_elementList for the given layout.
' It then replaces the corresponding substring in the 'resolved' field of the layout with
' the replacement string, padded or truncated to match the reference length.
'------------------------------------------------------------------------------
sub layoutUtils_populateIndex (layoutIndex as integer, reference as string, replacementString as string)
dim refIndex%
refIndex% = layoutUtils_findReference(layoutIndex, reference)
mid$( _
layoutUtils_layoutList(layoutIndex).resolved, _
layoutUtils_elementList(refIndex%).position _
) = _
left$( _
replacementString$ + space$( layoutUtils_elementList(refIndex%).length ), _
layoutUtils_elementList(refIndex%).length _
)
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_populate
' Purpose : Replaces a reference in the resolved layout content (by layout name) with a replacement string.
'
' Parameters:
' layoutName - (string) The name of the layout in layoutUtils_layoutList.
' reference - (string) The reference name to replace.
' replacementString- (string) The string to insert in place of the reference.
'
' Description:
' This subroutine finds the layout index by name and calls layoutUtils_populateIndex
' to replace the specified reference in the resolved content with the replacement string.
'------------------------------------------------------------------------------
sub layoutUtils_populate (layoutName as string, reference as string, replacementString as string)
dim index%
index% = layoutUtils_find(layoutName)
layoutUtils_populateIndex index%, reference, replacementString
end sub
'------------------------------------------------------------------------------
' Function: layoutUtils_getPopulatedIndex$
' Purpose : Returns the resolved (populated) content of a layout by its index.
'
' Parameters:
' index - (integer) The index of the layout in layoutUtils_layoutList.
'
' Returns:
' (string) The resolved content of the layout if the index is valid, otherwise an empty string.
'
' Description:
' This function checks if the provided index is within the valid range of layoutUtils_layoutList.
' If valid, it returns the 'resolved' field of the layout, which contains the content with all
' references replaced. If the index is invalid, it returns an empty string.
'------------------------------------------------------------------------------
function layoutUtils_getPopulatedIndex$ (index as integer)
if index > 0 and index <= ubound(layoutUtils_layoutList) then
layoutUtils_getPopulatedIndex = layoutUtils_layoutList(index).resolved
else
layoutUtils_getPopulatedIndex = ""
end if
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_getPopulated$
' Purpose : Returns the resolved (populated) content of a layout by its name.
'
' Parameters:
' layoutName - (string) The name of the layout in layoutUtils_layoutList.
'
' Returns:
' (string) The resolved content of the layout if found, otherwise an empty string.
'
' Description:
' This function finds the layout index by name and returns the 'resolved' field,
' which contains the content with all references replaced. If the layout is not
' found, it returns an empty string.
'------------------------------------------------------------------------------
function layoutUtils_getPopulated$ (layoutName as string)
dim index%
index% = layoutUtils_find(layoutName)
layoutUtils_getPopulated = layoutUtils_getPopulatedIndex(index%)
end function
'------------------------------------------------------------------------------
' Function: layoutUtils_getNext$ (layoutIndex, position%)
'
' Description:
' Extracts the next substring found between the markers "<&" and ">" from the
' content of a layout identified by layoutIndex, starting the search at position%.
'
' Parameters:
' layoutIndex - The index of the layout in layoutUtils_layoutList to search within.
' position% - The position in the content string to start searching from.
'
' Returns:
' A string containing the substring found between "<&" and ">" markers.
' Returns an empty string if no such substring is found.
'
' Example Usage:
' nextValue$ = layoutUtils_getNext$(1, 10)
'
' Notes:
' - The function updates position% to the start of the found substring.
' - If no marker is found, the function returns an empty string.
'------------------------------------------------------------------------------
function layoutUtils_getNext$ (layoutIndex as integer, position%)
dim beginning$, middle$, ending$
dim l%, b%, e%, m%
dim content$
content$ = layoutUtils_layoutList(layoutIndex).content
beginning$ = "<&"
middle$ = ""
ending$ = ">"
l% = len(beginning$)
b% = instr(position%,content$,beginning$)
if b% = 0 then
layoutUtils_getNext = ""
exit sub
end if
e% = instr(b%+l%,content$,ending$)
position% = b%+l%
middle$ = mid$(content$,position%,e%-b%-l%)
layoutUtils_getNext = middle$
end function
'------------------------------------------------------------------------------
' Subroutine : layoutUtils_extractColors
''
' Purpose : Parses a layout’s resolved content for embedded color directives
' and populates two global lists—one for color definitions (layoutUtils_colorList)
' and one for per‐character activators (layoutUtils_activatorList).
'
' Parameters :
' layoutIndex - (Integer) Index of the layout whose content is being processed.
' content - (String) The full, resolved text of the layout. May contain color
' directives prefixed by “$”.
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_extractColors
'
' Purpose : Extracts colorization information and activator positions from a specially
' formatted layout string. The function parses the content string, identifies
' colorization characters, and populates the global color and activator lists
' for the given layout index.
'
' Parameters:
' layoutIndex - (integer) The index of the layout to associate extracted colors
' and activators with.
' content - (string) The input string containing layout and colorization data.
' There are 2 colorization modes; the first mode, which we will call the pointed_mode,
' involves following a text line with a colorization line when necessary to indicate
' the positions of color changes; the second mode, which we will call the integrated_mode,
' uses activator characters directly within the text lines.
' The content must start with a '$' character and follow a specific format.
''
'
' $<newline>
' <colorizationCharacter><newline>
' <colorValue1><newline>
' <activatorChar1><newline>
' <colorValue2><newline>
' <activatorChar2><newline>
' ...
' $<newline>
' <text line><newline> x 1,1 \
' ) x 1,n
' <colorizationCharacter><colorization line><newline> x 0,1 /
'
' If content starts with a $ character, then everything up to the next $
' is used to manage colorization.
'
' If the colorizationCharacter is blank, then we are in integrated_mode;
' otherwise, we are in pointed_mode and the colorizationCharacter will be
' used to designate the colorization lines.
'
' After the colorizationCharacter there is a repetition of <colorValue>
' (in long hexadecimal format) followed by a color <activatorChar>.
'
' After this declarative part, which ends with $, you will find text lines
' and colorization lines.
'
' Integrated_mode
' In this mode, we have only text lines, and each activatorCharacter mark
' the beginning of the corresponding colorization. The activationCharacters
' are replaced by a space during display and causes a color change for the rest
' of the text until the next activatorCharacter.
' Pointed_mode :
' A colorization line starts with the colorizationCharacter.
' The line contains some activatorChars that are positioned relative
' to the start of the colorization in the preceding text line;
' the activated color remains until the end of the layout or
' until the next activator. Other lines are text..
'
' notes:
' This subroutine assumes that the global arrays are already initialized.
' It uses dynamic array resizing to append new color and activator entries.
'------------------------------------------------------------------------------
sub layoutUtils_extractColors ( layoutIndex as integer, content as string )
dim colorizationCharacter as string * 1
' color extraction is only performed if the layout content begins with $ or %
if left$(content,1) <> "$" then exit sub
dim result$(0)
layoutUtils_split content, _STR_LF, result$()
layoutUtils_clean result$()
' first line contains only $ or %
if result$(0) <> "$" then exit sub
colorizationCharacter = result$(1)
dim i%
i% = 2
' extract colors
do while result$(i%) <> "$"
ReDim _Preserve layoutUtils_colorList(ubound(layoutUtils_colorList) + 1) as layoutUtils_colorType
layoutUtils_colorList(ubound(layoutUtils_colorList)).layoutIndex = layoutIndex
layoutUtils_colorList(ubound(layoutUtils_colorList)).value = val(result$(i%))
i% = i% + 1
layoutUtils_colorList(ubound(layoutUtils_colorList)).activatorChar = result$(i%)
i% = i% + 1
loop
i% = i% + 1
redim textArray$(-1)
dim activatorArray$(-1)
dim lastTextLength as integer
lastTextLength = 0
' Retrieve the colorization lines
' and match a colorization line to each text line, even if there is no colorization.
' Note: There is no colorization line for the integrated_mode.
do while i% <= ubound(result$)
if _trim$(colorizationCharacter) <> "" and left$(result$(i%),1) = colorizationCharacter then
redim _preserve activatorArray$(ubound(activatorArray$) + 1)
dim k%
k% = ubound(activatorArray$)
activatorArray$(k%) = left$(mid$(result$(i%)+space$(lastTextLength),2),lastTextLength)
lastTextLength = 0
else
if lastTextLength > 0 then
redim _preserve activatorArray$(ubound(activatorArray$) + 1)
k% = ubound(activatorArray$)
activatorArray$(k%) = space$(lastTextLength)
lastTextLength = 0
end if
redim _preserve textArray$(ubound(textArray$) + 1)
k% = ubound(textArray$)
textArray$(k%) = result$(i%)
lastTextLength = len(textArray$(k%))
end if
i% = i% + 1
loop
if ubound(activatorArray$)<ubound(textArray$) then
redim _preserve activatorArray$(ubound(activatorArray$) + 1)
activatorArray$(ubound(activatorArray$)) = ""
end if
' Generate colorization lines for the integrated_mode
if _trim$(colorizationCharacter) = "" then
dim j%, p%
redim activatorArray$(-1)
for i% = lbound(textArray$) to ubound(textArray$)
redim _preserve activatorArray$(ubound(activatorArray$) + 1)
k% = ubound(activatorArray$)
activatorArray$(k%) = space$(len(textArray$(i%)))
for j% = lbound(layoutUtils_colorList) to ubound(layoutUtils_colorList)
if layoutUtils_colorList(j%).layoutIndex <> layoutIndex then _continue
do
p% = instr(1, textArray$(i%), layoutUtils_colorList(j%).activatorChar)
if p% = 0 then exit do
mid$(textArray$(i%),p%,1) = " "
mid$(activatorArray$(k%),p%,1) = layoutUtils_colorList(j%).activatorChar
loop
next j%
next i%
end if
' Update the list of colorization activators (determine the line
' and column of each activator as well as the color it activates).
dim position%, s$
position% = 0
for i%=lbound(activatorArray$) to ubound(activatorArray$)
for p% = 1 to len(activatorArray$(i%))
s$ = mid$(activatorArray$(i%),p%,1)
if s$ <> " " then
for j% = lbound(layoutUtils_colorList) to ubound(layoutUtils_colorList)
if layoutUtils_colorList(j%).layoutIndex = layoutIndex and _
layoutUtils_colorList(j%).activatorChar = s$ then
redim _preserve layoutUtils_activatorList(ubound(layoutUtils_activatorList) + 1) as layoutUtils_activatorType
layoutUtils_activatorList(ubound(layoutUtils_activatorList)).layoutIndex = layoutUtils_colorList(j%).layoutIndex
layoutUtils_activatorList(ubound(layoutUtils_activatorList)).position = position% + p% '+ linePosition%
layoutUtils_activatorList(ubound(layoutUtils_activatorList)).colorValue = layoutUtils_colorList(j%).value
layoutUtils_activatorList(ubound(layoutUtils_activatorList)).activatorChar = layoutUtils_colorList(j%).activatorChar
layoutUtils_activatorList(ubound(layoutUtils_activatorList)).row = i% + 1
layoutUtils_activatorList(ubound(layoutUtils_activatorList)).column = p%
end if
next j%
end if
next p%
position% = position% + len(activatorArray$(i%)) + 1
next i%
' Aggregate the extracted text lines into a single string with line breaks.
layoutUtils_join textArray$(), lbound(textArray$), ubound(textArray$), _STR_NAT_EOL, content
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_printWithoutColor
' Purpose : Prints the resolved layout content without any colorization.
'
' Parameters:
' layoutName - (string) The name of the layout to print.
'
' Description:
' This subroutine retrieves the populated (resolved) content of the specified
' layout and prints it directly to the screen, without applying any color formatting.
'------------------------------------------------------------------------------
sub layoutUtils_printWithoutColor (layoutName as string)
print layoutUtils_getPopulated(layoutName)
end sub
'------------------------------------------------------------------------------
' Function: layoutUtils_isColorized%
' Purpose : Determines if a layout has colorization information.
'
' Parameters:
' layoutIndex - (integer) The index of the layout in layoutUtils_layoutList.
'
' Returns:
' (integer) 1 if the layout has colorization data, 0 otherwise.
'
' Description:
' This function checks the layoutUtils_colorList array for any entries
' associated with the specified layout index. If at least one color entry
' is found, it returns 1; otherwise, it returns 0.
'------------------------------------------------------------------------------
function layoutUtils_isColorized%(layoutIndex)
layoutUtils_isColorized = 0
dim i%
for i% = lbound(layoutUtils_colorList) to ubound(layoutUtils_colorList)
if layoutUtils_colorList(i%).layoutIndex = layoutIndex then
layoutUtils_isColorized = 1
exit function
end if
next i%
end function
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_printWithColors
' Purpose : Prints the resolved layout content with colorization applied.
'
' Parameters:
' layoutName - (string) The name of the layout to print.
'
' Description:
' This subroutine retrieves the populated (resolved) content of the specified
' layout and prints it to the screen, applying color formatting based on the
' colorization and activator information extracted from the layout. If the
' layout is not colorized, it falls back to printing without color.
'------------------------------------------------------------------------------
sub layoutUtils_printWithColors (layoutName as string)
dim layoutIndex
layoutIndex = layoutUtils_find(layoutName)
if layoutIndex = 0 then exit sub
if layoutUtils_isColorized(layoutIndex) = 0 then
layoutUtils_printWithoutColor layoutName
exit sub
end if
dim text as string
text = layoutUtils_getPopulated(layoutName)
dim position%
position% = 1
dim i%, s$, j%, t$
dim currentRow%, currentColumn%, currentColor&
currentRow% = 1
currentColumn% = 1
currentColor& = &HFFFFFFFF
for i% = lbound(layoutUtils_activatorList) to ubound(layoutUtils_activatorList)
if layoutUtils_activatorList(i%).layoutIndex = layoutIndex then
s$ = mid$(text,position%,layoutUtils_activatorList(i%).position-position%)
layoutUtils_cutAndPrintWithColors s$, currentColor&, currentRow%, currentColumn%
position% = layoutUtils_activatorList(i%).position
currentColor& = layoutUtils_activatorList(i%).colorValue
currentRow% = layoutUtils_activatorList(i%).row
currentColumn% = layoutUtils_activatorList(i%).column
end if
next i%
s$ = mid$(text,position%)
layoutUtils_cutAndPrintWithColors s$, currentColor&, currentRow%, currentColumn%
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_cutAndPrintWithColors
' Purpose : Prints a string segment with colorization, handling line breaks and cursor position.
'
' Parameters:
' s$ - (string) The string segment to print (may contain line breaks).
' currentColor&- (long) The color attribute to use for printing.
' currentRow% - (integer) The starting row position for printing.
' currentColumn%- (integer) The starting column position for printing.
'
' Description:
' This subroutine prints the provided string segment s$ to the screen, applying
' the specified color and starting at the given row and column. It handles
' line breaks (_STR_NAT_EOL) by moving the cursor to the next line and resetting
' the column. Each line is printed with the current color.
'------------------------------------------------------------------------------
sub layoutUtils_cutAndPrintWithColors (s$, currentColor&, currentRow%, currentColumn%)
dim j%, t$
do
j% = instr(1,s$,_STR_NAT_EOL)
if j% = 0 then
color currentColor&
locate currentRow%, currentColumn%
print s$
exit do
else
color currentColor&
locate currentRow%, currentColumn%
t$ = left$(s$,j%-1)
print t$
currentRow% = currentRow% + 1
currentColumn% = 1
s$ = right$(s$,len(s$)-j%)
end if
loop
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_join
'
' Concatenates elements from a string array (source$) into a single string
' (destination$), joining elements from index beginning% to ending% (inclusive)
' with a specified delimiter$ between each element.
'
' Parameters:
' source$() - The array of strings to join.
' beginning% - The starting index in the array (inclusive).
' ending% - The ending index in the array (inclusive).
' delimiter$ - The string to insert between each joined element.
' destination$ - The resulting concatenated string (output parameter).
'
' Example:
' dim arr$(1 to 3)
' arr$(1) = "apple": arr$(2) = "banana": arr$(3) = "cherry"
' call layoutUtils_join(arr$(), 1, 3, ", ", result$)
' ' result$ will be "apple, banana, cherry, "
'------------------------------------------------------------------------------
sub layoutUtils_join (source$(), beginning%, ending%, delimiter$,destination$)
destination$ = ""
dim i%
for i% = beginning% to ending%
destination$ = destination$ + source$(i%) + delimiter$
next i%
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_split
' Purpose : Splits a string into an array of substrings using a specified delimiter.
'
' Parameters:
' in$ - (string) The input string to split.
' delimiter$ - (string) The delimiter to split the string on.
' result$() - (string array, output) The resulting array of substrings.
'
' Description:
' This subroutine splits the input string in$ into substrings wherever the delimiter$
' occurs, and stores the substrings in result$(). Leading delimiters are skipped.
' The result$ array is dynamically resized to fit the number of substrings found.
'------------------------------------------------------------------------------
Sub layoutUtils_split (in$, delimiter$, result$())
ReDim result$(-1)
dim start, finish
start = 1
Do
While Mid$(in$, start, 1) = delimiter$
start = start + 1
If start > Len(in$) Then Exit Sub
Wend
finish = InStr(start, in$, delimiter$)
If finish = 0 Then finish = Len(in$) + 1
ReDim _Preserve result$(0 To UBound(result$) + 1)
result$(UBound(result$)) = Mid$(in$, start, finish - start)
start = finish + 1
Loop While start <= Len(in$)
End Sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_clean
' Purpose : Removes trailing carriage return characters (CHR$(13)) from each string in the array.
'
' Parameters:
' result$() - (string array) The array of strings to clean in-place.
'
' Description:
' This subroutine iterates through the result$() array and, for each element,
' checks if the last character is a carriage return (CHR$(13)). If so, it removes
' the trailing character. This is useful for cleaning up lines read from files or DATA
' statements that may end with CRLF line endings.
'------------------------------------------------------------------------------
sub layoutUtils_clean (result$())
dim i%
for i% = lbound(result$) to ubound(result$)
if right$(result$(i%),1) = chr$(13) then
result$(i%) = left$(result$(i%),len(result$(i%))-1)
end if
next i%
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_dumpLayoutList
' Purpose : Logs the contents of the layoutUtils_layoutList array for debugging.
'
' Description:
' This subroutine iterates through the layoutUtils_layoutList array and logs
' each layout's index, id, name, full path, content, resolved content, and
' preparation status. Useful for inspecting the state of all loaded layouts.
'------------------------------------------------------------------------------
sub layoutUtils_dumpLayoutList ()
dim currentDest&
currentDest& = _dest
$CONSOLE
_dest _console
print "==========================="
print "dump layoutUtils_layoutList"
print "==========================="
dim i%
for i% = lbound(layoutUtils_layoutList) to ubound(layoutUtils_layoutList)
print _
str$(i%) + ":" + _
str$(layoutUtils_layoutList(i%).id) + ":" + _
layoutUtils_layoutList(i%).layoutName + ":" + _
layoutUtils_layoutList(i%).fullpath + ":" + _
layoutUtils_layoutList(i%).content + ":" + _
layoutUtils_layoutList(i%).resolved + ":" + _
str$(layoutUtils_layoutList(i%).prepared)
next i%
_dest currentDest&
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_dumpElementList
' Purpose : Logs the contents of the layoutUtils_elementList array for debugging.
'
' Description:
' This subroutine iterates through the layoutUtils_elementList array and logs
' each element's index, layout index, position, length, and reference name.
' Useful for inspecting the state of all extracted references.
'------------------------------------------------------------------------------
sub layoutUtils_dumpElementList ()
dim currentDest&
currentDest& = _dest
$CONSOLE
_dest _console
print "============================"
print "dump layoutUtils_elementList"
print "============================"
print "n: layoutIndex: position: length: reference"
dim i%
for i% = lbound(layoutUtils_elementList) to ubound(layoutUtils_elementList)
print _
str$(i%) + ":" + _
str$(layoutUtils_elementList(i%).layoutIndex) + ":" + _
str$(layoutUtils_elementList(i%).position) + ":" + _
str$(layoutUtils_elementList(i%).length) + ":" + _
layoutUtils_elementList(i%).reference
next i%
_dest currentDest&
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_dumpActivatorList
' Purpose : Logs the contents of the layoutUtils_activatorList array for debugging.
'
' Description:
' This subroutine iterates through the layoutUtils_activatorList array and logs
' each activator's index, layout index, position, activator character, color value,
' row, and column. Useful for inspecting the state of all color activators extracted
' from layouts.
'------------------------------------------------------------------------------
sub layoutUtils_dumpActivatorList ()
dim currentDest&
currentDest& = _dest
$CONSOLE
_dest _console
print "=============================="
print "dump layoutUtils_activatorList"
print "=============================="
print "n: layoutIndex: position: activatorChar: colorValue: row: column"
dim i%
for i% = lbound(layoutUtils_activatorList) to ubound(layoutUtils_activatorList)
print _
str$(i%) + ":" + _
str$(layoutUtils_activatorList(i%).layoutIndex) + ":" + _
str$(layoutUtils_activatorList(i%).position) + ":" + _
layoutUtils_activatorList(i%).activatorChar + ":" + _
str$(layoutUtils_activatorList(i%).colorValue) + ":" + _
str$(layoutUtils_activatorList(i%).row) + ":" + _
str$(layoutUtils_activatorList(i%).column)
next i%
_dest currentDest&
end sub
'------------------------------------------------------------------------------
' Subroutine: layoutUtils_dumpColorList
' Purpose : Logs the contents of the layoutUtils_colorList array for debugging.
'
' Description:
' This subroutine iterates through the layoutUtils_colorList array and logs
' each color's index, layout index, color value, and activator character.
' Useful for inspecting the state of all color definitions extracted from layouts.
'------------------------------------------------------------------------------
sub layoutUtils_dumpColorList ()
dim currentDest&
currentDest& = _dest
$CONSOLE
_dest _console
print "=============================="
print "dump layoutUtils_colorList"
print "=============================="
print "n: layoutIndex: value: activatorChar"
dim i%
for i% = lbound(layoutUtils_colorList) to ubound(layoutUtils_colorList)
print _
str$(i%) + ":" + _
str$(layoutUtils_colorList(i%).layoutIndex) + ":" + _
str$(layoutUtils_colorList(i%).value) + ":" + _
layoutUtils_colorList(i%).activatorChar
next i%
_dest currentDest&
end sub
layoutUtilsTesterFunctional.bas
Code: (Select All)
Option _Explicit
' Include layout utility declarations
'$include: '../library/layoutUtils.bi'
' Create two off‐screen buffers (800×600, 32‐bit) for text and graphics
screen _NewImage(800, 600, 32)
dim as single textDest, grafDest
textDest = _NewImage(800, 600, 32)
grafDest = _NewImage(800, 600, 32)
'=====================================================================
' Define first screen layout (User Information) using DATA statements
'=====================================================================
' use the pointed mode (the colorization activators are on dedicated lines).
LAYOUT1:
DATA "$","&",&HFF007FFF,"1",&HE0FFFFFF,"2",&HFF00E000,"3",&HFFB0B000,"4","$"
DATA " "
DATA " +-----------------------------------------------------------------------+"
DATA "& 2"
DATA " | User Information <&pg> |"
DATA "& 1 3 2"
DATA " +-----------------------------------------------------------------------+"
DATA " | |"
DATA " | Last Name <&lastName.................................> |"
DATA "& 1 4 2"
DATA " | First Name <&firstName................................> |"
DATA "& 1 4 2"
DATA " | |"
DATA " | Address: |"
DATA "& 1 2"
DATA " | |"
DATA " | Street Address <&streetAddress......................> |"
DATA "& 1 4 2"
DATA " | Apartment / Suite <&apartmentSuite...................> |"
DATA "& 1 4 2"
DATA " | City <&city...............................> |"
DATA "& 1 4 2"
DATA " | State / Province <&stateProvince.....................> |"
DATA "& 1 4 2"
DATA " | ZIP / Postal <&zipPostal.........................> |"
DATA "& 1 4 2"
DATA " | Country <&country...........................> |"
DATA "& 1 4 2"
DATA " | |"
DATA " +-----------------------------------------------------------------------+"
DATA " [L:list] [N:next] [P:prev] [Q:quit] "
'==================================================================
' Define second screen layout (User List) using DATA statements
'==================================================================
' use the integrated mode (the colorization activators are directly in the text).'
LAYOUT2:
DATA "$"," ",&HFF007FFF,"#",&HE0A0FFFF,"@",&HFF30E400,"*",&HFFB0B020,"?","$"
DATA " "
DATA " #+----------------------------------------------------------------------------+"
DATA " | @User List #|"
DATA " +------------------+------------------+--------------------+-----------------+"
DATA " |@Last Name #|@First Name #|@City #|@State #|"
DATA " +------------------+------------------+--------------------+-----------------+"
DATA " |?<&last1........>#|?<&first1.......>#|?<&city1..........>#|?<&state1......>#|"
DATA " |?<&last2........>#|?<&first2.......>#|?<&city2..........>#|?<&state2......>#|"
DATA " |?<&last3........>#|?<&first3.......>#|?<&city3..........>#|?<&state3......>#|"
DATA " +------------------+------------------+--------------------+-----------------+"
DATA " [R:return] [Q:quit] "
'==================================================================
' Load screen layouts into memory using layoutUtils library
'==================================================================
dim m1%, m2%
restore layout1
m1% = layoutUtils_loadData ("user", 41)
restore layout2
m2% = layoutUtils_loadData ("list", 22)
'==================================================================
' Dump shared data to the console
'==================================================================
layoutUtils_dumpLayoutList
layoutUtils_dumpElementList
layoutUtils_dumpActivatorList
layoutUtils_dumpColorList
'==================================================================
' Define sample database entries (3 users, each with 9 fields)
'==================================================================
TEST1:
DATA "1/3","Smith","John","123 Maple Street","Apt. 4B","Springfield","Illinois","62704","United States"
DATA "2/3","Doe","Jane","456 Oak Avenue","Suite 12","Madison","Wisconsin","53703","United States"
DATA "3/3","Brown","Charlie","789 Pine Road","Unit 5","Portland","Oregon","97205","United States"
' Initialize the in‐memory database array and fill it from TEST1
dim db$(1 To 3, 0 To 8)
restore TEST1
dim i%, j%
for i% = 1 to 3
for j% = 0 to 8
read db$(i%, j%)
next j%
next i%
'==================================================================
' Main loop showing the User Information screen
'==================================================================
dim k$, user%
user% = 1 ' Start with the first user
SCREEN1:
' Populate the layout placeholders for the current user
populateLayout1 m1%, db$(), user%
' Render text layer into off‐screen buffer
refreshDestWithLayout "user", textDest
do
_limit 60 ' Cap frame rate to ~60 FPS
_dest grafDest ' Draw graphics into grafDest
cls ' Clear graphics buffer
drawCircle ' Draw animated circle behind text
_putimage , textDest, grafDest ' Blit text buffer over graphics
_dest 0 ' Set drawing back to actual screen
_putimage , grafDest, 0 ' Present final composed image
select case _keyhit ' Handle key input
case 81 or 113 ' Q or q to quit
system
case 78 or 110 ' N or n to go to next user
if user% < 3 then
user% = user% + 1
exit do
end if
case 80 or 112 ' P or p to go to previous user
if user% > 1 then
user% = user% - 1
exit do
end if
case 76 or 108 ' L or l to show user list
gosub screen2
exit do
end select
loop
goto SCREEN1
'==================================================================
' Subroutine to display the User List screen
'==================================================================
SCREEN2:
' Fill in the list layout with all database entries
populateLayout2 "list", db$()
refreshDestWithLayout "list", textDest
do
_limit 60
_dest grafDest
cls
drawTriangle ' Draw rotating triangle graphic
_putimage , textDest, grafDest
_dest 0
_putimage , grafDest, 0
select case _keyhit
case 81 or 113 ' Q or q to quit
system
case 82 or 114 ' R or r to return to info screen
exit do
end select
loop
return ' Return back to SCREEN1 loop
system ' Exit the program
'==================================================================
' Subroutine: populateLayout1
' Fills placeholders in the User Information layout
'==================================================================
sub populateLayout1 (layoutId as integer, database() as string, user as integer)
layoutUtils_resetIndex layoutId
layoutUtils_populateIndex layoutId, "pg", database(user, 0)
layoutUtils_populateIndex layoutId, "lastName", database(user, 1)
layoutUtils_populateIndex layoutId, "firstName", database(user, 2)
layoutUtils_populateIndex layoutId, "streetAddress", database(user, 3)
layoutUtils_populateIndex layoutId, "apartmentSuite", database(user, 4)
layoutUtils_populateIndex layoutId, "city", database(user, 5)
layoutUtils_populateIndex layoutId, "stateProvince", database(user, 6)
layoutUtils_populateIndex layoutId, "zipPostal", database(user, 7)
layoutUtils_populateIndex layoutId, "country", database(user, 8)
end sub
'==================================================================
' Subroutine: populateLayout2
' Fills placeholders in the User List layout
'==================================================================
sub populateLayout2 (layoutName as string, database() as string)
layoutUtils_reset layoutName
layoutUtils_populate layoutName, "last1", database(1, 1)
layoutUtils_populate layoutName, "first1", database(1, 2)
layoutUtils_populate layoutName, "city1", database(1, 5)
layoutUtils_populate layoutName, "state1", database(1, 6)
layoutUtils_populate layoutName, "last2", database(2, 1)
layoutUtils_populate layoutName, "first2", database(2, 2)
layoutUtils_populate layoutName, "city2", database(2, 5)
layoutUtils_populate layoutName, "state2", database(2, 6)
layoutUtils_populate layoutName, "last3", database(3, 1)
layoutUtils_populate layoutName, "first3", database(3, 2)
layoutUtils_populate layoutName, "city3", database(3, 5)
layoutUtils_populate layoutName, "state3", database(3, 6)
end sub
'==================================================================
' Subroutine: refreshDestWithLayout
' Renders a populated layout into the specified destination buffer
'==================================================================
sub refreshDestWithLayout (layoutName as string, destination as double)
dim prevDestination
prevDestination = _dest ' Remember previous drawing target
_dest destination ' Switch to the off‐screen buffer
cls , _rgba(0,0,0,0) ' Clear with fully transparent background
_printmode _KEEPBACKGROUND ' Preserve any background pixels
layoutUtils_printWithColors(layoutName) ' Print layout text
_dest prevDestination ' Restore the original drawing target
end sub
'==================================================================
' Subroutine: drawCircle
' Draws an animated pulsing circle at the center of the screen
'==================================================================
sub drawCircle ()
static growing as integer
static rFactor as single
dim cx%, cy%, r%
cx% = _width / 2
cy% = _height / 2
' Initialize animation parameters on first run
if rFactor = 0 then
rFactor = 0.10
growing = 1
end if
' Pulse the radius factor up and down
if growing then
rFactor = rFactor + 0.001
if rFactor >= 0.48 then growing = 0
else
rFactor = rFactor - 0.001
if rFactor <= 0.10 then growing = 1
end if
r% = rFactor * _min(_width, _height)
circle (cx%, cy%), r%, _rgba(255,255,0,100)
end sub
'==================================================================
' Subroutine: drawTriangle
' Draws a continuously rotating semi‐transparent triangle
'==================================================================
sub drawTriangle ()
static angleOffset#
angleOffset# = angleOffset# - _pi / 180 ' Decrease rotation by 1° each frame
dim cx%, cy%, r%
cx% = _width / 2
cy% = _height / 2
r% = 0.48 * _min(_width, _height)
' Calculate the three vertex angles
dim angle1#, angle2#, angle3#
angle1# = angleOffset#
angle2# = angleOffset# + 2 * _pi / 3
angle3# = angleOffset# + 4 * _pi / 3
' Compute vertex coordinates
dim x1%, y1%, x2%, y2%, x3%, y3%
x1% = cx% + r% * cos(angle1#)
y1% = cy% - r% * sin(angle1#)
x2% = cx% + r% * cos(angle2#)
y2% = cy% - r% * sin(angle2#)
x3% = cx% + r% * cos(angle3#)
y3% = cy% - r% * sin(angle3#)
' Draw triangle edges
line (x1%, y1%)-(x2%, y2%), _rgba(0,255,0,100)
line (x2%, y2%)-(x3%, y3%), _rgba(0,255,0,100)
line (x3%, y3%)-(x1%, y1%), _rgba(0,255,0,100)
end sub
' Include the binary module for layoutUtils implementation
'$include: '../library/layoutUtils.bm'
|