Auto scroll down if user is at bottom of chat, but if they are not, display a "scroll down" button

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

I’m working on a chat app, but it is tough to figure out how to have it scroll down when there is a new message, but only when it is already scrolled down to the bottom of the chat.

Along with this, I would like to have it display a scroll down button if the user is scrolled up.

My code is as follows:

func load_messages():

if len(Messenger.messages[str(conversation_id)]["messages"]) != len(messages_container.get_children()):
	clear_messages()
	for message in Messenger.messages[conversation_id]["messages"]:
		var msg = Messenger.messages[conversation_id]["messages"][message]
		var hb = HBoxContainer.new()
		var msg_label = Label.new()
		
		messages_container.add_child(hb)
		hb.add_child(msg_label)
		
		if msg["sender_id"] == Account.account["id"]:
			msg_label.theme = load("res://resources/themes/message/message_you.tres")
			hb.alignment = BoxContainer.ALIGN_END
		else:
			msg_label.theme = load("res://resources/themes/message/message_them.tres")
			hb.alignment = BoxContainer.ALIGN_BEGIN
		
		msg_label.text = msg["content"]
	
	if scrolling:
		var sb = scroll_msg_container.get_v_scrollbar()
		scroll_msg_container.scroll_vertical = sb.max_value

The load_messages() function gets called every time there is a new message, and I’m thinking before I call this that it should check if the user is scrolled all the way down, and if they are, scroll them down again once it adds the messages, and if it is scrolled up at all, it won’t and it will display a ⌄ (scroll down) button that when clicked scrolls down to the bottom.

Thanks in advance!

:bust_in_silhouette: Reply From: cm

Your suspicion is correct - before a new message is added check to see if you’re scrolled to the bottom, then after adding the new message set the scroll position to maximum.

However there are two problems to deal with.

The first is detecting whether you’re scrolled to the bottom. When scrolled all the way down, a scrollbar’s value is not equal to its max_value. The max_value is closer to value plus the scroll container’s height, so compare the value to max_value minus the scroll container’s rect_size.y. I’m not sure if you’ll have issues with margins or custom theme settings, so this might need some tweaking.

The other problem is that the scroll container is not updated on the same frame as when the message is added, so be sure to wait a frame before attempting to set the scrollbar’s value. One way to do this is to wait for the SceneTree’s idle_frame signal.

In the following example I have a ScrollContainer named scroll_box and inside of that a VBoxContainer named msg_container.

func add_message(message: String) -> void:
	var scroll_bar = scroll_box.get_v_scrollbar()
	var should_auto_scroll = ((scroll_bar.max_value - scroll_box.rect_size.y) - scroll_bar.value) <= 0

	var label = Label.new()
	label.text = message
	msg_container.add_child(label)

	yield(get_tree(), "idle_frame")

	if should_auto_scroll:
		scroll_bar.value = scroll_bar.max_value