Category Archives: Tutorials

Create your own menus without limits !Crée tes propres menus sans limite !

Aujourd’hui nous allons apprendre quelque chose qui vous sera extrêmement utile dans vos programmes Lua.
Vous connaissez surement ces fameux menus sur la plupart des applications de votre TI-nspire, que l’on ouvre avec la touche menu, et ou l’on peut soit choisir l’option avec le touchpad, soit sélectionner une lettre ou un chiffre pour exécuter la commande.
Vous devriez penser que reproduire cela sur vos applications Lua est très peu envisageable, vue qu’il faudrait beaucoup de code pour reproduire ces menus de manière exacte.
Pourtant, c’est bien plus simple que vous ne le pensez.

En effet, depuis l’os 3.0, le Lua intègre une fonction très intéressante, la fonction toolpalette.register.
Mais comment elle fonctionne ?
Ce que nous allons faire maintenant, c’est créer une table (si vous ne connaissez pas ce genre de variable en Lua, allez vous renseigner).
Une table qui va contenir toutes les informations nécessaires à notre menu. Voici donc un exemple qui vous fera comprendre plus vite que des mots :

votreTable = {
    {"Couleur",  --Menu parent
        {"Rouge"},  -- Sous options
        {"Vert"},
        {"Bleue"}
    }
}

Notez que vous pouvez très bien construire votre table de la manière suivante :

 votreTable = {{"Couleur",{"Rouge"},{"Vert"},{"Bleue"}}}

Mais la manière montrée au dessus est bien plus claire et lisible, grâce à l’indentation.

Si vous l’avez bien compris, ce menu va nous servir d’abord à choisir l’option Couleur, puis choisir notre couleur entre Rouge, Vert et Bleue.
Super, vous avez crée votre table, qu’est ce qui vous reste à faire ?
Nous allons simplement utiliser le toolpalette.register cité plus haut :

toolpalette.register(votreTable)

Et voila, vous pouvez essayer et voire votre magnifique menu ! …Mais que ce passe-t-il, Erreur ?
Oui, comme l’indique votre erreur, vous devez associer une fonction à votre table. Sinon votre menu ne servirait qu’à décorer.
Notre superbe menu sert à changer la couleur du fond, nous allons donc devoir faire quelques réglages avant de pouvoir admirer le travail.

D’abord, nous allons créer la fonction qui changera notre couleur. Nous allons l’appeler changerCouleur(menuparent,option).
Les deux arguments de cet fonction sont prédéfinis par toolpalette, le premier renvoie le menu parent (ici, l’option “Couleur”) tandis que l’autre renvoie l’option qu’on a sélectionné (Rouge, Vert ou Bleue)
Nous allons également créer une table couleurdufond qui contiendra l’information de quelle est la couleur du fond (par défaut, le blanc : {255,255,255}).
Il nous suffit juste de rajouter un misérable test “If” pour changer la variable couleurdufond en fonction de notre choix d’option.
Voila ce que ca donne :

couleurdufond = {255,255,255}
 
function changerCouleur(menuparent,option)
    if option=="Rouge" then
        couleurdufond = {255,0,0}
    elseif option=="Vert" then
        couleurdufond = {0,255,0}
    elseif option=="Bleue" then
        couleurdufond = {0,0,255}
    end
    platform.window:invalidate()
end

Notez le platform.window:invalidate() à la fin de la fonction changerCouleur, qui sert à appeler on.paint(gc) pour repeindre l’écran avec la nouvelle couleur.
Voila, c’est presque terminé. Plus qu’à coder notre fonction on.paint(gc) :

w = platform.window:width()
h = platform.window:height()
 
function on.paint(gc)
    gc:setColorRGB(unpack(couleurdufond))
    gc:fillRect(0,0,w,h)
end

Le unpack comme son nom l’indique, sert simplement à “casser” la table pour revoyer les valeures qu’elle contient une par une.

Et voila le tant attenu résultat :

Et le beau code source qui va avec :

w = platform.window:width()
h = platform.window:height()
 
couleurdufond = {255,255,255}
 
function changerCouleur(menuparent,option)
    if option=="Rouge" then
        couleurdufond = {255,0,0}
    elseif option=="Vert" then
        couleurdufond = {0,255,0}
    elseif option=="Bleue" then
        couleurdufond = {0,0,255}
    end
    platform.window:invalidate()
end
 
votreTable = {
    {"Couleur",
        {"Rouge",changerCouleur},
        {"Vert",changerCouleur},
        {"Bleue",changerCouleur}
    }
}
 
toolpalette.register(votreTable)
 
function on.paint(gc)
    gc:setColorRGB(unpack(couleurdufond))
    gc:fillRect(0,0,w,h)
end

Nous allons maintenant apprendre à activer/désactiver une option, grâce à la fonction toolpalette.enable(menuparent,option,booléen)
Comme pour notre fonction changerCouleur, l’argument menuparent renvoie “Couleur” alors que l’option renvoie Rouge, Vert ou Bleue.
Si on veut activer l’option, on indique true en dernier argument, sinon, on indique false.
Pour l’exemple, nous allons désactiver l’option Vert :

toolpalette.enable("Couleur","Vert",false)

Voila ce que ça donne :

Pour finir, une bonne petite astuce : la création de raccourcis, très utiles pour éviter d’avoir à chaque fois de réouvrir le menu.
Il vous suffira juste d’indiquer quel est le raccourci dans le menu, et de créer votre fonction on.charIn(touche) :

Ici, notre raccourci seront les touches “r”, “v” ou “b” :

function on.charIn(touche)
    if touche=="r" then
        couleurdufond = {255,0,0}
    elseif touche=="v" then
        couleurdufond = {0,255,0}
    elseif touche=="b" then
        couleurdufond = {0,0,255}
    end
    platform.window:invalidate()
end

Et voila, c’est finit, vous maîtrisez les menus maintenant. C’est à vous de libérer votre créativité et coder des programmes epoustouflants en Lua, amusez vous bien 😉

Téléchargement du fichier : Classeur1

Extend string.match to test and capture patterns

Pattern matching is a powerful feature of Lua’s standard string library. I use it often to automate text file conversion and reporting. For instance, I use pattern matching in a Lua script to format and email my weekly report.

I have maintained a weekly diary of projects I’ve worked on, accomplishments, meetings attended, plans, business travel, and other information since 1988. Each week is logged in a simple text file. This means that despite the evolution of text editors and file media over the decades, I can still read my original diary files. The Lua script that prepares my weekly report scans my latest diary file and uses pattern matches to extract and format an email that adheres to the current reporting standards where I work.

The string.match function is useful for both testing if a pattern exists in a string and for extracting substrings that match a pattern enclosed in parentheses. Lua calls these substrings “captures”. Often I want to do both simultaneously–test for a pattern in an if statement and capture substrings. For instance, in my weekly report generator, I have a bit of code:

if line:match("^Weekly Report.+(%d%d)/(%d%d)/(%d%d%d%d)") then
    local month = _1
    local day   = _2
    local year  = _3
    email:setSubject(("John Powers - %s-%s-%s Weekly Report"):format(year, month, day))

The first line checks to see if variable line starts with the text “Weekly Report” and contains a date. If it matches, as a side effect it also sets global variables _1, _2, and _3 to the captures, i.e. the month, day, and year captured from string line. The built-in definition of string.match does not have this side effect. But we can extend string.match to gain this new capability.

Here is the code I used to modify string.match.

do
    local smatch = string.match     -- keep the original definition of string.match
 
    -- String matching function
    -- Same results as string:match but as a side effect
    -- places the captures in global variables _1, _2, ...
    function string:match(pat)
        local matches = {smatch(self, pat)}    -- call the original match to do the work
        for i = 1, #matches do                 -- #matches == 0 if no matches
            _G["_" .. i] = matches[i]          -- assign captures to global variables
        end
        return unpack(matches)                 -- return original results
    end
end

Note the use of a do … end block. This creates a block that limits the scope of local variable smatch. Only the new function string.match can call it.

Placing captures into global variables is nothing new. Anyone familiar with the AWK, Perl, and Ruby scripting languages will recognize this feature right away.

A new, smarter way to create a Screen Manager

It hasn’t been a long time since Jim Bauwens surprised us with an elegant way to customize the gc object with your own functions. But he’s striking again.

Indeed, he came up with a new, smarter way to create a screen manager.
If you’re not familiar with this great concept, you should head over here.
For those who are, though, you should know that it’s all about thoroughly listing and linking all the events you’ll have defined later in each screen implementation.
For example :

function on.arrowKey(key) activeScreen:arrowKey(key) end
function on.timer() activeScreen:timer() end
 
-- long list of all handled events...       Then, somewhere else :
 
function myScreen:arrowKey(key)
    -- Your awesome code goes here
end
function myScreen:timer()
    print("I'm the myScreen timer, ticking....")
end
 
-- etc.

It sure works, but …. quite boring, eh ?

Well, let’s look at what Jim created.
I define (t)his new screen manager concept as “Smarter” because with this code, you won’t even have to directly (explicitly) rely on the traditional event handling you’re used to, writing things like on.arrowKey, on.enterKey… or the good old on.paint.
That’s right, with this new method : no more “function on.paint(gc) …” etc.

“What’s this sorcery about ?” , you may wonder ?
Well, once more, it’s all about intelligently using the power of Lua metatables.
In short, metatables are sets of properties defining an object (generally a table) ‘s behaviour.
There is a “__index” property that you can define, that will describe how the table will react when the script calls an undefined element of that table. Pretty useful, believe me.
Well, the thing is that when you write “function on.paint(gc)”, you’re actually defining the “paint” method of the “on” table (thus the dot).
What we want to do, is to get rid of the explicit definition and to “redirect” the paint event to whichever screen we want to.
So, we’re going to use an “eventDistributer” method that takes as arguments whatever its passed, with the use of the “…” (the event followed by its parameters, if any), and “passes” them to the screen we want (checking that the event actually exists (defined) for the screen :

local triggeredEvent = "paint"  -- first declaration, it will be overwritten anyway
local eventDistributer = function (...)
     local currentScreen = GetScreen()
     if currentScreen[triggeredEvent] then
         currentScreen[triggeredEvent](currentScreen, ...)
     end
end

This code should be rather clear to you now.

Now, what we have to do is to actually bind that function to the “on” ‘s metatable __index (notice the smart use of closures) :

local eventCatcher = {}
 
eventCatcher.__index = function (tbl, event)
    triggeredEvent = event
    return eventDistributer
end
 
setmetatable(on, eventCatcher)

That code tells the Lua script that whenever an event without an explicit “on.xxx” handler occurs, it will execute this function (which returns the function – that’s the closure I was talking about – that will take the args of the event and pass it through the eventDistributer thus actually calling the correct screen’s appropriate event handler). You might find all this confusing, but at some point you sould be able to figure it out 🙂

Anyway, here’s the full code for the screen manager and event redistributer.
(You’ll still need to create (and push) your screens as usual.)

local screens = {}
local screenLocation = {}
local currentScreen = 0
 
function RemoveScreen(screen)
    screen:removed()
    table.remove(screens, screenLocation[screen])
    screenLocation[screen] = nil
    currentScreen = #screens -- sets the current screen as the top one on the stack.
    if #screens<=0 then print("Uh oh. This shouldn't have happened ! You must have removed too many screens.") end
end
 
function PushScreen(screen)
    -- if already activated, remove it first (so that it will be on front later)
    if screenLocation[screen] then
        RemoveScreen(screen)
    end
 
    table.insert(screens, screen)
    screenLocation[screen] = #screens
 
    currentScreen = #screens
    screen:pushed()
end
 
function GetScreen()
    return screens[currentScreen] or RootScreen
end
 
Screen = class()
 
function Screen:init() end
 
function Screen:pushed() end
function Screen:removed() end
 
RootScreen = Screen() -- "fake", empty placeholder screen.
 
local eventCatcher = {}
local triggeredEvent = "paint"
 
local eventDistributer = function (...)
     local currentScreen = GetScreen()
     if currentScreen[triggeredEvent] then
         currentScreen[triggeredEvent](currentScreen, ...)
     end
end
 
eventCatcher.__index = function (tbl, event)
    triggeredEvent = event
    return eventDistributer
end
 
-- finally linking everything
setmetatable(on, eventCatcher)

I’m sure a lot of you readers love a working .tns example directly, so, I made one just for you : click here. It’s a simple example showing a few events, without a single on.xxxx event handler explicitely defined, inside 2 screens. I’ve commented the code quite well so you’d understand quickly, I’m sure 🙂

For a more “real-life” example fully demonstrating this technique (as well as the gc-customizing trick), I suggest downloading Jim’s Memory game here.

 

How to add your own functions to “gc”

Hi all,

As you know, in order to do graphical things in Lua on the Nspire platform, you have to deal with gc and its methods TI created, like drawString, drawRect, fillArc, etc.

Well, what if you wanted to make a drawRoundRect routine ?

You could certainely do something like :

function drawRoundRect(gc, x, y, wd, ht, rd)
        if rd > ht/2 then rd = ht/2 end
        gc:drawLine(x + rd, y, x + wd - (rd), y)
        gc:drawArc(x + wd - (rd*2), y + ht - (rd*2), rd*2, rd*2, 270, 90)
        gc:drawLine(x + wd, y + rd, x + wd, y + ht - (rd))
        gc:drawArc(x + wd - (rd*2), y, rd*2, rd*2,0,90)
        gc:drawLine(x + wd - (rd), y + ht, x + rd, y + ht)
        gc:drawArc(x, y, rd*2, rd*2, 90, 90)
        gc:drawLine(x, y + ht - (rd), x, y + rd)
        gc:drawArc(x, y + ht - (rd*2), rd*2, rd*2, 180, 90)
end
 
function on.paint(gc)
        drawRoundRect(gc, 100, 50, 20, 15, 5)
        ...
end

Indeed, that works.

But wouldn’t it be cool to actually have it like, “gc:drawRoundRect(100,50,20,15,5)”, so it can feel way more natural and wouldn’t need you to explicitely pass gc as an argument ? 😉
Well, here is the definitive solution to this, by Jim Bauwens 😉

function AddToGC(key, func)
        local gcMetatable = platform.withGC(getmetatable)
        gcMetatable[key] = func
end
 
local function drawRoundRect(gc, x, y, wd, ht, rd)
        -- the code above
end
 
AddToGC("drawRoundRect", drawRoundRect)
 
function on.paint(gc)
        gc:drawRoundRect(100, 50, 20, 15, 5)
        ...
end

One more thing :

You may have noticed the use platform.withGC, which is an API “2.0”+ (OS 3.2+) function. Here’s how to “recreate” it for earlier versions :

if not platform.withGC then
        platform.withGC = function(func, ...)
            local gc = platform.gc()
            gc:begin()
            func(..., gc)
            gc:finish()
        end
    end

[Update] : John Powers from TI commented that this definition of platform.withGC has some limitations, and proposed this better version (thanks !) :

if not platform.withGC then
    function platform.withGC(f)
        local gc = platform.gc()
        gc:begin()
        local result = {f(gc)}
        gc:finish()
        return unpack(result)
    end
end

A-to-Z Nspire Lua tutorials

This page links to Steve Arnold’s website where you can find excellent detailed, step-by-step tutorials.

However, they are all in English. Inspired-Lua is making its best to translate them into French too.
The french version so far is available here.

 





The lessons in this next section require OS 3.2 functionality.




Leçon 3 : Varier le contenu d’une table et opérateur conditionnel

Dans cette leçon, nous allons continuer notre travail sur les tables et sur les variable issues de TI-Nspire. Nous allons facilement customiser notre document, et avec un changement d’une ligne, nous allons créer une page qui peut être utilse à ceux qui veulent tirer avantage du Lua dans leur programmes TI-Nspire.
Mais avant, nous allons quitter Oclua pour TI-Nspire Scripting Tools, ou pour Luna. Tous deux sont légers et faciles à utilser, mais différents de Oclua. Pour cela, je vous envoie vers ce lienqui présente les façons d’exécuter un script écrit en Lua.

 

Leçon 3.1 : Modifions le contenu de nos lignes d’affichage

Un simple changement dans notre script transforme notre affichage de plusieurs lignes en un outils plus…utile.
Souvenez-vous : la ligne du script qui créait chacune des lignes d’affichage était celle-ci :
table[k] = "Ligne#"..k
Elle concaténait la valeur actuelle de k avec le string “Ligne#”, ce qui faisait de la première “Ligne#1”.
Souvenez-vous : la commande var.recallva chercher la valeur d’une variable TI-Nspire et l’attribue à une variable du script lua. Dans notre exemple, nous l’avions utilisé pour saisir le nombre de lignes que l’on voulait afficher.Maintenant, supposons que nous ayons une variable appelée ligne1, contenant ce que l’on veut afficher en première ligne. Puis une autre appelée ligne2 et ainsi de suite ? On pourait facilement contrôler le contenu de notre affichage, et les modifier n’importe quand depuis la Ti-Nspire. Pour cela, on pourrait simplement changer la ligne ci-dessus par :
table[k] = (var.recall("line"..k) or "Line #"..k)

Etudiez ces nouvelles lignes et essayez de comprendre ce qui se passe. Lorsque k = 1, var.recall recherche une variable appelée ligne1 et la range dans table[1] (La première entrée de la table, “table”). Si on ne la trouve pas, alors elle est stockée en tant que “ligne #1”. (On aurait très bien pu laisser un blanc, avec juste “”.) Et ainsi de suite pour les différentes valeurs de k.

Donc, en stockant ces variables dans un document TI-Nspire, elles sont immédiatemment affichées dans la page du script Lua.

Leçon 3.2 : Les conditions

Pour notre introduction au lua, nous avons besoin d’autres ingrédients : les structures conditionnelles. Ces dernières se ressemblent fortement en Lua et en TI-Nspire.
if condition then
&nbsp; instructions
else
&nbsp; instructions
end

Seul le endif se transforme en end !

Prenons un exemple : on veut que la première et la dernière ligne soient de la même couleur. Reprenons notre script.

for k = 1, linecount do
&nbsp; gc:setFont("sansserif", "r", 10)
&nbsp; gc:setColorRGB(158, 5, 7)
&nbsp; table[k] = (var.recall("line"..k) or "Line #"..k)
&nbsp; strwidth = gc:getStringWidth(table[k])
&nbsp; strheight = gc:getStringHeight(table[k])
&nbsp; gc:drawString(table[k], w/2 - strwidth/2 ,h*k/(linecount+1) + strheight/2)
end
Maintenant, il suffit d’ajouter une condition dans le bloc For. Si la ligne actuelle est la première / dernière, alors elle sera colorée.
if k == 1 or k == linecount then
&nbsp; gc:setFont("sansserif", "b", 10)
&nbsp; gc:setColorRGB(20, 20, 137)
else
&nbsp;gc:setFont("sansserif", "r", 10)
&nbsp;gc:setColorRGB(158, 5, 7)
 
end
Il faut bien entendu mettre le tout dans la fonction on.paint(gc). Aussi, notez l’emploi du double signe égal que l’on utilise pour tester une égalité. Un simple signe égal est utilisé pour une définition. (k = 1)
A part cela, vous êtes largement en mesure de comprendre ce morceau de script. Sinon, retournez voir les leçons précédentes !Et de 3 ! Vous venez de finir la troisième leçon de ce tutoriel. Maintenant vous pouvez afficher du texte (un peu plus intéressant) et utiliser les conditions ! Prenez un peu de temps avant de passer à la suite ! ;)Dans le prochain cours, nous apprendrons à écrire du texte directement dans notre fenêtre Lua, ce qui est la base pour les jeux de type quizz !

How to make a .tns from a .lua file ?

< Back to Part 1

You have two options:

1) Use the Nspire computer software

Since version 3.2 (mid 2012), the Nspire software (“TINCS”) includes a script editor to directly create, edit, test, and debug your Lua code from within a TI-Nspire document 🙂

It is available from the Insert menu > Script Editor .

2) Use “Luna”

Luna is an open-source community tool, created by Olivier “ExtendeD” Armand, that can create .tns files (TI-Nspire) out of a .lua file, for instance.

You can download a Windows executable on TI-Planet.
For other platforms, you can compile the source code, available at the same link.

To use it, just write in a terminal / command prompt, the path to the Lua file followed by the path of .tns you want to create:

1
luna.exe myscript.lua mydocument.tns

Let’s go to Part 3 now!

How to create a Screen Manager

When you create a game or some tool in a “complete” way, it is often desired to achieve some sort of a set of screens/views throughout which the user navigates. There are several ways to achieve an engine that will allow us to manage all these screens, from simple code (but less convenient), to the most complex and/or difficult to understand, but easy to use after all. The goal of this tutorial is obviously not to give you a fixed idea of what a screen manager may look like (since there are many different types), but to offer you a set of choice. This tutorial is quite long as it presents comprehensive examples for the practice part and some necessary theory too, so stay focused !

First, what we mean by “Screen Manager” is a way of organizing the code so in such way that we can isolate the “drawing” parts from the functional parts. The clearer and visible this border is,  the neater and more understandable the code gets, therefore allowing easier integration of our “Screen Manager”.

Take this example : we want to make a game with : a simple menu, the game itself, a help page and a page of high-scores. At first glance, the number of screens is limited and fixed before the creation by our specifications. That number is low (4) and so it is easy to consider something like:

function on.create()
  screen = 0 -- 0=menu, 1=game, 2=help, 3=highscores
end
 
function on.paint(gc)
  if screen == 0 then -- we draw the menu
  elseif screen == 1 then -- we draw the game
  elseif screen == 2 then -- we draw the help
  elseif screen == 3 then -- we draw the highscores
  end
end
 
function on.enterKey()
  if screen == 0 then
    screen == 1
  elseif screen == 1 then
....

Ok! No need to go further! We get it. To create an ugly, tedious-to-write code, this would be the best solution.
It is primarily designed for very small applications easily doable.

There is however a less ugly way, using arrays of functions :

function on.create() -- or on.construction with OS >= 3.2
  menu, game, help, highscores = 1, 2, 3, 4
  screen = menu
  paints = {}
  enterKeys = {}
end
 
function on.paint(gc)
  paints[screen](gc)
end
 
function on.enterKey(gc)
  enterKeys[screen](gc)
end
 
paints[menu] = function(gc)... end -- we draw the menu here
paints[jeu] = function(gc) ... end -- we draw the game here
...
enterKeys[menu] = function(gc)... end -- menu handling here
enterKeys[jeu] = function(gc) ... end -- game handling here
....

Here, it is clear that reading the code will be much easier than before. Moreover, the use of labels instead of the values themselves ​​significantly improves reading the code !
Feel free to “abuse” of that… ! You can, if you have too many “constants”, using an array of constants in order to organize them, like this:

screen = {menu=1, jeu=2, aide=3, records=4}
--> screen.menu == 1

Or, if you want something even more “automatic” (no need to count/shift the numbers) :

function createEnv(t)
  local env = {}
  for i, v in ipairs(t) do
    env[v] = i
  end
  return env
end
screen = createEnv({"menu", "game", "help", "highscores"})
--> screen.menu == 1

That being said, handling different screens remains archaic for other uses than games. It is of course possible to create a more elaborate system that facilitates the use and coding. What could be better?

You guessed it … use classes ! (if you’re not familiar with them, go to our tutorial !)

What we will code is actually a superset of the TI-Nspire API. You know the on.paint(), on.arrowKey() etc. events … Well, we will pass them along to our screen manager. The interest of coding a “parent class” here is that we are sure that a screen has specific methods, which will be called from standard events, without the fear of forgetting any when we create a new screen.

First, we must create our Screen class:

Screen = class()
function Screen:init() end

First, a screen does not need special initialization. Let’s recall that again : what we define is a superset of the API TI-Nspire, so we will write (without defining the content!) an exhaustive list of event-driven methods that will be available:

function Screen:paint(gc) end
function Screen:arrowKey(key) end
function Screen:timer() end
function Screen:charIn(ch) end
....

These functions are called “virtual”, and will be redefined later, for each particular screen. Because the class contains here only virtual functions, it is also called “virtual” (a bit of vocabulary isn’t bad 🙂 ). The Screen Manager involves creating a virtual class, a model, a “buffer” that guarantee us the existence of functions when we will code the specific event-driven parts:

activeScreen = Screen()
function on.paint(gc) activeScreen:paint(gc) end
function on.arrowKey(key) activeScreen:arrowKey(key) end
function on.timer() activeScreen:timer() end
function on.charIn(ch) activeScreen:charIn(ch) end

Thus, when we want to add the code for a screen, we code as if we coded a screen! Just think that “Menu” is “on.” :

Menu = class(Screen)
function Menu:init() end
function Menu:paint(gc) gc:drawRectangle(0, 0, 50, 50) end
function Menu:arrowKey(key) ... end
activeScreen = Menu()

This technique is widely used but also has a weakness: each screen change leads to recreating a screen object. In some cases it can be very useful (reset the game), in others, it is useless (the menu does not move). One can always store it in variables, or only pass the class itself as parameter. But there are even smarter choices to make.

Indeed, we can often see that to couple this technique with a stack. A stack is a special list: a data structure called “first in, first out” (or FIFO). The use of a stack here is to store initialized displays in an ordered list, while keeping the previous process as we want.

So we will add the stack management part. To do this, we must change the access to ActiveScreen by ActiveScreen(), replace the assignments of ActiveScreen by a function that will add to the screen stack (PushScreen()) and  we must not forget to pop the screens out once it’s no longer used.

Here’s how that goes, and this is the final tutorial code:

------ Screen Manager
Screen = class()
 
function Screen:init() end
 
-- virtual functions to override
function Screen:paint(gc) end
function Screen:timer() end
function Screen:charIn(ch) end
function Screen:arrowKey(key) end
function Screen:escapeKey() end
function Screen:enterKey() end
function Screen:tabKey() end
function Screen:contextMenu() end
function Screen:backtabKey() end
function Screen:backspaceKey() end
function Screen:clearKey() end
function Screen:mouseMove(x, y) end
function Screen:mouseDown(x, y) end
function Screen:mouseUp() end
function Screen:rightMouseDown(x, y) end
function Screen:help() end
 
local Screens = {}
 
function PushScreen(screen)
    table.insert(Screens, screen)
    platform.window:invalidate()
end
 
function PullScreen()
    if #Screens > 0 then
        table.remove(Screens)
        platform.window:invalidate()
    end
end
 
function activeScreen()
    return Screens[#Screens] and Screens[#Screens] or Screen
end
 
-- Link events to ScreenManager
function on.paint(gc)
   for _, screen in pairs(Screens) do
        screen:paint(gc)
    end
end
 
function on.timer()
    for _, screen in pairs(Screens) do
        screen:timer()
    end
end
 
function on.charIn(ch) activeScreen():charIn(ch) end
function on.arrowKey(key) activeScreen():arrowKey(key) end
function on.escapeKey()	activeScreen():escapeKey() end
function on.enterKey() activeScreen():enterKey() end
function on.tabKey() activeScreen():tabKey() end
function on.contextMenu() activeScreen():contextMenu() end
function on.backtabKey() activeScreen():backtabKey() end
function on.backspaceKey() activeScreen():backspaceKey() end
function on.clearKey() activeScreen():clearKey() end
function on.mouseDown(x, y) activeScreen():mouseDown(x, y) end
function on.mouseUp() activeScreen():mouseUp() end
function on.mouseMove(x, y) activeScreen():mouseMove(x, y) end
function on.rightMouseDown(x, y) activeScreen():rightMouseDown(x, y) end
function on.help() activeScreen():help() end
 
function on.create() PushScreen(Menu()) end
function on.resize() end

NB: being exhaustive at this point is not required.
However, the number of functions defined in the Screen class must match the number of defined events.

Call the master class constructor

You are obviously a lot that enjoyed the easy-go of Lua classes on TI-Nspire, but also noticed that it is too simple to let you work correctly on a specific case. Let’s explain.

This case is the ability to call the master class constructor in the inherited class in order to avoid repetitions. Indeed, if you change one field in the master class, you must change that field in every inherited classes.

Here is an example :

A = class()
function A:init(x)
  self.t = 0
  self.x = x
end
 
B = class(A)
function B:init(x, y)
  self.t = 0
  self.x = x
  self.y = y
end

Here, if we would like to change t = 0 from the master class constructor, we also have to change it in the B class constructor in order to be coherent. Ones could think like that :

A = class()
function A:init(x)
  self.t = 0
  self.x = x
end
 
B = class(A(0, 0)) -- construction of the object before inheriting
function B:init(x, y)
  self.x = x
  self.y = y
end

But here again, the field x is repeated.

One thing we forgot to take care here is that B is an inherited class of A, so, each field of A are obviously in B. But for the constructor, we’re just redefining it. Is there a way to call the previous constructor from A giving B as parameter ?

Actually, yes !

Something important to know in order to understand the solution, is that this :

dummy = class()
function dummy:doSomething()
  self.mark = 0
end
dummy:doSomething()

is exactly the same of that :

dummy = class()
function dummy.doSomething(self)
  self.mark = 0
end
dummy.doSomething(dummy)

or again :

dummy = class()
function dummy:doSomething()
  self.mark = 0
end
dummy.doSomething(dummy)

The colons allow to give as first parameter the object that has this method. So nothing prevents us to not use this tip and then give to the method a manual first parameter as below !

A = class()
function A:init(x)
  self.t = 0
  self.x = x
end
 
B = class(A)
function B:init(x, y)
  A.init(self, x) -- call the A class constructor in order to avoir repetitions
  self.y = y
end

 

Create the is() operator to test an object type

On TI-Nspire, Lua lets you work easily with classes with class() function, that is also used to create inherited classes, based on the mother class. Sadly, since this class concept is purely superficial, there is neither function nor syntax to work with specific kind of classes. In this tutorial, we’re going to see how to reproduce the “is” feature that we meet in C# for example.

The is() operator lets you distinguish between an object created with one class and another object created with another class. We are going to create the is() operator specific to Lua because it doesn’t support inherited classes tests.

Here is an example of what we are trying to do :

function is(obj, class)
  -- we are going to create it
end
 
A = class()
function A:init(x)
  self.x = x
end
 
B = class(A)
function B:init(x)
  self.x = x * 2
end
 
t = { A(1),  B(1) }
for k, v in pairs(t) do
  if is(v, A) then
    print("A class, value = "..v.x)
  elseif is(v, B) then
    print("B class, value = "..(v.x / 2))
  end
end

The goal is here obvious : if we store in a table objects from different classes we some common fields, we d’like to be able to distinguish objects from a particular class.

But how write such a function in that case ? Add a specific field ? No need !
Indeed, the class() function does half of the work. It stores something in the __index field of your object (in reality a table). This one contains a reference to the mother class. When you do a = A(), a.__index is equal to A.__index (by reference) !

As simple as you guessed :

function is(obj, class)
  return obj.__index == class.__index
end

NB : Since the edge between classes and object is not so far big in Lua (contrary to other OOP langages where an object is created by a class), it is possible to test whether or not two objects have the same mother class with one test less :

A = class()
function A:init() end
a = A()
b = A()
is(a, b) -- returns true