ScrollContainer autoscroll

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By lxknvlk

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?

:bust_in_silhouette: Reply From: jgodfrey

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

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

The_Black_Chess_King | 2020-10-20 02:42

:bust_in_silhouette: Reply From: ulty

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

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

blurrred | 2021-08-14 04:20