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
layoutUtils.bm
And also a test program to demonstrate how it works and why it’s useful.
layoutUtilsTesterFunctional.bas
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'

