extends Control class_name TouchScroll const DRAG_THRESHOLD_CM: float = 0.250 export var click_target_path: String export var scroll_target_path: String export var scroll_bar_get_method: String export var scroll_velocity_decaying_factor: float = 2.5 var pointer: Dictionary var anchor: float var offset: float var screen_dpcm: float var click_target: Control var scroll_target: Control var target_scroll_bar: VScrollBar # @DAM # Change this into an event-driven approach by changing the touch_scroll into a signal emitter and # the target scrollable control a signal consumer. This way, the signal consumer will behave as the # central node and will have the capability to detect when different touch_scroll sensors are # activated and stop previous inputs (if required). # Signal emitters should signal: # - when an input drag starts # - when an input drag stops # - when an input drags # - when an input clicks func _ready(): click_target = get_node(click_target_path) scroll_target = get_node(scroll_target_path) target_scroll_bar = scroll_target.call(scroll_bar_get_method) pointer = { index = -1, initial_position = Vector2.ZERO, current_position = Vector2.ZERO, relative_position = Vector2.ZERO, velocity = Vector2.ZERO, was_dragged = false, is_active = false, } screen_dpcm = float(OS.get_screen_dpi()) / 2.54 func _process(delta: float): # @DAM This only works for the stage... for the database this hides the touch_scroll after the first click. if click_target.has_focus(): mouse_filter = Control.MOUSE_FILTER_IGNORE else: mouse_filter = Control.MOUSE_FILTER_STOP if pointer.is_active: var dragged_distance: float = - (pointer.current_position.y - pointer.initial_position.y) offset = anchor + dragged_distance # self.target_scroll_bar.value = offset self.target_scroll_bar.value -= pointer.relative_position.y pointer.relative_position = Vector2.ZERO elif pointer.velocity.length() > 0.5: pointer.velocity *= clamp((1.0 - scroll_velocity_decaying_factor * delta), 0.0, 1.0) offset -= pointer.velocity.y * delta # self.target_scroll_bar.value = offset self.target_scroll_bar.value -= pointer.velocity.y * delta func _gui_input(event: InputEvent): # @DAM A bug on GODOT-3.X makes events from non-mouse-emulated pointers (index > 0) unreliable. if event is InputEventScreenTouch || event is InputEventScreenDrag: if event.index != 0: return if event is InputEventScreenTouch && (pointer.is_active == false || pointer.index == event.index): var touch := event as InputEventScreenTouch pointer.is_active = event.pressed pointer.current_position = get_global_mouse_position() # touch.position if pointer.is_active: pointer.index = touch.index pointer.initial_position = get_global_mouse_position() # touch.position anchor = self.target_scroll_bar.value else: if pointer.was_dragged == false: # Click detected. simulate_gui_click() pointer.index = -1 pointer.was_dragged = false if event is InputEventScreenDrag && event.index == pointer.index: var drag := event as InputEventScreenDrag pointer.current_position = get_global_mouse_position() # drag.position pointer.velocity = drag.speed pointer.relative_position = drag.relative if pointer.current_position.distance_to(pointer.initial_position) / screen_dpcm > DRAG_THRESHOLD_CM: pointer.was_dragged = true func simulate_gui_click(): var position := self.get_global_mouse_position() - click_target.rect_global_position var event_touch := InputEventScreenTouch.new() event_touch.index = 0 event_touch.position = position var event_mouse := InputEventMouseButton.new() event_mouse.button_index = BUTTON_LEFT event_mouse.button_mask = BUTTON_MASK_LEFT event_mouse.position = position event_mouse.pressed = true event_touch.pressed = true click_target._gui_input(event_mouse) click_target._gui_input(event_touch) click_target.grab_focus() event_mouse.pressed = false event_touch.pressed = false click_target._gui_input(event_mouse) click_target._gui_input(event_touch)