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

Add blinking cursor to a numbers only input function

Started by Himself12794, 04 February 2014 - 03:16 PM
Himself12794 #1
Posted 04 February 2014 - 04:16 PM
I wrote a function that only accepts numbers as an input, and was quite pleased when it worked on the first try. However, I now want to add a blinking cursor so the user knows they are able to input something. Here's what I have so far:

local function getArraySize(array)
local count = 0
for _ in ipairs(array) do
  count=count+1
end
return count
end
local function numbersOnly(pre)
local x,y=term.getCursorPos()
num={}
while true do
  if pre then write(pre) end
  for _,v in ipairs(num) do
   write(v)
  end
  --write(' <')
  _,key=os.pullEvent('key')
  if key == 2 or key == 79 then
   table.insert(num,1)
  elseif key == 3 or key == 80 then
   table.insert(num,2)
  elseif key == 4 or key == 81 then
   table.insert(num,3)
  elseif key == 5 or key == 75 then
   table.insert(num,4)
  elseif key == 6 or key == 76 then
   table.insert(num,5)
  elseif key == 7 or key == 77 then
   table.insert(num,6)
  elseif key == 8 or key == 71 then
   table.insert(num,7)
  elseif key == 9 or key == 72 then
   table.insert(num,8)
  elseif key == 10 or key == 9 then
   table.insert(num,9)
  elseif key == 11 or key == 82 then
   table.insert(num,0)
  elseif key == 14 then
   table.remove(num, getArraySize(num))
  elseif key == 28 then
   final=0
   size=getArraySize(num)
   for i,v in ipairs(num) do
	final=final+(v*10^(size-i))
   end
   print()
   return final
  end
  term.setCursorPos(x,y)
  term.clearLine()
end
end
I can add a stationary cursor, but since I don't know how to time out an event, I can't make it blink. Any suggestions?
MKlegoman357 #2
Posted 04 February 2014 - 04:25 PM
Cursor blinking is implemented inside of ComputerCraft, You don't need to make it by yourself. The cursor's blinks at where the actual cursor (set with term.setCursorPos) is. For all ComputerCraft APIs just visit the APIs page in the Wiki.

Sidenote to myself: don't put that many links in one post.

Edit: Just had a read through your code and I have some suggestions and tips:

You check for specific keys, if they were pressed. That's not very good IMO (In My Opinion). I suggest using 'char' event. 'char' event returns the actual writable character that was pressed, so you don't have to check for specific key presses.


local event, char = os.pullEvent("char")

print("You pressed '" .. char .. "' character.") --// If you would press 'e' -->> You pressed 'e' character.

Also note that it will always return a string, so if you want that string to become a number just use tonumber(StringThatIsANumber) function:


local text = "123" --// We want this text to become a number
print(type(text)) -->> string

local number = tonumber(text) --// Convert that text to a number
print(type(number)) -->> number

--// Note: function type() returns the type of the variable, ex.: string, number, table, etc.

But what if the user pressed 'e' or any other non-number character/key? If you give a non-number value to tonumber it will return nil (nothing):


local notANumber = "test string"

if tonumber(notANumber) ~= nil then --// If tonumber() didn't return nil
  print("It's a valid number!")
else
  print("It's not a number!")
end

--// Note: You can use this instead:

if tonumber(notANumber) then

--// Because every value non-nil or non-false value evaluates to true

Another tip is about getting table's length. You can use # in front of your table's variable's name to get it's length:


local myTable = {1, 2, 3}

print(#myTable) -->> 3

And finally, looks like you know how to use local variables, then why didn't you localized your event and it's returnings, and the num table and a few other variables?


num = {}

...

_,key=os.pullEvent('key')

...

final=0
size=getArraySize(num)
Edited on 04 February 2014 - 03:56 PM
Himself12794 #3
Posted 04 February 2014 - 11:22 PM
Cursor blinking is implemented inside of ComputerCraft, You don't need to make it by yourself. The cursor's blinks at where the actual cursor (set with term.setCursorPos) is. For all ComputerCraft APIs just visit the APIs page in the Wiki.

Sidenote to myself: don't put that many links in one post.

Edit: Just had a read through your code and I have some suggestions and tips:

You check for specific keys, if they were pressed. That's not very good IMO (In My Opinion). I suggest using 'char' event. 'char' event returns the actual writable character that was pressed, so you don't have to check for specific key presses.


local event, char = os.pullEvent("char")

print("You pressed '" .. char .. "' character.") --// If you would press 'e' --&amp;gt;&amp;gt; You pressed 'e' character.

I'm still quite perplexed on adding the cursor though.

Also note that it will always return a string, so if you want that string to become a number just use tonumber(StringThatIsANumber) function:


local text = "123" --// We want this text to become a number
print(type(text)) --&amp;gt;&amp;gt; string

local number = tonumber(text) --// Convert that text to a number
print(type(number)) --&amp;gt;&amp;gt; number

--// Note: function type() returns the type of the variable, ex.: string, number, table, etc.

But what if the user pressed 'e' or any other non-number character/key? If you give a non-number value to tonumber it will return nil (nothing):


local notANumber = "test string"

if tonumber(notANumber) ~= nil then --// If tonumber() didn't return nil
  print("It's a valid number!")
else
  print("It's not a number!")
end

--// Note: You can use this instead:

if tonumber(notANumber) then

--// Because every value non-nil or non-false value evaluates to true

Another tip is about getting table's length. You can use # in front of your table's variable's name to get it's length:


local myTable = {1, 2, 3}

print(#myTable) --&amp;gt;&amp;gt; 3

And finally, looks like you know how to use local variables, then why didn't you localized your event and it's returnings, and the num table and a few other variables?


num = {}

...

_,key=os.pullEvent('key')

...

final=0
size=getArraySize(num)

For the local statements, some of them don't have them because this was my first version of the code, and I wasn't really too worried about them the moment. Also, I'm not just trying to get only a number, but I was setting it so the only keys that could be pressed were the numbers.
I didn't know about the char event and getting the length of a table the easy way, will definitely implement it as soon as I get around to modifying the code.
Since the when I want the cursor is actually during os.pullEvent() and not read(), it doesn't show up and that's why I'm looking for a way to add it.

Thanks for the tips!

Edit:

Thanks to your comments, I have dramatically decreased the size and complexity of the function:

local function numbersOnly(pre)
	local x,y=term.getCursorPos()
	local num={}
	while true do
		if pre then write(pre) end
		for _,v in ipairs(num) do
			write(v)
		end
		local event,key=os.pullEvent()
		if event=='char' then
			if tonumber(key) then
				key=tonumber(key)
				if key>=0 and key<10 then
					table.insert(num,tonumber(key))
				end
			end
		elseif event=='key' then
			if key==14 then
				table.remove(num, #num)
			elseif key==28 then
				final=0
				size=#num
				for i,v in ipairs(num) do
					final=final+(v*10^(size-i))
				end
				print()
				return final
			end
		end
		term.setCursorPos(x,y)
		term.clearLine()
	end
end
Edited on 04 February 2014 - 11:20 PM
theoriginalbit #4
Posted 04 February 2014 - 11:30 PM
term.setCursorBlink( boolean state )

if you want people to type only numbers you could also check the key code returned from the key event and make sure that it is between keys.one (code 2) and keys.zero (code 11). Additionally depending on requirements also allowing keys.period (code 52) and keys.minus (code 12). Key codes.
Himself12794 #5
Posted 05 February 2014 - 12:23 AM
term.setCursorBlink( boolean state )

if you want people to type only numbers you could also check the key code returned from the key event and make sure that it is between keys.one (code 2) and keys.zero (code 11). Additionally depending on requirements also allowing keys.period (code 52) and keys.minus (code 12). Key codes.
Yes, thank you. I've done something like that, making my code much simpler.
This is why I like to get feedback on my work from the forums. I always find there is a simpler way, and I learn much.

I guess the real question I'm asking is, How do I do a timeout event on os.pullEvent()?
Edited on 04 February 2014 - 11:22 PM
theoriginalbit #6
Posted 05 February 2014 - 12:31 AM
I guess the real question I'm asking is, How do I do a timeout event on os.pullEvent()?
Make use of timers.
Himself12794 #7
Posted 05 February 2014 - 12:33 AM
I think I've found the solution. By messing around with os.startTimer(), I think I have my answer. Now I just need to mess with timing to get perfect blinking rate.
Edited on 04 February 2014 - 11:34 PM
theoriginalbit #8
Posted 05 February 2014 - 12:38 AM
Now I just need to mess with timing to get perfect blinking rate.
Why not just stick with the default blink rate? :huh:/>
Himself12794 #9
Posted 05 February 2014 - 12:50 AM
So I have one problem after implementing this. Everything works fine, and the cursor blinks as desired, until I use backspace, when it then starts behaving erratically. Perhaps this has to do with how I remove a value from the list? Here's the code:

local function numbersOnly(pre)
	local cursor = true
	local x,y=term.getCursorPos()
	local num={}
	while true do
		if pre then write(pre) end
		for _,v in ipairs(num) do
			write(v)
		end
		if cursor then write('_') end--else write(' ') end
		local myTimer = os.startTimer(0.25)
		local event,key=os.pullEvent()
		if event=='char' then
			if tonumber(key) then
				key=tonumber(key)
				if key>=0 and key<10 then
					table.insert(num,key)
				end
			end
		elseif event=='timer' then
			cursor = not cursor
		elseif event=='key' then
			if key==14 then
				num[#num]=nil
			elseif key==28 then
				final=0
				size=#num
				for i,v in ipairs(num) do
					final=final+(v*10^(size-i))
				end
				
				term.setCursorPos(x,y)
				term.clearLine()
				if pre then write(pre) end
				for _,v in ipairs(num) do
					write(v)
				end
				
				print()
				return final
			end
		end
		term.setCursorPos(x,y)
		term.clearLine()
	end
end

Now I just need to mess with timing to get perfect blinking rate.
Why not just stick with the default blink rate? :huh:/>
Because I am making a psuedo read() method that accepts only numbers as input, and using os.pullEvent() does not show the cursor.
Edited on 04 February 2014 - 11:50 PM
theoriginalbit #10
Posted 05 February 2014 - 12:53 AM
you're not setting the cursor position back a space.

honestly though using a table to make your number is definitely inefficient!
Lyqyd #11
Posted 05 February 2014 - 01:01 AM
You should try using term.setCursorBlink(true) as mentioned earlier in the post.
Himself12794 #12
Posted 05 February 2014 - 01:02 AM
you're not setting the cursor position back a space.

honestly though using a table to make your number is definitely inefficient!
The reason I had to use a table was because if I used a variable, even if I designated the value as a string, if it is a number, it would treat it as an integer and add the values instead of adding to the end of the string.
theoriginalbit #13
Posted 05 February 2014 - 01:06 AM
The reason I had to use a table was because if I used a variable, even if I designated the value as a string, if it is a number, it would treat it as an integer and add the values instead of adding to the end of the string.
Is it possible that you post this code up? 'cause it definitely wouldn't do that.
Himself12794 #14
Posted 05 February 2014 - 01:06 AM
You should try using term.setCursorBlink(true) as mentioned earlier in the post.
Wow… I just realized what you guys were all saying. I thought you had misunderstood me when I had misunderstood you. I feel so dense now. Really sorry for that guys. I did not know that doing that would make the cursor blink no matter what was going on.
Again, I am sorry for being so dense.
Problem solved!
Himself12794 #15
Posted 05 February 2014 - 01:20 AM
The reason I had to use a table was because if I used a variable, even if I designated the value as a string, if it is a number, it would treat it as an integer and add the values instead of adding to the end of the string.
Is it possible that you post this code up? 'cause it definitely wouldn't do that.
Again, I'm feeling quite foolish. I was mixing up python with lua and tried to use c=a+b to add strings instead of ".." Still, using tables is still the easiest way to remove a value from the end. Here's what I'm left with, using strings instead of a table, but haven't cooked up a way to remove the last value beyond converting it to a number and messing with it there.

local function numbersOnly(pre)
    term.setCursorBlink(true)
    local x,y=term.getCursorPos()
    local num=''
    while true do
        if pre then write(pre) end
        write(num)
        local event,key=os.pullEvent()
        if event=='char' then
            if tonumber(key) then
                if tonumber(key)>=0 and tonumber(key)<10 then
                    num=num..key
                end
            end
        elseif event=='key' then
            if key==14 then
                num[#num]=nil
            elseif key==28 then
                return tonumber(num)
            end
        end
        term.setCursorPos(x,y)
        term.clearLine()
    end
end
theoriginalbit #16
Posted 05 February 2014 - 01:21 AM
The reason I had to use a table was because if I used a variable, even if I designated the value as a string, if it is a number, it would treat it as an integer and add the values instead of adding to the end of the string.
take a look at this example I just whipped together, uses strings instead of a table

example code

local function readNumber( pre )
  --# turn the blinking cursor on
  term.setCursorBlink(true)

  --# this is the variable to hold the users input
  --# it should be noted that :match("(%d+)") is used to remove any non-number character from the initial input
  local input = pre and pre:match("(%d+)") or ""
  --# initial location of cursor
  local sx, sy = term.getCursorPos()
  --# draw the initial input
  term.setCursorPos(sx, sy)
  write(input)
  term.setCursorPos(sx + #input, sy)
  --# loop
  while true do
	--# wait for an event
	local event, code = os.pullEvent()
	--# if the event was a character event, and the character is a number
	if event == "char" and tonumber(code) then
	  --# append it to the end of the input
	  input = input..tonumber(code)
	  --# redraw the input to the screen
	  term.setCursorPos(sx, sy)
	  write(input)
	  term.setCursorPos(sx + #input, sy)
	--# if the event was a key event
	elseif event == "key" then
	  --# if enter was pressed exit the loop
	  if code == keys.enter then
		break
	  --# if backspace was pressed
	  elseif code == keys.backspace then
		--# set the input to what it is currently minus the backspaced character
		input = input:sub(1, #input-1)
		--# redraw input to the screen with a blank space on the end to remove the old character
		term.setCursorPos(sx, sy)
		write(input..' ')
		term.setCursorPos(sx + #input, sy)
	  end
	end
  end
  --# turn off the cursor blinking
  term.setCursorBlink(false)
  --# return the users input back to the calling function making sure to convert it to a number
  return tonumber(input)
end

test code


local num = readNumber('123')
print(num)
print(type(num))

local num = readNumber('123abc') --# note, because of the :match("(%d+)") the 'abc' is removed from this
print(num)
print(type(num))
Edited on 05 February 2014 - 12:23 AM
Himself12794 #17
Posted 05 February 2014 - 01:29 AM
The reason I had to use a table was because if I used a variable, even if I designated the value as a string, if it is a number, it would treat it as an integer and add the values instead of adding to the end of the string.
take a look at this example I just whipped together, uses strings instead of a table

example code

local function readNumber( pre )
  --# turn the blinking cursor on
  term.setCursorBlink(true)

  --# this is the variable to hold the users input
  --# it should be noted that :match("(%d+)") is used to remove any non-number character from the initial input
  local input = pre and pre:match("(%d+)") or ""
  --# initial location of cursor
  local sx, sy = term.getCursorPos()
  --# draw the initial input
  term.setCursorPos(sx, sy)
  write(input)
  term.setCursorPos(sx + #input, sy)
  --# loop
  while true do
	--# wait for an event
	local event, code = os.pullEvent()
	--# if the event was a character event, and the character is a number
	if event == "char" and tonumber(code) then
	  --# append it to the end of the input
	  input = input..tonumber(code)
	  --# redraw the input to the screen
	  term.setCursorPos(sx, sy)
	  write(input)
	  term.setCursorPos(sx + #input, sy)
	--# if the event was a key event
	elseif event == "key" then
	  --# if enter was pressed exit the loop
	  if code == keys.enter then
		break
	  --# if backspace was pressed
	  elseif code == keys.backspace then
		--# set the input to what it is currently minus the backspaced character
		input = input:sub(1, #input-1)
		--# redraw input to the screen with a blank space on the end to remove the old character
		term.setCursorPos(sx, sy)
		write(input..' ')
		term.setCursorPos(sx + #input, sy)
	  end
	end
  end
  --# turn off the cursor blinking
  term.setCursorBlink(false)
  --# return the users input back to the calling function making sure to convert it to a number
  return tonumber(input)
end

test code


local num = readNumber('123')
print(num)
print(type(num))

local num = readNumber('123abc') --# note, because of the :match("(%d+)") the 'abc' is removed from this
print(num)
print(type(num))
Oh I'm sorry, I should use comments more. The argument 'pre' is supposed to stand for prefix, and it is just there to print a little header in front of the number input.
theoriginalbit #18
Posted 05 February 2014 - 01:30 AM
Oh I'm sorry, I should use comments more. The argument 'pre' is supposed to stand for prefix, and it is just there to print a little header in front of the number input.
Ah, well in any case, you should be able to see what you need to do in order to get it to work.
Himself12794 #19
Posted 05 February 2014 - 01:45 AM
Oh I'm sorry, I should use comments more. The argument 'pre' is supposed to stand for prefix, and it is just there to print a little header in front of the number input.
Ah, well in any case, you should be able to see what you need to do in order to get it to work.
I do, and I have. I ended up with this:

local function numbersOnly(pre)
    term.setCursorBlink(true)
    local x,y=term.getCursorPos()
    local num=''
    while true do
        if pre then write(pre) end
        write(num)
        local event,key=os.pullEvent()
        if event=='char' and tonumber(key) then
            num=num..key
        elseif event=='key' then
            if key==14 then
                num=string.sub(num, 1, -2)
            elseif key==28 then
                print()
			    term.setCursorBlink(false)
                return tonumber(num)
            end
        end
        term.setCursorPos(x,y)
        term.clearLine()
    end
end
Thanks for all the help!