using function in externalising multiple times with different parameters

Hi I’m trying to create a module with a function that will take a text string and display it as a digitype effect with a leading blinking cursor.

I know there’s probably code online to do this but it’s a learning exercise.

I’ve got two problems and I wonder if anyone could offer any guidance?

1- I can link to the external module and pass a string to the function to display, but when I add a second instance, the second params are used instead of the first and I get errors, even though I’m setting the function instance as a different local variable name and the params table has a different name.

2 - I can set up a preceding flashing cursor for single line text but the problem with multi line text is the word wrap. Is there any way to detect a new line, or the x and y position of the last character prInted?

Many thanks.

Can you paste in an example of what you’ve done so far? I’m finding it a bit hard to visualise what your module is doing based on your explanation.

Hi Alan, thanks for your reply - I posted this using my phone earlier during the commute to work so my dreary state and the auto-correct has done a proper job on clarity!

Don’t think I can change the title so here’s a new one:

‘Using a function in an external module a number of times with different parameters’

Basically I’m trying to learn how to put functions in external modules and bring them in as needed in the central body of code.

I’d like to enter a string of text in main.lua and have an external module function display that in a typed effect.

The first problem is that when I use a function a second time with new parameters, the new text string replaces the first and I get errors. 

The second problem is the positioning of the cursor when it comes to multi-line text. With a single line of text I used myText.width to position it - it works well. With multiline text I tried counting characters and replacing them with a space, typing over the text string and then concatenating with the cursor on the end… except that word wrap causes the text to jump to a new line leaving the cursor behind. Any ideas here would be much appreciated.

Here’s my code:


----- myFunc.lua -------


[lua]

local M = {}

function M.digitype (params)

local key = media.newEventSound( “key.wav” )

– bring in params from main.lua

    local string = params.text     

    local x = params.x

    local y = params.y

    local width = params.width  

    local height = params.height   --required for multi-line and alignment

    local font = params.font

    local fontSize = params.fontSize

    local align = params.align

– Display text

local myText = display.newText( string, x, y, width, height, font, fontSize )

myText:setFillColor = (1, 1, 1)

myText.anchorX = 0

myText.anchorY = 0

– Display cursor

local cursor = display.newText( “_” , x, y, width, height, font, fontSize )

cursor.anchorY = 0

cursor.anchorX = 0

cursor.alpha = 1

local function cursorBlink ()

if cursor.alpha == 1 then cursor.alpha = 0

elseif cursor.alpha == 0 then cursor.alpha = 1

end

end

timer.performWithDelay ( 400, cursorBlink, 0)

– Put string characters into a table

typer = {}

length = string.len( string )

for i = 1, length do

table.insert( typer, i, string:sub(1, i))

print ( typer[i])

end

– Change the display text sequentially to type to screen

– (I’ve played around with this and it may now be incorrect)

local a = 1

function typo ()

myText.text = “” … typer[a]

cursor.x = (myText.width+(fontSize/3))

media.playEventSound( key )

a=a+1

timer.performWithDelay( 62, typo, length)

end

return M


– main.lua ------


local M = require (“myFunc”)

local options = 

{

    text = “First text string”,     

    x = 100,

    y = 200,

    width = 300,  – for multiline text

    height = 400, – for multiline text

    font = “native.systemFont”, 

    fontSize = 20,

    color = {1, 1, 1}

}

local firstFunction = M.digitype (options)

local options2 = 

{

    text = “Second text string”,     

    x = 100,

    y = 300,

    width = 150,  – for multiline text

    height = 200, – for multiline text

    font = “native.systemFont”, 

    fontSize = 20,

    color = {1, 1, 1}

}

local secondFunction= M.digitype (options2)

[/lua]

Ok so I ran your code as it was pasted here and it failed to run because of what I assume were typos.

Line 21 should not have an equals sign in it:

--instead of this myText:setFillColor = (1, 1, 1) --should be this myText:setFillColor(1, 1, 1)

In your params in main.lua, if you are using a specific font then the font name should be surrounded in quotes. However you are using native.systemFont, so you should not use quotes otherwise it will search for a font called “native.systemFont” rather than whichever font is assigned to the native.systemFont variable. e.g:

--this is ok font = native.systemFont, --this is not font = "native.systemFont", --this is ok font = "Arial", --this is not font = Arial,

Also, there was an “end” missing at the bottom. I’m not sure if pasting into the forum messed up your formatting, but as soon as I fixed the indenting so that function beginning/end was not in the same column as the content inside it was much easier to see.

i.e this is difficult to keep track of where each function ends, especially if nested:

function M.digitype (params) local function cursorBlink () if cursor.alpha == 1 then cursor.alpha = 0 elseif cursor.alpha == 0 then cursor.alpha = 1 end end timer.performWithDelay ( 400, cursorBlink, 0) typer = {} length = string.len( string ) for i = 1, length do table.insert( typer, i, string:sub(1, i)) print ( typer[i]) end local a = 1 function typo () myText.text = "" .. typer[a] cursor.x = (myText.width+(fontSize/3)) a=a+1 timer.performWithDelay( 62, typo, length) end end

Whereas this is slightly easier:

function M.digitype (params) local function cursorBlink () if cursor.alpha == 1 then cursor.alpha = 0 elseif cursor.alpha == 0 then cursor.alpha = 1 end end timer.performWithDelay ( 400, cursorBlink, 0) typer = {} length = string.len( string ) for i = 1, length do table.insert( typer, i, string:sub(1, i)) print ( typer[i]) end local a = 1 function typo () myText.text = "" .. typer[a] cursor.x = (myText.width+(fontSize/3)) a=a+1 timer.performWithDelay( 62, typo, length) end end

As I say, perhaps your code was already indented nicely and the forum messed it up, but since this is the newbie forum I just thought I’d point that out.

Onto your issue with the second string replacing the first.

typer = {}

should be 

local typer = {}

You created a global variable. So when your second function was called it would overwrite the data in that global table, rather than create a new variable. As a general rule in Lua, if data passed to one object/function is affecting another then there is probably a global variable/function to blame somewhere.

I haven’t had a change to look into your cursor position issue I’m afraid, hopefully someone else can jump in on that one.

Edit: your function called “typo” should probably be local too, currently it’s a global. If it’s any use, I also tried adjusting the length of time between timer calls to that function to use random numbers (to more accurately replicate a person typing)

local function typo () myText.text = "" .. typer[a] cursor.x = (myText.contentWidth+(fontSize/3)) media.playEventSound( key ) a=a+1 --call this function again if we have not reached the end of the string if typer[a] then timer.performWithDelay( math.random(30, 200), typo, 1) end end timer.performWithDelay( math.random(30, 200), typo, 1)

Hi Alan thank you very much that’s really a great help. Sorry for posting bad code - all your advice is very much appreciated. I love the random number tweak.

Just had a thought about a fairly simply way to position the cursor, it’s not perfect but hopefully can give you something to work with. 

In main.lua I’ve changed the height param in the table passed into M.digitype to 0:

local options2 = { text = "Second text string Second text string Second text string", x = 100, y = 300, width = 150, -- for multiline text height = 0, -- for multiline text font = native.systemFont, fontSize = 20, color = {1, 1, 1} } local secondFunction= M.digitype (options2)

This still allows multitext (as long as width is set), but allows it to resize itself as it goes. So when there are only 5 characters in the string the height will be 24 pixels for instance, but when there are 15 chars and the text wraps to the next line the text object height will be 48 chars.

For testing, I also added a bit more text to the string just so that I could see the effect for longer.

Now for your function:

local letters = {} local length = string.len( string ) local a = 1 local numLines = nil local lastKnownHeight = 0 local cursorX = myText.x local letterWidth = 0 local newLetter for i = 1, length do     table.insert( letters, i, string:sub(i, i))     --print ( letters[i]) end local function typo ()     myText.text = myText.text..letters[a]     if myText.contentHeight \> lastKnownHeight then         lastKnownHeight = myText.contentHeight         if numLines then             numLines = numLines + 1         else              numLines = 0         end         cursorX = myText.x     end     newLetter = display.newText( letters[a], x, y, font, fontSize )     letterWidth = newLetter.contentWidth     newLetter:removeSelf()     newLetter = nil     cursorX = cursorX + letterWidth     cursor.x = cursorX     cursor.y = myText.y + (numLines \* 24)     a=a+1          if letters[a] then         timer.performWithDelay( math.random(30, 200), typo, 1)     end end timer.performWithDelay( math.random(30, 200), typo, 1)

lastKnownHeight is the height of the text field. I track this so that when it changes, I know that the text field has wrapped the text onto a new line:

if myText.contentHeight \> lastKnownHeight then

I then increment the variable numLines by 1 (except on the first call when I initially set it). The reason I set it to 0 for the first line is because it’s really an offset i.e. how many ‘extra’ lines of text are there.

This is then multiplied with another value of your choosing (I just hardcoded 24 but you should use something based on the fontSize I guess) to reposition the y position of the cursor:

cursor.y = myText.y + (numLines \* 24)

Instead of storing increasing substrings, I’ve just stored the individual chars from the string. e.g. {“S”, “e”, “c” …etc} instead of {“S”, “Se”, “Sec” …etc}

Which I add to the text object by taking the existing string and concatenating the new char to it.

myText.text = myText.text..letters[a]

The reason for this is so that I can find out how big each letter is and assign it to the letterWidth variable:

newLetter = display.newText( letters[a], x, y, font, fontSize ) letterWidth = newLetter.contentWidth newLetter:removeSelf() newLetter = nil

In addition to adding the letter to existing text object, I make a new text object with just the next letter, find out how wide it is, and then immediately remove it. Corona will do this so quickly that you will not see the extra text object.

I then use this it move the x position of the cursor:

cursorX = cursorX + (fontSize \* 0.75) cursor.x = cursorX

When the text wrap triggers the contentHeight if statement, I reset cursorX back to the start of the textfield.

cursorX = myText.x

The main issue that remains is that when a word wraps onto a new line, I don’t know how many chars from that word already existed in order to offset the initial cursor x position on the new line.

E.g. if line 1 is in the process of typing the word “string” but runs out of space after typing “stri”, it will move the word down onto the next line.

Line 2 now already has the letters “stri” in it, so you need to offset the x position so that it starts further across.

One thing you could do (if this is all going to be predefined text rather than user input), is break up the lines of text into multiple text objects so that you never see any word wrapping. E.g.

"This is my" --call M.digitype (options1) "very long string" --call M.digitype (options2) "which does not" --call M.digitype (options3) "fit on one line." --call M.digitype (options4)

Hopefully that’s enough to get you going though  :slight_smile:

P.S. I typed some of this out this morning, and some during lunch, so there may be the odd typo to look out for.

Hi Alan, again thanks very much that’s really given me a lot of help, I’ve learned a lot from you. I’ll sit down and go through step by step but the text won’t be user input and so maybe your line by line method could be the way for me to go.

Can you paste in an example of what you’ve done so far? I’m finding it a bit hard to visualise what your module is doing based on your explanation.

Hi Alan, thanks for your reply - I posted this using my phone earlier during the commute to work so my dreary state and the auto-correct has done a proper job on clarity!

Don’t think I can change the title so here’s a new one:

‘Using a function in an external module a number of times with different parameters’

Basically I’m trying to learn how to put functions in external modules and bring them in as needed in the central body of code.

I’d like to enter a string of text in main.lua and have an external module function display that in a typed effect.

The first problem is that when I use a function a second time with new parameters, the new text string replaces the first and I get errors. 

The second problem is the positioning of the cursor when it comes to multi-line text. With a single line of text I used myText.width to position it - it works well. With multiline text I tried counting characters and replacing them with a space, typing over the text string and then concatenating with the cursor on the end… except that word wrap causes the text to jump to a new line leaving the cursor behind. Any ideas here would be much appreciated.

Here’s my code:


----- myFunc.lua -------


[lua]

local M = {}

function M.digitype (params)

local key = media.newEventSound( “key.wav” )

– bring in params from main.lua

    local string = params.text     

    local x = params.x

    local y = params.y

    local width = params.width  

    local height = params.height   --required for multi-line and alignment

    local font = params.font

    local fontSize = params.fontSize

    local align = params.align

– Display text

local myText = display.newText( string, x, y, width, height, font, fontSize )

myText:setFillColor = (1, 1, 1)

myText.anchorX = 0

myText.anchorY = 0

– Display cursor

local cursor = display.newText( “_” , x, y, width, height, font, fontSize )

cursor.anchorY = 0

cursor.anchorX = 0

cursor.alpha = 1

local function cursorBlink ()

if cursor.alpha == 1 then cursor.alpha = 0

elseif cursor.alpha == 0 then cursor.alpha = 1

end

end

timer.performWithDelay ( 400, cursorBlink, 0)

– Put string characters into a table

typer = {}

length = string.len( string )

for i = 1, length do

table.insert( typer, i, string:sub(1, i))

print ( typer[i])

end

– Change the display text sequentially to type to screen

– (I’ve played around with this and it may now be incorrect)

local a = 1

function typo ()

myText.text = “” … typer[a]

cursor.x = (myText.width+(fontSize/3))

media.playEventSound( key )

a=a+1

timer.performWithDelay( 62, typo, length)

end

return M


– main.lua ------


local M = require (“myFunc”)

local options = 

{

    text = “First text string”,     

    x = 100,

    y = 200,

    width = 300,  – for multiline text

    height = 400, – for multiline text

    font = “native.systemFont”, 

    fontSize = 20,

    color = {1, 1, 1}

}

local firstFunction = M.digitype (options)

local options2 = 

{

    text = “Second text string”,     

    x = 100,

    y = 300,

    width = 150,  – for multiline text

    height = 200, – for multiline text

    font = “native.systemFont”, 

    fontSize = 20,

    color = {1, 1, 1}

}

local secondFunction= M.digitype (options2)

[/lua]

Ok so I ran your code as it was pasted here and it failed to run because of what I assume were typos.

Line 21 should not have an equals sign in it:

--instead of this myText:setFillColor = (1, 1, 1) --should be this myText:setFillColor(1, 1, 1)

In your params in main.lua, if you are using a specific font then the font name should be surrounded in quotes. However you are using native.systemFont, so you should not use quotes otherwise it will search for a font called “native.systemFont” rather than whichever font is assigned to the native.systemFont variable. e.g:

--this is ok font = native.systemFont, --this is not font = "native.systemFont", --this is ok font = "Arial", --this is not font = Arial,

Also, there was an “end” missing at the bottom. I’m not sure if pasting into the forum messed up your formatting, but as soon as I fixed the indenting so that function beginning/end was not in the same column as the content inside it was much easier to see.

i.e this is difficult to keep track of where each function ends, especially if nested:

function M.digitype (params) local function cursorBlink () if cursor.alpha == 1 then cursor.alpha = 0 elseif cursor.alpha == 0 then cursor.alpha = 1 end end timer.performWithDelay ( 400, cursorBlink, 0) typer = {} length = string.len( string ) for i = 1, length do table.insert( typer, i, string:sub(1, i)) print ( typer[i]) end local a = 1 function typo () myText.text = "" .. typer[a] cursor.x = (myText.width+(fontSize/3)) a=a+1 timer.performWithDelay( 62, typo, length) end end

Whereas this is slightly easier:

function M.digitype (params) local function cursorBlink () if cursor.alpha == 1 then cursor.alpha = 0 elseif cursor.alpha == 0 then cursor.alpha = 1 end end timer.performWithDelay ( 400, cursorBlink, 0) typer = {} length = string.len( string ) for i = 1, length do table.insert( typer, i, string:sub(1, i)) print ( typer[i]) end local a = 1 function typo () myText.text = "" .. typer[a] cursor.x = (myText.width+(fontSize/3)) a=a+1 timer.performWithDelay( 62, typo, length) end end

As I say, perhaps your code was already indented nicely and the forum messed it up, but since this is the newbie forum I just thought I’d point that out.

Onto your issue with the second string replacing the first.

typer = {}

should be 

local typer = {}

You created a global variable. So when your second function was called it would overwrite the data in that global table, rather than create a new variable. As a general rule in Lua, if data passed to one object/function is affecting another then there is probably a global variable/function to blame somewhere.

I haven’t had a change to look into your cursor position issue I’m afraid, hopefully someone else can jump in on that one.

Edit: your function called “typo” should probably be local too, currently it’s a global. If it’s any use, I also tried adjusting the length of time between timer calls to that function to use random numbers (to more accurately replicate a person typing)

local function typo () myText.text = "" .. typer[a] cursor.x = (myText.contentWidth+(fontSize/3)) media.playEventSound( key ) a=a+1 --call this function again if we have not reached the end of the string if typer[a] then timer.performWithDelay( math.random(30, 200), typo, 1) end end timer.performWithDelay( math.random(30, 200), typo, 1)

Hi Alan thank you very much that’s really a great help. Sorry for posting bad code - all your advice is very much appreciated. I love the random number tweak.

Just had a thought about a fairly simply way to position the cursor, it’s not perfect but hopefully can give you something to work with. 

In main.lua I’ve changed the height param in the table passed into M.digitype to 0:

local options2 = { text = "Second text string Second text string Second text string", x = 100, y = 300, width = 150, -- for multiline text height = 0, -- for multiline text font = native.systemFont, fontSize = 20, color = {1, 1, 1} } local secondFunction= M.digitype (options2)

This still allows multitext (as long as width is set), but allows it to resize itself as it goes. So when there are only 5 characters in the string the height will be 24 pixels for instance, but when there are 15 chars and the text wraps to the next line the text object height will be 48 chars.

For testing, I also added a bit more text to the string just so that I could see the effect for longer.

Now for your function:

local letters = {} local length = string.len( string ) local a = 1 local numLines = nil local lastKnownHeight = 0 local cursorX = myText.x local letterWidth = 0 local newLetter for i = 1, length do     table.insert( letters, i, string:sub(i, i))     --print ( letters[i]) end local function typo ()     myText.text = myText.text..letters[a]     if myText.contentHeight \> lastKnownHeight then         lastKnownHeight = myText.contentHeight         if numLines then             numLines = numLines + 1         else              numLines = 0         end         cursorX = myText.x     end     newLetter = display.newText( letters[a], x, y, font, fontSize )     letterWidth = newLetter.contentWidth     newLetter:removeSelf()     newLetter = nil     cursorX = cursorX + letterWidth     cursor.x = cursorX     cursor.y = myText.y + (numLines \* 24)     a=a+1          if letters[a] then         timer.performWithDelay( math.random(30, 200), typo, 1)     end end timer.performWithDelay( math.random(30, 200), typo, 1)

lastKnownHeight is the height of the text field. I track this so that when it changes, I know that the text field has wrapped the text onto a new line:

if myText.contentHeight \> lastKnownHeight then

I then increment the variable numLines by 1 (except on the first call when I initially set it). The reason I set it to 0 for the first line is because it’s really an offset i.e. how many ‘extra’ lines of text are there.

This is then multiplied with another value of your choosing (I just hardcoded 24 but you should use something based on the fontSize I guess) to reposition the y position of the cursor:

cursor.y = myText.y + (numLines \* 24)

Instead of storing increasing substrings, I’ve just stored the individual chars from the string. e.g. {“S”, “e”, “c” …etc} instead of {“S”, “Se”, “Sec” …etc}

Which I add to the text object by taking the existing string and concatenating the new char to it.

myText.text = myText.text..letters[a]

The reason for this is so that I can find out how big each letter is and assign it to the letterWidth variable:

newLetter = display.newText( letters[a], x, y, font, fontSize ) letterWidth = newLetter.contentWidth newLetter:removeSelf() newLetter = nil

In addition to adding the letter to existing text object, I make a new text object with just the next letter, find out how wide it is, and then immediately remove it. Corona will do this so quickly that you will not see the extra text object.

I then use this it move the x position of the cursor:

cursorX = cursorX + (fontSize \* 0.75) cursor.x = cursorX

When the text wrap triggers the contentHeight if statement, I reset cursorX back to the start of the textfield.

cursorX = myText.x

The main issue that remains is that when a word wraps onto a new line, I don’t know how many chars from that word already existed in order to offset the initial cursor x position on the new line.

E.g. if line 1 is in the process of typing the word “string” but runs out of space after typing “stri”, it will move the word down onto the next line.

Line 2 now already has the letters “stri” in it, so you need to offset the x position so that it starts further across.

One thing you could do (if this is all going to be predefined text rather than user input), is break up the lines of text into multiple text objects so that you never see any word wrapping. E.g.

"This is my" --call M.digitype (options1) "very long string" --call M.digitype (options2) "which does not" --call M.digitype (options3) "fit on one line." --call M.digitype (options4)

Hopefully that’s enough to get you going though  :slight_smile:

P.S. I typed some of this out this morning, and some during lunch, so there may be the odd typo to look out for.

Hi Alan, again thanks very much that’s really given me a lot of help, I’ve learned a lot from you. I’ll sit down and go through step by step but the text won’t be user input and so maybe your line by line method could be the way for me to go.