From Acorn to LuaCodex icon

A modest toolbox example in RiscLua II

This is the !RunImage file. The first line is a comment, useful only for StrongED. The second declares local variables given by values in the riscos library, that correspond in function to their namesakes in BASIC. Some readers may find it useful to look at these notes before proceding.
#! lua
local sys, !, $, dim in riscos
@ = require "wimp.toolbox"
-------------- drawfile stuff ---------------
local Drawfile_Load = \ (fpath)
  local r0,r1,r2,r3,r4 = sys (8, 17, fpath) -- OS_File
  if r0 ~= 1 then => nil,fpath.." not a file" end -- if
  local buf = dim (r4)
  sys (8, 16, fpath, buf, 0) -- load file into b
  => buf, r4
  end

local Drawfile_Render = \ (buf, buf_size)
       => \ (@, x, y)
   local b in @
   local u, v = 1<<16, 1<<8
   ![b + 256] = u
   ![b + 260] = 0
   ![b + 264] = 0
   ![b + 268] = u
   ![b + 272] = v*x
   ![b + 276] = v*y
   sys (0x45540, 0, buf, buf_size, b + 256, b + 28)     -- DrawFile_Render
   end end
-----------------------------------------------
local page = dofile "codex:index"
local cache = { } -- storing loaded Drawfile data
local where = "codex:%s"
local getfile = \ (n) => where:format (page[n]) end
local getpage = \ (n)
      local data = cache[n]
      if data then => data.buf, data.bufsize end -- if
      local b, bs = Drawfile_Load (getfile (n))
      cache[n] = { buf = b, bufsize = bs }
      => b, bs
      end

_ENV = @
handler[1] = \ (@)          -- Wimp_RedrawWindow
      local Drawfile_Redraw = Drawfile_Render (getpage (@.pageno))
      => @:user_redraw (Drawfile_Redraw) end

handler.tbox[0xa0] = \ (@)   -- Select clicked on iconbar
  local self_id, parent_id, parent_comp in @.obj.codex_win
  if 1 & (@:GetObjectState (0, self_id)) == 1 then
    if pageno < #page then pageno + = 1 end -- if
    @:winredraw (self_id)
  else
    pageno = 1
    @:Show_Object (0, self_id, 0, 0, parent_id, parent_comp)
  end -- if
  end

handler.tbox[0xa1] = \ (@)    -- Adjust clicked on iconbar
  local self_id, parent_id, parent_comp in @.obj.codex_win
  if 1 & (@:GetObjectState (0, self_id)) == 1 then
    if pageno > 1 then pageno - = 1 end -- if
    @:winredraw (self_id)
  else
    pageno = #page
    @:Show_Object (0, self_id, 0, 0, parent_id, parent_comp)
  end -- if
  end

handler.tbox[0xa2] = \ (@)   -- Help on iconbar menu
        local b in @
        $[b] = "filer_run codex:!Help\0"
        sys (0x400de, b)  -- Wimp_StartTask
        end

$[resdir] = arg[1]    -- Tell the toolbox where to find res
init (@, { }, { })             -- All wimp, all toolbox events signify.
mask = 257                    -- No null or kbd events
run (@)                       -- This has to be last.
The third line loads the library wimp.toolbox and calls it @. Lua libraries are tables. The keys in the table name values that are made available to programs that require the library, hiding the implementation details. As yet the toolbox library does not export any Drawfile stuff, so the program has to define it itself. The next section defines two functions: Drawfile_Load and Drawfile_Render. Drawfile_Load uses OS_File to find the size of the file at the given path, reserve an appropriately sized buffer, load the file into the buffer, and return the address of the buffer and its size. Drawfile_Render takes a pointer to a buffer, and its size, as arguments, and returns a function. The function's first argument is a wimp task, its second and third the coordinates of where the Drawfile is to be rendered. It stores the appropriate affine transformation in the message buffer b of its first argument, then calls the SWI Drawfile_Render. This perhaps calls for an explanatory interlude about how RiscLua sees wimp tasks.

A wimp task is a coroutine of the taskmanager. When it calls Wimp_Poll or Wimp_PollIdle the thread of execution passes out of its control to the taskmanager, and returns when those SWIs return. Information is conveyed between the two processes in CPU registers and the message buffer. The SWI Wimp_Init registers the program with the taskmanager, telling it where the message buffer is and so forth. The RiscLua library wimp.task spawns wimp tasks as copies of itself. Two special methods stand out: init which registers the wimp task with the taskmanager, and run which sets it going. The core of the latter is a loop:

local run = \ (self, idle)
     local poll <const> = idle and 0x400e1 or 0x400c7
     local b, handler, preclosedown in self
     local reason, quit, action, err
     local when = ((not idle) and 0) or self.time or 100
     while not (quit or err) do
         reason = sys (poll, self.mask or 0, b, when)
         action = handler[reason]
         quit, err = action and action (self)
         if err then
             self:report (err)
             quit = true
         end -- if
     end -- while
     self:closedown ( )
     end
Wimp tasks come with an array of functions, handler, indexed by the codes returned by the taskmanager, which determine how the wimp task reacts when control returns to it. I find it convenient to adopt the convention that handlers only return a non-nil value in order to signal that the wimp task should close down. This means that if a handler is not defined for some circumstance, that does not shut the wimp task down. Another array of handlers handler.mesg responds to messages. The wimp.toolbox library extends the wimp.task library with an array handler.tbox of responders to toolbox events.

Recall that I used three numbers, &a0, &a1 and &a2 in setting up the Resource file. These appear above in the definitions of handler.tbox[0xa0], handler.tbox[0xa1] and handler.tbox[0xa2] which deal respectively with clicks of Select on the iconbar icon, Adjust on the iconbar icon and either on the Help option in the iconbar menu. There is one more handler definition, for handler[1] -- Wimp_RedrawWindow.

The statement

_ENV = @
is worth explaining. This makes the table @, our wimp task, the global environment. So that any variable not previously declared as local, is looked up from, or written to, @. This is a syntactic trick making for more readable code. Without it the last four lines would read
 $[@.resdir] = arg[1]
 @:init ({ }, { })
 @.mask = 257
 @:run ( )
Note how the Drawfile data is cached. Once a Drawfile has been loaded there is no point in it being loaded again.