Cant forward input event with _gui_input from control to scrollbar

Godot Version

4.5.1

Question

Hi! I’m working on a custom virtualized list in Godot 4 using C++ (GDExtension).

I have a custom control that handles its own layout and rendering of rows. I also use a VScrollBar as a separate node to represent scroll state.

When the mouse is over my class, I receive input in _gui_input(). I wanted to forward wheel events to the VScrollBar, so it behaves like a native scroll container.

What I tried

From my ScrollerList::_gui_input():

void ScrollerList::_gui_input(const Ref<InputEvent> &event) {
    scrollbar->_gui_input(event);
}

Also tried:

scrollbar->_input(event);
scrollbar->_unhandled_input(event);
call("_gui_input", scrollbar, event);
call("gui_input", scrollbar, event);

Expected behavior

Since ScrollBar::gui_input() in the engine clearly handles MouseButton::WHEEL_UP/DOWN, I expected that forwarding the event would trigger scrolling.

Actual behavior

Nothing happens — the scroll value does not change, and the wheel handling code inside ScrollBar::gui_input() is not triggered effectively.

Additional context / why I ended up here

Before implementing my own list, I tried using ScrollContainer as a base.

That approach worked quite well: I placed a Control inside it with the appropriate height and rendered my rows there. Scrolling behaved correctly out of the box.

However, I needed to support multiple data sources (tabs/views), each with its own dataset and potentially very different sizes. I also wanted to preserve scroll position independently for each dataset.

So my approach was:

  • store scroll position per data source

  • on switching source → update content size + restore scroll position

The problem I encountered is that when I switch the data source and update the Control height, I cannot immediately restore the scroll position:

  • calling set_value() on the scrollbar does not work correctly

  • it seems like layout/scroll range is not updated yet

Just put the wheel mouse events in _input(). _gui_input() is only for certain events for certain objects, and that’s not one of the supported events AFAIK.

Alternately, just put the whole thing in a ScrollContainer and it’ll handle the mouse for you.

1 Like

I understand that I can just handle wheel input myself (either in _input() or _gui_input()), and that’s what I think I will end up doing for now.

What confused me is more about the API/behavior itself.

Conceptually, it felt very natural to forward input to another Control. In my case, I already have a VScrollBar, and instead of reimplementing its behavior, I expected I could just pass the event to it and let it handle scrolling.

So something like:

scrollbar->_gui_input(event);

felt like the “correct” thing to do.

However, this does not work at all. Even more confusing: when I step through in GDB, calling _gui_input() externally sometimes ends up in Node::gui_input() (empty), not in ScrollBar::gui_input() as expected.

I checked godot sources and void ScrollBar::gui_input(const Ref &p_event) is where vscrollbar handles inputs…

That’s why I said don’t use _gui_input(), use _input(). I have no idea why it works that way, but I have noticed over time that functionality in _gui_input() seems to have been deprecated. There are other reasons not to use it. You can stop processing for _process(), _physics_process(), _input(), and _unhandled_input()` separately, but not _gui_input(). It’s just not that versatile.

1 Like

I tried what you suggested, but my forwarding approach still does not work.

For some reason, in C++ I end up calling an empty method instead of the one from the engine.

I also tried calling it from _input():

void ScrollerList::_input(const godot::Ref<godot::InputEvent> &event) {
  scrollbar->godot::VScrollBar::_gui_input(event);
}

but the behavior is the same — it does not trigger the expected logic.

I also tried to call it “asynchronously” through the local handlers, but that did not help either.

I don’t understand why this is not supposed to work if it isn’t.

It feels like your suggestion addresses a different problem.

Then perhaps I miscommunicated. I wasn’t suggesting you forward it. I was suggesting you put it in the code for the scrollbar.

Your use of forwarding to the scrollbar’s _gui_input will not work - because the mouse is not hovering over the scrollbar.

That statement makes no sense. But if you mean you don’t understand why it doesn’t work, when you tyink it should - it’s because you do not understand how _gui_input works.

I feel like I’m trying to help someone screw in a flathead screw, and they’re refusing to put down the phillips head screwdriver.

I read this and honestly I can only cry :sob::sob::sob::sob::sob:

About the callback — I briefly looked through the docs. As I understand it, the event is offered to nodes in the tree based on their position, I think I get that.

My idea was different: I wanted to receive the event in one node and then manually pass it to another node (to reuse existing behavior, like mouse wheel scrolling), instead of reimplementing it.

The problem is that in C++ bindings the original internal callbacks of Godot controls are not really accessible in the way I expected, and this approach simply doesn’t work.

As for the scrollbar sync issue — I actually solved it:

get_v_scroll_bar()->set_allow_greater(true);

This fixes the value clamping, so it behaves correctly and allows restoring the state.

Also, just to share — I ran into another issue: scrolling had very low FPS.

I checked the profiler, and it turned out to be quite interesting: rotating elements on screen was triggering font fallback resolution through libtextconfig.

I fixed it by configuring the theme and setting a proper fallback font (there were some issues with emoji).

In the end, I got exactly what I wanted from ScrollListContainer — it works great now (very smooth, ~2000 FPS, no performance penalty when scrolling).

1 Like