RE: Menus and submenus (WAS: Forward Declaration)

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

RLak-2
Interesting discussion.

I have just a few comments.

First of all, Lua does not preserve order when inserting keys into a 
table. So:

  Menu =
  {
        ["New"] = SubMenu1,
        ["Load Game"] = ID_LOADGAME,
        ["Exit"] = ID_EXIT
  }

is probably not going to do what you want. You are very likely to find 
that it is displayed in some other order:

  +------------+
  |  Exit      |
  |  Load Game |
  |  New...    |
  +------------+

when you really wanted:

  +============+
  |Main:       |
  +============+
  |  New...    |
  |  Load Game |
  +------------+
  |  Exit      |
  +------------+

So I think you probably want to use a more complicated object than a 
simple table. It could, for example, be a pair of tables, one of which is 
as you have described, and the second of which is an ordered list of menu 
keys. (Of course, if you are planning on internationalizing, you might not 
want to use the menu text as a key, either. Or you might.)

This would motivate the creation of a Menu object, which implements the 
:add() method. You might then be able to write, for example:
  mainMenu = Menu.new "Main"
               :add("New", subMenu1)
               :add("Load Game", ID_LOADGAME,
               :add_separator
               :add("Exit", ID_EXIT)

Now, you actually could just include the sub-menu without giving it a 
name, using an interface like this:

  mainMenu = Menu.new "Main"
               :sub "New"
                    :add("New Campaign", ID_CAMPAIGN)
                    :add("New Random Map", ID_RANDOMMAP)
               .super
               :add("Load Game", ID_LOADGAME,
               .separator
               :add("Exit", ID_EXIT)


The call to :sub() automatically adds a Back link to the newly created 
submenu, which is then used by .super
to get back to the previous menu. This solves the problem expressed by 
Alex:

  > Well, the only thing I dislike about the solution below is that 
SubMenu1
  > is effectively split in two parts. If the Menu table is a page long it
  > would be easy to miss that <SubMenu1["Back"] = Menu> while browsing
  > through the file. What if there were 50 sub menus? All of them would
  > be split in at least (but not limited to) two parts and the file
  > can become a real mess with time.

But it doesn't address the question of what happens if a menu is a submenu 
of more than one parent menu, which is certainly a possibility in the 
general case.

eg:

    +==============+
    |Maze:         |
    +==============+
    |  Simplify    |
    |  Complicate  |
    |  New...      |\
    +--------------+ \
<---|  Back        +  \
    +--------------+   \
                        \
    +==============+     \   +============+
    |Character:    |      -> |From:       +
    +==============+     /   +============+
    |  Empower     |    /    |  CD        |
    |  Kill        |   /     |  Website   |
    |  New...      |>-/      |  File...   |
    +--------------+         +------------+
<---|  Back        +   ? <---|  Back      |
    +--------------+         +------------+

That is, the same "From" menu is used in both the Maze and Character 
menus.

This would suggest that Back entries should be added automatically when 
the submenu is presented, rather than when it is first created. A similar 
mechanism would allow addition of contextual information (Recent, for 
example).

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

Since I've got a dental appointment in 15 minutes, I'll leave 
implementation of the
above interface as an exercise. Hints: 1) every method returns its self 
object,
except for :sub() which returns the new submenu. 2) .super and .separator 
are
implemented by the __index metamethod; the first one simply returns the 
back
link, and the second one modifies the object by adding the separator and 
then
returns the original object. You could avoid the mix of : and . by using .
throughout, and faking the method call by returning a closed function.

Rici


Anyway, rough outline code for the first implementation, where "Back" is 
added automatically.


Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

Speight.Marcus
Hi,
	I tried to implement the following but came up with a runtime error.
	I can't seem to be able to locate it can anyone help please?

function Menu.new()
    local menu = {}
    menu.size = 0
    return menu
end

function Menu:sub(name)
    local submenu = Menu.new
    self:add(name, submenu)
    return submenu
end

function Menu:add(name, id)
    local item = {}
    item[name] = id
    self[self.size] = item
    self.size = self.size+1
    return self
end

mainMenu = Menu.new
    :sub "New"
        :add("New Campaign", ID_CAMPAIGN)
        :add("New Random Map", ID_RANDOMMAP)
        :add("Back", ID_BACK)
    :add("Load Game", ID_LOADGAME)
    :add("Exit", ID_EXIT)

Marcus Speight
Software Engineer
Vivid Gaming 

The views expressed in this message are those of the sender and not
necessarily those of IGT - UK Limited and its associated companies.



Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

Tomas-14
> mainMenu = Menu.new
>     :sub "New"
>         :add("New Campaign", ID_CAMPAIGN)
>         :add("New Random Map", ID_RANDOMMAP)
>         :add("Back", ID_BACK)
>     :add("Load Game", ID_LOADGAME)
>     :add("Exit", ID_EXIT)
	You have to write the parenthesis to call the functions:

mainMenu = Menu.new ()
submenu = mainMenu:sub ("New")
submenu:add ("New Campaign", ID_CAMPAIGN)

etc.

	Tomas


Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

Kevin Baca-2
In reply to this post by Speight.Marcus
There are A LOT of things wrong here.  It's clear what you're trying to
do, but you're lacking some understanding of basic lua syntax.  Take
baby steps.

Here's a start:

Menu = {}

function Menu:new()
    local menu = setmetatable( {}, { __index = Menu } )
    menu.items = {}
    return menu
end

function Menu:add(name, id)
    local item = { name = name, id = id }
    table.insert( self.items, item )
    return self
end

mainMenu = Menu:new()
    :add("Item0", 0)
    :add("Item1", 1)

print( mainMenu.items[ 1 ].name )
print( mainMenu.items[ 1 ].id )


> 
> Hi,
> 	I tried to implement the following but came up with a 
> runtime error.
> 	I can't seem to be able to locate it can anyone help please?
> 
> function Menu.new()
>     local menu = {}
>     menu.size = 0
>     return menu
> end
> 
> function Menu:sub(name)
>     local submenu = Menu.new
>     self:add(name, submenu)
>     return submenu
> end
> 
> function Menu:add(name, id)
>     local item = {}
>     item[name] = id
>     self[self.size] = item
>     self.size = self.size+1
>     return self
> end
> 
> mainMenu = Menu.new
>     :sub "New"
>         :add("New Campaign", ID_CAMPAIGN)
>         :add("New Random Map", ID_RANDOMMAP)
>         :add("Back", ID_BACK)
>     :add("Load Game", ID_LOADGAME)
>     :add("Exit", ID_EXIT)
> 
> Marcus Speight
> Software Engineer
> Vivid Gaming 
> 
> The views expressed in this message are those of the sender 
> and not necessarily those of IGT - UK Limited and its 
> associated companies.
> 


Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

RLak-2
In reply to this post by RLak-2
OK, here is a fully-worked example.

I figured that people might be using the crippled Windows NT console (at 
least, that's what I have here)
so I didn't put in any VT100 codes; this will therefore do something on 
pretty well any console.

The initial do... end block (about 150 lines including comments) 
implements a Menu "class" -- actually it creates a single menu object 
called "Menu" -- which you can then play with as you will. The example at 
the end (starting with the comment "OK, let's give it a whirl") shows most 
of the possibilities.

Adding "Back" automatically when the menu is created is really redundant: 
it could just as well be done by drawmenu. Certain modifications will be 
needed to enable this, but it would allow the possibility of multiple 
"Back" paths.

I tried to put in comments, but the code really depends on understanding 
first-order functions. And tail calls (which is how it moves from one menu 
to the next, or to the previous, for that matter.)

On the other hand, it may be a useful learning experience.

R.

------ Cut here and put it in "menu.lua" then execute "lua 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 Rici Lake 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



Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

RLak-2
In reply to this post by RLak-2
OK, I did what I should have done in the first place, which is put it on 
the Wiki. E-mail can be a pain.

see: <http://lua-users.org/wiki/AsciiMenu>

Thanks to Peter Shook for the colour version.

R.


Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

Virgil Smith
OK, I'll bite...
I don't remember it off the top of my head (and can't take the time to look
anything up at the moment, but don't want to forget this), however...

Isn't there a simply way in Lua 5.0 to declare a function that takes all of
its arguments in a table?
Also, isn't there a way to expand a table back into a list of arguments?

Could this (or something simpler) be used to allow the trick Riki used to
add an automatic Self argument to a function taking 2 parameters to be used
with a function regardless of the number of parameters?

Frankly I'm a bit off balance in my Lua knowledge (too much C API use and
too little actual Lua coding), so maybe I'm missing something obvious here.

-----Original Message-----
From: [hidden email]
[[hidden email] Behalf Of
[hidden email]
Sent: Wednesday, October 15, 2003 1:40 PM
To: Lua list
Subject: RE: Menus and submenus (WAS: Forward Declaration)


OK, I did what I should have done in the first place, which is put it on
the Wiki. E-mail can be a pain.

see: <http://lua-users.org/wiki/AsciiMenu>

Thanks to Peter Shook for the colour version.

R.


Reply | Threaded
Open this post in threaded view
|

RE: Menus and submenus (WAS: Forward Declaration)

Eric Tetz-2
--- Virgil Smith <[hidden email]> wrote:
> OK, I'll bite...
> I don't remember it off the top of my head (and can't take the
> time to look anything up at the moment, but don't want to forget
> this), however...
> 
> Isn't there a simply way in Lua 5.0 to declare a function that
> takes all of its arguments in a table? Also, isn't there a way to
> expand a table back into a list of arguments?

function foo(...)
 print(unpack(arg))
end

foo(1,2,3,"a","b",{"c"})




__________________________________
Do you Yahoo!?
The New Yahoo! Shopping - with improved product search
http://shopping.yahoo.com