[GUI] various questions on lining stuff up
Moderator: Forum Moderators
-
- Posts: 1433
- Joined: August 26th, 2018, 11:46 pm
- Location: A country place, far outside the Wire
[GUI] various questions on lining stuff up
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.
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.
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.
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)"
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.
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.
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
- Attachments
-
- LotI1-Prison.gz
- (429.45 KiB) Downloaded 100 times
-
- new_menu.tar.bz2
- extract in addons/Legend_of_the_Invincibles
- (12.2 KiB) Downloaded 101 times
Speak softly, and carry Doombringer.
-
- Posts: 1433
- Joined: August 26th, 2018, 11:46 pm
- Location: A country place, far outside the Wire
Re: [GUI] various questions on lining stuff up
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.
-
- Posts: 1433
- Joined: August 26th, 2018, 11:46 pm
- Location: A country place, far outside the Wire
Re: [GUI] various questions on lining stuff up
For item 2, you can use a stacked_widget with an invisible image layer set to the minimum size you want to reserve, then put your widget on top of that. Or look at the size_lock widget.
Speak softly, and carry Doombringer.