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.
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.
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.
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.
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).