Skip to content

30. Fun with the keyboard

April 10, 2013

This post provides a simple class to handle keyboard input, and it also gives me the chance to demonstrate some of Codea’s ability to do powerful things without much code, which I find exciting.

Codea has some functions for keyboard input, but you’ll probably want to wrap them in something like a class to keep them out of the way of your main code. So that’s what I’ve done.

What I want is to be able to

  • call up the keyboard with a button
  • enter text and see it on the screen as I go
  • have the option to end text entry by pressing Enter, or a button
  • capture the final text for use in my program
  • .
    I note before I start that I haven’t needed to use the keyboard before now, so my code below may miss something obvious that could have been done better. But there is also a lot of other stuff that you should find useful. I also note that I’ve tried to explain everything fully, even if I’ve discussed it in earlier posts, because repetition helps learning.

    The obvious problems with entering text are firstly that there is no textbox to put text into, and also that your program has to pause while text is entered.

    I was thinking of providing a textbox in my Input class, but there are so many different ways you might want to draw it (all the colours, backgrounds, fonts, font size, screen position etc) that I thought it just made the code too complicated. So Main is going to have to draw the text on screen, but that’s pretty easy, anyway.

    NB I’m going to be referring to the draw function in Main a lot below, so for convenience, I’m going to call it “Main:draw”.

    Using state variables to pause the program

    You’ll hear reference to “state” when people talk about animated programs like Codea that are constantly refreshing the screen. What they mean is telling Main:draw what “state” the program is in, so it knows what to draw. So a state variable is a bit like a traffic controller at an intersection, and that’s all. So you need some “if” tests in Main:draw just to see what is going on.

    I’ve given the Input class a variable called self.state which is “Open” if the user is entering text, otherwise nil. So Main:draw can use that as a guide. For example, if I initialised the Input class with

        kb=Input(true,fn) --don't worry what the parameters mean for now
    

    and we start entering text, so the Input class sets

    	self.state='Open'
    

    then Main:draw can directly get hold of the state like this..

    	if kb.state=='Open' then 
    		--go on to draw text on screen
    

    ie Main can get hold of the “self” variables inside the class without having to call a function.

    When input is finished, the Input class sets self.state=nil, and Main:draw will pick this up and can get on with whatever happens next.

    Now you could get Main:draw to pick up the text when it’s all done, by (say) setting the self.state in the Input class to “”Ready”, and getting Main:draw to watch out for this.

    This would tell Main:draw that the input was done but not collected, so it could pick it up (I’ve stored the text in self.text, so Main:draw could get it from kb.text). The problem is we only want to do this once, so we need to change the value of self.state in Input to nil afterwards.

    Probably the easiest way is to write a function in Input that returns the final text and sets state to nil. So Main:draw, when it sees kb.state=”Ready”, can call this function to get the final text, and next time Main:draw runs, kb.state=nil so it won’t try and get the text more than once.

    But I didn’t do it that way. I figured that you’ll probably want to do something with the text when it’s entered, and Main:draw probably isn’t the best place to be managing text input. Ideally, you want to write a function in Main that will handle the final text. I could simply get Main:draw to call that function when it saw kb.state==”Ready”, but there may be a better way, which creates less clutter in Main:draw.

    Callback

    Callbacks seem mysterious at first, but they are quite simple. In this case, what happens is that when we set kb=Input, we pass through the name of the function we want to run when text entry is finished, and Input will remember it, and call that function when the input is done.

    So, supposing the function I want to call is getText(), I create an Input object

        kb=Input(true,getText) --specify callback
    

    if you wondered, the “true” parameter just tells Input that pressing enter ends the input

    The init function in Input stores this function name (I didn’t have to use the variable name “callback”, by the way, you can call it anything)

    function Input:init(c,fn)
        self.callback=fn --store the callback
    

    then, when the entry is done, the Input class simply runs the callback function

        self.callback()
        self.state=nil
    

    Note this doesn’t involve Main:draw at all, except that the state is set to nil so Main:draw knows to stop drawing the text on screen.

    If you load a webpage or picture from the internet, you’ll find it also uses callbacks, because the time taken is unpredictable, so Codea basically says, “give me a function to call when I’m done, and I’ll get back to you”.

    Managing the keyboard

    The commands showKeyboard() and hideKeyboard() do what you expect. But what about the actual keystrokes?

    If you use buttons to turn the keyboard on and off, then there’s no need to watch for individual keystrokes, and when the user presses the “Off” button, you can simply capture the text with

        self.text=keyboardBuffer()
    

    However, if you want to show the text as it’s being entered, or if you’re a smart alec like me who may want to be able to press enter to show I’ve finished, then I need to check individual keystrokes.

    To do this, I need to include this in Main, because it is the only place that traps keyboard entry

    function keyboard(key) --traps keyboard entries
        kb:keyboard(key) --pass details through to Input class
    end
    

    So I pass it straight through to my Input class to handle, and I put a keyboard function in there too (note, I could call it something else, but this way, there is no confusion)

        --if users is able to press Enter to finish, check if char 10 is pressed and exit if yes
        if self.closeOnEnter and string.byte(key)==10 then self:Close() end
        self.newKey=true --to tell the draw function there is new text
    

    The first line above just checks if the enter key has been pressed, and if we’d said that means we’ve finished, then we go to the Close function and wrap up.

    The second line sets an indicator that tells the Input:draw function that the text has changed, so it knows to pick up the latest version, like so

        if self.newKey==true then --update text if new chars have been typed
            self.text=keyboardBuffer()   --capture latest text
            self.newKey=false   --reset indicator
        end
    

    ..and we need to include kb:draw() in Main:draw, to ensure it gets called before Main:draw does anything with the text.

    Now at this point you should be jumping up and down on your chair, spluttering “why do you need Input:draw at all? Can’t you simply capture the new text in Input:keyboard with

       self.text=keyboardBuffer() 
    

    Well, yes you can, but there are a couple of problems. First, keyboardBuffer only adds the latest keystroke on exiting from the keyboard function, so we would have to append key to keyboardBuffer. However, the more serious problem is that the latest keystroke might be a backspace or enter – something that can’t be appended to the current string. So what we need to do is let Codea add the key itself, which means we can’t do anything inside the keystroke function. So how do we capture a text change as soon as possible after it’s entered? The Input:draw function. But we don’t want to be calling the keyboardBuffer function 60 times a second, so I created an indicator function so Input:draw only looks in the buffer when it needs to.

    Note – I wondered why Codea didn’t append key to keyboardBuffer before entering the keyboard function, and I figure it’s because sometimes you may want to change the value of the keystroke or do something other than append it to the current string, so it’s kept separate for you to play with.

    Finishing up

    I hope the Main:draw function is clear enough. It draws the text onscreen while it is being entered.

    The only other interesting thing is that you’ll see that when you are editing, an extra parameter button pops up to allow you to stop entering text (as an alternative to pressing enter), and when you’re done editing, that parameter disappears. I’ve done this with a function called SetupParameters, which deletes all the parameters and redraws only the ones I need.

    So here is the code. Please take the time to go through it and understand everything, because these concepts are important. And maybe the input code will be of some use to you.

    From → Programming

    5 Comments
    1. briarfox permalink

      I really liked this tutorial. I figured a good use would be as an dev console. Have you found a way to output to the command line? I really dislike having to open the side panel to type. It would be awesome if this could be done as it is in your demo.

      Thanks again, I look forward on a daily basis to your tutorials.

    2. briarfox permalink

      I figured it out from a post by @mpilgrem on the codea forum. I set it up as a function in Input. Works rather nicely!

      function setup()
      parameter.text(“Command”, “”)
      parameter.action(“Run”, processCommand)
      end

      function draw() background(0) end

      function processCommand()
      local g = _G[Command]
      if g then — Is Command a global variable?
      print(“=> “..tostring(g))
      else
      local f = loadstring(Command)
      if f then — Is Command a valid function body?
      local r = {f()} — Preserve any results
      local n = #r — Count them
      if n == 0 then — No results?
      print(“=> nil”) — Strictly, ‘no result’ not ‘nil’
      else
      for i = 1, n do
      r[i] = tostring(r[i]) — Convert to strings
      end
      print(“=> “..table.concat(r, ” “)) — Output
      end
      else
      print(“attempt to call a string value”) — Error
      end
      end
      end

    3. briarfox permalink

      I didn’t write the code 🙂 just incorporated it into your Input class 🙂 np, thanks again for the great tutorials.

    4. macattacam permalink

      How do I get this to actually display on the screen after I type it? I see it on the output section, but I want to type and have it display on the screen. Should I follow what briarfox did? Thanks.

    Leave a comment