Ascii Menu

lua-users home
wiki

This is a simple console-based menu system. It demonstrates some interesting syntactic possibilities in Lua for defining the menu.

Source

-- [Download]

------------------------------------------------------------------

-- menu.lua

------------------------------------------------------------------



do



  local curriedMethod, method, meta = {}, {}, {}



  -- __index either executes a method from method or curries a method from

  -- curriedMethod with its self argument. This allows all calls to be with

  -- "." rather than ":" and also allows you to write obj.foo instead of

  -- obj.foo() for methods which don't require arguments. It might

  -- not be great design, but it is interesting. :)

  

  function meta:__index(key)

    local func = method[key]

    if func then

      return func(self)

     else

      func = curriedMethod[key]

      if func then

        local rv = function(a, b) return func(self, a, b) end

        self[key] = rv

        return rv

      end

    end

  end



  -- quick and dirty display routine.

  local DASHES = string.rep('-', 80)

  local DOUBLES = string.rep('=', 80)



  local function drawmenu(self)

    local maxsize = string.len(self.name) + 2

    local item = 0

    for i = 1, self.n do

      local sz = 6 + string.len(self[1][i])

      if maxsize < sz then maxsize = sz end

    end

    if maxsize > 75 then maxsize = 75 end

    local sepformat = "  +%-"..maxsize.."."..maxsize.."s+\n"

    local nameformat = "  | %-"..(maxsize - 2).."."..(maxsize-2).."s |\n"

    local itemformat = "  | %2i. %-"..(maxsize - 6).."."..(maxsize-6).."s |\n"

    local sepline = string.format(sepformat, DASHES)

    io.write("\n", string.format(sepformat, DOUBLES))

    io.write(string.format(nameformat, self.name))

    io.write(string.format(sepformat, DOUBLES))

    for i = 1, self.n do

      if self[2][i] then

        item = item + 1

        io.write(string.format(itemformat, item, self[1][i]))

      else

        io.write(sepline)

      end

    end

    io.write(sepline)

  end



  -- Equally quick and dirty menu execution. Tail calls the function

  -- associated with the selected menu item.

  local function domenu(self)

    drawmenu(self)

    io.write("\n\nSelect a menu item: ")

    while true do

      local choice = io.read("*l")

      if choice == nil then return false end

      local _, _, item = string.find(choice, "^%s*(%d+)%s*$")

      if item then



        item = item + 0 -- force numeric conversion

        for i = 1, self.n do

          if self[2][i] then

            if item == 1 then return self[2][i]() end

            item = item - 1

          end

        end

      end

      io.write("\nSelection not valid. Try again: ")

    end

  end



  -- Create a new menu with given name and back reference.

  local function newmenu(name, back)

    return setmetatable({

      {}, {},  -- [1] is the menu label, [2] is the associated function

      name = name,

      back = back,

      n = 0

    },

    meta)

  end



  -- insert a label and a function at the end of a menu

  local function put(self, name, action)

    local n = self.n + 1

    self.n = n

    self[1][n] = name

    self[2][n] = action

    return self

  end



  -- Now the actual menu methods.

  -- add(label, id)

  function curriedMethod:add(name, id)

    return put(self, name, function() return id end)

  end

  

  -- I personally would use functions instead of ids

  curriedMethod.addf = put



  -- create and open a submenu with the given name

  function curriedMethod:sub(name)

    local submenu = newmenu(self.name .. " / " .. name, self)

    put(self, name.." -->", function() return domenu(submenu) end)

    return submenu

  end



  -- create a new, unrelated menu. You cannot use super afterwards

  function curriedMethod:new(name)

    return newmenu(name)

  end

  

  -- go back to the previous level, after introducing the automatic Back label

  -- unless this is a top-level menu

  function method:super()

    local mom = self.back

    if mom then

      put(self, "-")

      put(self, "<-- Back", function() return domenu(self.back) end)

      return self.back

     else return self

    end

  end



  -- insert a separator line

  function method:sep()

    return put(self, "-")

  end



  -- and a function to actually execute the thing

  curriedMethod.run = domenu



  -- Finally, we define the Menu "object"

  -- This is a bit of a kludge, because all menus respond to "new"

  -- in the same way. So you could actually just use Menu as your

  -- top-level menu.

  Menu = newmenu("")

end



-- OK, let's give it a whirl



local function about_dialog()

  io.write [[

  

  Menu system written by RiciLake in order to demonstrate

  some interesting syntactic possibilities in Lua



  This program is released into the public domain. But if you find

  it useful, you could certainly buy me a coffee sometime



]]

  return "About menu"

end



local ID_CAMPAIGN,   ID_RANDOMMAP,   ID_LOADGAME,   ID_EXIT =

      "ID_CAMPAIGN", "ID_RANDOMMAP", "ID_LOADGAME", false

  

mainMenu = Menu.new "Main"

    .sub "New"

        .add("New Campaign", ID_CAMPAIGN)

        .add("New Random Map", ID_RANDOMMAP)

        .super

    .add("Load Game", ID_LOADGAME)

    .sep

    .addf("About", about_dialog)

    .sep

    .add("Exit", ID_EXIT)



while true do  

  local selection = mainMenu.run()

  if not selection then break end

  print("Selected: ", selection)

end



--RiciLake


RecentChanges · preferences
edit · history
Last edited December 8, 2007 5:01 pm GMT (diff)