[solved] GUI: Embedded listboxes

Discussion of Lua and LuaWML support, development, and ideas.

Moderator: Forum Moderators

Post Reply
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

[solved] GUI: Embedded listboxes

Post by white_haired_uncle »

In this campaign, units can pick up custom items. I want to initially show a list of units which have said items, hopefully eventually adding support for acting on them (dropping an item for example). I have a table geared_units which contains units and for each unit a table of objects representing the gear they have collected.

The body of my dialog (hopefully I get these terms right) contains a listbox with one entry for each geared unit. The listbox is populated in preshow based on the unit contents of the geared_units table (if it makes a difference, it's done that way because that's how the example I had did it when I "learned" this stuff by copying the LotI guis).
Screenshot from 2023-12-27 10-41-44.png
Screenshot from 2023-12-27 10-41-44.png (268.11 KiB) Viewed 1431 times
Now my dilemna. I want those right hand columns to contain a list of items (discovered at runtime based on what that unit has collected, with each item potentially selectable in the future) instead of "List of items goes here".

My gut says that's another listbox, but I'm not sure if that's right, where I should define/populate it, or if it's even possible to put a listbox in a listbox. For starters, I tried just building a grid with one row for each item when I built the object table for each unit, which I think worked out fine, but I don't know how to insert them into each listbox entry.

I know I'm probably not wording my question well. I would if I could. I'm hoping someone will know much better than I do what I'm trying to accomplish and can kick me in the right direction. I've omitted my code at this point, as I worry that until I understand what I'm trying to do displaying what I've tried may just muddy the issue.

TIA

P.S. Yes, the right way to do this would be to use the LotI items menu. That's what I would do if it were up to me and I planned to go any further with this. At this point, I'm pretty much done messing with it, but I have a problem I wasn't able to solve and I've yet to mature to the point of being able to walk away defeated by a stupid gui.

P.P.S. I tried to force the sizing of the left hand column of each listbox entry so that they'd all be the same and things would line up nice. I failed. I don't really care enough to worry about layout details, but if it's an obvious fix I'd take it.
Last edited by white_haired_uncle on December 28th, 2023, 5:56 am, edited 1 time in total.
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2238
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: GUI: Embedded listboxes (maybe?)

Post by Celtic_Minstrel »

I don't think there's any reason why a listbox in a listbox won't work. I do think the game won't be able to lay it out properly, though, so you'd probably need some kind of layout trick to force the inner listbox to a fixed size. Have you tried using a nested listbox yet?

Another option might be to use a treeview, so the "nested listbox" becomes indented entries in the tree.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: Embedded listboxes (maybe?)

Post by white_haired_uncle »

Celtic_Minstrel wrote: December 27th, 2023, 4:33 pm I don't think there's any reason why a listbox in a listbox won't work. I do think the game won't be able to lay it out properly, though, so you'd probably need some kind of layout trick to force the inner listbox to a fixed size. Have you tried using a nested listbox yet?

Another option might be to use a treeview, so the "nested listbox" becomes indented entries in the tree.
I have not tried either. First I've heard of them. I'm not finding much of anything on the wiki, do you have any links/examples?

Maybe a GUI guide? Pretty much everything on wiki/devdocs seems to be reference aimed at an audience who has already read "the guide".

Thanks

P.S.

Assuming I wanted to try listbox in listbox...I create a reference to an image in my listbox item definition with something like

Code: Select all

wml.tag.row {
            wml.tag.column {
                      wml.tag.image { id = "unitimage"  }
                  }
}
and then populate it with

Code: Select all

listbox[index].unitimage.label = gunit.unit.__cfg.image
But I haven't figured out what that would look like if I wanted to insert another listbox (or grid, etc). Is there something like a wml.tag.widget, or more specific like wmg.tag.listbox (I can't even find a list of what "wml.tag.*" are valid)?
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2238
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: GUI: Embedded listboxes (maybe?)

Post by Celtic_Minstrel »

white_haired_uncle wrote: December 27th, 2023, 6:10 pm (I can't even find a list of what "wml.tag.*" are valid)?
Everything is valid in theory. Of course, certain tags only fit in certain places.
white_haired_uncle wrote: December 27th, 2023, 6:10 pm Assuming I wanted to try listbox in listbox...I create a reference to an image in my listbox item definition with something like

Code: Select all

wml.tag.row {
            wml.tag.column {
                      wml.tag.image { id = "unitimage"  }
                  }
}
and then populate it with

Code: Select all

listbox[index].unitimage.label = gunit.unit.__cfg.image
But I haven't figured out what that would look like if I wanted to insert another listbox (or grid, etc).
I don't understand what's confusing about this? Just replace the "image" with a "listbox"? Your path to assign something would have two listboxes in it, something like:

Code: Select all

listbox[index].sub_listbox[index2].label
white_haired_uncle wrote: December 27th, 2023, 6:10 pm Maybe a GUI guide? Pretty much everything on wiki/devdocs seems to be reference aimed at an audience who has already read "the guide".
It's true that the GUI2 documentation situation is pretty bad. I'm not sure what's a good way to deal with this. The devdocs is the authoritative reference, certainly, but I think it's not very discoverable, and there's not much in the way of a general overview or a tutorial.

Encoding your dialog as WML instead of Lua does mean you'd be able to run it through the schema validator, but that's only helpful if you already roughly know the structure of a dialog.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: Embedded listboxes (maybe?)

Post by white_haired_uncle »

Celtic_Minstrel wrote: December 27th, 2023, 6:16 pm I don't understand what's confusing about this? Just replace the "image" with a "listbox"? Your path to assign something would have two listboxes in it, something like:

Code: Select all

listbox[index].sub_listbox[index2].label
I tried replacing image with listbox, but got "bad argument #2 to 'newindex' (invalid modifiable property of widget)" when I first tried. Then I did actually have the idea that I needed to use that "array in array" syntax and made a note to try and investigate that, but I had reached my limit of learning by making things up and hoping and stopped to ask for help at that point. Sounds like I may have been onto something, and now that you've shown me this I'll continue on with a bit of confidence (while searching for more on the treeview/nested concepts).

Thanks
Speak softly, and carry Doombringer.
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: Embedded listboxes (maybe?)

Post by white_haired_uncle »

So I took what was working, a dialog with a populated list box, and drew it out on paper to show how the pieces fit together to create a listbox inside a dialog. Then I drew in another listbox ("items_listbox") as a member of the first and tried to implement it as just another layer. Of course, it didn't actually work (bad argument #2 to 'index' (invalid property of widget), coming from the line "local items_listbox = " in preshow() ).

I think (well, hope) my issue stems from the fact that I really don't understand what the first line in preshow ( local listbox = dialog[listbox_id] ) is doing, that's something I just copied along with most of the rest of the code but never needed to understand. I tried to mimic it based on where my new listbox resides within the rest of the GUI, but it seems I guessed wrong. Pretty sure it's never going to work if I can't initiate that items_listbox, even if the rest is okay.

Code: Select all

function wesnoth.wml_actions.show_geared_units()
        -- GUI code (except mistakes) pretty much lifted from LotI

        -- for now, we'll just look at units on map (not recall), and only for side 1
        local units=wesnoth.units.find_on_map{side=1}
        if #units<1 then wml.error("[show_geared_units]: No units found on map for side 1!") end

        local geared_units = {}  -- a geared_unit is a unit, and a list of that unit's gear (TDM objects)
                -- for example geared_units[3].unit.name = "Adon"  (the 3rd unit in the table has the name Adon)
                --      geared_units[3].gear[1].name = "The Holy Grail" (the 1st object held by the 3rd unit is The Holy Grail)

        for _, unit in pairs(units) do
                -- wesnoth.interface.add_chat_message(string.format("Looking at %s, a.k.a. %s", unit.id, unit.name))
                local gear = {}  -- a list of TDM objects held by this unit
                local modifications = wml.get_child(unit.__cfg, "modifications")

                for object in wml.child_range(modifications, "object") do
                        if object.can_be_dropped_by ~= nil then   -- can_be_dropped_by is an attribute of objects specific to TDM
                                table.insert(gear,object)
                        end
                end
                if next(gear) ~= nil then  -- gear is not empty
                        table.insert(geared_units, { unit = unit, gear = gear })
                end
        end

        -- the inner listbox filled with a unit's goodies
        local items_listbox_id = "items" 

        -- an entry in the items listbox
        local items_listbox_template = wml.tag.grid {
                wml.tag.row {
                        wml.tag.column {
                                wml.tag.image { id = "itemimage" } 
                         },
                        wml.tag.column {
                                wml.tag.label { id = "itemname" }
                        }
                }        
        }

        -- the box to put the list of items (per unit) into
        local items_listboxDefinition = wml.tag.listbox { id = items_listbox_id,
                wml.tag.list_definition {
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.toggle_panel { items_listbox_template }
                                }
                        }
                }
        }

        -- Our GUI will show a list of units that have gear
        local listbox_id = "geared_units"

        -- a listbox_template defines the information about a single geared unit
        -- it's one entry in a list 
        local listbox_template = wml.tag.grid {
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.grid {
                                                wml.tag.row {
                                                        wml.tag.column {
                                                                wml.tag.label {
                                                                        use_markup = true,
                                                                        id = "unitname"  -- we'll use this to identify this field when we add the actual data
                                                                }
                                                        }
                                                },
                                                wml.tag.row {
                                                        wml.tag.column {
                                                                wml.tag.image { id = "unitimage" }
                                                        }
                                                },
                                                wml.tag.row {
                                                        wml.tag.column {
                                                                wml.tag.label { id = "unittype" }
                                                        }
                                                }
                                        }
                                },
                                --wml.tag.column { wml.tag.listbox { id = "unititems"} }
                                wml.tag.column { items_listboxDefinition }
                        }
                }

        -- listboxDefinition defines a box which will contain the list of our geared units (a list of listbox_template's)
        local listboxDefinition = wml.tag.listbox { id = listbox_id,
                wml.tag.list_definition {
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.toggle_panel { listbox_template }
                                }
                        }
                }
        }
        -- Top level GUI grid, a header, a box containing geared units, and an Ok button
        local dialogDefinition = {
                wml.tag.tooltip { id = "tooltip_large" },
                wml.tag.helptip { id = "tooltip_large" },
                wml.tag.grid {
                        wml.tag.row {  -- Header
                                wml.tag.column {
                                        border = "bottom",
                                        border_size = 10,
                                        wml.tag.label {
                                                use_markup = true,
                                                label = "<span size='large' weight='bold'>" .. _"Geared Units" .. "</span>"
                                        }
                                }
                        },
                        wml.tag.row {  -- Our listbox, not yet populated, just described here
                                wml.tag.column { listboxDefinition }
                        },
                        wml.tag.row {  -- Ok button
                                wml.tag.column {
                                        wml.tag.button { id = "ok", label = _"OK" },
                                }
                        }
                }
        }

        -- A function to populate our listbox with our listbox_templates
        local function preshow(dialog)
                local listbox = dialog[listbox_id]
                for index, gunit in ipairs(geared_units) do
                        listbox[index].unitname.label = "<span color='yellow' weight='bold'>" .. gunit.unit.name .. "</span>"
                        listbox[index].unitimage.label = gunit.unit.__cfg.image
                        listbox[index].unittype.label = gunit.unit.__cfg.language_name

                        -- Something must go here to initiate items_listbox, BUT WHAT???
                        -- local items_listbox = listbox[items_listbox_id]  -- Seems (to me!) analogous to creation of listbox, only wrong
                        
                        for itemindex, object in ipairs(gunit.gear) do
                                listbox[index].items_listbox[itemindex].itemimage.label=object.image
                                listbox[index].items_listbox[itemindex].itemname.label=object.name
                        end
                        -- listbox[index].unititems.label = "List of items goes here"
                end
        end

        -- Draw the GUI
        gui.show_dialog(dialogDefinition,preshow)

end
Speak softly, and carry Doombringer.
gfgtdf
Developer
Posts: 1432
Joined: February 10th, 2013, 2:25 pm

Re: GUI: Embedded listboxes (maybe?)

Post by gfgtdf »

In lua a.b is always the same as a["b"] so the following codes are equivalent:

Code: Select all

a.b.c = 5

Code: Select all

a["b"].c = 5

Code: Select all

local var = "b"
a[var].c = 5

Code: Select all

local var = "b"
a[var]["c"] = 5

Code: Select all

local var = "b"
local b = a[var]
b["c"] = 5
Scenario with Robots SP scenario (1.11/1.12), allows you to build your units with components, PYR No preperation turn 1.12 mp-mod that allows you to select your units immideately after the game begins.
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: Embedded listboxes (maybe?)

Post by white_haired_uncle »

Progress!

It struck me that items_listbox should be a "child" of each listbox "entry" (not sure of the right terms), not of the (outer) listbox itself.

Code: Select all

        -- A function to populate our listbox with our listbox_templates
        local function preshow(dialog)
                local listbox = dialog[listbox_id]
                for index, gunit in ipairs(geared_units) do
                        listbox[index].unitname.label = "<span color='yellow' weight='bold'>" .. gunit.unit.name .. "</span>"
                        listbox[index].unitimage.label = gunit.unit.__cfg.image
                        listbox[index].unittype.label = gunit.unit.__cfg.language_name

                        for itemindex, object in ipairs(gunit.gear) do
                                local items_listbox = listbox[index][items_listbox_id]
                                items_listbox[itemindex].itemimage.label=object.image
                                items_listbox[itemindex].itemname.label=object.name
                        end
                end
        end
The formatting is terrible, but I'm getting the info I want in the area I want it.
Screenshot from 2023-12-27 19-57-46.png
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2238
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: GUI: Embedded listboxes (maybe?)

Post by Celtic_Minstrel »

You should use linked groups to keep the list box entries all the same size. It probably makes sense to use two linked groups, one for the inner listbox and one for the outer one.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: Embedded listboxes (maybe?)

Post by white_haired_uncle »

Nice. I saw linked_group in LuaAPI/gui/example, but wasn't sure what that did. Couldn't find any docs, but a bit of searching led me to data/campaigns/World_Conquest/gui/help_dialog.cfg which served as enough of an example.

Thanks for all your help with this.
Screenshot_2023-12-27_23-35-16.png
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2238
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: Embedded listboxes

Post by Celtic_Minstrel »

You can probably make it a bit better by setting border in some of your grid cells. Despite the name, "border" in GUI2 is actually like margins or padding, so you would be able to add a bit of space between the inner and outer listboxes on the right edge.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1208
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: Embedded listboxes (maybe?)

Post by white_haired_uncle »

gfgtdf wrote: December 27th, 2023, 11:24 pm In lua a.b is always the same as a["b"] so the following codes are equivalent:
That thought did cross my mind. I really don't care for that notation (square brackets are for array indices, periods for structure members, IMO), so I tried changing dialog[listbox_id] to dialog.listbox_id which seems (seemed) sensible. That I could accept.

It didn't work. Probably because it's wrong. dialog["listbox_id"] == dialog.listbox_id, I see that now, but I have no idea what if anything is equivalent to dialog[listbox_id] using "dot-notation".
Celtic_Minstrel wrote: December 28th, 2023, 7:49 am You can probably make it a bit better by setting border in some of your grid cells. Despite the name, "border" in GUI2 is actually like margins or padding, so you would be able to add a bit of space between the inner and outer listboxes on the right edge.
I did set the border/border_size in at least one place, but it looks like there's more to do which I'll leave for the actual campaign author if he chooses to use this (this whole exercise was really about helping someone else get up to speed with a basic lua gui, I just ran into a problem I couldn't solve with the embedded listboxes and couldn't leave it alone). I wanted to get rid of those highlighted border box things completely. In the process, I ran into border_thickness and border_color, but I couldn't get them to do anything. "has_minimum = false" helped initially, though I still get a box around my data if I click on it. Looking for a way I could make the items non-selectable, I tried ".enabled = false", and something using "on_click" to try and turn it back off if it got turned on, no dice. Curiously, when I tried using panel instead of toggle_panel I got an error that only buttons and panels are allowed in a listbox grid (or something close to that).
Speak softly, and carry Doombringer.
gnombat
Posts: 710
Joined: June 10th, 2010, 8:49 pm

Re: GUI: Embedded listboxes (maybe?)

Post by gnombat »

white_haired_uncle wrote: December 30th, 2023, 11:13 am It didn't work. Probably because it's wrong. dialog["listbox_id"] == dialog.listbox_id, I see that now, but I have no idea what if anything is equivalent to dialog[listbox_id] using "dot-notation".
In general, there is no way to write dialog[listbox_id] using dot-notation. But in your code, you could just write it as dialog.geared_units. (Because listbox_id is always "geared_units".)
User avatar
Celtic_Minstrel
Developer
Posts: 2238
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: Embedded listboxes

Post by Celtic_Minstrel »

There is no way to make listbox items unselectable. Setting has_minimum=false just means that it supports having no item selected, but the items themselves are still selectable. If making them unselectable is that important, you could try using a tree view instead. In practice I think the code using a tree view is very similar to a listbox.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
Post Reply