Random beginner-question: advanced swiping mechanics

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

Hi everyone,

I have a VboxContainer as a child of a ScrollContainer and some text and buttons in it – a long list I can scoll through by finger-swiping on a touchscreen.

The code (based on https://github.com/godotengine/godot/issues/21137#issuecomment-414959543):

extends ScrollContainer

var swiping = false
var swipe_start
var swipe_mouse_start
var swipe_mouse_times = []
var swipe_mouse_positions = []

signal finger_is_swiping

func _ready():
	mouse_filter = Control.MOUSE_FILTER_IGNORE

func _input(ev):
	if ev is InputEventMouseButton:
		if ev.pressed:
			swiping = true
			swipe_start = Vector2(get_h_scroll(), get_v_scroll())
			swipe_mouse_start = ev.position
			swipe_mouse_times = [OS.get_ticks_msec()]
			swipe_mouse_positions = [swipe_mouse_start]
		else:
			swipe_mouse_times.append(OS.get_ticks_msec())
			swipe_mouse_positions.append(ev.position)
			var source = Vector2(get_h_scroll(), get_v_scroll())
			var idx = swipe_mouse_times.size() - 1
			var now = OS.get_ticks_msec()
			var cutoff = now - 100
			for i in range(swipe_mouse_times.size() - 1, -1, -1):
				if swipe_mouse_times[i] >= cutoff: idx = i
				else: break
			var flick_start = swipe_mouse_positions[idx]
			var flick_dur = min(0.3, (ev.position - flick_start).length() / 1000)
			if flick_dur > 0.0:
				var tween = Tween.new()
				add_child(tween)
				var delta = ev.position - flick_start
				var target = source - delta * flick_dur * 8.0
				#tween.interpolate_method(self, 'set_h_scroll', source.x, target.x, flick_dur, Tween.TRANS_QUAD, Tween.EASE_OUT)
				tween.interpolate_method(self, 'set_v_scroll', source.y, target.y, flick_dur, Tween.TRANS_QUAD, Tween.EASE_OUT)
				tween.interpolate_callback(tween, flick_dur, 'queue_free')
				tween.start()
			swiping = false
	elif swiping and ev is InputEventMouseMotion:
		var delta = ev.position - swipe_mouse_start
		set_h_scroll(swipe_start.x - delta.x)
		set_v_scroll(swipe_start.y - delta.y)
		swipe_mouse_times.append(OS.get_ticks_msec())
		swipe_mouse_positions.append(ev.position)
		emit_signal("finger_is_swiping")
		print ("finger is swiping")

It works quite nicely, but there are three hurdles I need help with:

  1. While swiping, the print “finger is swiping” is being printed all the time, many times, and I therefore assume that the signal “finger_is_swiping” is being emitted as often as well. How can I have the signal being emitted only once per swipe? (Or is that a problem at all?)

  2. How can I set a signal when my finger stops swiping (so when it’s lifted from the touchscreen)?

  3. I’d like the swipe-movement not to cause scrolling at all when touching a button in the list first. (My buttons work in a way so I can hold them down and make them not trigger by dragging the finger off… in such a case I’d like the list not to scroll despite the dragging.) I’d assume that I could have the button emit a signal which causes some “ignore swipe” or set the swipe speed to 0 maybe?
    EDIT:
    Yes, a simple swiping = false does this trick!

Any suggestions are much appreciated!

Swipe = false is pretty good but it will have to trigger the button pressed signal which
would set your swipe actually to false. And for the second problem of printing finger is swiping then it should be no problem unless it does cause a problem (like it continues to scroll down even if the fingers are not touching.

Dinoking | 2020-11-20 04:27

Thanks for your thoughts.
Good to know that such a repeated print doesn’t really harm! I just thought that there might be some sort of overload in the debugger maybe…

The scrolling works really nice. The only thing I just realized is that swipe-input is naturally being registered all over the screen, not just inside the window… no big deal really, but worth mentioning.

pferft | 2020-11-20 16:57

Hello Dinoking,

returning here after some time, I wonder if you would have any idea how to make the auto-scrolling stop with the next fingertip.
At this state, placing the finger down during the scrolling doesn’t stop but wait until the scrolling ends, only to then jump to the position where (when) the finger was put down again… which can be bewildering.

pferft | 2021-06-11 16:52

Hey sorry for the late reply, but I quite didn’t get the problem you are facing, could you explain better?

Dinoking | 2021-06-12 03:02

Oh no haste! I just popped in again spontaneously after stumbling over that “issue”… I’ll try to explain:

Imagine text in the scroll container, say, 100 lines. Swiping from the top down scolls down until the scrolling gradually comes to a halt after some time at about line 50… however, if I put my finger down again while that scrolling is running, say when line 30 is passing by, scrolling will still end at line 50, only to then jump back to line 30. The scrolling does not stop/interrupt on putting the finger down.

So I wonder how to “manually” (fingerually? ; ) stop the scrolling could be done.

pferft | 2021-06-12 15:07

Sorry bud , but for this question , I do not have an answer (maybe because I haven’t worked with scroll widget that much) . But hey! I have a more hackish way to create ui in Godot. You just have to :-

Create roote node as Node or Node2D.

Then add a sprite as a child.

In the texture or image , add the text that you wrote in the scroll widget instead.(you can use fancy text editors or photoshop even to make the sprite, it can or cannot be transperant as per your wish).

Then add another sprite as child with the same background color that of the editor ultimately the game.

Now place the sprite 2 on the top of the screen bounds or where you would like the text to be not seen or get clipped.

Same for the bottom one.

Add a kinematic body node.

Add the sprite 1 (the sprite which contains the text) as child of the kinematic body.

Now add the collision shapes of length of the whole screen, on the top and the bottom, where you want the text to stop scrolling (some precise try and error methods would do the job.) So now your sprite stops at the end of the scroll and at the start of the scroll just like scroll widget.

So for actual up down movement, add you swipe code (except for the functions you used of scroll widget or only the logic in sense)

Now you could bind the left mouse button or touch for immidiate stop in y velocity of the kinematic body and yo! Your problem is solved. And if you think this is weird, it is, because essentially this is how the scroll thing works .

Dinoking | 2021-06-13 09:48

Haha, alright, that’s creative!
(Isn’t everything about programming kind of weird!? ; )

Thanks for the input. I’ll show the code if I ever come to a working solution. Wish me luck!

pferft | 2021-06-13 15:34

Good luck ! And I sincerely hope you complete the project you are making. And if ever you create a commercial game, do post the link here! I’ll be active to check it out.

Dinoking | 2021-06-14 03:09

Thanks. Not quite a game really what I’m working on, but the mobile app you know you’ll want when it’s finished! ; )
It’s a long and rocky road… but with people like you helping me along the way I’m quite confident I’ll get there one fine day!

pferft | 2021-06-14 14:19

Yup surely you’ll finish it. And online forums are for help and not to have toxic behaviour towards the person asking for help. Anyway I’ll not disturb you by continuing the thread more and more, but as soon as the mobile app is available do send the link here. Once again, good luck mate!

Dinoking | 2021-06-14 17:04

Hope you don’t have too much bad experience in that regard on online forums…

I have to share here that I found a solution!

I added a var tween_running = false and put tween_running = true right under tween.start(). At the beginning of func _input(ev): I added

if ev.is_action_pressed("mousebuttonclick"):
  if tween_running:
    tween.stop(self)

The only issue is that the line add_child(tween) now throws the error

add_child: Can't add child to 'ScrollContainer', already has a parent 'ScrollContainer'.

It doesn’t crash the program at least. But still, I’d appreciate any idea how I could get rid of the error!

pferft | 2021-06-16 11:52

…and of course, minutes later I found the easy fix.

Place a “real” Tween Node as a child of the ScrollContainer.
Get rid of var tween = Tween.new() and add_child(tweenG).
Replace all tween with $Tween.

The whole thing works like a charm (for now :wink:

pferft | 2021-06-16 12:51

Wow , I am glad you found the solution, the authentic one. Yea I had some rough forum experience at the start, but it’s okay, there are always people like you who are nice and respectful to the OP. (By the way I think I shouldn’t have ranted all that).

Dinoking | 2021-06-16 17:25

I’m surprised I found a solution! ; D

Then again, to really finish this: with the help of golddotasksquestions on reddit I figured that the ScrollContainer actually features swipe-scrolling from the get-go, no code needed at all.
That’s fine for what it is, but the hard-coded “sensitivity” of the scrolling might be far too speedy for some and there’s currently no way to brake things down a little other than by coding as, for example, shown above. That’s why a request was proposed: ScrollContainer: Expose "max_scroll_speed" and "friction" properties to GDScript and the Inspector · Issue #2873 · godotengine/godot-proposals · GitHub
Fingers crossed that this will develop!

(And by the way, I think it’s perfectly alright to exchange thoughts about the forum on the forum.)

pferft | 2021-06-16 19:22

Umm, after reading the issue on GitHub , after you post the link, it seems like Godot handles the scroll widget just like the hackish way, of course behind the curtains. And it will develop because Godot is open sourced , and many pro coders work on it every day to improve it and minor to major bug fixes (I haven’t done it btw) and I am also a noob (you can say that)

Dinoking | 2021-06-17 10:01