+7 votes

I am adding new elements to ScrollContainer from script. Like a chat. How to make the scroll container auto scroll down to the newest message?

in Engine by (75 points)

2 Answers

+4 votes

Here's an example that adds 100 buttons to a ScrollContainer, and scrolls the container to the bottom after each button is added. Also, just to better see the effect, it waits 0.1 seconds between adding each button...

extends CanvasLayer

onready var container = $ScrollContainer
onready var vbox = $ScrollContainer/VBoxContainer
onready var scrollbar = $ScrollContainer.get_v_scrollbar()

func _ready():
    for i in range(100):
        var btn = Button.new()
        btn.text = "Button" + str(i)
        addItem(btn)
        yield(get_tree().create_timer(.1), "timeout")

func addItem(btn):
    vbox.add_child(btn)
    yield(get_tree(), "idle_frame")
    container.scroll_vertical = scrollbar.max_value
by (19,164 points)
edited by

Hello people,
I used your answer as a base for a smoothed scrolling version, thanks @Jgodfrey:

extends ScrollContainer

#Call from a parent node the function Scroll_To_Bottom(), it will cycle infinitely.

signal Scroll_Reached_End
signal Scroll_Reached_Top

var TIMER = Timer.new()
var SCROLL = 0

export (float) var DELAY = 2.0 #in seconds
export (int) var SPEED = 1

func _ready():
    connect("Scroll_Reached_End",self,"Scroll_Restart")
    connect("Scroll_Reached_Top",self,"Scroll_To_Bottom")

    TIMER.autostart = 0
    TIMER.one_shot = 1
    TIMER.wait_time = DELAY
    add_child(TIMER)

func _process(delta):
    #Will only work if timer isn't active or simply the time_left == 0.
    if !TIMER.time_left:
        #SCROLL DOWN
        if SCROLL == 1:
            if (scroll_vertical + get_v_scrollbar().page) < get_v_scrollbar().max_value:
                scroll_vertical += SPEED
            else:
                SCROLL = 0
                emit_signal("Scroll_Reached_End")

        #SCROLL UP
        elif SCROLL == -1:
            if scroll_vertical != 0:
                scroll_vertical -= SPEED
            else:
                SCROLL = 0
                emit_signal("Scroll_Reached_Top")

func Scroll_Back_Up():
    TIMER.start(DELAY)
    SCROLL = -1

func Scroll_Restart():
    TIMER.start(DELAY*1.5) #Here delay is longer.
    SCROLL = -1 #Loop back up.

func Scroll_To_Bottom():
    scroll_vertical = 0 #Reset to top first.
    TIMER.start(DELAY)
    SCROLL = 1
+2 votes

Maybe an easier way to do it is to use v_scrollbar signal "changed":

func _ready():
    v_scroll = scroll_c.get_v_scrollbar()
    v_scroll.connect("changed", self, "scroll_now")

func resize_item_in_scroll_container_or_add_a_child_to_it():
    ...
    lock_out = false       # prevent recursion

func scroll_now():
    if lock_out == true:   # prevent recursion
        return
    lock_out = true
    scroll_c.scroll_vertical = backdrop.rect_min_size.y - scroll_c.rect_size.y
by (176 points)

This is good, but it'd be better if we didn't need a flag.

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 Frequently asked questions and 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 [email protected] with your username.