Jump to content




Touchpoint API


277 replies to this topic

#1 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 16 August 2013 - 09:58 PM

One of the common things we see in questions in Ask a Pro is people trying to make Direwolf20's button API fit their use case. It usually doesn't work so well, and requires significant modification of either the API, or their program, or both. The Touchpoint API is designed as a replacement API. The usage is somewhat different, but is similar enough to easily transition existing code from the DW20 button API over to Touchpoint. The code is fairly lightweight, coming in just slightly over 150 LOC.

The API can be downloaded from pastebin: pFHeia96

Touchpoint is also available via packman, with the command: packman install touchpoint

Methods:
--# create a new instance of buttons on the top monitor (assumes API loaded as "touchpoint").
--# you can also not specify a side, and the touchpoint instance will use the current terminal and watch for mouse_click events (ComputerCraft 1.6+ only).
t = touchpoint.new("top")

--# add a button to the instance
t:add("label", function() doStuffButNotRequiredForEventsMode() end, 2, 2, 28, 11, colors.red, colors.lime)
--# coordinates are minX, minY, maxX, maxY.
--# uses red for inactive color and green for active color.

--# draw the set of buttons in the instance
t:draw()

--# catch and handle monitor touch events on buttons to transform them to button_click events
--# (does not require functions when creating buttons, you can pass nil instead)
event, p1, p2, p3, p4, p5 = t:handleEvents(os.pullEvent())

--# or instead handle the button clicks by calling the buttons' functions.
t:run()

--# change a button from inactive color to active or vice versa
t:toggleButton("label")

--# briefly toggle a button's state, then change it back
t:flash("label")

--# change a button's label
t:rename("label", "newLabel")

Different pages of buttons are relatively easy to achieve by simply creating multiple instances and drawing the desired page's instance on the screen and using it to handle events.

Here is a quick example program that controls a redstone output on each of the left and right sides:

Spoiler

A note: One can also fully specify the text characters of the button by providing a table for the label. The table should contain exactly the text of the button, in numerical indices, top to bottom. Each index should be as long as the button is wide. A label entry should be present in the table, which should be a single string to be used as the name of the button. For example:

local buttonName = {
	"		  ",
	" A button ",
	"  label   ",
	"		  ",
	label = "a button"
}

t:add(buttonName, nil, 2, 2, 11, 5, colors.red, colors.lime)


#2 Delete this account

  • Members
  • 5 posts
  • LocationUnited Kingdom

Posted 17 August 2013 - 12:22 PM

I've given this a test. I like it; a lot. Feels a lot smoother and reads better than a lot of the Direwolf-esque API's out there. Cheers!

#3 GopherAtl

  • Members
  • 888 posts

Posted 20 August 2013 - 12:22 AM

you'd told me you were gonna make this, glad you finally did. Simple and to the point! Thumbs up.

#4 NeverCast

  • Members
  • 400 posts
  • LocationChristchurch, New Zealand

Posted 20 August 2013 - 11:54 PM

Lyqyd, I love it!
It's far better than Direwolf's API.

Only thing I think is having
local event = {...} or os.pullEvent()

On line 70, so that the event argument is optional
Not sure how nicely that'll evaluate, probably expand to { nil } which will be an empty table, in which case you'd need to check that next(event) ~= nil

But all in all, it's great! Nice Lyqyd.

#5 Zudo

  • Members
  • 800 posts
  • LocationUK

Posted 21 August 2013 - 02:10 AM

For some reason I can never do object oriented stuff :(
Anyway, this is a great API!

#6 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 24 August 2013 - 11:56 PM

Updated to include NeverCast's suggestion. If the :handleEvents function does not receive an event (table with zero length), it will pull an event.

#7 Mitchfizz05

  • Members
  • 125 posts
  • LocationAdelaide, Australia

Posted 28 August 2013 - 02:25 AM

Poor Direwolf20. Lyqyd's API is taking over.
(Hehe, Lyqyd is the same backwards).

This looks pretty good though; I do quite like Object Oriented stuff, although I don't really see it anywhere, I think I might look into it a bit.

#8 theoriginalbit

    Semi-Professional ComputerCrafter

  • Moderators
  • 7,332 posts
  • LocationAustralia

Posted 28 August 2013 - 02:36 AM

View Postmitchfizz05, on 28 August 2013 - 02:25 AM, said:

Poor Direwolf20. Lyqyd's API is taking over.
That's because his API just doesn't quite work for people. It needed to be replaced.

View Postmitchfizz05, on 28 August 2013 - 02:25 AM, said:

Hehe, Lyqyd is the same backwards.
Lyqyd ~= dyqyL

View Postmitchfizz05, on 28 August 2013 - 02:25 AM, said:

I do quite like Object Oriented stuff, although I don't really see it anywhere, I think I might look into it a bit.
Mustn't be looking hard enough, there are quite a lot of programs and API's on these forums that use OO.

#9 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 28 August 2013 - 12:36 PM

Thanks for all the positive feedback so far!

I'm not sure this counts as object-oriented (though I suppose it could). Really, the main point of structuring it the way I did was so that one would use a self-contained button "page" instance, containing a set of buttons on a given monitor. That gives the easy ability to swap out pages of buttons (simply :draw() and then :handleEvents() for whichever page should be displayed), which is a usage pattern I've seen attempted a few times with the DW20 API, usually poorly or with great effort.

#10 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 07 September 2013 - 08:46 PM

Just updated the pastebin link. You can now set your own text scale (the draw function no longer overrides the scale to 1 each time it draws). I also added a rename function. See the first post for usage. It allows you to easily rename a button, giving it a different label and also correctly updating the click map the instance uses. Now you won't have to re-create an entire page of buttons just to relabel one button.

I just hope the rename feature doesn't get abused to try to make multiple pages of buttons using only a single touchpoint instance. :P

#11 chrdov

  • Members
  • 86 posts
  • Locationchrdov's Domain, Cyberspace

Posted 29 September 2013 - 07:31 AM

here is an error the api made when i ran my program:
Spoiler

well... i guess that's what happens when i try to make multiple instances.

#12 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 29 September 2013 - 03:42 PM

Would you mind posting or PMing me the code you were running? It will help me reproduce the error and fix it more easily.

#13 gr8pefish

  • Members
  • 7 posts

Posted 04 November 2013 - 02:08 PM

Hi,

I was playing around with your code for a while, it seems very solid. However, I only know basic coding so some of it goes over my head. Would it be possible to easily implement a header or labels that aren't buttons? I tried to do it myself but it seems like :draw() refreshes the page only looking for button instances, so at every refresh my labels were lost. Any ideas?

Also, could you give me an example on how you would use the page instance to access different pages? I am a little lost on your explanation.

Edit: After a lot of testing I almost have a working monitor, except for one main issue. I try to go back to the main menu, but the buttonList does not update, however it does update going into a different menu. I made a few changes to your code (mainly because I couldn't understand yours, my fault not yours). I also commented out "error, overlapping button" to allow me to make buttons (on different pages) in the same spot.
Spoiler

Here is my main tester code. The redstone option works just how I want, it toggles and keeps that output until I hit the button again. The menu change from main menu to redMenu works fine as well, it is transitioning back to the main Menu that outputs nothing and does not update buttonList for some unknown reason:
Spoiler

I know my code is not the best and I am open to any and all suggestions. Keep in mind this is just a tester code though.

Thanks a lot!

#14 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 04 November 2013 - 08:20 PM

Here's a modified version of your code that uses multiple button instances, which will work much better than trying to clear the button list, especially since you were not also clearing the click map. Note that using the API as intended with multiple instances removes the need to modify it for simple pages of buttons. :)

--Loads the API, straightforward
os.loadAPI("touchpoint")
--establish two "pages" of buttons
local page1 = touchpoint.new("top")
local page2 = touchpoint.new("top")
--one variable to put each page into in order to simplify switching
local t

--Two redstone testing functions
--add toggling where it makes sense
function rsRight()
    page1:toggleButton("Activate Right")
    rs.setOutput("right", not rs.getOutput("right"))
end

function rsLeft()
    page1:toggleButton("Activate Left")
    rs.setOutput("left", not rs.getOutput("left"))
end

--Redstone, switch out
function redMenu()
    t = page2
end

---Main Menu
function mainTable()
    t = page1
end

--set up two pages
do
    page1:add("Redstone", redMenu, 5, 2, 20, 6)
    page1:add("Turtle",function() turtleMenu() end, 25, 2, 40, 6)
    
    page2:add("Activate Left", rsLeft, 5, 12, 20, 16)
    page2:add("Activate Right", rsRight, 25, 12, 40, 16)
    page2:add("Back",mainTable, 15, 18, 25, 22)
end

--Starts with the main Table, then checks for buttons clicked, toggles them, and runs the program it has with my modified t:run2()
mainTable()
while true do
    t:draw()
    -- local event, p1, p2, p3 = os.pullEvent() ---monitor_touch, side, xpos, ypos
    local event, p1 = t:handleEvents(os.pullEvent())   ---button_click, name
    if event == "button_click" then
        --remove toggling and simplify button running
        t.buttonList[p1].func()
    end
end

Headers and labels that aren't buttons could be set up by using buttons that don't do anything and have a black background:

t:add("Label Text Here", nil, 2, 2, 18, 4, colors.black, colors.black)

To use those in your existing script, you'd need to change out the function-calling part at the bottom so it wouldn't attempt to call nil. You might do this by adding this to your script:

function callButton(name)
    if type(t.buttonList[name].func) == "function" then
        t.buttonList[name].func()
    end
end

And you'd of course change the function calling line in the modified version to simply be callButton(p1). If you have any other questions, feel free to ask!

#15 gr8pefish

  • Members
  • 7 posts

Posted 05 November 2013 - 10:58 PM

Thanks a lot Lyqyd! That method of creating pages is truly brilliant! It makes sense now that I know how it works :)

I have another question though:
How would you print a display-like feature? A simple example would be just a countdown from 10 to 0 on the screen. Is this API not relevant anymore if that is what I want to accomplish? I guess I could use another monitor for displays utilizing a different API if that is the case. Ideally I wanted a menu to access the displays, but if that doesn't work I understand. Edit: On second though I could use rednet, two computers and two monitors. The first computer/monitor has all the buttons and "tells" the second computer/monitor pair what to display. With a bunch more coding, that could work...
Know of any good monitor display API's off the top of your head?

For another example, say I had a (sub)menu of a turtle's actions. I could move him forward, dig, turn, etc. One button option could be to check to see a turtle's fuel level (using rednet). He could send that number back to me and I could flash that number on a new page for a couple seconds (I tested it and that works), but how would I print and update a number, say in the top left, that reflects the real-time changes of the turtle's fuel levels without having to hit a button? Essentially a display in the same screen as the button menu.

If that don't make sense just ask me for clarification and I will be more than happy to explain it again!

Thanks again and sorry for all the questions, I am just starting with CC and feedback really helps.
Cheers!

#16 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 06 November 2013 - 12:41 AM

There's a couple ways to do this. One way would be to reserve a section of the screen and just draw it on there after the t:draw() in the main loop (so that it isn't cleared when the buttons draw), but that obviously would have it on every page. Another way would be to create a button and use the table naming feature to manually change the label of the button (while keeping the name the same for easier tracking). So, for instance, you'd have a button like this:

local labelTable = {
  "    ",
  " 20 ",
  "    ",
  label = "turtleFuel"
}

--# use table labelling to enable total control of button appearance.
page2:add(labelTable, nil, 2, 2, 5, 4, colors.black, colors.black)

--# use rename to change appearance of button without changing label.

local newLabel = {
  "    ",
  " 19 ",
  "    ",
  label = "turtleFuel"
}

--# rename uses the label entry in the table to pick which button to rename, then we give it the new table (with the same label entry to make things easy later)
page2:rename("turtleFuel", newLabel)

Hope that makes sense! You'd then just use rednet events in your main loop to decide when to rename/relabel the buttons on the relevant pages (so they'd even be accurate as soon as you switched to them), and changes would be automatically drawn if you were on them. I also just added a noDraw option to rename since I noticed it was missing one (so you could rename things on non-displayed pages without a forced draw, which would require you to redraw the current page to keep it displayed). A non-nil, non-false argument as a third argument (best to use true) on a rename call will prevent the internal self:draw() call from being made.

No worries about the questions, I'm happy to help. :)

#17 gr8pefish

  • Members
  • 7 posts

Posted 06 November 2013 - 07:46 PM

Hey again,

So I don't completely understand how the table naming feature works. Here is my (full) code:

Computer by monitor (ID #1):
http://pastebin.com/LUQRX8FL

Turtle Code (ID #3):
Spoiler

This works by flashing the data to a new page. If I try and add this though:

local testTable = {
  "			",
  " Fuel Level ",
  "			",
  label = "turtleFuel"
}
and this in do:
page3:add(testTable, nil, 5, 20, 21, 24, colors.black, colors.black)
and making the page changing functions to look more like this"

function turtleMenu()
	t = page3
	t:draw()
end
And change my main code to this:

t = page1
t:draw()
while true do
	local event, id, msg, dst = os.pullEvent("rednet_message")
	if id == 1 then
	  page3:rename("turtleFuel", ("Fuel Level: "..msg), true) --msg is the turtle doing rednet.send(1, tostring(turtle.getFuelLevel()))
	end
	local event, p1 = t:handleEvents(os.pullEvent())
	if event == "button_click" then
	  callButton(p1)
	end
end
A couple problems occur.
1: The loop runs and only checks for rednet_message instead of checking for button_clicks so the touchscreen doesn't work. I am not sure how to check both simultaneously without one clogging the system.
2: Even if I comment out the check for rednet message part, when the turtle screen is drawn the monitor prints out the table with a blinking entry after it. The monitor doesn't respond to button presses but if I type into the computer it appears on the monitor. When I ctr+t to terminate the program the computer restarts (and it prints out program terminated on the monitor) and I have no idea why that would happen.
3: I figured out the rename command, but I don't know how to use it to constantly update the table. Would my function actually work given it was constantly receiving input from the turtle?

Basically, can you maybe be more explicit? Because I simply don't know how to do it :P

Thanks, I really appreciate it.

#18 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 06 November 2013 - 08:30 PM

Gonna throw some code at you and then talk about it:

t = page1
t:draw()
while true do
	--# handleEvents will return any events that it doesn't need to convert, so we can take care of rednet messages here.
	--# We'll put all of the event argument returns in a table for simplicity.
	--# so event is now event[1] and p1 is now event[2].
	local event = {t:handleEvents(os.pullEvent())}
	if event[1] == "button_click" then
		callButton(event[2])
	elseif event[1] == "rednet_message" and event[2] == 1 then
		--# trim string to fit, will only show highest digits.
		local fuelLevel = string.sub(tostring(event[3]), 1, 4)
		--# add padding if necessary
		local fuelLevel = fuelLevel..string.rep(" ", 4 - #fuelLevel)
		--# build entire table for renaming
		local nameTable = {
			"				 ",
			"				 ",
			" Fuel Level: "..fuelLevel,
			"				 ",
			"				 ",
			label = "turtleFuel"
		}
		page3:rename("turtleFuel", nameTable, true)
	end
end

--# the table method requires us to specify every space of every line of the button, and only that many spaces.
--# In other words, it must be the exact size of the button.
local nameTable = {
	"				 ",
	"				 ",
	" Fuel Level	  ",
	"				 ",
	"				 ",
	label = "turtleFuel"
}

For the problems you were having,

1. Check out the revised event loop. Notice that you can get the rednet_message events when they are passed through by the handleEvents() function, so you don't need a separate os.pullEvent() call.
2. Sounds like something is causing a crash on the turtle screen. If these fixes to the code don't fix the issue, toss the whole script my way and I'll see if I can figure out what's causing it.
3. You shouldn't have moved the t:draw() command out of the loop. It belongs where I had it in the loop. That will cause the screen to update on any event, which means that it will automatically redraw correctly whenever the turtle fuel level changes.

Edit: I am noticing that the code formatting tags is screwing up the whitespace count. Just be sure that you have one character or space for every character on the screen that the button is supposed to take up, since you're telling it with that table the raw data to write to the screen. I get the feeling that since you were trying to write to a five-line button with a three-line table, it was attempting to use a nil string to write to the screen or some similar problem.

#19 gr8pefish

  • Members
  • 7 posts

Posted 06 November 2013 - 09:49 PM

Thanks again, that really helps me :) However, it still doesn't work, even copying your code down exactly. Here is the relevant part of my code:
local testTable = {
"				", --17 spaces
"Fuel Level", --12 (17-5) spaces (also tried with 17 spaces)
"				", --17 spaces
"				", -- 17 spaces
label = "turtleFuel".
}

page3:add(testTable, nil, 4, 21, 21, 24, colors.black, colors.black) -- I edited your API for my readability so it goes ..., func(), xMin, xMax, yMin, yMax, ...
---Sorry I forgot to mention that before; 21-4 = 17 (hence 17 spaces) and (y)lines 21 to 24 are 4 lines vertically

elseif event[1] =="rednet_message" and event[2] == 3 then
			local fuelLevelUpdate = string.sub(event[3], 1, 5) --changed it to be 5 characters</div>
			local fuelLevelUpdate = fuelLevelUpdate..string.rep(" ", 5 - string.len(fuelLevelUpdate)) --this is a string, not a table anymore so I had to use string.len(), not #
			local nameTable = {
					"			   ", --17 spaces
					"Fuel Level: "..fuelLevelUpdate, --12 spaces (the variables should add 5 more for 17)
					"			   " --17 spaces
					"			   " --17 spaces
					label = "turtleFuel"
			}
			page3:rename("turtleFue", nameTable, true) --This throws the error


The error is touchpoint:125 (line 125 below) attempt to index ? (a nil value)
for i = self.buttonList[newName].xMin, self.buttonList[newName].xMax do --line 125
I tried to call self.buttonList[newName]xMin but that didn't work, so
after some debugging, I ended with typing in this to line 124:
if not self.buttonList[newName] then print ("newName = false/nil") end
I verified that newName is nil.

Any ideas? Thanks again for all the assistance by the way. Cheers!

Edit: I found out that odd monitor (#2 in my last post) error occurs when the the table is too short and the yMin/yMax of the button:add() is too large.

#20 Lyqyd

    Lua Liquidator

  • Moderators
  • 8,465 posts

Posted 06 November 2013 - 10:25 PM

Screw-up on my part. I was assuming in the rename function that the old name of the button and the new name would never be the same, so it was setting the "old" name to nil, which was killing the entry in the button list! I've added a check to ensure that they actually are different and updated the pastebin.

Noting your comments, please be aware that just as rows 21 - 24 is actually four lines (lines 21, 22, 23, and 24), columns 4 - 21 is actually 18 columns, not seventeen (4, 5, 6, ..., 19, 20, 21).

Thanks for the clarification on the button table y-size being the issue there.





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users