├── .github └── FUNDING.yml ├── .gitignore ├── CustomInputEvents ├── InputEventMultiScreenDrag.gd ├── InputEventMultiScreenLongPress.gd ├── InputEventMultiScreenSwipe.gd ├── InputEventMultiScreenTap.gd ├── InputEventScreenCancel.gd ├── InputEventScreenPinch.gd ├── InputEventScreenTwist.gd ├── InputEventSingleScreenDrag.gd ├── InputEventSingleScreenLongPress.gd ├── InputEventSingleScreenSwipe.gd ├── InputEventSingleScreenTap.gd └── InputEventSingleScreenTouch.gd ├── InputManager.gd ├── LICENSE ├── README.md ├── RawGesture.gd └── Util.gd /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['federico-ciuffardi'] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.paypal.com/donate?hosted_button_id=8Y6RJVLLSABGC'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventMultiScreenDrag.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventMultiScreenDrag 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var relative : Vector2 6 | var fingers : int 7 | var raw_gesture : RawGesture 8 | 9 | func _init(_raw_gesture : RawGesture = null, event : InputEventScreenDrag = null) -> void: 10 | raw_gesture = _raw_gesture 11 | if raw_gesture: 12 | fingers = raw_gesture.size() 13 | position = raw_gesture.centroid("drags", "position") 14 | relative = event.relative/fingers 15 | 16 | func as_string() -> String: 17 | return "position=" + str(position) + "|relative=" + str(relative) + "|fingers=" + str(fingers) 18 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventMultiScreenLongPress.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventMultiScreenLongPress 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var fingers : int 6 | var raw_gesture : RawGesture 7 | 8 | func _init(_raw_gesture : RawGesture = null) -> void: 9 | raw_gesture = _raw_gesture 10 | if raw_gesture: 11 | fingers = raw_gesture.size() 12 | position = raw_gesture.centroid("presses", "position") 13 | 14 | func as_string() -> String: 15 | return "position=" + str(position) + "|fingers=" + str(fingers) 16 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventMultiScreenSwipe.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventMultiScreenSwipe 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var relative : Vector2 6 | var fingers : int 7 | var raw_gesture : RawGesture 8 | 9 | func _init(_raw_gesture : RawGesture = null) -> void: 10 | raw_gesture = _raw_gesture 11 | if raw_gesture: 12 | fingers = raw_gesture.size() 13 | position = raw_gesture.centroid("presses", "position") 14 | relative = raw_gesture.centroid("releases", "position") - position 15 | 16 | func as_string() -> String: 17 | return "position=" + str(position) + "|relative=" + str(relative) + "|fingers=" + str(fingers) 18 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventMultiScreenTap.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventMultiScreenTap 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var positions : Array 6 | var fingers : int 7 | var raw_gesture : RawGesture 8 | 9 | func _init(_raw_gesture : RawGesture = null) -> void: 10 | raw_gesture = _raw_gesture 11 | if raw_gesture: 12 | fingers = raw_gesture.size() 13 | position = raw_gesture.centroid("presses", "position") 14 | positions = raw_gesture.get_property_array("presses", "position") 15 | 16 | func as_string() -> String: 17 | return "position=" + str(position) + "|fingers=" + str(fingers) 18 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventScreenCancel.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventScreenCancel 2 | extends InputEventAction 3 | 4 | var raw_gesture : RawGesture 5 | var event : InputEvent 6 | 7 | func _init(_raw_gesture : RawGesture, _event : InputEvent) -> void: 8 | raw_gesture = _raw_gesture 9 | event = _event 10 | 11 | func as_string() -> String: 12 | return "gesture canceled" 13 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventScreenPinch.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventScreenPinch 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var relative : float 6 | var distance : float 7 | var fingers : int 8 | var raw_gesture : RawGesture 9 | 10 | func _init(_raw_gesture : RawGesture = null, event : InputEventScreenDrag = null) -> void: 11 | raw_gesture = _raw_gesture 12 | if raw_gesture: 13 | fingers = raw_gesture.drags.size() 14 | position = raw_gesture.centroid("drags", "position") 15 | 16 | distance = 0 17 | for drag in raw_gesture.drags.values(): 18 | var centroid_relative_position = drag.position - position 19 | distance += centroid_relative_position.length() 20 | 21 | var centroid_relative_position = event.position - position 22 | relative = ((centroid_relative_position + event.relative).length() - centroid_relative_position.length()) 23 | 24 | 25 | func as_string() -> String: 26 | return "position=" + str(position) + "|relative=" + str(relative) +"|distance ="+str(distance) + "|fingers=" + str(fingers) 27 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventScreenTwist.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventScreenTwist 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var relative : float 6 | var fingers : int 7 | var raw_gesture : RawGesture 8 | 9 | func _init(_raw_gesture : RawGesture = null, event : InputEventScreenDrag = null) -> void: 10 | raw_gesture = _raw_gesture 11 | if raw_gesture: 12 | fingers = raw_gesture.drags.size() 13 | position = raw_gesture.centroid("drags", "position") 14 | 15 | var centroid_relative_position = event.position - position 16 | relative = centroid_relative_position.angle_to(centroid_relative_position + event.relative)/fingers 17 | 18 | func as_string() -> String: 19 | return "position=" + str(position) + "|relative=" + str(relative) + "|fingers=" + str(fingers) 20 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventSingleScreenDrag.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventSingleScreenDrag 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var relative : Vector2 6 | var raw_gesture : RawGesture 7 | 8 | func _init(_raw_gesture : RawGesture = null) -> void: 9 | raw_gesture = _raw_gesture 10 | if raw_gesture: 11 | var dragEvent = raw_gesture.drags.values()[0] 12 | position = dragEvent.position 13 | relative = dragEvent.relative 14 | 15 | func as_string(): 16 | return "position=" + str(position) + "|relative=" + str(relative) 17 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventSingleScreenLongPress.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventSingleScreenLongPress 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var raw_gesture : RawGesture 6 | 7 | func _init(_raw_gesture : RawGesture = null) -> void: 8 | raw_gesture = _raw_gesture 9 | if raw_gesture: 10 | if !raw_gesture.presses.has(0): 11 | print("RAW GESTURE:\n" + raw_gesture.as_text()) 12 | var linear_event_history = raw_gesture.get_linear_event_history() 13 | var history = "\nHISTORY:\n" 14 | for e in linear_event_history: 15 | if e is RawGesture.Drag: 16 | history += "D | " 17 | else: 18 | history += "T | " 19 | history += e.as_text() 20 | history +="\n" 21 | print(history) 22 | var error_msg="Hello! we are trying to fix this bug.\nTo help us please copy the output and comment it (attached as a file) in the following issue: https://github.com/Federico-Ciuffardi/GodotTouchInputManager/issues/20\nAlso, if you can, include in that comment what version of Godot you are using, what platform you are running on, and what you were doing when the error occurred.\nThanks!" 23 | print(error_msg) 24 | position = raw_gesture.presses.values()[0].position 25 | 26 | 27 | func as_string() -> String: 28 | return "position=" + str(position) 29 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventSingleScreenSwipe.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventSingleScreenSwipe 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var relative : Vector2 6 | var raw_gesture : RawGesture 7 | 8 | func _init(_raw_gesture : RawGesture = null) -> void: 9 | raw_gesture = _raw_gesture 10 | if raw_gesture: 11 | position = raw_gesture.presses[0].position 12 | relative = raw_gesture.releases[0].position - position 13 | 14 | 15 | func as_string() -> String: 16 | return "position=" + str(position) + "|relative=" + str(relative) 17 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventSingleScreenTap.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventSingleScreenTap 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var raw_gesture : RawGesture 6 | 7 | func _init(_raw_gesture : RawGesture = null) -> void: 8 | raw_gesture = _raw_gesture 9 | if raw_gesture: 10 | position = raw_gesture.presses.values()[0].position 11 | 12 | 13 | func as_string() -> String: 14 | return "position=" + str(position) 15 | -------------------------------------------------------------------------------- /CustomInputEvents/InputEventSingleScreenTouch.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventSingleScreenTouch 2 | extends InputEventAction 3 | 4 | var position : Vector2 5 | var canceled : bool 6 | var raw_gesture : RawGesture 7 | 8 | func _init(_raw_gesture : RawGesture = null) -> void: 9 | raw_gesture = _raw_gesture 10 | if raw_gesture: 11 | pressed = raw_gesture.releases.is_empty() 12 | if pressed: 13 | position = raw_gesture.presses.values()[0].position 14 | else: 15 | position = raw_gesture.releases.values()[0].position 16 | canceled = raw_gesture.size() > 1 17 | 18 | func as_string() -> String: 19 | return "position=" + str(position) + "|pressed=" + str(pressed) + "|canceled=" + str(canceled) 20 | -------------------------------------------------------------------------------- /InputManager.gd: -------------------------------------------------------------------------------- 1 | # warning-ignore-all:return_value_discarded 2 | # warning-ignore-all:unused_signal 3 | 4 | extends Node 5 | 6 | ########## 7 | # Config # 8 | ########## 9 | 10 | const DEFAULT_BINDIGS : bool = true 11 | 12 | const DEBUG : bool = false 13 | 14 | const DRAG_STARTUP_TIME : float = 0.02 15 | 16 | const FINGER_SIZE : float = 100.0 17 | 18 | const MULTI_FINGER_RELEASE_THRESHOLD : float = 0.1 19 | 20 | const TAP_TIME_LIMIT : float = 0.2 21 | const TAP_DISTANCE_LIMIT : float = 25.0 22 | 23 | const LONG_PRESS_TIME_THRESHOLD : float = 0.75 24 | const LONG_PRESS_DISTANCE_LIMIT : float = 25.0 25 | 26 | const SWIPE_TIME_LIMIT : float = 0.5 27 | const SWIPE_DISTANCE_THRESHOLD : float = 200.0 28 | 29 | ######### 30 | # CONST # 31 | ######### 32 | 33 | const Util : GDScript = preload("Util.gd") 34 | 35 | const swipe2dir : Dictionary = \ 36 | { 37 | "swipe_up" : Vector2.UP, 38 | "swipe_up_right" : Vector2.UP + Vector2.RIGHT, 39 | "swipe_right" : Vector2.RIGHT, 40 | "swipe_down_right" : Vector2.DOWN + Vector2.RIGHT, 41 | "swipe_down" : Vector2.DOWN, 42 | "swipe_down_left" : Vector2.DOWN + Vector2.LEFT, 43 | "swipe_left" : Vector2.LEFT, 44 | "swipe_up_left" : Vector2.UP + Vector2.LEFT 45 | } 46 | 47 | 48 | ########### 49 | # Signals # 50 | ########### 51 | 52 | signal touch 53 | signal drag 54 | signal single_tap 55 | signal single_touch 56 | signal single_drag 57 | signal single_swipe 58 | signal single_long_press 59 | signal multi_drag 60 | signal multi_tap 61 | signal multi_swipe 62 | signal multi_long_press 63 | signal pinch 64 | signal twist 65 | signal raw_gesture 66 | signal cancel 67 | signal any_gesture 68 | 69 | ######## 70 | # Enum # 71 | ######## 72 | 73 | enum Gesture {PINCH, MULTI_DRAG, TWIST, SINGLE_DRAG, NONE} 74 | 75 | ######## 76 | # Vars # 77 | ######## 78 | 79 | var raw_gesture_data : RawGesture = RawGesture.new() # Current raw_gesture 80 | 81 | var _mouse_event_press_position : Vector2 82 | var _mouse_event : int = Gesture.NONE 83 | 84 | 85 | var _drag_startup_timer : Timer = Timer.new() 86 | var _long_press_timer : Timer = Timer.new() 87 | 88 | var _single_touch_cancelled : bool = false 89 | var _single_drag_enabled : bool = false 90 | 91 | ############# 92 | # Functions # 93 | ############# 94 | 95 | func _ready() -> void: 96 | process_mode = Node.PROCESS_MODE_ALWAYS # Emit events even if the scene tree is paused 97 | 98 | _add_timer(_drag_startup_timer, "_on_drag_startup_timer_timeout") 99 | _add_timer(_long_press_timer, "_on_long_press_timer_timeout") 100 | 101 | if DEFAULT_BINDIGS: 102 | _set_default_action("multi_swipe_up" , _native_key_event(KEY_I)) 103 | _set_default_action("multi_swipe_up_right" , _native_key_event(KEY_O)) 104 | _set_default_action("multi_swipe_right" , _native_key_event(KEY_L)) 105 | _set_default_action("multi_swipe_down_right", _native_key_event(KEY_PERIOD)) 106 | _set_default_action("multi_swipe_down" , _native_key_event(KEY_COMMA)) 107 | _set_default_action("multi_swipe_down_left" , _native_key_event(KEY_M)) 108 | _set_default_action("multi_swipe_left" , _native_key_event(KEY_J)) 109 | _set_default_action("multi_swipe_up_left" , _native_key_event(KEY_U)) 110 | 111 | _set_default_action("single_swipe_up" , _native_key_event(KEY_W)) 112 | _set_default_action("single_swipe_up_right" , _native_key_event(KEY_E)) 113 | _set_default_action("single_swipe_right" , _native_key_event(KEY_D)) 114 | _set_default_action("single_swipe_down_right", _native_key_event(KEY_C)) 115 | _set_default_action("single_swipe_down" , _native_key_event(KEY_X)) 116 | _set_default_action("single_swipe_down_left" , _native_key_event(KEY_Z)) 117 | _set_default_action("single_swipe_left" , _native_key_event(KEY_A)) 118 | _set_default_action("single_swipe_up_left" , _native_key_event(KEY_Q)) 119 | 120 | # _set_default_action("single_touch" , _native_mouse_button_event(MOUSE_BUTTON_LEFT)) 121 | _set_default_action("multi_touch" , _native_mouse_button_event(MOUSE_BUTTON_MIDDLE)) 122 | # _set_default_action("pinch" , _native_mouse_button_event(MOUSE_BUTTON_RIGHT)) # TODO 123 | _set_default_action("pinch_outward" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_UP)) 124 | _set_default_action("pinch_inward" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_DOWN)) 125 | _set_default_action("twist" , _native_mouse_button_event(MOUSE_BUTTON_RIGHT)) 126 | # _set_default_action("twist_clockwise" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_UP)) # TODO 127 | # _set_default_action("twist_counterclockwise" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_DOWN)) # TODO 128 | 129 | func _unhandled_input(event : InputEvent) -> void: 130 | if event is InputEventScreenDrag: 131 | _handle_screen_drag(event) 132 | elif event is InputEventScreenTouch: 133 | _handle_screen_touch(event) 134 | elif event is InputEventMouseMotion: 135 | _handle_mouse_motion(event) 136 | else: 137 | _handle_action(event) 138 | 139 | func _handle_mouse_motion(event : InputEventMouseMotion) -> void: 140 | if raw_gesture_data.size() == 1 and _mouse_event == Gesture.SINGLE_DRAG: 141 | _emit("drag", _native_drag_event(0, event.position, event.relative, event.velocity)) 142 | elif raw_gesture_data.size() == 2 and _mouse_event == Gesture.MULTI_DRAG: 143 | var offset = Vector2(5,5) 144 | var e0 = _native_drag_event(0, event.position-offset, event.relative, event.velocity) 145 | raw_gesture_data._update_screen_drag(e0) 146 | var e1 = _native_drag_event(1, event.position+offset, event.relative, event.velocity) 147 | raw_gesture_data._update_screen_drag(e1) 148 | _emit("multi_drag", InputEventMultiScreenDrag.new(raw_gesture_data,e0)) 149 | _emit("multi_drag", InputEventMultiScreenDrag.new(raw_gesture_data,e1)) 150 | elif _mouse_event == Gesture.TWIST: 151 | var rel1 = event.position - _mouse_event_press_position 152 | var rel2 = rel1 + event.relative 153 | var twist_event = InputEventScreenTwist.new() 154 | twist_event.position = _mouse_event_press_position 155 | twist_event.relative = rel1.angle_to(rel2) 156 | twist_event.fingers = 2 157 | _emit("twist", twist_event) 158 | 159 | func _handle_screen_touch(event : InputEventScreenTouch) -> void: 160 | if event.index < 0: 161 | _emit("cancel", InputEventScreenCancel.new(raw_gesture_data, event)) 162 | _end_gesture() 163 | return 164 | 165 | # ignore orphaned touch release events 166 | if !event.pressed and not event.index in raw_gesture_data.presses: 167 | return 168 | 169 | raw_gesture_data._update_screen_touch(event) 170 | _emit("raw_gesture", raw_gesture_data) 171 | var index : int = event.index 172 | if event.pressed: 173 | if raw_gesture_data.size() == 1: # First and only touch 174 | _long_press_timer.start(LONG_PRESS_TIME_THRESHOLD) 175 | _single_touch_cancelled = false 176 | _emit("single_touch", InputEventSingleScreenTouch.new(raw_gesture_data)) 177 | elif !_single_touch_cancelled : 178 | _single_touch_cancelled = true 179 | _cancel_single_drag() 180 | _emit("single_touch", InputEventSingleScreenTouch.new(raw_gesture_data)) 181 | else: 182 | var fingers : int = raw_gesture_data.size() 183 | if index == 0: 184 | _emit("single_touch", InputEventSingleScreenTouch.new(raw_gesture_data)) 185 | if !_single_touch_cancelled: 186 | var distance : float = (raw_gesture_data.releases[0].position - raw_gesture_data.presses[0].position).length() 187 | if raw_gesture_data.elapsed_time < TAP_TIME_LIMIT and distance <= TAP_DISTANCE_LIMIT: 188 | _emit("single_tap", InputEventSingleScreenTap.new(raw_gesture_data)) 189 | if raw_gesture_data.elapsed_time < SWIPE_TIME_LIMIT and distance > SWIPE_DISTANCE_THRESHOLD: 190 | _emit("single_swipe", InputEventSingleScreenSwipe.new(raw_gesture_data)) 191 | if raw_gesture_data.active_touches == 0: # last finger released 192 | if _single_touch_cancelled: 193 | var distance : float = (raw_gesture_data.centroid("releases","position") - raw_gesture_data.centroid("presses","position")).length() 194 | if raw_gesture_data.elapsed_time < TAP_TIME_LIMIT and distance <= TAP_DISTANCE_LIMIT and\ 195 | raw_gesture_data.is_consistent(TAP_DISTANCE_LIMIT, FINGER_SIZE*fingers) and\ 196 | _released_together(raw_gesture_data, MULTI_FINGER_RELEASE_THRESHOLD): 197 | _emit("multi_tap", InputEventMultiScreenTap.new(raw_gesture_data)) 198 | if raw_gesture_data.elapsed_time < SWIPE_TIME_LIMIT and distance > SWIPE_DISTANCE_THRESHOLD and\ 199 | raw_gesture_data.is_consistent(FINGER_SIZE, FINGER_SIZE*fingers) and\ 200 | _released_together(raw_gesture_data, MULTI_FINGER_RELEASE_THRESHOLD): 201 | _emit("multi_swipe", InputEventMultiScreenSwipe.new(raw_gesture_data)) 202 | _end_gesture() 203 | _cancel_single_drag() 204 | 205 | func _handle_screen_drag(event : InputEventScreenDrag) -> void: 206 | if event.index < 0: 207 | _emit("cancel", InputEventScreenCancel.new(raw_gesture_data, event)) 208 | _end_gesture() 209 | return 210 | 211 | raw_gesture_data._update_screen_drag(event) 212 | _emit("raw_gesture", raw_gesture_data) 213 | if raw_gesture_data.drags.size() > 1: 214 | _cancel_single_drag() 215 | var gesture : int = _identify_gesture(raw_gesture_data) 216 | if gesture == Gesture.PINCH: 217 | _emit("pinch", InputEventScreenPinch.new(raw_gesture_data, event)) 218 | elif gesture == Gesture.MULTI_DRAG: 219 | _emit("multi_drag", InputEventMultiScreenDrag.new(raw_gesture_data, event)) 220 | elif gesture == Gesture.TWIST: 221 | _emit("twist",InputEventScreenTwist.new(raw_gesture_data, event)) 222 | else: 223 | if _single_drag_enabled: 224 | _emit("single_drag", InputEventSingleScreenDrag.new(raw_gesture_data)) 225 | else: 226 | if _drag_startup_timer.is_stopped(): _drag_startup_timer.start(DRAG_STARTUP_TIME) 227 | 228 | func _handle_action(event : InputEvent) -> void: 229 | if InputMap.has_action("single_touch") and (event.is_action_pressed("single_touch") or event.is_action_released("single_touch")): 230 | _emit("touch", _native_touch_event(0, get_viewport().get_mouse_position(), event.pressed)) 231 | if event.pressed: 232 | _mouse_event = Gesture.SINGLE_DRAG 233 | else: 234 | _mouse_event = Gesture.NONE 235 | elif InputMap.has_action("multi_touch") and (event.is_action_pressed("multi_touch") or event.is_action_released("multi_touch")): 236 | _emit("touch", _native_touch_event(0, get_viewport().get_mouse_position(), event.pressed)) 237 | _emit("touch", _native_touch_event(1, get_viewport().get_mouse_position(), event.pressed)) 238 | if event.pressed: 239 | _mouse_event = Gesture.MULTI_DRAG 240 | else: 241 | _mouse_event = Gesture.NONE 242 | elif InputMap.has_action("twist") and (event.is_action_pressed("twist") or event.is_action_released("twist")): 243 | _mouse_event_press_position = get_viewport().get_mouse_position() 244 | if event.pressed: 245 | _mouse_event = Gesture.TWIST 246 | else: 247 | _mouse_event = Gesture.NONE 248 | elif (InputMap.has_action("pinch_outward") and event.is_action_pressed("pinch_outward")) or (InputMap.has_action("pinch_inward") and event.is_action_pressed("pinch_inward")): 249 | var pinch_event = InputEventScreenPinch.new() 250 | pinch_event.fingers = 2 251 | pinch_event.position = get_viewport().get_mouse_position() 252 | pinch_event.distance = 400 253 | pinch_event.relative = 40 254 | if event.is_action_pressed("pinch_inward"): 255 | pinch_event.relative *= -1 256 | _emit("pinch", pinch_event) 257 | else: 258 | var swipe_emulation_dir : Vector2 = Vector2.ZERO 259 | var is_single_swipe : bool 260 | for swipe in swipe2dir: 261 | var dir = swipe2dir[swipe] 262 | if InputMap.has_action("single_"+swipe) and event.is_action_pressed("single_"+swipe): 263 | swipe_emulation_dir = dir 264 | is_single_swipe = true 265 | break 266 | if InputMap.has_action("multi_"+swipe) and event.is_action_pressed("multi_"+swipe): 267 | swipe_emulation_dir = dir 268 | is_single_swipe = false 269 | break 270 | 271 | if swipe_emulation_dir != Vector2.ZERO: 272 | var swipe_event 273 | if is_single_swipe: 274 | swipe_event = InputEventSingleScreenSwipe.new() 275 | else: 276 | swipe_event = InputEventMultiScreenSwipe.new() 277 | swipe_event.fingers = 2 278 | swipe_event.position = get_viewport().get_mouse_position() 279 | swipe_event.relative = swipe_emulation_dir*SWIPE_DISTANCE_THRESHOLD*2 280 | if is_single_swipe: 281 | _emit("single_swipe", swipe_event) 282 | else: 283 | _emit("multi_swipe", swipe_event) 284 | 285 | # Emits signal sig with the specified args 286 | func _emit(sig : String, val : InputEvent) -> void: 287 | if DEBUG: print(val.as_string()) 288 | emit_signal("any_gesture", sig, val) 289 | emit_signal(sig, val) 290 | Input.parse_input_event(val) 291 | 292 | 293 | # Disables drag and stops the drag enabling timer 294 | func _cancel_single_drag() -> void: 295 | _single_drag_enabled = false 296 | _drag_startup_timer.stop() 297 | 298 | func _released_together(_raw_gesture_data : RawGesture, threshold : float) -> bool: 299 | _raw_gesture_data = _raw_gesture_data.rollback_relative(threshold)[0] 300 | return _raw_gesture_data.size() == _raw_gesture_data.active_touches 301 | 302 | # Checks if the gesture is pinch 303 | func _identify_gesture(_raw_gesture_data : RawGesture) -> int: 304 | var center : Vector2 = _raw_gesture_data.centroid("drags","position") 305 | 306 | var sector : int = -1 307 | for e in _raw_gesture_data.drags.values(): 308 | var adjusted_position : Vector2 = center - e.position 309 | var raw_angle : float = fmod(adjusted_position.angle_to(e.relative) + (PI/4), TAU) 310 | var adjusted_angle : float = raw_angle if raw_angle >= 0 else raw_angle + TAU 311 | var e_sector : int = int(floor(adjusted_angle / (PI/2))) 312 | if sector == -1: 313 | sector = e_sector 314 | elif sector != e_sector: 315 | return Gesture.MULTI_DRAG 316 | 317 | if sector == 0 or sector == 2: 318 | return Gesture.PINCH 319 | else: # sector == 1 or sector == 3: 320 | return Gesture.TWIST 321 | 322 | func _on_drag_startup_timer_timeout() -> void: 323 | _single_drag_enabled = raw_gesture_data.drags.size() == 1 324 | 325 | func _on_long_press_timer_timeout() -> void: 326 | var ends_centroid : Vector2 = Util.centroid(raw_gesture_data.get_ends().values()) 327 | var starts_centroid : Vector2 = raw_gesture_data.centroid("presses", "position") 328 | var distance : float = (ends_centroid - starts_centroid).length() 329 | 330 | if raw_gesture_data.releases.is_empty() and distance <= LONG_PRESS_DISTANCE_LIMIT and\ 331 | raw_gesture_data.is_consistent(LONG_PRESS_DISTANCE_LIMIT, FINGER_SIZE*raw_gesture_data.size()): 332 | if _single_touch_cancelled: 333 | _emit("multi_long_press", InputEventMultiScreenLongPress.new(raw_gesture_data)) 334 | else: 335 | _emit("single_long_press", InputEventSingleScreenLongPress.new(raw_gesture_data)) 336 | 337 | 338 | func _end_gesture() -> void: 339 | _single_drag_enabled = false 340 | _long_press_timer.stop() 341 | raw_gesture_data = RawGesture.new() 342 | 343 | # create a native touch event 344 | func _native_touch_event(index : int, position : Vector2, pressed : bool) -> InputEventScreenTouch: 345 | var native_touch : InputEventScreenTouch = InputEventScreenTouch.new() 346 | native_touch.index = index 347 | native_touch.position = position 348 | native_touch.pressed = pressed 349 | return native_touch 350 | 351 | # create a native touch event 352 | func _native_drag_event(index : int, position : Vector2, relative : Vector2, velocity : Vector2) -> InputEventScreenDrag: 353 | var native_drag : InputEventScreenDrag = InputEventScreenDrag.new() 354 | native_drag.index = index 355 | native_drag.position = position 356 | native_drag.relative = relative 357 | native_drag.velocity = velocity 358 | return native_drag 359 | 360 | func _native_mouse_button_event(button : int) -> InputEventMouseButton: 361 | var ev = InputEventMouseButton.new() 362 | ev.button_index = button 363 | return ev 364 | 365 | func _native_key_event(key : int) -> InputEventKey: 366 | var ev = InputEventKey.new() 367 | ev.keycode = key 368 | return ev 369 | 370 | func _set_default_action(action : String, event : InputEvent) -> void: 371 | if !InputMap.has_action(action): 372 | InputMap.add_action(action) 373 | InputMap.action_add_event(action,event) 374 | 375 | # Macro to add a timer and connect it's timeout to func_name 376 | func _add_timer(timer : Timer, func_name : String) -> void: 377 | timer.one_shot = true 378 | if func_name: 379 | timer.connect("timeout", Callable(self, func_name)) 380 | self.add_child(timer) 381 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Federico Ciuffardi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Godot Touch Input Manager 4 | Godot Touch Input Manager (GDTIM) is an asset that improves touch input support (includes [new gestures](#supported-gestures)) in the Godot game engine. You just need to autoload a script and it will start analyzing the touch input. When a gesture is detected a Custom Input Event corresponding to the detected gesture will be created and [fed up](https://docs.godotengine.org/en/stable/classes/class_input.html#class-input-method-parse-input-event) to the Godot built in Input Event system so it triggers functions like [`_input(InputEvent event)`](https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-method-input). There is also a signal for each gesture if you prefer using signals to the aforementioned. 5 | 6 | There are two active PRs that add some GDTIM gestures as native Godot events, one for [version 3.x](https://github.com/godotengine/godot/pull/37754) and one for [version 4.x](https://github.com/godotengine/godot/pull/39055), if you are interested, please show your support there. 7 | 8 | ## Table of contents 9 | * [How to use](#how-to-use) 10 | * [Examples](#examples) 11 | * [Documentation](#documentation) 12 | * [FAQ](#faq) 13 | 14 | ## How to use 15 | * Download the latest release from https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/releases 16 | * Extract the downloaded *.zip* file somewhere in you project 17 | * Locate the extracted `InputManager.gd`, and [Autoload](https://docs.godotengine.org/en/3.4/tutorials/scripting/singletons_autoload.html) it. 18 | * Done! Now you can use GDTIM [signals and Custom Input Events](#supported-gestures). 19 | 20 | ## Examples 21 | ### [GodotTouchInputManager-Demo](https://github.com/Federico-Ciuffardi/GodotTouchInputManager-Demo) 22 | ![Demo](https://media.giphy.com/media/wnMStTBUdhQcnXLXpB/giphy.gif) 23 | ### [GestureControlledCamera2D](https://github.com/Federico-Ciuffardi/GestureControlledCamera2D) 24 | ![Demo](https://media.giphy.com/media/Xzdynnlx4XAqndgVe0/giphy.gif) 25 | 26 | ## Documentation 27 | 28 | * [Supported gestures](#supported-gestures) 29 | * [Gesture emulation](#gesture-emulation) 30 | * [Configuration](#configuration) 31 | 32 | ### Supported gestures 33 | 34 | | Gesture name | Signal | Custom input event / Signal arg | Description | 35 | |----------------------------|-------------------|---------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| 36 | | Single finger touch | single_touch | [InputEventSingleScreenTouch](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventSingleScreenTouch) | Touch with a single finger | 37 | | Single finger tap | single_tap | [InputEventSingleScreenTap](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventSingleScreenTap) | Fast press and release with a single finger | 38 | | Single finger long press | single_long_press | [InputEventSingleScreenLongPress](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventSingleScreenLongPress) | Press and hold with a single finger | 39 | | Single finger drag | single_drag | [InputEventSingleScreenDrag](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventSingleScreenDrag) | Drag with a single finger | 40 | | Single finger swipe | single_swipe | [InputEventSingleScreenSwipe](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventSingleScreenSwipe) | Fast drag and release with a single finger | 41 | | Multiple finger tap | multi_tap | [InputEventMultiScreenTap](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventMultiScreenTap) | Fast press and release with multiple fingers | 42 | | Multiple finger long press | multi_long_press | [InputEventMultiScreenLongPress](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventMultiScreenLongPress) | Press and hold with multiple fingers | 43 | | Multiple finger drag | multi_drag | [InputEventMultiScreenDrag](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventMultiScreenDrag) | Drag with multiple fingers (same direction) | 44 | | Multiple finger swipe | multi_swipe | [InputEventMultiScreenTap](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventMultiScreenTap) | Fast drag and release with multiple fingers | 45 | | Pinch | pinch | [InputEventScreenPinch](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventScreenPinch) | Drag with multiple fingers (inward/outward) | 46 | | Twist | twist | [InputEventScreenTwist](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/InputEventScreenTwist) | Drag with multiple fingers (rotate) | 47 | | Raw gesture | raw_gesture | [RawGesture](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/wiki/RawGesture) | Raw gesture state 48 | 49 | When one of these gestures is detected a Custom Input Event corresponding to the detected gesture will be created and [fed up](https://docs.godotengine.org/en/stable/classes/class_input.html#class-input-method-parse-input-event) to the Godot built in Input Event system so it triggers functions like [`_input(InputEvent event)`](https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-method-input). 50 | 51 | ### Gesture emulation 52 | 53 | The gestures can be triggered by named [input actions](https://docs.godotengine.org/en/stable/tutorials/inputs/input_examples.html#inputmap) with specific names. If the input 54 | action does not exists there is a default event that will trigger the gesture. 55 | 56 | The following table shows the default event and the names of the input actions 57 | that will trigger each of the gestures that can be emulated. 58 | 59 | | Gesture name | Input action name | Default event | 60 | |------------------------------------|-------------------------|---------------| 61 | | Single touch | single_touch | **\*** | 62 | | Multiple touch (2 fingers) | multi_touch | Middle click | 63 | | Pinch (outward) | pinch_outward | Scroll up | 64 | | Pinch (inward) | pinch_inward | Scroll down | 65 | | Twist | twist | Right click | 66 | | Single finger swipe (up) | single_swipe_up | w | 67 | | Single finger swipe (up-right) | single_swipe_up_right | e | 68 | | Single finger swipe (right) | single_swipe_right | d | 69 | | Single finger swipe (down-right) | single_swipe_down_right | c | 70 | | Single finger swipe (down) | single_swipe_down | x | 71 | | Single finger swipe (down-left) | single_swipe_down_left | z | 72 | | Single finger swipe (left) | single_swipe_left | a | 73 | | Single finger swipe (left-up) | single_swipe_up_left | q | 74 | | Multiple finger swipe (up) | multi_swipe_up | i | 75 | | Multiple finger swipe (up-right) | multi_swipe_up_right | o | 76 | | Multiple finger swipe (right) | multi_swipe_right | l | 77 | | Multiple finger swipe (down-right) | multi_swipe_down_right | . | 78 | | Multiple finger swipe (down) | multi_swipe_down | , | 79 | | Multiple finger swipe (down-left) | multi_swipe_down_left | m | 80 | | Multiple finger swipe (left) | multi_swipe_left | j | 81 | | Multiple finger swipe (left-up) | multi_swipe_up_left | u | 82 | 83 | **\*** There are two options to enable single finger gestures: 84 | 1. Go to **Project > Project Settings > General > Input Devices > Pointing** 85 | and turn on *Emulate Touch From Mouse* to emulate a single finger touch with 86 | the left click. 87 | 2. Go to **Project > Project Settings > General > Input Devices > Pointing** 88 | and turn off both *Emulate Touch From Mouse* and *Emulate Mouse From Touch*. 89 | Then set an input action called `single_touch`. 90 | 91 | ## Configuration 92 | 93 | These are located in the first lines of [InputManager.gd](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/blob/master/InputManager.gd), to change them modify the 94 | values on the script. 95 | 96 | | Name | Default value | Description | 97 | |--------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 98 | | DEFAULT_BINDIGS | true | Enable or disable default events for [gesture emulation](#gesture-emulation) | 99 | | DEBUG | false | Enable or disable debug information | 100 | | DRAG_STARTUP_TIME | 0.2 | Seconds from the first native drag event to the first [single finger drag](#gestures-supported) custom event | 101 | | FINGER_SIZE | 100.0 | The distance between the fingers must be less than `fingers*FINGER_SIZE` pixels for the [multiple finger tap](#supported-gestures) and [multiple finger swipe](#supported-gestures) gestures to be recognized. Setting it to `INF` removes this restriction. | 102 | | MULTI_FINGER_RELEASE_THRESHOLD | 0.1 | All fingers must be released within `MULTI_FINGER_REALEASE_THRESHOLD` seconds before the gesture ends for the [multiple finger tap](#gestures-supported) and [multiple finger swipe](#gestures-supported) gestures to be recognized | 103 | | TAP_TIME_LIMIT | 0.2 | The time between the first press and the last release must be less than `TAP_TIME_LIMIT` seconds for the [single finger tap](#supported-gestures) and [multiple finger tap](#supported-gestures) gestures to be recognized | 104 | | TAP_DISTANCE_LIMIT | 25.0 | The centroid of the finger presses must differ less than `TAP_DISTANCE_LIMIT` pixels from the centroid of the finger releases for the [single finger tap](#supported-gestures) and [multiple finger tap](#supported-gestures) gestures to be recognized. | 105 | | SWIPE_TIME_LIMIT | 0.5 | The time between the first press and the last release must be less than `SWIPE_TIME_LIMIT` seconds for the [single finger swipe](#supported-gestures) and [multiple finger swipe](#supported-gestures) gestures to be recognized. | 106 | | SWIPE_DISTANCE_THRESHOLD | 200.0 | The centroid of the finger presses must differ by more than `SWIPE_DISTANCE_THRESHOLD` pixels from the centroid of the finger releases for the [single finger swipe](#supported-gestures) and [multiple finger swipe](#supported-gestures) gestures to be recognized. | 107 | | LONG_PRESS_TIME_THRESHOLD | 0.75 | The fingers must press for `LONG_PRESS_TIME_THRESHOLD` seconds for [single-finger long press](#gestures-supported) and [multi-finger long press](#gestures-supported) gestures to be recognized. | 108 | | LONG_PRESS_DISTANCE_LIMIT | 25.0 | The centroid of the finger presses must differ less than `LONG_PRESS_DISTANCE_LIMIT` pixels from the centroid of the fingers last positions for the [single finger long press](#supported-gestures) and [multiple finger long press](#supported-gestures) gestures to be recognized. | 109 | 110 | ## FAQ 111 | ### How can I get GDTIM to work when using control nodes? 112 | 113 | By default, the control nodes consume events and therefore GDTIM cannot analyze them. To prevent this, set `Mouse>Filter` to `Ignore` on control nodes as needed. 114 | 115 | ![image](https://user-images.githubusercontent.com/45585143/235382152-1c99f7eb-eed3-4f96-b1b2-ba0a899d5225.png) 116 | 117 | For more information see the [documentation](https://docs.godotengine.org/en/stable/classes/class_control.html#enum-control-mousefilter). 118 | 119 | ### GDTIM events don't trigger collisions, is there a way to fix it? 120 | 121 | Custom input events do not trigger collisions, at the moment the solution is to manually check for collisions between shapes and events. For more information and ideas on how to do this see [this issue](https://github.com/Federico-Ciuffardi/GodotTouchInputManager/issues/16). 122 | 123 | ## Versioning 124 | Using [SemVer](http://semver.org/) for versioning. For the versions available, see the [releases](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/releases). 125 | 126 | ## Authors 127 | * Federico Ciuffardi 128 | 129 | Feel free to append yourself here if you've made contributions. 130 | 131 | ## Note 132 | Thank you for checking out this repository, you can send all your questions and comments to Federico.Ciuffardi@outlook.com. 133 | 134 | If you are willing to contribute in any way, please contact me. 135 | -------------------------------------------------------------------------------- /RawGesture.gd: -------------------------------------------------------------------------------- 1 | # warning-ignore-all:return_value_discarded 2 | 3 | extends InputEventAction 4 | class_name RawGesture 5 | 6 | ######### 7 | # Const # 8 | ######### 9 | 10 | const Util : GDScript = preload("Util.gd") 11 | 12 | ########### 13 | # Classes # 14 | ########### 15 | 16 | class Event: 17 | var time : float = -1 # (secs) 18 | var index : int = -1 19 | func as_string() -> String: 20 | return "ind: " + str(index) + " | time: " + str(time) 21 | 22 | class Touch: 23 | extends Event 24 | var position : Vector2 = Vector2.ZERO 25 | var pressed : bool 26 | func as_string() -> String: 27 | return super.as_string() + " | pos: " + str(position) + " | pressed: " + str(pressed) 28 | 29 | 30 | class Drag: 31 | extends Event 32 | var position : Vector2 = Vector2.ZERO 33 | var relative : Vector2 = Vector2.ZERO 34 | var velocity : Vector2 = Vector2.ZERO 35 | 36 | func as_string() -> String: 37 | return super.as_string() + " | pos: " + str(position) + " | relative: " + str(relative) 38 | 39 | 40 | ############# 41 | # Variables # 42 | ############# 43 | 44 | var presses : Dictionary # Touch 45 | var releases : Dictionary # Touch 46 | var drags : Dictionary # Drag 47 | var history : Dictionary # Array of events 48 | 49 | var active_touches : int = 0 50 | 51 | var start_time : float = -1 # (secs) 52 | var elapsed_time : float = -1 # (secs) 53 | 54 | ############# 55 | # Functions # 56 | ############# 57 | 58 | func size() -> int: 59 | return presses.size() 60 | 61 | func get_property_array(events_name : String , property_name : String): 62 | var arr : Array = get(events_name).values() 63 | arr = Util.map_callv(arr , "get", [property_name]) 64 | return arr 65 | 66 | func centroid(events_name : String , property_name : String): 67 | var arr : Array = get_property_array(events_name, property_name) 68 | return Util.centroid(arr) 69 | 70 | func get_ends() -> Dictionary: 71 | var ends : Dictionary = {} 72 | 73 | for i in presses: 74 | ends[i] = presses[i].position 75 | 76 | for i in drags: 77 | ends[i] = drags[i].position 78 | 79 | for i in releases: 80 | ends[i] = releases[i].position 81 | 82 | return ends 83 | 84 | # Check for gesture consistency 85 | func is_consistent(diff_limit : float, length_limit : float = -1) -> bool: 86 | if length_limit == -1: length_limit = length_limit 87 | 88 | var ends : Dictionary = get_ends() 89 | 90 | var ends_centroid : Vector2 = Util.centroid(ends.values()) 91 | var starts_centroid : Vector2 = centroid("presses", "position") 92 | 93 | var valid : bool = true 94 | for i in ends: 95 | var start_relative_position : Vector2 = presses[i].position - starts_centroid 96 | var end_relative_position : Vector2 = ends[i] - ends_centroid 97 | 98 | valid = start_relative_position.length() < length_limit and \ 99 | end_relative_position.length() < length_limit and \ 100 | (end_relative_position - start_relative_position).length() < diff_limit 101 | 102 | if !valid: 103 | break 104 | 105 | return valid 106 | 107 | func rollback_relative(time : float) -> Array: 108 | return rollback_absolute(start_time+elapsed_time - time) 109 | 110 | func rollback_absolute(time : float) -> Array: 111 | var discarded_events : Array = [] 112 | var rg : RawGesture = copy() 113 | 114 | var latest_event_id : Array = rg.latest_event_id(time) 115 | while !latest_event_id.is_empty(): 116 | var latest_index : int = latest_event_id[0] 117 | var latest_type : String = latest_event_id[1] 118 | var latest_event = rg.history[latest_index][latest_type].pop_back() 119 | discarded_events.append(latest_event) 120 | if latest_type == "presses": 121 | rg.active_touches -= 1 122 | elif latest_type == "releases": 123 | rg.active_touches += 1 124 | if rg.history[latest_index][latest_type].is_empty(): 125 | rg.history[latest_index].erase(latest_type) 126 | if rg.history[latest_index].is_empty(): 127 | rg.history.erase(latest_index) 128 | latest_event_id = rg.latest_event_id(time) 129 | 130 | for index in rg.presses.keys(): 131 | if rg.history.has(index): 132 | if rg.history[index].has("presses"): 133 | var presses_history: Array = rg.history[index]["presses"] 134 | rg.presses[index] = presses_history.back() 135 | else: 136 | rg.presses.erase(index) 137 | 138 | if rg.history[index].has("releases"): 139 | var releases_history : Array = rg.history[index]["releases"] 140 | # !releases_history.empty() -> rg.presses.has(index) (touch precedes a release) 141 | if releases_history.back().time < rg.presses[index].time: 142 | rg.releases.erase(index) 143 | else: 144 | rg.releases[index] = releases_history.back() 145 | else: 146 | rg.releases.erase(index) 147 | 148 | if rg.history[index].has("drags"): 149 | var drags_history : Array = rg.history[index]["drags"] 150 | # rg.releases.has(index) -> rg.releases[index].time >= rg.presses[index].time -> 151 | # rg.releases[index] >= drags_history.back().time (drag should needs a new touch after the release) 152 | if rg.releases.has(index): 153 | rg.drags.erase(index) 154 | else: 155 | rg.drags[index] = drags_history.back() 156 | else: 157 | rg.drags.erase(index) 158 | else: 159 | rg.presses.erase(index) 160 | rg.releases.erase(index) 161 | rg.drags.erase(index) 162 | 163 | return [rg, discarded_events] 164 | 165 | func get_linear_event_history(): 166 | return rollback_absolute(0)[1] 167 | 168 | func copy() -> RawGesture: 169 | var rg : RawGesture = get_script().new() 170 | rg.presses = presses.duplicate(true) 171 | rg.releases = releases.duplicate(true) 172 | rg.drags = drags.duplicate(true) 173 | rg.history = history.duplicate(true) 174 | rg.active_touches = active_touches 175 | rg.start_time = start_time 176 | rg.elapsed_time = elapsed_time 177 | return rg 178 | 179 | func latest_event_id(latest_time : float = -1) -> Array: 180 | var res : Array = [] 181 | for index in history: 182 | for type in history[index]: 183 | var event_time = history[index][type].back().time 184 | if event_time >= latest_time: 185 | res = [index, type] 186 | latest_time = event_time 187 | return res 188 | 189 | func as_string() -> String: 190 | var txt = "presses: " 191 | for e in presses.values(): 192 | txt += "\n" + e.as_string() 193 | txt += "\ndrags: " 194 | for e in drags.values(): 195 | txt += "\n" + e.as_string() 196 | txt += "\nreleases: " 197 | for e in releases.values(): 198 | txt += "\n" + e.as_string() 199 | return txt 200 | 201 | func _update_screen_drag(event : InputEventScreenDrag, time : float = -1) -> void: 202 | if time < 0: 203 | time = Util.now() 204 | var drag : Drag = Drag.new() 205 | drag.position = event.position 206 | drag.relative = event.relative 207 | drag.velocity = event.velocity 208 | drag.index = event.index 209 | drag.time = time 210 | _add_history(event.index, "drags", drag) 211 | drags[event.index] = drag 212 | elapsed_time = time - start_time 213 | 214 | func _update_screen_touch(event : InputEventScreenTouch, time : float = -1) -> void: 215 | if time < 0: 216 | time = Util.now() 217 | var touch : Touch = Touch.new() 218 | touch.position = event.position 219 | touch.pressed = event.pressed 220 | touch.index = event.index 221 | touch.time = time 222 | if event.pressed: 223 | _add_history(event.index, "presses", touch) 224 | presses[event.index] = touch 225 | active_touches += 1 226 | releases.erase(event.index) 227 | drags.erase(event.index) 228 | if active_touches == 1: 229 | start_time = time 230 | else: 231 | _add_history(event.index, "releases", touch) 232 | releases[event.index] = touch 233 | active_touches -= 1 234 | drags.erase(event.index) 235 | elapsed_time = time - start_time 236 | 237 | func _add_history(index : int, type : String, value) -> void: 238 | if !history.has(index): 239 | history[index] = {} 240 | if !history[index].has(type): 241 | history[index][type] = [] 242 | history[index][type].append(value) 243 | -------------------------------------------------------------------------------- /Util.gd: -------------------------------------------------------------------------------- 1 | const SEC_IN_USEC : int = 1000000 2 | 3 | static func map_callv(i_es : Array, f : String, vargs : Array) -> Array: 4 | var o_es : Array = [] 5 | for e in i_es: o_es.append(e.callv(f,vargs)) 6 | return o_es 7 | 8 | # Precondition: 9 | # * !arr.empty() 10 | static func centroid(es : Array): 11 | var sum = es[0] 12 | for i in range(1,es.size()): 13 | sum += es[i] 14 | return sum / es.size() 15 | 16 | static func now() -> float: 17 | return float(Time.get_ticks_usec())/SEC_IN_USEC 18 | --------------------------------------------------------------------------------