How to create textbox in love2d - lua

I have been trying to make a box for a user to input data once the user clicks the box. This is what I have so far, but clicking the box does not cause text input to be added to it. Where is the issue?
function love.load ()
txt1 = ""
columnx = { 50, 160, 260, 375, 495, 600, 710 }
columny = { 130, 230, 330, 440, 540, 640 }
if show1 == true then
function love.textinput(t)
txt1 = t
end
end
end
function love.mousepressed (x, y)
if
x >= columnx[4] and
x <= 435 and
y >= columny[1] and
y >= 145 then
show1 = true
end
end
function love.draw ()
love.graphics.print(txt1, columnx[4], columny[1])
end

You're pretty much on the way, but I'll give you some tips on how to create a basic textbox.
Firstly, consider a conceptual textbox to be a singular table, that contains everything it needs to know about itself to properly update and render. It's much easier to reason about something that is self contained.
A textbox needs to know its position, its size, whether it is active, and which text it contains. We can condense that into a table that looks like the following.
local textbox = {
x = 40,
y = 40,
width = 400,
height = 200,
text = '',
active = false,
colors = {
background = { 255, 255, 255, 255 },
text = { 40, 40, 40, 255 }
}
}
(We also store some color info.)
After that the simple way to add text is through love.textinput, as you've seen. In your code we only check if the textbox is active once, in love.load, which it of course isn't since we likely haven't taken any user input yet. Instead of attempting to overload the function, we simply check if the textbox is active or not inside the handler, and proceed accordingly.
function love.textinput (text)
if textbox.active then
textbox.text = textbox.text .. text
end
end
We've covered how to check if the user has clicked within a rectangular area in the question: Love2d cursor positions. We want to deactivate the textbox if it is currently active and the user clicks outside of its space.
function love.mousepressed (x, y)
if
x >= textbox.x and
x <= textbox.x + textbox.width and
y >= textbox.y and
y <= textbox.y + textbox.height
then
textbox.active = true
elseif textbox.active then
textbox.active = false
end
end
And finally we need to render our textbox. We use unpack to expand our color tables, and love.graphics.printf to make sure our text wraps within our textbox's space.
function love.draw ()
love.graphics.setColor(unpack(textbox.colors.background))
love.graphics.rectangle('fill',
textbox.x, textbox.y,
textbox.width, textbox.height)
love.graphics.setColor(unpack(textbox.colors.text))
love.graphics.printf(textbox.text,
textbox.x, textbox.y,
textbox.width, 'left')
end
These are the basic ideas of creating a very rough textbox. It's not perfect. Note that you will need to consider what happens when the text gets longer height-wise than we initially set our textbox's height to, as the two are only loosely related.
To make your program easier to read, and easier to extend, everything you've seen above should really be placed into their own functions that handle textbox tables, instead of cluttering the love handlers with common code. Take a look at Chapter 16 of Programming in Lua, which covers Object-Oriented Programming - a generally vital topic for game development.
See the love.textinput page on how to handle a backspace, to delete characters.
Some extra things to think about:
How can we distinguish an active textbox from an inactive one?
How can we create a list of textboxes, so we can have multiple on screen (but only one active)?

I guess, the love.textinput() callback function is what you are looking for. But as the term 'callback' implies, it will be called by the LÖVE engine, not your code. It will be called whenever the user inputs some text while your game is running. Therefore you need to put it outside the love.load() function.
In the love2d.org wiki is an example for this (the lower one).
As for your example, move the love.textinput() out of love.load() and add an if statement:
function love.load()
txt1 = ""
columnx = {50, 160, 260, 375, 495, 600, 710}
columny = {130, 230, 330, 440, 540, 640}
end
function love.textinput(t)
if (show1) then
txt1 = txt1 .. t
end
end
-- The rest of your code.
-- And maybe mix it with the 'backspace example' from the wiki...
-- But you also might want to have some function to set 'show1' back to 'false' after the text input. Maybe something like this:
function love.keypressed(key)
if (key == "return") and (show1) then
show1 = false
end
end
I hope I could help you a bit!

Related

Lua get table that function is attached to

Hello I want to get the table that a function is attached to, I can't really find a nice way to explain it but I think i've explained it good enough within the code for. Basically I need to get the table that the function is attached to from another function, without passing in the table.
function DrawRect()
print(debug.getinfo(1).name) -- this gets the name of the function that is invoking DrawRect ('Paint')..
-- I want to be able to get the table that is attached to this function
-- So I can do table.x inside this function, and have it print 123
end
local r = math.random(1, 100)
_G["abc" .. r] = {
x = 123,
Paint = function(self)
DrawRect()
end
}
_G["abc" .. r]:Paint()
Example of the problem i'm trying to solve
This is my current code right now
function DrawRect(x,y,w,h)
draw.DrawRect(x,y,w,h)
end
local Button = {
Init = function(self)
self.label = gui.Label("Button")
self.label:SetPos(10, 5) -- see the position is relative to the Button's position
self.label:SetColor(255,255,255)
end,
Paint = function(self,x,y,w,h)
Color(40,40,40)
DrawRect(x,y,w,h) -- Draws dark background
end
}
As you can see paint has 4 args, x,y,w,h. I want to do away with x,y and only have w,h. I want to achieve this like this.
function DrawRect(x,y,w,h)
local relative_x = parent_table_of_paint.INTERNAL.draw_x
local relative_y = parent_table_of_paint.INTERNAL.draw_y
draw.DrawRect(relative_x + x, relative_y + y,w,h)
end
local Button = {
Init = function(self)
self.label = gui.Label("Button")
self.label:SetPos(10, 5) -- see the position is relative to the Button's position
self.label:SetColor(255,255,255)
end,
Paint = function(self,w,h)
Color(40,40,40)
DrawRect(0,0,w,h) -- Draws dark background
end
}
I know you can't see some of the properties in my example, but they are there.
Edit 2:
I am recreating the a frame, "VGUI".
https://wiki.facepunch.com/gmod/draw.RoundedBox
draw.RoundedBox( number cornerRadius, number x, number y, number width, number height, table color )
and as you can see this has the functionality I want
https://wiki.facepunch.com/gmod/PANEL:Paint
local panel = vgui.Create( "DPanel" )
panel:SetSize( 100, 100 )
panel:SetPos( ScrW() / 2 - 50, ScrH() / 2 - 50 )
function panel:Paint( w, h )
draw.RoundedBox( 8, 0, 0, w, h, Color( 0, 0, 0 ) )
end
A function is a first class value, so there may be many tables and variables that have a reference to the same function. There is no way for that function to know what those tables and variables are.
That's not how you would implement something like that. You don't implement a function that through some magic gets its caller's container so it can access its other values.
DrawRect should just draw a rect. If you want to draw a rect somewhere else you should provide that offset through the parameters of DrawRect.
I modified your button so it will simply put the buttons x and y coordinates (if they exist) into DrawRect
local Button = {
Init = function(self)
self.label = gui.Label("Button")
self.label:SetPos(10, 5) -- see the position is relative to the Button's position
self.label:SetColor(255,255,255)
end,
Paint = function(self,w,h)
Color(40,40,40)
local x = self.x or 0
local y = self.y or 0
DrawRect(x,y,w,h) -- Draws dark background
end
}
That way you can call Button:Paint(20,30) to draw a 20 by 30 rectangle at the buttons coordinates. If you want to add an offset, do that outside of DrawRect
# Edit 2:
This is something different. The host program will call the Paint function.
vgui.Create( "DPanel" ) will return a new panel instance and the game will add a reference to it to a list. Everytime the gui is updated it will call the Paint functions of all the panels in that list. That's how the function knows width and height of the panel.

Am I able to use table.concat as a set of arguments?

I'm using LOVE2D to get used to lua a bit more, and I am trying to call a function to make a circle appear on screen, there's 5 arguments, and I have a table known as 'button' with the required arguments in it. I want to use table.concat to to fill in all the blank arguments but it won't let me. Is there anyway to do this?
function toRGB(r,g,b)
return r/255,g/255,b/255
end
function love.load()
button = {}
button.mode = "fill"
button.x = 0
button.y = 0
button.size = 30
end
function love.draw()
love.graphics.setColor(toRGB(60,60,60))
love.graphics.circle(table.concat(button))
end
table.concat returns a string. That's not what you want.
To get a list of table elements use table.unpack. But this function does only work with tables that have consecutive numeric indices starting from 1.
Also love.graphics.circle accesses its parameters by position, not by name. Hence you have to ensure that the expression list you put into that function has the right order.
So something like:
button = {"fill", 0, 0, 30}
love.graphics.circle(table.unpack(button))
would work.
If you're using other table keys as in your example you'll have to write a function that returns the values in the right order.
In the simplest case
button = {}
button.mode = "fill"
button.x = 0
button.y = 0
button.size = 30
button.unpack = function() return button.mode, button.x, button.y, button.size end
love.graphics.circle(button.unpack())
Or you can do something like this:
function drawCircle(params)
love.graphics.circle(params.mode, params.x, params.y, params.size)
end
drawCircle(button)
There are many other ways to achieve this.

How to change image based on number of taps in Corona?

I'm using Corona SDK and currently I have this. When you tap on the image the number increases. I was wonder how would I change the image once it reached a certain amount of clicks?
display.setStatusBar(display.HiddenStatusBar)
local newButton = display.newImage ("button.png",0,0)
newButton.x = display.contentWidth - 60
newButton.y = display.contentHeight - 62.5
local number = 0
local textField = display.newText(number, 30, 30, native.systemFont, 25)
local function moveButtonRandom(event)
number = number + 1
textField:removeSelf()
textField = display.newText(number, 30, 30, native.systemFont, 25)
end
newButton:addEventListener("tap", moveButtonRandom)
The best practice would be to use a sprite and when you increment to a certain point, tell the sprite to play the next frame.
There are multiple tutorial on working with imageSheets and sprites here: http://coronalabs.com/resources/tutorials/images-audio-video-animation/
Rob
You can make an if then decision statement in your function and use number as argument. When statement true switch the pictures.
Example: if number >= 3 then
--[[after 3 clicks Remove existing picture and add new picture code executes here--]]
End
Good luck
For example if you want an image to show up when you get to 15 clicks, you can modify your tap function to look like this, so that the number gets replaced by an image:
local function moveButtonRandom(event)
number = number + 1
-- Here goes our if check
if number >= 15 then
-- The number of clicks is 15 or more, replace the numbers with an image
textField:removeSelf()
textField = display.newImage("yourImage.png")
else
-- Do things the 'normal' way
textField:removeSelf()
textField = display.newText(number, 30, 30, native.systemFont, 25)
end
end
If you dont want the image to replace the text, then create a new local variable at the top of your file, and assign it inside the if check

Corona SDK display.remove() in lua

this code (lua) gives you a random value from the table "local a" by pressing the text "new". Unfortunately the new random value just appears above the old one. I've tried to remove the old value e.g. with display.remove(mmDis), but it doesn't work.
The second problem is that sometimes I also get back the value "nil" and not only the four entries from the table.
Both things must be easy to solve, but as newbie to lua and working on these small things for almost 4 hours now I just don't get what to change to make it work.
-- references
local mmDis
-- functions
function randomText(event)
display.remove(mmDis)
local a = {"Banana!","Apple!","Potato","Pie"}
com = (a[math.random(0.5,#a)])
local mmDis = display.newText(tostring(com),
display.contentWidth*0.57, display.contentHeight*0.7,
display.contentWidth*0.9, display.contentHeight*0.8, "Calibri", 60)
end
-- menu button
local textnew = display.newText("New", 0, 0, "Calibri", 40)
textnew.x = display.contentWidth*0.2
textnew.y = display.contentHeight*0.9
textnew:addEventListener ("tap", randomText )
It's hard to understand what you are trying to do because when you create textnew you have it in one position and size, while in randomeText() you seem to want to replace that text object with a new one, but you put it at a different pos and size. It appears you want to change the object's text every time you press on tap; in this case, you don't need to replace the text object, you just replace its text.
Also:
you have two "local mmDis", the second one will hide the first, not sure what you're after there
read carefully the docs for math.random
com is already a string so you don't need tostring
Try this code and let me know if this is not what you are after:
local menuTextOptions = {
text = "New",
x = display.contentWidth*0.2,
y = display.contentHeight*0.9,
align = 'left',
font = "Calibri",
fontSize = 40,
}
local textnew = display.newText(menuTextOptions)
-- functions
function randomText(event)
local a = {"Banana!","Apple!","Potato","Pie"}
local com = a[math.random(1,#a)]
textnew.text = com
-- if you want to change position too:
-- textnew.x = display.contentWidth*0.2
-- textnew.y = display.contentHeight*0.9
-- if you want to change size too, but only used for multiline text:
-- textnew.width = display.contentWidth*0.9
-- textnew.height = display.contentHeight*0.8
end
textnew:addEventListener ("tap", randomText )
Sometimes it looks like the button doesn't do anything when you tap but that's because the random number happens to be same as previous, you could put a loop to guard against that. Ig you actually want to change the pos and/or width, then the above code makes it clear where you should do that.
So, that's the basic code (without any extras at the moment :)) how it should be. Big thanks to Scholli!:
local menuTextOptions = {
text = "New",
x = display.contentWidth*0.2,
y = display.contentHeight*0.9,
align = 'left',
font = "Calibri",
fontSize = 40,
}
local textnew = display.newText(menuTextOptions)
local replacement = display.newText(menuTextOptions)
replacement.y = display.contentHeight*0.5
-- functions
function randomText(event)
local a = {"Banana!","Apple!","Potato","Pie"}
local com = a[math.random(1,#a)]
replacement.text = com
end
textnew:addEventListener ("tap", randomText )

How to hide native.newTextField after clicking submit?

I'm trying trying to have users name fade in one letter at a time vertically. Example: Adam "A" would appear after 1 second , "d" would appear after 3 seconds under the displayed A, "a" would appear after 5 seconds under the displayed d, "m" would appear after 7 seconds under the displayed a. The visuals would have a sort of domino effect.When they appear they would stay displayed on screen.
When if I comment out userNameField:removeSelf () then the code works fine. I get the effect I want, but the problem is that I still have the userNamefield showing.
Please let me know if you need to see more code.
local greeting = display.newText( "Greetings, enter your name",0,0,native.systemFont, 20 )
greeting.x = display.contentWidth/2
greeting.y = 100
local submitButton = display.newImage( "submit.png" ,display.contentWidth/2, display.contentHeight - 50 )
local userNameField = native.newTextField( display.contentWidth * 0.5 , 150, 250, 45)
userNameField.inputtype = "defualt"
local incrementor = 0
function showNameLetters()
userNameField:removeSelf( )
if incrementor <= string.len ("userNameField.text") then
incrementor = incrementor + 1
personLetters = string.sub (userNameField.text, incrementor,incrementor)
display_personLetters = display.newText (personLetters, 290,30*incrementor, native.systemFont, 30)
display_personLetters.alpha = 0
transition.to(display_personLetters,{time = 3000, alpha = 1, onComplete = showNameLetters})
end
end
Update:
I've found a solution to my problem, by adding userNameField.isVisible = false in my function.
I've also found something very weird, and wish to have someone explain why this happens. If I add greeting:removeSelf() and submitButton:removeSelf() (I've commented them out in my code below to show you where I put them for testing). I get weird result of only the first letter fading in. If i set greeting.isVisible = false and submitButton.isVisible = false. The code works fine.
I'm so confused why object:removeSelf() wouldn't work. Can someone please clear this up for me.
That is, if I replace the following line:
userNameField:removeSelf( )
with:
userNameField.isVisible = false
then the app works fine. Please suggest me why/ any solution for the question. THanks in advance...
It seems like you're calling the showNameLetters multiple times, this means that you're removing the native text field more than once. Nil it and check for nil before removing it like this:
if userNameField ~= nil then
userNameField:removeSelf()
userNameField = nil
end
It shows that you are using data from userNameField after removing it (in the line below):
personLetters = string.sub (userNameField.text, incrementor,incrementor)
As well as you are calling object:removeSelf() again and again without checking its existance (as mentioned by hades2510). So before removing userNameField, check for it's existance:
if userNameField ~= nil then
userNameField:removeSelf()
userNameField = nil
end
And you won't get userNameField.text while userNameField is nil. So use a temporary variable to hold the previous userNameField.text and get the saved data from that variable when needed.
Extra note: Are you sure, you have to check the length of the text "userNameField.text" in the following line, or the variable userNameField.text? If you have to use the data from the text field, then this will also matter.
if incrementor <= string.len ("userNameField.text") then
........
end
Keep coding....................... :)

Resources