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_children
and 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 get_children
.
extends VBoxContainer
onready var scroll_container = get_parent()
func _ready():
for child in get_children():
child.connect('focus_enter', self, '_on_focus_change')
func _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:
scroll_container.set_v_scroll(focus_offset)
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.