This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
grand_mind1's profile picture

Button API

Started by grand_mind1, 07 July 2013 - 09:20 PM
grand_mind1 #1
Posted 07 July 2013 - 11:20 PM
I was trying to make a button API to draw boxes, center text, and all that good stuff a while ago. I was getting frustrated so I took about a two week break from the program. At the moment all I have is the drawing part, no click detection or use for the buttons.

Now that I've come back to it I was trying to familiarize myself with what everything was supposed to do again and discovered something that I spent almost and hour trying to solve but had no luck. I'm going to try to explain the problem after I paste the code:

Button API:
Spoiler

button = {}
--mon = peripheral.wrap("left")
mon = term
function fillTable(name, xmin, xmax, ymin, ymax, text, color)
button[name] = {}
button[name]["text"] = text
button[name]["xmin"] = xmin
button[name]["xmax"] = xmax
button[name]["ymin"] = ymin
button[name]["ymax"] = ymax
button[name]["color"] = color
end

function createBox(name)
tableData = button[name]
currentY = tableData["ymin"]
while currentY ~= tableData["ymax"] do
  mon.setCursorPos(tableData["xmin"], currentY)
  for i = 1,tableData["xmax"] do
   write(" ")
		end
		currentY = currentY +1
	end
end
function createButton(name)
tableData = button[name]
mon.setBackgroundColor(tableData["color"])
xLoc = tableData["xmin"] + (math.ceil((tableData["xmax"] - string.len(tableData["text"]))/2))
yLoc = math.ceil((tableData["ymax"]+tableData["ymin"])/2)-1
createBox(name)
mon.setCursorPos(xLoc,yLoc)
mon.write(tableData["text"])
end

Program I'm using to test API:
Spoiler

os.loadAPI("btn")
btn.fillTable("Test1",9,14,4,9,"Test 1",colors.blue)
btn.createButton("Test1")
term.setCursorPos(1,1)

Program I'm using to find coordinates on screen:
Spoiler

while true do
  term.setCursorPos(1,1)
  term.clearLine()
  term.setCursorPos(1,1)
  event, button, x, y =os.pullEvent("mouse_click")
  if x == nil or y == nil then
	print("What")
  end
  write(x..":"..y)
  sleep(.8)
end

So my problem is with drawing the boxes to the correct size. The dimensions are all off. For example:
In my test program I set the x and y like so:
xMin: 9
xMax: 14
yMin: 4
yMax: 9
I believe this should make the dimensions of the button 6x6 however they end up being 14x5. The width of the button(which is associated with x) is 8 off and the length( which is associated with y) is 1 off. When I use my program for finding coordinates these are the coordinates of the corners:
Top Left: 9:4
Bottom Left: 9:8
Top Right: 22:4
Bottom Right: 22:8
Just from looking at the top and bottom right I can immediately tell that something is wrong. 22? I don't even know where that came from. As far as I know the x coordinate on the right side should be the xMax which is 14. And then there is that 8 on the bottom. Shouldn't that be the yMax which is 9?

After looking at all of this, I know that there is something seriously wrong with my createBox() function which is what draws the buttons before the text is added. This will cause serious problems when I start working on finding where the user clicked and what button they clicked, if the button isn't where it's supposed to be. Before I finish the post I would like to say that I'm not the best at math or Lua so I very well could be completely wrong. I could also be completely over thinking this(which I do often) and there is actually a very easy fix.
All help is appreciated!
Thanks! :D/>
NOTUSEDPLEASEDELETE #2
Posted 08 July 2013 - 01:11 AM
I can't help with the drawing part but I can help with the mouse clicks:

button, x, y = os.pullEvent("mouse_click")
if button == left and x == 1 and y == 1 then --Change my parameters
--Do the action
end
That was the code for mouse click detection but in no function.
grand_mind1 #3
Posted 08 July 2013 - 01:31 AM
Um, I know how to do mouse click detection. And while I do thank you for your time, your code won't work for my purposes. For one, it doesn't check all of the buttons in the button table. And secondly, the parameters on os.pullEvent("mosue_click") are wrong. I'm pretty sure it has four parameters:

event, button, x, y = os.pullEvent("mouse_click")

EDIT: Oops, didn't know you didn't have to put the event parameter
DiabolusNeil #4
Posted 08 July 2013 - 10:03 AM
Direwolf20 already made a fully functioning Button API. Check it out.
BlankWolf #5
Posted 08 July 2013 - 10:29 AM
Direwolf20 already made a fully functioning Button API. Check it out.
Well… yeah Direwolf20 made a ButtonAPI but it's better to figure out by self.

To the topic:

Let's say
xmin = 10 – we start by 10 on the x coordiante
xmax = 20 – to make it 10 width

with your code you will get a button that is 20 width


function createBox(name)
  tableData = button[name]
  currentY = tableData["ymin"]
  while currentY ~= tableData["ymax"] do
	mon.setCursorPos(tableData["xmin"], currentY)
	for i = 1,tableData["xmax"] do -- have a look here simply change i = 1 to i = tableData[xmin]
	  write(" ")
	end
	currentY = currentY +1
  end
end

After the changing, the loop starts by 10 and ends by 20
so you get a 10 width button

I hope I coud help you a little bit.
H4X0RZ #6
Posted 08 July 2013 - 01:23 PM
@OP
The screensize is 51*19 (x*y)
grand_mind1 #7
Posted 08 July 2013 - 02:42 PM
Thanks so much BlankWolf! As I said in the original post, it was something pretty simple that I just failed to look at. Thanks again!

Direwolf20 already made a fully functioning Button API. Check it out.
Hmmm, yes I've seen his API and he is a much better programmer than I, but where's the fun in that? Where's the fun in just copying someone else's code if you have the ability to try and make it yourself? If I have the opportunity to test myself I always take it. When I finish this API I will feel far more pride in my code than I would've just copying someone else's.

So after I tested with the new code I remembered one problem and ran into another.
1.The y coordinate is one short.
Example: I set ymin to 4 and ymax to 9 but the bottom coordinates of the button are: x:8. The 8 should be the xmax which is 9 not 8.

Solved by changing:

while currentY ~= tableData["ymax"] do
to:

while currentY ~= tableData["ymax"]+1 do
Please tell me if this is the wrong way to fix it.
2.The centering of text no longer functions as expected.
There's really no other way to describe it. The xLoc and yLoc coordinates are of course correct when it comes to the math but the math doesn't result in the centering of text. It is usually far to the right which means that the xLoc is no longer being calculated correctly with the new method for drawing the button.
BlankWolf #8
Posted 08 July 2013 - 03:02 PM
Thanks so much BlankWolf! As I said in the original post, it was something pretty simple that I just failed to look at. Thanks again!
No problem.

Hmmm, yes I've seen his API and he is a better programmer than I, but where's the fun in that? Where's the fun in just copying someone else's code if you have the ability to try and make it yourself? If I have the opportunity to test myself I always take it. When I finish this API I will feel far more pride in my code than I would've just copying someone else's.
Well over time you will definitive be better. So no worry.
grand_mind1 #9
Posted 08 July 2013 - 03:11 PM
Well over time you will definitive be better. So no worry.
Thanks!
grand_mind1 #10
Posted 09 July 2013 - 12:32 AM
So I've been looking at the math for determining the xLoc and yLoc for the text. The y seems to be ok but the xLoc is very wrong. It seems to be very far to the right. However, I can't see anything that would make the xLoc much greater than it should be.
BlankWolf #11
Posted 09 July 2013 - 01:51 AM

function createButton(name)
tableData = button[name]
mon.setBackgroundColor(tableData["color"])
xLoc = tableData["xmin"] + (math.ceil((tableData["xmax"] - string.len(tableData["text"]))/2)) -- your math is wrong
yLoc = math.ceil((tableData["ymax"]+tableData["ymin"])/2)-1
createBox(name)
mon.setCursorPos(xLoc,yLoc)
mon.write(tableData["text"])
end

Same deal as your button width math you forgett the xmin.
let's say again your button x coordiantes are xmin = 10 and xmax = 20
currently your only take the xmax so the program will think "I center the text in a 20 width button" but as you see it is only 10
so you have to change
xLoc = tableData["xmin"] + (math.ceil((tableData["xmax"] - string.len(tableData["text"]))/2))
to
xLoc = tableData["xmin"] + (math.ceil((tableData["xmax"] - tableData["xmin"] - string.len(tableData["text"])) / 2))
well maybe you use math.floor instead of math.ceil… but thats not necessary
grand_mind1 #12
Posted 10 July 2013 - 05:06 AM
So after I got all of that figured out I moved on to the click detection. I assumed this would be a simple task that I could get done very quickly, however when I started working on it, I realized I have no idea what I'm doing.

So what I'm trying to do is create a function that uses the x and y parameters from

button, x, y = os.pullEvent("mouse_click")
to detect where you clicked and find out if where you clicked is inside a button and which button it was. I know I can do something like:

if x >= tableData["xmin"] and x <= tableData["xmax"] then
  if y >= tableData["ymin"] and x <= tableData["ymax"] then
    --code stuff
  end
end

This is good and all but I have no idea how I would go about making the name in tableData loop through all of the existing buttons to check for all of their coordinates. I've seen people use something like

for variable,variable2 in pairs(table) do
  --code stuff
end
to loop through tables but I have no idea how or why.
I've googled how it works but none of it seems to make any sense in my mind.
Once again, all help is appreciated!
Thanks! :D/>
BlankWolf #13
Posted 10 July 2013 - 05:40 AM

button, x, y = os.pullEvent("mouse_click")
Well first you miss a variable
event, button, x, y = os.pullEvent("mouse_click")


if x >= tableData["xmin"] and x <= tableData["xmax"] then
  if y >= tableData["ymin"] and x <= tableData["ymax"] then
	--code stuff
  end
end
This is correct.


for variable,variable2 in pairs(table) do
  --code stuff
end
Well what you want is cycle through your table with all your buttons

for name, tableData in pairs(button) do
  -- what you will do
end
grand_mind1 #14
Posted 10 July 2013 - 10:04 PM
Alright so I've been working on it a bit more and I've added a few more functions.
Here is the new code:
Spoiler

button = {}
mon = term
mon.clear()
mon.setCursorPos(1,1)
mon.setBackgroundColor(colors.black)
mon.setTextColor(colors.white)

function fillTable(name, xmin, xmax, ymin, ymax, text, func, color)
button[name] = {}
button[name]["text"] = text
button[name]["xmin"] = xmin
button[name]["xmax"] = xmax
button[name]["ymin"] = ymin
button[name]["ymax"] = ymax
button[name]["func"] = func
button[name]["color"] = color
button[name]["active"] = false
end

function createBox(name)
tableData = button[name]
currentY = tableData["ymin"]
while currentY ~= tableData["ymax"]+1 do
  mon.setCursorPos(tableData["xmin"], currentY)
  for i = tableData["xmin"],tableData["xmax"] do
   write(" ")
		end
		currentY = currentY +1
	end
end
function createButton(name)
tableData = button[name]
xLoc = tableData["xmin"] + (math.floor((tableData["xmax"] - tableData["xmin"] - string.len(tableData["text"])) / 2))
yLoc = math.floor((tableData["ymax"]+tableData["ymin"])/2)
if tableData["color"] == nil then
  if tableData["active"] == false then
   mon.setBackgroundColor(colors.red)
  elseif tableData["active"] == true then
   mon.setBackgroundColor(colors.lime)
  end
else
  mon.setBackgroundColor(tableData["color"])
end
createBox(name)
mon.setCursorPos(xLoc,yLoc)
mon.write(tableData["text"])
mon.setBackgroundColor(colors.black)
end
function createScreen()
for name,tableData in pairs(button) do
  createButton(name)
end
end
function checkClick(x,y)
for name, tableData in pairs(button) do
  if x >= tableData["xmin"] and x <= tableData["xmax"] then
   if y >= tableData["ymin"] and y <= tableData["ymax"] then
	tableData["func"]()
   end
  end
end
end
function toggle(dur, name)
tableData = button[name]
if tableData["active"] == false then
  tableData["active"] = true
elseif tableData["active"] == true then
  tableData["active"] = false
end
createScreen()
if dur ~= nil or dur ~= 0 then
  sleep(dur)
  tableData["active"] = false
  createScreen()
end
end
I've added three functions:
createScreen()
checkClick()
toggle()
And made some changes to the createButton() function which have to do with the toggle() function and the "active" variable in the table.

checkClick() and createScreen() seem to be working fine. My problem is with the toggle() function and the bits of code in the createButton() that have to do with it.

When I click on a button that has a nil color, I have the color default to red and when I click on it I have to option to use the toggle() function to toggle it between red and lime green(red being inactive and lime being active).

I am able to add a duration time of it being active but if I set it to 0 or nil then it will act as a lever and only toggle the next time I click the button. My problem mainly has to do with deactivating the button after the duration time is over. The parts that handle this action are:

if dur ~= nil or dur ~= 0 then
  sleep(dur)
  tableData["active"] = false
  createScreen()
end
and

if tableData["color"] == nil then
  if tableData["active"] == false then
   mon.setBackgroundColor(colors.red)
  elseif tableData["active"] == true then
   mon.setBackgroundColor(colors.lime)
  end
else
  mon.setBackgroundColor(tableData["color"])
end
When the duration time is up, the button remains lime and active even though I set the active variable to false on line 78:

tableData["active"] = false
After doing some debugging, I'm pretty sure it gets to the part in createButton() where it decides the color but it still thinks that the active variable is set to true so it keeps the color lime green.

I've done everything I can think of but can't seem to figure it out.
Help is appreciated!
Thanks! :D/>
BlankWolf #15
Posted 11 July 2013 - 02:05 AM

if dur ~= nil or dur ~= 0 then	 -- think about that
  sleep(dur)
  tableData["active"] = false
  createScreen()
end

let's say "dur = nil" the program will go in that if cause "dur ~= 0"
change the "or" to an "and" and you will be fine.


if tableData["active"] == false then  
  tableData["active"] = true
elseif tableData["active"] == true then  
  tableData["active"] = false
end
All that you can write in 1 line.

tableData["active"] = not tableData["active"]
Because you have an boolean (true/false/nil). The not will reverse whatever is in tableData["active"]
(well nil is not a boolean but it will work like the false ;)/> )
The not will return "true" when the variable behind is false or nil.


if tableData["color"] == nil then
  if tableData["active"] == false then
   mon.setBackgroundColor(colors.red)
  elseif tableData["active"] == true then
   mon.setBackgroundColor(colors.lime)
  end
else
  mon.setBackgroundColor(tableData["color"])  -- well that can't be correct
end

You set the color allways to the same (think about it). Currently you have only 1 color and if active true or false you allways set this color as the background.
grand_mind1 #16
Posted 11 July 2013 - 04:09 AM

if dur ~= nil or dur ~= 0 then	 -- think about that
  sleep(dur)
  tableData["active"] = false
  createScreen()
end

let's say "dur = nil" the program will go in that if cause "dur ~= 0"
change the "or" to an "and" and you will be fine.
Oh, right. That makes sense.


if tableData["active"] == false then  
  tableData["active"] = true
elseif tableData["active"] == true then  
  tableData["active"] = false
end
All that you can write in 1 line.

tableData["active"] = not tableData["active"]
Because you have an boolean (true/false/nil). The not will reverse whatever is in tableData["active"]
(well nil is not a boolean but it will work like the false ;)/> )
The not will return "true" when the variable behind is false or nil.
Oh, thanks! I didn't know that.


if tableData["color"] == nil then
  if tableData["active"] == false then
   mon.setBackgroundColor(colors.red)
  elseif tableData["active"] == true then
   mon.setBackgroundColor(colors.lime)
  end
else
  mon.setBackgroundColor(tableData["color"])  -- well that can't be correct
end

You set the color allways to the same (think about it). Currently you have only 1 color and if active true or false you allways set this color as the background.
I'm not sure you really understand how the active thing is working. If I leave the color as nil then it can change between red and lime based on if it is active or no. That if statement is to decide if color is nil and if it isn't then it will run

mon.setBackgroundColor(tableData["color"])
However, if it is then it will check if the active variable is and change the color depending on that. I don't really get what you mean by I always set the color to the same. It should be red if active = false and lime if active = true.
BlankWolf #17
Posted 11 July 2013 - 04:20 AM
- snipe -

However, if it is then it will check if the active variable is and change the color depending on that. I don't really get what you mean by I always set the color to the same. It should be red if active = false and lime if active = true.
Well. You have an option to set the button color write?
function fillTable(name, xmin, xmax, ymin, ymax, text, func, color) 
What I mean was, when you set the color to what ever (so it is not nil) your button will allways have the same color (active or not doesn't matter)
grand_mind1 #18
Posted 11 July 2013 - 04:23 AM
Yes, this is supposed to happen. If you give your button a color then it should ignore everything having to do with it changing the color between red and lime because it already has a color. The switch between red and lime is a sort of default thing. You have to leave color as nil for it to take effect.
BlankWolf #19
Posted 11 July 2013 - 04:30 AM
Yes, this is supposed to happen. If you give your button a color then it should ignore everything having to do with it changing the color between red and lime because it already has a color. The switch between red and lime is a sort of default thing. You have to leave color as nil for it to take effect.

I know and I can't see any mistakes for your default.
Maybe you correct your code and checks if the color change is all right.
grand_mind1 #20
Posted 11 July 2013 - 04:34 AM
So I've made the changes to the code but my problem persists. After the duration time of a button being active is over, it doesn't turn back to red.
New code:
Spoiler

button = {}
mon = term
mon.clear()
mon.setCursorPos(1,1)
mon.setBackgroundColor(colors.black)
mon.setTextColor(colors.white)

function fillTable(name, xmin, xmax, ymin, ymax, text, func, color)
button[name] = {}
button[name]["text"] = text
button[name]["xmin"] = xmin
button[name]["xmax"] = xmax
button[name]["ymin"] = ymin
button[name]["ymax"] = ymax
button[name]["func"] = func
button[name]["color"] = color
button[name]["active"] = false
end

function createBox(name)
tableData = button[name]
currentY = tableData["ymin"]
while currentY ~= tableData["ymax"]+1 do
  mon.setCursorPos(tableData["xmin"], currentY)
  for i = tableData["xmin"],tableData["xmax"] do
   write(" ")
		end
		currentY = currentY +1
	end
end
function createButton(name)
tableData = button[name]
xLoc = tableData["xmin"] + (math.floor((tableData["xmax"] - tableData["xmin"] - string.len(tableData["text"])) / 2))
yLoc = math.floor((tableData["ymax"]+tableData["ymin"])/2)
if tableData["color"] == nil then
  if tableData["active"] == false then
   mon.setBackgroundColor(colors.red)
  elseif tableData["active"] == true then
   mon.setBackgroundColor(colors.lime)
  end
else
  mon.setBackgroundColor(tableData["color"])
end
createBox(name)
mon.setCursorPos(xLoc,yLoc)
mon.write(tableData["text"])
mon.setBackgroundColor(colors.black)
end
function createScreen()
for name,tableData in pairs(button) do
  createButton(name)
end
end
function checkClick(x,y)
for name, tableData in pairs(button) do
  if x >= tableData["xmin"] and x <= tableData["xmax"] then
   if y >= tableData["ymin"] and y <= tableData["ymax"] then
	tableData["func"]()
   end
  end
end
end
function toggle(dur, name)
tableData = button[name]
tableData["active"] = not tableData["active"]
createScreen()
if dur ~= nil and dur ~= 0 then
  sleep(dur)
  tableData["active"] = false
  createScreen()
end
end
BlankWolf #21
Posted 11 July 2013 - 04:42 AM

function toggle(dur, name)
tableData = button[name]
tableData["active"] = not tableData["active"]
createScreen()
if dur ~= nil and dur ~= 0 then
  sleep(dur)
  tableData["active"] = false
  createScreen()
end

Wait I think I know whats happen. Why I don't see that bevor :o/>
change that so:

function toggle(dur, name)
button[name]["active"] = not button[name]["active"]
createScreen()
if dur ~= nil and dur ~= 0 then
  sleep(dur)
  button[name]["active"] = false
  createScreen()
end
You don't have change the active value at all. Just a "copy" of it
grand_mind1 #22
Posted 11 July 2013 - 04:49 AM
Wait, what? This does work now. The color changes from red to lime and back again but I don't understand why. You mention a "copy" of it when I was using

tableData = button[name]
but I was able to use that in other functions without it causing problems.
BlankWolf #23
Posted 11 July 2013 - 05:03 AM
Wait, what? This does work now. The color changes from red to lime and back again but I don't understand why. You mention a "copy" of it when I was using

tableData = button[name]
but I was able to use that in other functions without it causing problems.

Well of cours in the other functions you don't have change anything.
But if you want to change a value from your button you have to write the changes to it not to the "copy"
An example:

varA = {active = 'string'} -- just a diffrent way to write a table
varB = varA -- we make a copy of it

write(varB['active']) -- will write string
varB['active'] = 'other string' -- we change the active value form varB

varA['active'] will be the same as at the beginning

grand_mind1 #24
Posted 11 July 2013 - 05:05 AM
Ah I see. Well, with that I believe it is finished(for now). I'm gonna go head over to the programs forum and post it. Thanks so much for all your help.
BlankWolf #25
Posted 11 July 2013 - 05:20 AM
Ah I see. Well, with that I believe it is finished(for now). I'm gonna go head over to the programs forum and post it. Thanks so much for all your help.

No problem. :P/>