Lua Programming
The TI-Nspire allows, since OS v3.0, programming with the Lua language through a hidden application "TI.ScriptApp".
This page describes how to setup a Lua development environment and documents the currently known Lua functions for the Nspire, and a brief description of how to use the functions.
Note: Neither the io nor os libraries are present for the TI-Nspire, which seems to be running a light version of Lua 5.1.4.
Here's a dump of a lot of functions available in the API : Link to the dump.
Prerequisites
Lua is only supported starting from OS v3.0.1. Creating Lua scripts is currently not officially supported by TI, you need one of the following third-party tools to convert Lua scripts to TI-Nspire documents:
- LUAtoTNS.sh (bash script, works on Linux, Mac OS and Unix, or Windows + Cygwin or Windows + MSYS).
- maketns.py (Python script, cross-platform)
- lua2ti (Windows + .NET Framework v4.0)
- lua2ti_linux (Linux (No longer requires Mono, it is now a native Linux binary!))
You may also want to install Lua on your computer: luac -p can be used to check the syntax of your script before running it on a TI-Nspire or an emulator.
Standard Library
- _G - Global Variable - A global variable (not a function) that holds the global environment (that is, _G._G = _G). Lua itself does not use this variable; changing its value does not affect any environment, nor vice-versa. (Use setfenv to change environments.) - taken from Lua docs
- print - print (···) - Receives any number of arguments, and prints their values to stdout, using the tostring function to convert them to strings. print is not intended for formatted output, but only as a quick way to show a value, typically for debugging. For formatted output, use string.format. - taken from Lua Docs
- tostring - tostring (e) - Receives an argument of any type and converts it to a string in a reasonable format. For complete control of how numbers are converted, use string.format. If the metatable of e has a "__tostring" field, then tostring calls the corresponding value with e as argument, and uses the result of the call as its result. - taken from Lua Docs.
- xpcall - xpcall (f, err) - xpcall calls function f in protected mode, using err as the error handler. Any error inside f is not propagated; instead, xpcall catches the error, calls the err function with the original error object, and returns a status code. Its first result is the status code (a Boolean), which is true if the call succeeds without errors. In this case, xpcall also returns all results from the call, after this first result. In case of any error, xpcall returns false plus the result from err. - taken from Lua Docs
- select - select (index, ···)- If index is a number, returns all arguments after argument number index. Otherwise, index must be the string "#", and select returns the total number of extra arguments it received. - Taken from Lua Docs
- getmetatable -
- unpack -
etc.
Concepts and Basics
This part will explain you how the Lua actually works inside the OS and help you to figure out what you're doing when you write a script for the TI-Nspire. Before reading this, you have to know all about the Lua basics.
The Lua is an interpreted script language which means that it isn't as fast as ASM/C programs, but is still better than the TI-BASIC. One good thing is that this language is in a complete harmony with the OS with basic events and a powerfull graphic context. First, we have to understand how this works. Basicly the script is launched before the executive engine. This means that we can neither evaluate expressions nor use some of the standard librairy (like math) until the engine is launched. Here is a call summary :
- Launch string librairy
- ...
- Open and launch User's Lua script
- Launch math librairy
- Launch var API (store, recall, ...)
- Launch platform API (window, gc, ...)
- ...
- Link events (pseudo code)
function on.paint(gc) end function on.charIn(ch) end function on.arrowKey(key) end ----- etc ... while(Exit) ------- Some OS routines here ---- Begin of Event link buffer:captureDataFromKeyPad() -- (N) some underground routines to catch events if buffer.charInput ~= "" then -- (N) on.charIn(buffer.charInput) buffer.charInput= "" -- (N) end if buffer.arrowKey ~= "" then -- (N) on.arrowKey(buffer.arrowKey) buffer.arrowKey = "" -- (N) end ----- etc ... if platform.window:invalidate then platform.gc():purge() -- (N) Empty buffer before starting to draw on.paint(platform.gc()) -- save all the things we have to draw platform:paint() -- (N) draw all those things platform.window:invalidate = false -- say that the window has been drawn end ----- End of Event link end
Note : the (N) commented line only indicates the meaning of the routine. Those functions doesn't really exist.
Now we can understand how everything is linked, just by a main loop. This helps to understand that you haven't to do a loop yourself, otherwise the screen won't be refreshed. This also helps to see when the Screen is refreshed. In other words, we cannot use niether gc nor platform.gc() to draw somthing on the screen if we are in an other event of on.paint(). This also means that we haven't the need to pass gc as parameter and use platform.gc() instead only if you're from the on.paint() event.
Here an example of a simple Lua program which display on screen a message when a key is pressed and let it blank otherwise.
function on.paint(gc) if message then gc:setFont("sansserif", "r", 10) -- initialize font drawing gc:drawString(message, 0, 0, "top") -- display the message at (0, 0) coordinates message = nil -- erase the message timer.start(0.1) -- start a timer to exit on.paint() but keep in mind that we have to redraw the screen end end function on.timer() timer.stop() platform.window:invalidate() end function on.charIn(ch) message = "Hello World !" -- store a message platform.window:invalidate() -- force display end
When you open the document, the script is read once. It initialize and overwrite all the functions and globals you defined. Thus, message is nil. Once the on.paint() event is called, message is nil, thus, nothing is done. When you press a key that calls on.charIn() (see below for more information), message is valued to "Hello World" and we tell to the platform that the screen has to be refreshed. Then, on.paint() is called again, message is not nil then we display it, erase message and launch a timer. Why ? Because if we do a platform.window:invalidate() just here, we won't refresh the screen. Why again ? Just look at the pseudo code above. We set the window as drawn after each call of on.paint(). Thus a timer is necessary, to recall manually on.paint() and exit the on.paint() function to draw the screen. When the timer is ended, on.timer() is called and we demand to refresh the screen. The screen is redrawn but there is nothing to draw because message is nil. Thus, the graphic context lets the screen blank.
GC (as in Graphics Context)
Note: You need to add “gc:” before each of these commands in order to use them. ex. gc:setAlpha().
The amount of the screen available to Lua programs is 318 by 212 pixels.
- begin -
- clipRect -
- drawArc(x, y, width, height, start angle, finish angle) Note, to draw a circle, use drawArc(x - diameter/2, y - diameter/2, diameter,diameter,0,360) where x and y are the coordinates of the middle.
- drawImage ? First argument in format “TI.Image”
- drawLine(xstart, ystart, xend, yend) Draws a line starting at the point (xstart,ystart) and ending at the point (xend, yend)
- drawPolyLine(int list1 [,int list2, .., int listN]) Draws a shape from a list contaning successively the x and y coordinates of each point the line have to draw.
For example
drawPolyLine({0,0, 0,100, 100,100, 100,0, 0,0})
and
drawRect(0,0,100,100)
do the same. If there are multiple argument (which can be 4 elements list to represent lines), each list has to contain an even number of element.
- drawRect(x, y, xwidth, yheight) Draws a rectangle at (x,y) with the “x” side being “xwidth” long and the “y” side being “yheight” long
- drawString(string, x, y, PositionString) PositionString is the string’s anchor point and can be “bottom”, “middle”, or “top”.
- fillArc(x, y, width, height, start angle, finish angle) see drawArc
- fillPolygon(int list1 [,int list2, .., int listN]) see drawPolyLine
- fillRect(x, y, width, height) see drawRect
- finish -
- getStringHeight(string)
- getStringWidth(string)
- isColorDisplay Bool (Read-only) Returns 1 if color, 0 if not.
- setAlpha ≈ transparency ?
- setColorRGB(red, green, blue) Values range from 0 to 255.
- setFont(font, type, size) font {“sansserif”, ..}, type {“b”, “r”, “i”}, size(int)
- setPen(size, smooth) size {“thin”, “medium”, "thick"}, smooth {“smooth”, ..}
Platform
These are mainly read-only. These work by writing "platform." in front of them. Example : "platform.window:invalidate()"
- apilevel() : Returns 1.0.0
- window
- *width() - Returns the width of the window
- *height() - Returns the height of the window
- *invalidate() - Repaints the window
- isDeviceModeRendering() Returns true or false whether the unit is "rendering" or not
- gc ?
- isColorDisplay() Returns true if the unit the code is being run on has a color display (-> Nspire CX), and false otherwise.
Cursor
- hide() - hides the cursor (mouse pointer)
- set(x,y) - makes the cursor appear at coords (x,y)
- show() - Shows the cursor on screen
Document
- markChanged() - Flag the document as "changed" so the user has to save it after using it.
Events
- on.charIn(string) is called when the Nspire detects a non arrow key being pressed. ch is the character it detect. If you want to auto start an event in the file linked to key r(like a reset) put on.charIn(“r”) where you want. This Lua program lets you display the value of a valid non arrow key :
c = “” function on.charIn(ch) c = ch platform.window:invalidate() -- we force the screen draw end function on.paint(gc) gc:setFont(“sansserif”, “r”, 10) gc:drawString(c, 0, 0, “top”) end
- on.paint(gc) is called when the GUI is painted. 'gc' is the Graphics Context (see above)
- on.arrowKey(key) is called when an arrow key from the clickPad/TouchPad is pressed
- on.enterKey() is called when the enter key is pressed.
- on.escapeKey() is called when the escape key is pressed.
- on.tabKey() is called when the tab key is pressed.
- on.mouseDown(x, y) is called when we press the left mouse button. X and Y are the pressed point coordinates.
- on.mouseUp() is called when we release the left mouse button.
- on.help() is called when the combo-key Ctrl H is pressed.
- on.clearKey() is called when the combo-key Ctrl Clear is pressed.
- on.timer() is called when the timer has been finished
Here an example of using timer to play an animation :
x = 1 animating = false function on.paint(gc) gc:setFont("sansserif", "r", 10) gc:drawString(tostring(x), 0, 0, "top") if animating then x = x + 1 timer.start(0.5) end end function on.charIn(ch) animating = not animating -- switch state platform.window:invalidate() -- recall graph engine end function on.timer() timer.stop() platform.window:invalidate() -- recall graph engine end
- on.resize()
D2Editor
- newRichText() creates a new RichText object (default values : x, y, width, height = 0)
- resize(width, height)
- move(x, y)
- setText(string)
- getText() returns the RichText value
- setReadOnly()
- setFormattedText - ?
Example of a valid function using the D2Editor
function createRichTextBox box = D2Editor.newRichText() box:move(50, 50) box:resize(50, 50) box:setText("Hello World !") end
It seems that we can edit the RichText box while the document is openned because of the "setReadOnly" function.