I'm trying to make an ItemList that wraps around: if you're at the top and you press Up, you'll go to the bottom. But try as I might I can't figure out how to handle InputEvents in my script such that the ItemList code doesn't also handle them.
Here's an example ItemList script that demonstrates my issue:
extends ItemList
func _ready():
add_item("One")
add_item("Two")
add_item("Three")
add_item("Four")
add_item("Five")
set_process_input(true)
func _input(event):
if event.type == InputEvent.KEY and event.is_pressed():
print("_input: ", event)
get_tree().set_input_as_handled()
func _input_event(event):
if event.type == InputEvent.KEY and event.is_pressed():
print("_input_event: ", event)
accept_event()
The official documentation states
First of all, the standard input function will be called [...] If any function consumes the event, it can call SceneTree.setinputashandled(), and the event will not spread any more.
[...]
Second, it will try to feed the input to the GUI, and see if any control can receive it. If so, the Control will be called via the virtual function Control.inputevent() and the signal “inputevent” will be emitted [...] If the control wants to “consume” the event, it will call Control.acceptevent() and the event will not spread any more.
In input(event) I call gettree().setinputashandled(), which should prevent the event from propagating. But it still gets sent to _inputevent(event). From there I call accept_event(), which should also prevent the event from propagating, but the ItemList code will still happily act on it.
Try creating an ItemList with this code, clicking within it, and pressing Up or Down (as appropriate). You'll see two printouts for each InputEvent and the ItemList selection will move accordingly, even though the documentation suggests that this should not happen.
Clearly I'm misunderstanding something. How do I go about preventing an InputEvent from propagating?
Update
I haven't figured out how to stop an InputEvent from propagating, but I did figure out how to make a circular ItemList, which is good enough for this discussion. (Thanks, avencherus).
Assign the script below to an ItemList and you'll be able to navigate from the top to the bottom or the bottom to the top:
extends ItemList
func _ready():
set_process_input(true)
# Wrap around from the top to the bottom and vice versa.
func _input(event):
if not has_focus():
return
var first = 0
var last = get_item_count() - 1
if (event.type == InputEvent.KEY or event.type == InputEvent.JOYSTICK_BUTTON) and event.is_pressed() and not event.is_echo():
if event.is_action("ui_up"):
if is_selected(first):
call_deferred("_self_select", last)
if event.is_action("ui_down"):
if is_selected(last):
call_deferred("_self_select", first)
func _self_select(index):
get_tree().set_input_as_handled()
select(index)
emit_signal("item_selected", index)
This appears to work correctly because of call_deferred: with immediate calls, this code and ItemList's code will step on each others' toes when moving into the top or bottom slot. Try it for yourself if you're curious.