I was able to get this working pretty well with this script on the VBoxContainer (though you could put the script anywhere - just update the references to
get_parent so they point to the list items and the scroll container, respectively. If
get_children ends up targeting more than it should, I'd suggest having all your list items belong to a node group and using
get_nodes_in_group in place of
onready var scroll_container = get_parent()
for child in get_children():
child.connect('focus_enter', self, '_on_focus_change')
var focused = get_focus_owner()
var focus_offset = focused.get_pos().y
var scrolled_top = scroll_container.get_v_scroll()
var scrolled_bottom = scrolled_top + scroll_container.get_size().y - focused.get_size().y
if focus_offset < scrolled_top or focus_offset >= scrolled_bottom:
Basically, whenever one of the buttons in our scroll area gains focus, we find out its vertical position and compare that to the current upper and lower bounds of the visible scrolled area. We get the upper bound with
ScrollContainer.get_v_scroll; to get the lower bound, take that number and add the height of the scroll container; then we subtract one button-height from that, just to cover cases where the button's very close to that bottom edge or partially below it.
Once we have those two numbers, we check whether our focused element is between them; if it's not, we update the
v_scroll of the container, bringing the focused element into view.