+5 votes

Structure:
ScrollContainer
-- VboxContainer
----ItemEntry

When navigating through entries with keyboard, scroll container doesn't follow the focus and you can't see what item is selected.
As far as I can see, there is no way to enable following focus somehow. How would I make this work?

On screenshot the topmost item is the one with keyboard focus. If I press up, it just goes out of bounds.
enter image description here

in Engine by (47 points)

2 Answers

+6 votes
Best answer

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.

by (328 points)
selected by

Thanks a lot, I was thinking about writing a script for this, but wasn't sure if there's a standard way of doing it. This seems like a pretty roundabout way of doing things since godot should handle this without scripting involved. Made an issue on github, but for now I'll deal with it through script.

Great script!

For anybody trying to use it now, here's an updated version (methods and signals are a bit different).

I've also made it so it scrolls just enough for the new entry to be visible when moving to the bottom.

extends VBoxContainer

onready var _scroll_container = get_parent()


func _ready():
    for child in get_children():
        child.connect("focus_entered", self, "_on_focus_change")


func _on_focus_change():
    var focused = get_focus_owner()
    var focus_size = focused.rect_size.y
    var focus_top = focused.rect_position.y

    var scroll_size = _scroll_container.rect_size.y
    var scroll_top = _scroll_container.get_v_scroll()
    var scroll_bottom = scroll_top + scroll_size - focus_size

    if focus_top < scroll_top:
    _scroll_container.set_v_scroll(focus_top)

    if focus_top > scroll_bottom:
        var scroll_offset = scroll_top + focus_top - scroll_bottom
        _scroll_container.set_v_scroll(scroll_offset)
0 votes
by (18 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.