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

The Complete Monitor Buttons and Control Tutorial Part I

Started by surferpup, 04 February 2014 - 12:36 PM
surferpup #1
Posted 04 February 2014 - 01:36 PM
The Complete Monitor Buttons and Control Tutorial


Introduction



There are a lot of questions in Ask-a-Pro on how to create a touchscreen interface on monitors. Often times, people want to click a button to open/close doors or control things. While there are APIs to help you create buttons, there are a lot of "doityerselfers" in the community, and we fully understand that desire.

So, in response to one of those "How do I" questions, [member=surferpup] wrote up a complete example. Then we ([member='surferpup'],[member='ingie'] and [member='awsmazinggenius']) decided to pitch in and write this tutorial series to go along with it.

In this tutorial series, you will learn everything you need to know to use a monitor to create touchscreen buttons which can control basic redstone circuits. We will create three buttons on the monitor. Each button, when pressed, will control the redstone output of a specific side of the computer. You will be able to toggle outputs or pulse them according to what you decide. You can change the color, size and labels of the buttons. You will also learn how to use the event listener to respond to keyboard and monitor touch events.

Series Pre-Requisites

In order to work with this tutorial, you need to understand how to use yor ComputerCraft Computer. Knowledge of the concepts and techniques covered in Computer Basics 1 and Computer Basics 2 by [member='Lyqyd'] is therefore required. Basic Lua concepts are also necessary – these are covered in Lua Basics -- Variable Scope, Code Blocks and Control Structures by [member='surferpup'].

This tutorial is broken into four parts: Monitor Basics (Part I) where we start on buttons, Monitor Buttons (Part II), Monitor Touch Events (Part III) and finally, everything is put together in a Monitor Touchscreen Door Control program (Part IV). All parts are cummulative in nature and build to the final part. We start simple, yet build big. By the end of this tutorial, a beginning coder following the entire tutorial will be well on their way to becoming an ask-a-pro themselves. So let's start.


Part I: Monitor Basics



Attaching a Monitor to a Computer

There are only two ways to attach a monitor to a computer to get it to display something – either directly attached to a side of the computer, or attached using wired (not wireless) modems and networking cable.

Direct Attachment

Place down your advanced computer. Place an advanced monitor on top of it. You have now attached a monitor to the computer. This method is simple, quick, resource conscientious, and can be highly effective. There are disadvantages to this method. You only have so many sides on a computer to attach monitors and things. Plus, you don't always want to have a computer right next to your monitor – think about the case where you want your computer protected, and the monitor serves as the door control.

Attachment with Network Cables and Wired (not Wireless) Modems

Place down an advanded computer. Place a wired modem on the back of it (you need to sneak right-click). Attach network cable to the modem. Run the network cable a short distance from the computer. Place an advanced monitor down. Connect the cable to the advanced monitor with another wired modem. Right click both modems (the black trim on the modem will become red to indicate it is on). When the modem attached to the monitor is on, you will see a message "monitor_0" connected. Remember the name of the modem – it is the name we will use later.

What our Test World Should Look Like Now

Take a look at the our setup. This is what your test world should look like now. We have an advanced computer with an advanced monitor directly on top of it. We have a wired modem on the back of my advanced computer with network cable extending to the right 2 blocks. We then have another advanced monitor placed next to the cable with a wired modem connected on the left side of the monitor and then to the cable. Our modems are both on. Our networked monitor is labeled "monitor_0"

Screenshot

Note: Wireless Modems Don't Work with Monitors

Notice how we keep saying to use a wired (not wireless) modem? That is because there is no implementation to directly attach a wireless modem to a monitor and communicate directly with a monitor. Monitors require direct attachment to or cabling to a computer (there are implementations like AwsmazingOS which emulates the idea of a wireless monitor effectively, however the monitor itself is not wireless per se).

But It Still Doesn't Work

Even though we have attached monitors to our computer, they are still blank. That's ok. Let's make them display something. We will use what should become one of your best friends in ComputerCraft – the peripheral API.

To get the computer and monitor talking to each other, we need to "wrap" the monitor up in a "handle." Essentially, we are going to create a variable (handle) that is your monitor. The peripheral.wrap() function requires an argument telling what side the monitor is attached to. So for the monitor on top of the computer, use the following code:


local attachedMonitor = peripheral.wrap("top")

This will tell the computer it has a peripheral mounted on top that you want to do something with.

For the monitor connected to the network cable, you might think that it is actually connected to the back of the computer. But, you would be wrong. What is connected to the back of the computer is a modem and network cable. We could have any number of devices connected to that cable: other computers, printers, disk units and yes– lots of monitors. For networked monitors, we tell the peripheral wrap function the name of the monitor or device. In our case it is "monitor_0" or whatever name you saw on your screen. If you forgot, just turn the monitor's modem off and then back on. You will see the name again (there is another way to do that using the peripheral API, but we won't get into that method now).

So, your code for the networked monitor should be:


local networkedMonitor = peripheral.wrap("monitor_0")

Now, let's get some words on those screens! To write anything to a monitor, we use a lot of the same functions we would use with a normal computer screen. These are found in the term API. Let's user the write() function.


attachedMonitor.write("Hello!")

Save your program and run it. What happened?

Let's do it again, but first, let's clear the attached monitor.


attachedMonitor.clear()
attachedMonitor.write("Hi!!!")

But this only showed up on the attached monitor. Can you get it something to display on the networked monitor? Try and figure it out. The spoiler below reveals our solution.

Solution

local attachedMonitor = peripheral.wrap("top")
local networkedMonitor = peripheral.wrap("monitor_0")
attachedMonitor.clear()
networkedMonitor.clear()
attachedMonitor.write("A")
networkedMonitor.write("B")

Screenshot

Cool. So we can attach monitors, clear their screens and write a simple message to them. Before we can do much more, we need to learn how to control the dimensions of our monitors and where we are going to write our text.

Getting the Monitor Size and Positioning Text

Let's figure out how big our monitors are. Add the following to the code and run it:


local widthA, heightA = attachedMonitor.getSize()
local widthB, heightB = networkedMonitor.getSize()

print("Attached Monitor Size: # of Columns ="..widthA..", Z="..heightA)
print("Networked Monitor Size: # of Rows="..widthB..", Z="..heightB)

The width represents how many columns the monitor can print. The height represents how many rows the monitor will print. The tricky thing to remember is that the first row is the top of the monitor and the last (or greatest valued row) is at the bottom – just like a spreadsheet application. A simple way to remember is that "column" starts with a "c" and "row" starts with an "r". And we all know that "C" comes before "R" – always column before row.

Now that we know the size of our monitor, we can position the text accordingly. To do that, we are going to use the method setCursorPos(column,row).

Try this code in your program.



attachedMonitor.clear()
networkedMonitor.clear()

attachedMonitor.setCursorPos(1,5)
networkedMonitor.setCursorPos(5,1)

attachedMonitor.write("A")
networkedMonitor.write("B")


What happened? Note that the attached monitor displayed its text in column 1 and row 5. So it displayed to the left and down in comparison to the networked monitor which displayed its text in column 5 and row 1 (up and to the right).

Screenshot

Let's have some fun with that. Here is a simple program that plays with cursor position:



local attachedMonitor = peripheral.wrap("top")
local networkedMonitor = peripheral.wrap("monitor_0")
attachedMonitor.setTextScale(1)
networkedMonitor.setTextScale(1)
attachedMonitor.clear()
networkedMonitor.clear()
local widthA, heightA = attachedMonitor.getSize()
local widthB, heightB = networkedMonitor.getSize()

local column = 1
  for row = 1,heightA do
  attachedMonitor.setCursorPos(column,row)
  attachedMonitor.write(row)
  column = column+1
  sleep(1)
end

column = 1
for row = heightB,1,-1 do
  networkedMonitor.setCursorPos(column,row)
  networkedMonitor.write(row)
  column = column+1
  sleep(1)
end


Screenshot

Monitor Text Scale

Let's see if we can change the monitor size without adding additional monitors. Unlike computer screens, monitors can change the scale of text printed. The smaller the scale, the more text will fit. The larger, the less. Monitor text scales vary in increments of 0.5 – from 0.5 to 5.0. Create the following program and run it:



local attachedMonitor = peripheral.wrap("top")
local networkedMonitor = peripheral.wrap("monitor_0")
attachedMonitor.clear()
networkedMonitor.clear()
for scale = 0.5,5.0,.5 do
  attachedMonitor.setTextScale(5.5-scale)
  networkedMonitor.setTextScale(scale)
  attachedMonitor.setCursorPos(1,1)
  networkedMonitor.setCursorPos(1,1)
  attachedMonitor.write("A")
  networkedMonitor.write("B")
  sleep(1)
end
sleep(1)
attachedMonitor.setTextScale(1)
networkedMonitor.setTextScale(1)


Screenshot

Can We Get Color with That??

We now know how to attach a monitor using peripheral.wrap, clear() the screen, set the text scale using setTextScale(), position the cursor using setCursorPos(column,row), and write() to either monitor. But what about color? We are using an advanced monitor for a reason, afterall.

Using color is fairly straightforward. There are two types of color – text color and background color. Not surprisingly, the functions to set color will be setTextColor(colorNumber) and setBackgroundColor(colorNumber). Fortunately for us, there is a colors API which eliminates our need to memorize color values (although for custom colors, values are still useful).

The key with using color is to remember what you are trying to color – text or background. Background color is like the markings of a highlighter, whereas text color is like the colors of your ball-point pens. So let's try it. Write the following program and run it once.


local attachedMonitor = peripheral.wrap("top")
attachedMonitor.setTextScale(0.5)
attachedMonitor.setBackgroundColor(colors.blue)
attachedMonitor.clear();
attachedMonitor.setCursorPos(1,3)
attachedMonitor.write("This is white")
attachedMonitor.setTextColor(colors.yellow)
attachedMonitor.setCursorPos(1,4)
attachedMonitor.write("This is yellow")



What happened here? Well, we found out that wehn you clear the monitor, whatever color the background color is set to will fill the whole screen. We also should have two lines of text displayed, one in white and one in yellow. However, color also persists. Run the program one more time and you will find that both lines are printed in yellow (not what we wanted).

Screenshots[

If you want to see all the colors, run the following code. In it, you will find that we created a table called colorTable and we iterate through it to set the background color on the monitor, clearing the screen each time to fill the screen with color and sleeping for one second so you can see the display. When it is done, we make sure to reset the backround color to the default (black) and clear the screen.

All the Colors

local colorTable = {
  colors.white;
  colors.lightGray;
  colors.gray;
  colors.black;
  colors.yellow;
  colors.orange;
  colors.red;
  colors.magenta;
  colors.purple;
  colors.lime;
  colors.green;
  colors.cyan;
  colors.lightBlue;
  colors.blue;
  colors.brown;
}

local attachedMonitor = peripheral.wrap("top")
for index,color in ipairs(colorTable) do
  attachedMonitor.setBackgroundColor(color)
  attachedMonitor.clear()
  sleep(1)
end
attachedMonitor.setBackgroundColor(colors.black)


Pretty neat, as long as we remember that color persists, and we need to make sure to set the color to what we want it to be and reset it each time we want it to change.


What if the Monitor is not an Advanced Monitor?

If you try to set the color of a regular monitor to anything other than black or white, you will get an error message. So how do we prevent setting color on a regular monitor? There is a test you can do.


if attachedMonitor.isColor() then
-- the monitor is color
else
-- the monitor is black and white
end

If you are writing code for yourself, you will probably know when you need to ue color or black and white. If you are writing for others, you probably want to include some error checking using isColor().


Putting Things Together

At this point, we know what we need to do to control displaying to a monitor. Obviously we need to attach the monitor to the computer either by setting it on one of the computers sides or attaching it with modems and network cable. From there, we need to think about the following:
  • What is the monitors handle? use peripheral.wrap(<side or monitor label>) to obtain a handle for the monitor
  • What size is the monitor? use <monitor handle>.getSize() to get width and height.
  • Do we want to set the scale (is it big enough)? use <monitor handle>.setTextScale(<scale>)
  • What size is the monitor after setting scale? use <monitor handle>.getSize() to get width and height.
  • Is the monitor a color monitor? use <monitor handle>.isColor()
  • What color do we want the background to be? use <monitor handle>.setBackgroundColor(colors.<color name>)
  • What color do we want the text to be? use <monitor handle>.setText(colors.<color name>)
  • Do we need to clear the screen to set the background color all the same? use <monitor handle>.clear()
  • Where do we want to start displaying text at? use <monitor handle>.setCursorPosition(<column>,<row>)
  • Do we need to clear the line first (new idea alert)? use <monitor handle>.clearLine()
  • What text do we want to display? use <monitor handle>.write(<text as string>)
Here is an example program that does most of these things. See if you can follow what is happening.

ExampleA PasteBin link follows this code listing.



ocal attachedMonitor = peripheral.wrap("top")
attachedMonitor.setTextScale(0.5)
attachedMonitor.setBackgroundColor(colors.blue)
attachedMonitor.clear();
attachedMonitor.setCursorPos(1,3)
attachedMonitor.write("This is white")
attachedMonitor.setTextColor(colors.yellow)
attachedMonitor.setCursorPos(1,4)
attachedMonitor.write("This is yellow")

local attachedMonitor = peripheral.wrap("top")

local backgroundColorTable = {
	colors.white;
	colors.lightGray;
	colors.gray;
	colors.black;
	colors.lightBlue;
	colors.blue;
	colors.brown;
	colors.green;
}


local textColorTable = {
	colors.yellow;
	colors.orange;
	colors.red;
	colors.magenta;
	colors.purple;
	colors.lime;
	colors.cyan;
}

local sentence = "The quick colorful fox jumped over the lazy dog"
local backgroundColor,textColor
local width,height

-- Break the sentence into a table of words
local wordTable = {}
for word in string.gmatch(sentence,"%w+") do
	table.insert(wordTable, word)
end

--set the scale so we can print one word on each line.
for scale = 5,0.5,-0.5 do
	attachedMonitor.setTextScale(scale)
	width,height = attachedMonitor.getSize()
	if height >= #wordTable then
		print("Scale ="..tostring(scale))
		break
	end
end

-- scale is set, and we know width and height.

-- set a random background color

attachedMonitor.setBackgroundColor(backgroundColorTable[math.random(#backgroundColorTable)])
attachedMonitor.clear() -- will repaint monitor background

-- For each word, print it on a line in a random background and random text color
for line = 1,#wordTable do
	local word = wordTable[line]
	-- set a random background color
	attachedMonitor.setBackgroundColor(backgroundColorTable[math.random(#backgroundColorTable)])
	-- set a random text color
	attachedMonitor.setTextColor(textColorTable[math.random(#textColorTable)])
	--set a random column for our line position
	attachedMonitor.setCursorPos(math.random(width-#word),line)
	attachedMonitor.write(word)
		sleep(1)
end

-- reset monitor colors
attachedMonitor.setBackgroundColor(colors.black)
attachedMonitor.setTextColor(colors.white)


PasteBin Link for Code: fw52JfG3

Screenshot

Do you see the start of buttons in that? We positioned text on the screen in random background and text colors. For those whose background colors varied from the underlying background, thems be buttons! We'll make cooler ones in the next part. For now, let's talk about this program.

We created a handle for our attached monitor (first things first). We created two tables of colors – one for the background colors and one for the text colors. We did that so that we wouldn't have any "invisible" buttons (ones where the background and text colors are the same). Next we created a sentence and declared some local variables.

What we are going to do is split the sentence into a table of words, and for each word, we are going to print it on a line in a random background color and a random text color. We are also going to randomly choose the column position, making sure that the whole word will print on the line.

We used a string.gmatch function to go through the sentence and add each word found in the sentence into a table of words. Once done, we know how many words we need to print – so we can see if we can find a monitor text scale which will allow us to print all the words on the monitor, one on each row. To find the scale, we start at the largest scale, get its size, and check and see if its height will allow all of the words to print on the same screen. If it doesn't, we reduce the text scale by 0.5 and check again until we find one that allows us to print all the words, one on each line (row). As it turns out, the scale required is 0.5, as there are 9 lines needed.

Now for the display stuff. We set a random background color for the monitor and clear the screen to make the monitor display all in that color. We can then begin displaying words.

The loop which allows us to display each word on a separate line at first can be confusing to decipher. But all we are doing is getting a word from the table, setting the background color, setting the text color, setting the cursor position and displaying the word. What is confusing is realizing that we are pulling the colors randomly from the two color tables we created, and we are doing something wierd with the cursor position.

To set the cursor position, we know that we want to print the word so that the whole word shows up on a row. So, we can't start printing in a column that is greater than the width of the monitor less the width of the word itself. We submit that limit to the math.random function, and the word will then print randomly on any column where the word will fit. Then we display the word (write), sleep for the value of sleepInterval (which is 1 second), and do it all over again until we are out of words.

Finally, we reset the background and text colors to their normal colors and end the program.

A Note on Style

You might wonder why we had all of these variables and tables and #count functions in the code. After all, we could have counted the number of words ourselves and used 9 instead of #wordTable. Likewise, we could have figured out how many colors were in each table and used the hard-coded value instead of #backgroundColorTable. Why did we declare and set the montior_side_or_label variable to "top", and for that matter, why did we use that variable at all when we could have just used peripheral.wrap("top")??? Moreover, why even declare and use the variable sleepInterval?

The answer lies in the fact that we want flexiblity in our code. We want to be able to change one thing in the code at an easily accessible location and alter the behavior of our program. We also want to be able to read our code in a sensible way – once you are familiar with the commands, the variable names help make sense of what we are doing at any point in our code. This is part of a programming concept known as self-documenting code, and it makes your code much easier to debug. Finally, coding in this fashion – with tables, variables, loops and lots of flexibility will make button programming all that easier – both in whatever program you are currently writing, AND in the program you decide to paste your code into down the road.

Wrapping Up Part I

We have come a long way down the road of using monitors to flexibly create buttons and control stuff. If you have followed along, you pretty much have no trouble setting up a monitor, getting it to communicate with the computer, and displaying text anywhere at all in any color you desire. You can rescale text to suit your needs. And you are now familiar with a lot of the functions, variable types and code structures we will use in the remaining parts of this tutorial.

In the next part of this tutorial, Part II, we will learn to create actual buttons, and then we will create a button function that allows us to flexibly draw whatever size button we want, with whatver label we want. In Part III, we will attach touch screen events and key-press events to our code to make our buttons clickable and have them do something for us. In the final part (Part IV), we will create a flexible program that ties all of these concepts together, and leave you with a highly-functional Button program that you can use to control all sorts of functionality in Minecraft.

End of Part I




If you liked this tutorial or it was helpful, vote it up. If you have a comment or suggestion, leave it below. Thanks.
Edited on 08 February 2014 - 06:22 PM
surferpup #2
Posted 06 February 2014 - 04:38 PM
Part II in draft stage, will be posted in two days.
surferpup #3
Posted 08 February 2014 - 07:23 PM
Part II Posted here.
Andrew2060 #4
Posted 11 February 2014 - 09:33 AM
cool
surferpup #5
Posted 17 February 2014 - 12:20 AM
Part III posted today here.