[solved] GUI: dynamic return values

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

Moderator: Forum Moderators

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

[solved] GUI: dynamic return values

Post by white_haired_uncle »

I have a simple button, as part of a page definition, and I want to set the return value in preshow, something like:

Code: Select all

wml.tag.button { id = "monster_pick" }
...
dialog.monsters_mp[i].monster_pick.label = "Pick me, I'm " .. monster.name
--dialog.monsters_mp[i].monster_pick.return_value = monster.name
I'm guessing this has something to do with return_value_id? I've been messing around trying things and can't figure it out. Every example I can find just has return_value_id = "ok".

devdocs has this to say about return_value_id, which makes no sense to me:
When the button doesn't have a standard id, but you still want to use the return value of that id, use return_value_id instead. This has a higher precedence as return_value.
When I still want to use the id that doesn't exist?

I suspect there's probably an alternative based on selected_item, but I'm trying to demonstrate how to use return_value(_id).

Also, most of what I try gets me "invalid (modifiable) property of widget". Are said properties documented/listed somewhere?

P.S. https://devdocs.wesnoth.org/group__GUIWidgetWML.html has every other widget listed as gui2::<widget>, except gui::button, which means button is the only one that doesn't show up in alphabetical order.
Last edited by white_haired_uncle on January 5th, 2024, 2:09 am, edited 2 times in total.
Speak softly, and carry Doombringer.
User avatar
Ravana
Forum Moderator
Posts: 3016
Joined: January 29th, 2012, 12:49 am
Location: Estonia
Contact:

Re: GUI: dynamic return values

Post by Ravana »

This example is quite low quality and for older version, but maybe useful for concept. https://github.com/ProditorMagnus/Royal ... s/menu.lua

Specific parts I would point out are L225 with return value based on button user used to exit, but there is also L182 which provides additional value based on user selection.
gfgtdf
Developer
Posts: 1432
Joined: February 10th, 2013, 2:25 pm

Re: GUI: dynamic return values

Post by gfgtdf »

return values can only be integers (and not settable in preshow),

And the list of modifiable properties is here https://wiki.wesnoth.org/LuaAPI/types/widget.

what you can do however is to create ac custom on_button_click callback that closes the dialog and sets some variable
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.
gfgtdf
Developer
Posts: 1432
Joined: February 10th, 2013, 2:25 pm

Re: GUI: dynamic return values

Post by gfgtdf »

example code

Code: Select all

local dialog_wml = ...
local res = nil
local function preshow(dialog)
  function dialog.some_button.on_button_click()
    res = "boo"
    dialog:close()
  end
end
gui.show_dialog(dialog_wml, preshow)
return res
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: 1207
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: GUI: dynamic return values

Post by white_haired_uncle »

Thank you both again.
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2235
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: dynamic return values

Post by Celtic_Minstrel »

white_haired_uncle wrote: January 4th, 2024, 9:55 pm I suspect there's probably an alternative based on selected_item, but I'm trying to demonstrate how to use return_value(_id).
A button's return value cannot be modified at runtime. If you need some sort of dynamic return value, use a post_show function – the function can put your custom return value into a local variable, and you can just ignore the standard dialog return value. This is what gfgtdf's example does.
white_haired_uncle wrote: January 4th, 2024, 9:55 pm Also, most of what I try gets me "invalid (modifiable) property of widget". Are said properties documented/listed somewhere?
If you're running in 1.17, you can use the wesnoth.print_attributes function (documented here) to determine what properties are valid and exist in any variable. There might be variables it doesn't work on, but it should certainly work on everything in the GUI2 system. For example, if you add the following:

Code: Select all

wesnoth.print_attributes(dialog.monsters_mp[i].monster_pick)
gui.show_lua_console()
in your pre_show, it will pop up the Lua console with a list of all the non-deprecated attributes available on that particular widget. (Note: That probably includes all the functions in the gui.widget module as well.)

The list on the wiki is intended to be comprehensive, so if you find something that's not mentioned on either of the wiki pages (here and here) then you can feel free to add it to the wiki.
white_haired_uncle wrote: January 4th, 2024, 9:55 pm P.S. https://devdocs.wesnoth.org/group__GUIWidgetWML.html has every other widget listed as gui2::<widget>, except gui::button, which means button is the only one that doesn't show up in alphabetical order.
gui::button is just a legacy thing that's not related to GUI2 at all – it's the button used in the main game UI, such as for "End Turn", the top menubar, etc.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1207
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: [solved] GUI: dynamic return values

Post by white_haired_uncle »

Celtic_Minstrel wrote: January 5th, 2024, 8:19 am
If you're running in 1.17, you can use the wesnoth.print_attributes function (documented here) to determine what properties are valid and exist in any variable. There might be variables it doesn't work on, but it should certainly work on everything in the GUI2 system. For example, if you add the following:

Code: Select all

wesnoth.print_attributes(dialog.monsters_mp[i].monster_pick)
gui.show_lua_console()
in your pre_show, it will pop up the Lua console with a list of all the non-deprecated attributes available on that particular widget. (Note: That probably includes all the functions in the gui.widget module as well.)
I built 1.17.24 to try this. Everything I try to inspect gives me:

Code: Select all

20240105 10:08:51 error scripting/lua: attempt to call a table value
stack traceback:
	[C]: in ?
	[C]: in field 'print_attributes'
	..._Dark_Master_Project_Resources_beta/lua/gui_tutorial.lua:772: in upvalue 'preshow'
	lua/core/gui.lua:143: in function <lua/core/gui.lua:140>
	[C]: in upvalue 'old_show_dialog'
	lua/core/gui.lua:138: in function 'gui.show_dialog'
	..._Dark_Master_Project_Resources_beta/lua/gui_tutorial.lua:781: in local 'cmd'
	lua/wml-utils.lua:145: in field 'handle_event_commands'
	lua/wml-flow.lua:5: in function <lua/wml-flow.lua:4>
	[C]: in ?
I've tried with just a simple button(/grid/column), and also with the following so I could cut and paste from your example.

The error confuses me a bit, since the wiki says "For tables, by default this iterates the table..."

EDIT: Okay, not everything gets that error. Pass it a table, like wesnoth.(listboxItem) or wesnoth.print_attributes({"a","b","c"}) and it doesn't have much to say, but doesn't error. Maybe I just misunderstand the error.

According to

Code: Select all

wesnoth.type(dialog.monsters_mp[i].monster_pick),
...monster_pick is a widget, and the wiki says about print_attributes "...if used on a GUI2 widget object..."

While I want to understand this tool in general, I'm specifically hoping it will show me things like the height of a row/column/cell(?)/widget to help me understand the grow/placement/alignment stuff from another thread.

Code: Select all

function wesnoth.wml_actions.current_test_gui()
        local monsters = {
                { image = "units/trolls/grunt.png", label = "A troll", name = _"Bob", race = "Trolls" },
                { image = "units/trolls/whelp.png", label = "A troll whelp", name = "Junior", race = "Trolls" },
                { image = "units/trolls/shaman.png", label = "A troll shaman", name = _"Alice", race = "Trolls" },
                { image = "units/monsters/cuttlefish.png", label = "A cuttlefish", race = "Seamonsters" },
                { image = "units/monsters/yeti.png", label = "A yeti", race = "Coolers" }
        }

        local listbox_id = "monsters"

        local listboxItem = wml.tag.grid {
                wml.tag.row {
                        wml.tag.column {
                                wml.tag.image { id = "monster_image" }
                        }
                }
        }

        local listboxDefinition = wml.tag.listbox { id = listbox_id,
               wml.tag.list_definition {
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.toggle_panel { listboxItem }
                                }
                        }
                }
        }


        local multi_page = wml.tag.multi_page { id = "monsters_mp",
                wml.tag.page_definition { id = "trolls_page",
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.label { id = "monster_name" }
                                },
                        },
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.image { id = "monster_image" }
                                },
                        },
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.button { id = "monster_pick",label="pick me" }
                                }
                        },
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.label { id = "monster_label" }
                                }
                        }
                },
                wml.tag.page_definition { id = "nottrolls_page",
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.image { id = "monster_image" }
                                },
                        },
                        wml.tag.row {
                                wml.tag.column {
                                        wml.tag.label { id = "monster_label" }
                                }
                        },
                }
        }
       local dialogDefinition = {
                wml.tag.tooltip { id = "tooltip_large" },
                wml.tag.helptip { id = "helptip_large" },
                wml.tag.grid {
                        wml.tag.row {  -- A header 
                                wml.tag.column {
                                        border = "bottom",
                                        border_size = 10,
                                        wml.tag.label {
                                                use_markup = true,
                                                label = "<span size='large'>" .. _"Here there be " ..
                                                        "<span color='yellow'>" ..
                                                        _"MONSTERS!" .. "</span></span>"
                                        }
                                }
                        },
                        wml.tag.row {  -- The body of our GUI
                                wml.tag.column {
                                        wml.tag.grid {
                                                wml.tag.row {
                                                        wml.tag.column {
                                                                listboxDefinition
                                                        },
                                                        wml.tag.column {
                                                                wml.tag.spacer {
                                                                        width = 30
                                                                }
                                                        },
                                                        wml.tag.column {
                                                                multi_page
                                                        },
                                                }
                                        }
                                }
                        },
                        wml.tag.row {
                                wml.tag.column {  -- An "OK" button
                                        wml.tag.button { id = "ok",
                                                label = _"OK"
                                        },
                                }
                        }
                }
        }

        local function preshow(dialog)  -- Prepare the GUI before display
                local listbox = dialog[listbox_id]
                for i, monster in ipairs(monsters) do
                        listbox[i].monster_image.label = monster.image
                        if monster.race == "Trolls" then
                                dialog.monsters_mp:add_item_of_type("trolls_page")
                                dialog.monsters_mp[i].monster_name.label = monster.name


                        wesnoth.print_attributes(dialog.monsters_mp[i].monster_pick)
                        gui.show_lua_console()


                        else
                                dialog.monsters_mp:add_item_of_type("nottrolls_page")
                        end
                        dialog.monsters_mp[i].monster_image.label = monster.image
                        dialog.monsters_mp[i].monster_label.label = monster.label
                end
                local function switch_page()
                        dialog.monsters_mp.selected_index = listbox.selected_index
                end
                listbox.on_modified = switch_page
        end
        gui.show_dialog(dialogDefinition,preshow)
end
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2235
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: dynamic return values

Post by Celtic_Minstrel »

white_haired_uncle wrote: January 5th, 2024, 3:31 pm EDIT: Okay, not everything gets that error. Pass it a table, like wesnoth.(listboxItem) or wesnoth.print_attributes({"a","b","c"}) and it doesn't have much to say, but doesn't error. Maybe I just misunderstand the error.
In the second case I believe you should see a line with "1 2 3" printed in the console, is that correct? Or it may print nothing if I set it to skip numeric keys. A better basic test case would be wesnoth.print_attributes({a=1,b=2,c=3}) which should print out "a b c".

I'm not sure what to think of your error. What's happening is probably that print_attributes is trying to call a function from the widget or its metatable that it expects will return a list of valid attributes (or some other internal mechanism), but the function turns out to be a table instead. In short, it seems like a bug inprint_attributes. I'll see if I can reproduce it myself.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Celtic_Minstrel
Developer
Posts: 2235
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: dynamic return values

Post by Celtic_Minstrel »

I found the issue. Due to the last-minute change of the function name from "dir" to "wesnoth.print_attributes", grabbing names from a widget object broke. I've pushed a fix to master. Thank you for finding this issue.

Also, as a side note, do you have a reason to be defining your dialog directly in the Lua instead of in a separate WML file, like the example on the wiki?
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1207
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: [solved] GUI: dynamic return values

Post by white_haired_uncle »

Celtic_Minstrel wrote: January 6th, 2024, 3:22 pm
white_haired_uncle wrote: January 5th, 2024, 3:31 pm EDIT: Okay, not everything gets that error. Pass it a table, like wesnoth.(listboxItem) or wesnoth.print_attributes({"a","b","c"}) and it doesn't have much to say, but doesn't error. Maybe I just misunderstand the error.
In the second case I believe you should see a line with "1 2 3" printed in the console, is that correct? Or it may print nothing if I set it to skip numeric keys. A better basic test case would be wesnoth.print_attributes({a=1,b=2,c=3}) which should print out "a b c".
I didn't really have any expectations, I just wanted to see if passing it a simple table (I tried {} first) would throw an error. Using your better test case I do get "a b c". It also handled listboxItem okay, but hung for a few minutes and then cored when I tried "dialog". Again, no expectations, I was just curious (screwing around).

This is what I get for a button, which isn't what I expected:

Code: Select all

add_item ƒ           add_item_of_type ƒ   close ƒ              
enabled !            find ƒ               focus ƒ              
label !              marked_up_text !     on_button_click !    
on_left_click !      on_modified !        remove_items_at ƒ    
set_callback ƒ       set_canvas ƒ         tooltip !            
type                 use_markup !         visible !            
along with errors on the terminal such as

Code: Select all

20240106 19:15:17 error scripting/lua: invalid property of 'N4gui26buttonE' widget :on_button_click
20240106 19:15:17 error scripting/lua: invalid property of 'N4gui26buttonE' widget :on_left_click
I do have a label set for that widget, if it matters. And I set up an on_button_click to see if that changed anything, but it didn't. If I read the above correctly, it looks like add_item_of_type is an attribute that can be set on this object, which seems odd since it's a button, I wondered if I'm seeing the valid attributes of the button's (grand)parent (a multi_page).

I don't know if any of the above makes any sense, or if I simply don't understand the tool. But that's what I saw.
Also, as a side note, do you have a reason to be defining your dialog directly in the Lua instead of in a separate WML file, like the example on the wiki?
I just find lua a lot easier to read/write, and I don't like to switch back and forth. I will admit, I did experiment with LISP a good bit in my college days, I suppose that may have had lasting effects.
Speak softly, and carry Doombringer.
User avatar
Celtic_Minstrel
Developer
Posts: 2235
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: dynamic return values

Post by Celtic_Minstrel »

white_haired_uncle wrote: January 7th, 2024, 12:41 am This is what I get for a button, which isn't what I expected:
In what way does it differ from what you expected?
white_haired_uncle wrote: January 7th, 2024, 12:41 am along with errors on the terminal such as
Those errors are a side-effect of how the code determines the type of each property. It's because attempting to read an nonexistent or write-only property on a widget raises that error, but the only way to determine the type of a property is to read it and check the type.
white_haired_uncle wrote: January 7th, 2024, 12:41 am I do have a label set for that widget, if it matters. And I set up an on_button_click to see if that changed anything, but it didn't. If I read the above correctly, it looks like add_item_of_type is an attribute that can be set on this object, which seems odd since it's a button, I wondered if I'm seeing the valid attributes of the button's (grand)parent (a multi_page).

I don't know if any of the above makes any sense, or if I simply don't understand the tool. But that's what I saw.
It kind of sounds like you thought the ! meant the property was unset, but if you read the documentation, it clearly states that it means the property is likely write-only.
white_haired_uncle wrote: January 7th, 2024, 12:41 am I just find lua a lot easier to read/write, and I don't like to switch back and forth. I will admit, I did experiment with LISP a good bit in my college days, I suppose that may have had lasting effects.
I see. Since you were writing a guide on the wiki, it might be a good idea to at least try the alternate file method so you can add that to the guide, though someone else could always do it later.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
white_haired_uncle
Posts: 1207
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: [solved] GUI: dynamic return values

Post by white_haired_uncle »

For a button, I would expect to see that on_button_click is a function. I only added a function to see if that made a difference. I would not expect to see add_item_of_type listed as a function, does it make any sense to say my_button:add_item_of_type("foo")?

The wiki says "! if there was an error attempting to determine the type (which includes write-only attributes)". I can't think of anything I would refer to as a write-only attribute, besides a function, so along with the terminal error I assumed there was a problem getting the type of on_button_click, for example.

Right now, I mention that you can use WML, and some would prefer it, but that I am going to use lua. I'm considering adding something like a side by side example of both for a simple gui for comparison.
Speak softly, and carry Doombringer.
gfgtdf
Developer
Posts: 1432
Joined: February 10th, 2013, 2:25 pm

Re: [solved] GUI: dynamic return values

Post by gfgtdf »

In most of these cases there is no deeper reason why these are read only, it's just that no one has implemented the (c++) code to read the attribute (yet?). Unlike "pure" lua objects, these attributes aren't stored in the lua data structures themself, but in the c++ data structures so to support reading writing them we need to add a separate code to make them available to lua which sometimes just isn't there,
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.
User avatar
Celtic_Minstrel
Developer
Posts: 2235
Joined: August 3rd, 2012, 11:26 pm
Location: Canada
Contact:

Re: [solved] GUI: dynamic return values

Post by Celtic_Minstrel »

white_haired_uncle wrote: January 7th, 2024, 3:06 am For a button, I would expect to see that on_button_click is a function. I only added a function to see if that made a difference.
It's not a function, exactly. It's a callback. It shows as ! because you can set it but not query it. You can't manually call the callback, it only exists for the engine to call it at an appropriate time.
white_haired_uncle wrote: January 7th, 2024, 3:06 am I would not expect to see add_item_of_type listed as a function, does it make any sense to say my_button:add_item_of_type("foo")?
I understand your thought here, but in fact it's entirely valid to write that, though it'll raise an "unsupported widget" error. The reason it's valid is because that function is defined in the gui.widget module, which is treated as if it were the "base class" for all widgets. Thus, all functions in that module can be called on a widget as you demonstrated, though not all of them make sense to be called on any type of widget.
white_haired_uncle wrote: January 7th, 2024, 3:06 am The wiki says "! if there was an error attempting to determine the type (which includes write-only attributes)". I can't think of anything I would refer to as a write-only attribute, besides a function, so along with the terminal error I assumed there was a problem getting the type of on_button_click, for example.
Those errors you saw in the terminal are because the attributes are write-only. The print_attributes function attempted to read those attributes to determine the type, which raised those errors.

You're right that many of them don't make sense to be write-only. That's an area where there's still room for improvement in the API. For me, the only ones in your button example that make sense to be write-only are the three callbacks, plus marked_up_text. The reason for the latter is because assigning to marked_up_text is a shorthand that sets label to the assigned value while also setting use_markup to true.
Last edited by Celtic_Minstrel on January 8th, 2024, 3:54 am, edited 1 time in total.
Author of The Black Cross of Aleron campaign and Default++ era.
Former maintainer of Steelhive.
User avatar
Ravana
Forum Moderator
Posts: 3016
Joined: January 29th, 2012, 12:49 am
Location: Estonia
Contact:

Re: [solved] GUI: dynamic return values

Post by Ravana »

Celtic_Minstrel wrote: January 7th, 2024, 2:11 am I see. Since you were writing a guide on the wiki, it might be a good idea to at least try the alternate file method so you can add that to the guide, though someone else could always do it later.
For guide purpose it can be converted with wml.tostring.
Post Reply