[GUI] various questions on lining stuff up

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

Moderator: Forum Moderators

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

[GUI] various questions on lining stuff up

Post by white_haired_uncle »

At this point, I simply detest trying to get anything to line up nicely in a gui. After countless hours of error and trial, I've come no closer to having even a theory about how this stuff works.

I have two lists of units. In the right column, a header and a list of units on the recall list. It's a treeview, because that's the only thing I was able to use for which I could find a way to get the names to line up neatly all left aligned. In the left column, I have a header, a (treeview) list of units that will be automatically recalled at the start of a scenario, and a multi_page that will hold either a spacer (big blank spot when nothing is selected) or a unit_preview_pane displaying the selected unit.

Issue #1. How do I force the headers (yellow text) to line up vertically? I've had a tiny bit of success with linked_groups getting things to line up horizontally, but that's mostly luck at this point. The "header_label" linked groups are my failed attempt.
The beginning, nothing selected.
The beginning, nothing selected.
Issue #2. When I select a unit, the unit_preview_pane sizes itself based on some rules I haven't figured out. Might be something like "if you can fit everything you have on the screen without using a scrollbar, but leaving at least ONE visible unit listed in the treeview above, take as much space as you need. What I'd really like to say is "treeview, your height is X, mp your height is Y. Always. Deal with it." (My gut says I'd prefer height in %, I'd settle for pixels). I don't want anything re-sizing itself, I want to define the layout.

I'll select item 12 from the list on the left for example. Not at all what I want.
Take all the space you need there partner.
Take all the space you need there partner.
Issue #3. I've selected item 12 on the list on the left, but you can't see that above. The list got scrunched and a scrollbar added, but the focus is not on the selected entry. Can I force this? I really do want to see which I have selected.

Also, those up and down buttons move selected units up or down on the left list (unit stays selected, but its position in list moves). If the selected unit moves to a place where it can't be seen without moving the scrollbar, the focus doesn't go with it and the bar doesn't move automatically.

I suspect this issue might have something to do with the fact that I'm using a treeview and sort of forcing it to act like a listbox. I'm not against using a listbox, that's what I started with, but I couldn't get the names to line up (I think I might be able to now based on something similar I'm also working on).

I'll apologize for the code now. It's a WIP, I barely know what I'm doing, and I'm still trying to develop a style which is readable but saves real estate. I wish I could cut out all the non-relevant stuff, but I'd probably mess something up so here it is warts and all.

Code: Select all

function wesnoth.wml_actions.autorecall_menu()
-- luacheck: ignore 612
-- luacheck: ignore 614

-- TODO: up/down -> repeating_button
-- TODO: dismiss button???
-- TODO: clean up abilities: [2], formatting, don't show header if no ability[2].name, fix/confirm remove/add
-- TODO: Check the whole unit -> unit.__cfg and all that, may not be necessary now using wesnoth.units.find
--              (might assume old menu will not be used)

        local gold = wesnoth.sides[wesnoth.current.side].gold
        local max_autorecall = wml.variables.max_autorecall or 8
        local next_autorecall_price
        local function update_next_autorecall_price()
                next_autorecall_price = 100 * (max_autorecall - 7)
                if wesnoth.scenario.difficulty == "EASY" then
                        next_autorecall_price = math.floor(next_autorecall_price / 1.5)
                elseif wesnoth.scenario.difficulty == "NORMAL" then
                        next_autorecall_price = math.floor(next_autorecall_price / 1.2)
                end
        end
        update_next_autorecall_price()

        local ar_list = wml.array_access.get "autorecall"
        local av_list = {}

        -- Create list of available units
        local on_ar = {}
        for _,unit in pairs(ar_list) do
                on_ar[unit.id] = true
        end
        local our_team = wesnoth.units.find{ side =  wesnoth.current.side }
        for _,unit in pairs(our_team) do
                if not on_ar[unit.id] and unit.id ~= 'Efraim' and unit.id ~= 'Lethalia' and unit.id ~= 'akulas_sister' and
                        unit.id ~= 'Lethalia_evil' and unit.id ~= 'Lilith'
                        then table.insert(av_list,unit.__cfg) end
        end
        -- autorecall list from older saves may not contain full unit data, update
        local new_ar = {}
        for _,oldunit in ipairs(ar_list) do
                local units = wesnoth.units.find{ id=oldunit.id }
                if #units == 1 then table.insert(new_ar,units[1].__cfg) end
        end
        ar_list = new_ar

        local ar_selected_index = nil
        local av_selected_index = nil

        local autorecall_treeview = T.tree_view {
                id = "ar_tv",
                T.node { id = "ar_node",
                        T.node_definition {
                                T.row {
                                        T.column {
                                                T.toggle_panel { id = "ar_tp",
                                                        definition = "default",
                                                        T.grid {
                                                                T.row {
                                                                        T.column {
                                                                                T.label { id = "ar_position",
                                                                                        text_alignment = "right",
                                                                                        linked_group = "ar_position"
                                                                                 }
                                                                        },
                                                                        T.column {
                                                                                T.label { id = "ar_unit",
                                                                                        linked_group = "ar_unit"
                                                                                }
                                                                        }
                                                                },
                                                                T.row {
                                                                        T.column { T.spacer {} },
                                                                        T.column { T.image { id = "ar_threshold" } }
        }       }       }       }       }       }       }       }

        local avail_treeview = T.tree_view {
                id = "av_tv",
                T.node { id = "av_node",
                        T.node_definition {
                                T.row {
                                        T.column {
                                                T.toggle_panel { id = "av_tp",
                                                        definition = "default",
                                                        T.grid {
                                                                T.row {
                                                                        T.column {
                                                                                T.label { id = "av_unit",
                                                                                        linked_group = "av_unit"
        }       }       }       }       }       }       }       }       }       }

        local empty_page = T.page_definition { id = "empty_page", T.row { T.column { T.spacer {} } } }

        local unit_info_frame = T.grid { T.row { T.column { T.unit_preview_pane { id = "unit_info_preview" } } } }
        local unit_info_page = T.page_definition { id = "unit_info_page",
                T.row { T.column { unit_info_frame } } }

        local unit_info_mp = T.multi_page { id = "unit_info_mp",
                empty_page,
                unit_info_page
        }
       local done = false
        while not done do

                local dialogDefinition = {
                        T.tooltip { id = "tooltip" },
                        T.helptip { id = "tooltip_large" },
                        T.linked_group { id = "ar_position", fixed_width = true, },
                        T.linked_group { id = "ar_unit", fixed_width = true },
                        T.linked_group { id = "av_unit", fixed_width = true },
                        T.linked_group { id = "header_label", fixed_height = true },
                        T.grid {
                                T.row {
                                        T.column {
                                                T.grid {
                                                        T.row {
                                                                T.column { id = 'autorecall_list',
                                                                        border = "right",
                                                                        border_size = 50,
                                                                        T.grid {
                                                                                T.row {
                                                                                        T.column {
                                                                                                T.label {
                                                                                                        linked_group = "header_label",
                                                                                                        use_markup = true,
                                                                                                        label = "<span size='large' color='yellow'>"
                                                                                                        .. _"Units to be Automatically Recalled (" ..
                                                                                                        #ar_list .. "/" ..
                                                                                                        max_autorecall .. ")</span>" }
                                                                                        }
                                                                                },
                                                                               T.row {
                                                                                        T.column {
                                                                                                border = "bottom",
                                                                                                border_size = 5,
                                                                                                autorecall_treeview }
                                                                                },
                                                                                T.row {
                                                                                        T.column { unit_info_mp }
                                                                }       }       },
                                                                T.column { id = 'av_list',
                                                                        T.grid {
                                                                                T.row {
                                                                                        T.column {
                                                                                                T.label {
                                                                                                        linked_group = "header_label",
                                                                                                        use_markup=true,
                                                                                                        label = "<span size='large' color='yellow'>"
                                                                                                        .. _"Available Units (" .. #av_list .. ")</span>" }
                                                                                        }
                                                                                },
                                                                                T.row {
                                                                                        T.column { avail_treeview }
                                }        }       }       }       }       }      },
                                T.row { T.column { T.spacer { height = 25 } } },
                                T.row {
                                        T.column {
                                                T.grid {
                                                        T.row {
                                                                T.column { T.spacer { width = 10 } },
                                                                T.column {
                                                                        border = "right",
                                                                        border_size = 40,
                                                                        T.button { id = "add_slot",
                                                                                definition = "add",
                                                                                return_value_id = "ok",
                                                                                tooltip =_ "Buy space to automatically recall an additional unit" .. "\n" ..
                                                                                        _"Need: " .. next_autorecall_price .. _"  Have: " .. gold
                                                                        }
                                                                },
                                                                T.column {
                                                                        border = "right",
                                                                        border_size = 10,
                                                                        T.button { id = "ar_up",
                                                                                return_value_id = "ok",
                                                                                definition = "up_arrow"
                                                                        }
                                                                },
                                                                T.column {
                                                                        border = "right",
                                                                        border_size = 40,
                                                                        T.button { id = "ar_down",
                                                                                return_value_id = "ok",
                                                                                definition = "down_arrow"
                                                                        }
                                                                },
                                                               T.column { T.spacer { width = 10 } },
                                                                T.column {
                                                                        border = "left,right",
                                                                        border_size = 10,
                                                                        T.button { id = "ar_remove",
                                                                                return_value_id = "ok",
                                                                                tooltip = "Remove unit from the autorecall list",
                                                                                definition = "right_arrow"
                                                                        }
                                                                },
                                                                T.column { T.spacer { width = 100 } },
                                                                T.column {
                                                                        border = "right",
                                                                        border_size = 10,
                                                                        T.button {
                                                                                id = "ok_button",
                                                                                label =_ "OK",
                                                                                return_value = -99
                                                                        }
                                                                },
                                                                T.column {
                                                                        border = "right",
                                                                        border_size = 100,
                                                                        T.button {
                                                                                id = "cancel",
                                                                                label = "Cancel"
                                                                        }
                                                                },
                                                                T.column {
                                                                        border = "right",
                                                                        border_size = 40,
                                                                        T.button { id = "av_remove",
                                                                                return_value_id = "ok",
                                                                                definition = "left_arrow",
                                                                                tooltip =_ "Move unit to the autorecall list"
                                                                        }
                                                                },
                                                                T.column {
                                                                        T.button { id = "av_info",
                                                                                return_value_id = "ok",
                                                                                definition = "action_about"
                                                                        }
                }       }        }       }       }       }       }

                local function preshow(dialog)
                        local function refresh_buttons()
                                dialog.ar_up.enabled = false
                                dialog.ar_down.enabled = false
                                dialog.ar_remove.enabled = false
                                dialog.av_remove.enabled = false
                                dialog.av_info.enabled = false
                                if ar_selected_index ~= nil then
                                        if ar_selected_index ~= 1 then dialog.ar_up.enabled = true end
                                        if ar_selected_index ~= #ar_list then dialog.ar_down.enabled = true end
                                        dialog.ar_remove.enabled = true
                                elseif av_selected_index ~= nil then
                                        dialog.av_info.enabled = true
                                        dialog.av_remove.enabled = true
                                end
                        end
                        local function refresh_multipage()
                                local unit = nil
                                if ar_selected_index ~= nil then
                                        unit = ar_list[ar_selected_index]
                                elseif av_selected_index ~= nil then
                                        unit = av_list[av_selected_index]
                                else
                                        return
                                end

                                dialog.unit_info_mp[2].unit_info_preview.unit = wesnoth.units.find{ id = unit.id }[1]
                                dialog.unit_info_mp.selected_index = 2
                        end

                        local function ar_unit_toggle(index)
                                if dialog.ar_tv[index].ar_tp.selected == true then
                                        if ar_selected_index ~= nil then
                                                dialog.ar_tv[ar_selected_index].ar_tp.selected = false
                                        elseif av_selected_index ~= nil then
                                                dialog.av_tv[av_selected_index].av_tp.selected = false
                                        end
                                        ar_selected_index = index
                                        av_selected_index = nil
                                        dialog.unit_info_mp.selected_index = 2
                                else
                                        ar_selected_index = nil
                                        dialog.unit_info_mp.selected_index = 1
                                end
                                refresh_buttons()
                                refresh_multipage()
                        end
                        local function av_unit_toggle(index)
                                if dialog.av_tv[index].av_tp.selected == true then
                                        if ar_selected_index ~= nil then
                                                dialog.ar_tv[ar_selected_index].ar_tp.selected = false
                                        elseif av_selected_index ~= nil then
                                                dialog.av_tv[av_selected_index].av_tp.selected = false
                                        end
                                        av_selected_index = index
                                        ar_selected_index = nil
                                        dialog.unit_info_mp.selected_index = 2
                                else
                                        av_selected_index = nil
                                        dialog.unit_info_mp.selected_index = 1
                                end
                                refresh_buttons()
                                refresh_multipage()
                        end

                        local function add_slot()
                                local poor_msg =_ "You do not have enough gold to do that"
                                if gold < next_autorecall_price then
                                        gui.show_prompt("",poor_msg)
                                        return
                                end
                                max_autorecall = max_autorecall + 1
                                gold = gold - next_autorecall_price
                                update_next_autorecall_price()
                        end
                        local function move_up()  -- move unit at ar_list[index] to ar_list[index-1]
                                if ar_selected_index == 1 then return end
                                local tmp
                                for i,_ in ipairs(ar_list) do
                                        if i == ar_selected_index - 1 then
                                                tmp = ar_list[i]
                                                ar_list[i]=ar_list[ar_selected_index]
                                        elseif i == ar_selected_index then
                                                ar_list[ar_selected_index]=tmp
                                        end
                                end
                                ar_selected_index = ar_selected_index - 1
                        end
                        local function move_down()  -- move unit at ar_list[ar_selected_index] to ar_list[ar_selected_index+1]
                                if ar_selected_index == #ar_list then return end
                                local tmp
                                for i,_ in ipairs(ar_list) do
                                        if i == ar_selected_index then
                                                tmp = ar_list[i]
                                                ar_list[i] = ar_list[i+1]
                                        elseif i == ar_selected_index + 1 then
                                                ar_list[i] = tmp
                                        end
                                end
                                ar_selected_index = ar_selected_index + 1
                        end
                        local function switch_lists(cfg) -- remove unit from from[index], append to to[]
                                local from = cfg.from
                                local to = cfg.to
                                local index = cfg.index
                                local new_list = {}
                                for i,unit in ipairs(from) do
                                        if i == index then
                                                table.insert(to,unit)
                                        else
                                                table.insert(new_list,unit)
                                        end
                                end
                                return new_list,to
                        end
                        local function show_inventory(cfg) -- Items menu
                                local units = wesnoth.units.find(cfg)
                                if #units < 1 then wml.error("show_inventory(): no units found.") end
                                --open_inventory_dialog(units[1])
                                wesnoth.require("~add-ons/Legend_of_the_Invincibles/lua/inventory/dialog.lua").open_inventory_dialog(units[1])
                        end

                        dialog.add_slot.on_button_click = add_slot
                        dialog.ar_up.on_button_click = move_up
                        dialog.ar_down.on_button_click = move_down
                        dialog.ar_remove.on_button_click = function() ar_list,av_list=
                                switch_lists({index=ar_selected_index,from=ar_list,to=av_list})
                                if ar_selected_index > #ar_list then ar_selected_index = #ar_list end
                        end
                        dialog.av_remove.on_button_click = function() av_list,ar_list=
                                switch_lists({index=av_selected_index,from=av_list,to=ar_list})
                                av_selected_index = nil
                                ar_selected_index = #ar_list
                        end
                        for i,unit in ipairs(ar_list) do
                                -- This part may not be necessary since we already did it above
                                local units = wesnoth.units.find{ id = unit.id } -- or what? better error handling here
                                if #units < 1 then
                                        wesnoth.interface.add_chat_message(string.format("Ack! %s not found!",unit.id))
                                else
                                        dialog.ar_tv:add_item_of_type("ar_node")
                                        local markup = ""
                                        if i > max_autorecall then markup = "font-style='oblique'" end
                                        dialog.ar_tv[i].ar_position.marked_up_text = string.format("<span font_family='monospace'>%d) </span>",i)
                                        dialog.ar_tv[i].ar_unit.marked_up_text = string.format("<span %s>%s (%s)</span>",markup,units[1].name,
                                                units[1].__cfg.language_name)
                                        if i == max_autorecall then
                                                dialog.ar_tv[i].ar_threshold.label = "images/misc/blank.png~SCALE(1,10)"
                                        else
                                                dialog.ar_tv[i].ar_threshold.label = "images/misc/blank.png~SCALE(1,1)"
                                        end
                                        dialog.ar_tv[i].ar_tp.selected = false
                                        if ar_selected_index == i then dialog.ar_tv[i].ar_tp.selected = true end
                                        dialog.ar_tv[i].ar_tp.on_modified = function() ar_unit_toggle(i) end
                                        -- This works as long as you don't click on items in the inventory
                                        --dialog.autorecall_tv[i].ar_info.on_button_click = function() show_inventory({ id = unit.id }) end
                                        -- This works only for units on map
                                        --dialog.autorecall_tv[i].ar_info.on_button_click = function() wesnoth.game_events.fire("unit information", unit) end
                                        --              
                                end
                        end

                        av_list = sort_unit_list(av_list)
                        for i,unit in ipairs(av_list) do
                                dialog.av_tv:add_item_of_type("av_node")
                                dialog.av_tv[i].av_unit.label = string.format("%s (%s)",unit.name,unit.language_name)
                                if av_selected_index == i then dialog.av_tv[i].av_tp.selected = true end
                                dialog.av_tv[i].av_tp.on_modified = function() av_unit_toggle(i) end
                                -- This works as long as you don't click on items in the inventory
                                --dialog.av_tv[i].av_info.on_button_click = function() show_inventory({ id = unit.id }) end
                        end

                        dialog.unit_info_mp:add_item_of_type("empty_page")
                        dialog.unit_info_mp:add_item_of_type("unit_info_page")
                        refresh_multipage()

                        refresh_buttons()
                end
                
                local ret_val = gui.show_dialog(dialogDefinition,preshow)
                if ret_val == -99 then  -- the ok button
                        wml.array_access.set("autorecall", ar_list)
                        wesnoth.sides[wesnoth.current.side].gold = gold
                        wml.variables.max_autorecall = max_autorecall
                        done = true
                end
                if ret_val == -2 then done = true end  -- cancel/escape - abort changes
        end
end
Just in case anyone should care to try it to see what I'm struggling with, I've attached a saved game and my changes to the addon (https://github.com/Dugy/Legend_of_the_Invincibles). Menu item "Configure Autorecall (New)"
Attachments
LotI1-Prison.gz
(429.45 KiB) Downloaded 78 times
new_menu.tar.bz2
extract in addons/Legend_of_the_Invincibles
(12.2 KiB) Downloaded 83 times
Speak softly, and carry Doombringer.
white_haired_uncle
Posts: 1226
Joined: August 26th, 2018, 11:46 pm
Location: A country place, far outside the Wire

Re: [GUI] various questions on lining stuff up

Post by white_haired_uncle »

I fixed issue #1 by setting vertical_alignment="top" on the column that contains the grid that contains the left "half" of the dialog.

Code: Select all

                local dialogDefinition = {
                        T.tooltip { id = "tooltip" },
                        T.helptip { id = "tooltip_large" },
                        T.linked_group { id = "ar_position", fixed_width = true, },
                        T.linked_group { id = "ar_unit", fixed_width = true },
                        T.linked_group { id = "av_unit", fixed_width = true },
                        T.linked_group { id = "header_label", fixed_width = true },
                        T.grid {
                                T.row {
                                        T.column {
                                                T.grid {
                                                        T.row {
                                                                T.column { id = 'autorecall_list',
                                                                        vertical_alignment="top",
                                                                        T.grid {
                                                                                T.row {
                                                                                        T.column {
                                                                                                T.label {
                                                                                                        use_markup=true,
                                                                                                        label = "<span size='large' color='yellow'>"
                                                                                                        .. _"Units to be Automatically Recalled (" ..
                                                                                                        #ar_list .. "/" ..
                                                                                                        max_autorecall .. ")</span>" }
                                                                                        }
                                                                                },
                                                                                T.row {
                                                                                        T.column { autorecall_treeview }
                                                                                },
                                                                        }
                                                                },
Speak softly, and carry Doombringer.
Post Reply