├── .gitattributes ├── Sprite.jpg ├── default_env.tres ├── GCC2D.tscn ├── .gitignore ├── GDTIM-4.x-v2_1_2 ├── CustomInputEvents │ ├── InputEventScreenCancel.gd │ ├── InputEventSingleScreenTap.gd │ ├── InputEventMultiScreenTap.gd │ ├── InputEventMultiScreenLongPress.gd │ ├── InputEventSingleScreenDrag.gd │ ├── InputEventSingleScreenSwipe.gd │ ├── InputEventMultiScreenDrag.gd │ ├── InputEventMultiScreenSwipe.gd │ ├── InputEventSingleScreenTouch.gd │ ├── InputEventScreenTwist.gd │ ├── InputEventScreenPinch.gd │ └── InputEventSingleScreenLongPress.gd ├── Util.gd ├── LICENSE ├── RawGesture.gd ├── README.md └── InputManager.gd ├── Demo.tscn ├── .github └── FUNDING.yml ├── project.godot ├── Sprite.jpg.import ├── LICENSE ├── README.md ├── GCC2D.gd └── export_presets.cfg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /Sprite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Federico-Ciuffardi/GestureControlledCamera2D/HEAD/Sprite.jpg -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=3 uid="uid://bf6o1x6iugov8"] 2 | 3 | [sub_resource type="Sky" id="1"] 4 | 5 | [resource] 6 | background_mode = 2 7 | sky = SubResource("1") 8 | -------------------------------------------------------------------------------- /GCC2D.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://GCC2D.gd" type="Script" id=1] 4 | 5 | [node name="GCC2D" type="Camera2D"] 6 | ignore_rotation =true # reversed "rotating" for Camera2D 7 | current = true 8 | script = ExtResource( 1 ) 9 | movement_gesture = 1 10 | -------------------------------------------------------------------------------- /.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 | 17 | # System/tool-specific ignores 18 | .directory 19 | *~ 20 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/CustomInputEvents/InputEventMultiScreenTap.gd: -------------------------------------------------------------------------------- 1 | class_name InputEventMultiScreenTap 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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /Demo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bywicuxckrdfc"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cvjxijew13y74" path="res://Sprite.jpg" id="1_dy1uf"] 4 | [ext_resource type="PackedScene" path="res://GCC2D.tscn" id="2"] 5 | 6 | [node name="Node2D" type="Node2D"] 7 | 8 | [node name="Sprite2D" type="Sprite2D" parent="."] 9 | texture = ExtResource("1_dy1uf") 10 | 11 | [node name="GCC2D" parent="." instance=ExtResource("2")] 12 | ignore_rotation = false 13 | limit_left = -1250 14 | limit_top = -600 15 | limit_right = 1250 16 | limit_bottom = 600 17 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="GestureControlledCamera2D" 14 | run/main_scene="res://Demo.tscn" 15 | config/features=PackedStringArray("4.0") 16 | 17 | [autoload] 18 | 19 | InputManager="*res://GDTIM-4.x-v2_1_2/InputManager.gd" 20 | 21 | [input_devices] 22 | 23 | pointing/emulate_touch_from_mouse=true 24 | 25 | [rendering] 26 | 27 | textures/vram_compression/import_etc2_astc=true 28 | environment/defaults/default_environment="res://default_env.tres" 29 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /Sprite.jpg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cvjxijew13y74" 6 | path="res://.godot/imported/Sprite.jpg-306cbf6b765bbdaedd3643d633e176f2.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Sprite.jpg" 14 | dest_files=["res://.godot/imported/Sprite.jpg-306cbf6b765bbdaedd3643d633e176f2.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # GestureControlledCamera2D 4 | A Camera2D node controlled through gestures. It's also an example of how to use the [Godot Touch Input Manager](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager). 5 | 6 | ## Demo 7 | ![Demo](https://media.giphy.com/media/Xzdynnlx4XAqndgVe0/giphy.gif) 8 | 9 | ## How to use 10 | ### 1 - Seting up [Godot Touch Input Manager](https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager) 11 | * Dowload the latest release from https://github.com/Federico-Ciuffardi/Godot-Touch-Input-Manager/releases 12 | * Extract the downloaded *.zip* file somewhere in you project 13 | * Locate the extracted `InputManager.gd`, and [Autoload](https://docs.godotengine.org/en/3.4/tutorials/scripting/singletons_autoload.html) it. 14 | 15 | ### 2 - Using GestureControlledCamera2D 16 | * Dowload the latest release from https://github.com/Federico-Ciuffardi/GestureControlledCamera2D/releases 17 | * Extract the downloaded *.zip* file somewhere in you project 18 | * Add the extracted GestureControlledCamera2D node (GCC2D.tscn) to the scene and make sure to set `Current` to `On` 19 | * Customize the [script variables](#script-variables) to your liking (optional) 20 | 21 | Check out the [example](https://github.com/Federico-Ciuffardi/GestureControlledCamera2D/releases/download/v1.1.0/GestureControlledCamera2D-Example.zip)! 22 | 23 | ## Script variables 24 | 25 | | Name | Description | 26 | |------------------|-----------------------------------------------------| 27 | | Max Zoom | The camera will not zoom in any further than this | 28 | | Min Zoom | The browser will not zoom out any further than this | 29 | | Zoom Gesture | The gesture that will control the camera zoom | 30 | | Rotation gesture | The gesture that will control the camera rotation | 31 | | Movement Gesture | The gesture that will control the camera movement | 32 | 33 | ## Versioning 34 | Using [SemVer](http://semver.org/) for versioning. For the versions available, see the [releases](https://github.com/Federico-Ciuffardi/GestureControlledCamera2D/releases) 35 | 36 | ## Authors 37 | * Federico Ciuffardi 38 | 39 | Feel free to append yourself here if you've made contributions. 40 | 41 | ## Note 42 | Thank you for checking out this repository, you can send all your questions and feedback to Federico.Ciuffardi@outlook.com. 43 | 44 | If you are up to contribute on some way please contact me :) 45 | -------------------------------------------------------------------------------- /GCC2D.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | 3 | # Configuration 4 | @export var MAX_ZOOM: float = 4 5 | @export var MIN_ZOOM: float = 0.16 6 | 7 | enum ZOOM_GESTURE { DISABLED , PINCH } 8 | enum ROTATE_GESTURE { DISABLED , TWIST } 9 | enum MOVEMENT_GESTURE { DISABLED, SINGLE_DRAG, MULTI_DRAG } 10 | 11 | @export var zoom_gesture : ZOOM_GESTURE = ZOOM_GESTURE.PINCH 12 | @export var rotation_gesture : ROTATE_GESTURE = ROTATE_GESTURE.TWIST 13 | @export var movement_gesture : MOVEMENT_GESTURE = MOVEMENT_GESTURE.SINGLE_DRAG 14 | 15 | var effective_limit_left = -10000000 16 | var effective_limit_right = 10000000 17 | var effective_limit_top = -10000000 18 | var effective_limit_bottom = 10000000 19 | 20 | func set_camera_position(p): 21 | var camera_limits; 22 | var camera_size = get_camera_size()/zoom 23 | 24 | if anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT: 25 | camera_limits = [ 26 | Vector2(), 27 | Vector2(camera_size.x, 0), 28 | Vector2(0, camera_size.y), 29 | Vector2(camera_size.x, camera_size.y), 30 | Vector2(camera_size.x, 0), 31 | ] 32 | 33 | elif anchor_mode == ANCHOR_MODE_DRAG_CENTER: 34 | camera_limits = [ 35 | Vector2(-camera_size.x/2, -camera_size.y/2), 36 | Vector2( camera_size.x/2, -camera_size.y/2), 37 | Vector2( camera_size.x/2, camera_size.y/2), 38 | Vector2(-camera_size.x/2, camera_size.y/2), 39 | ] 40 | 41 | for i in camera_limits.size(): 42 | camera_limits[i] = camera_limits[i].rotated(rotation); 43 | 44 | for camera_limit in camera_limits: 45 | if(p.x > effective_limit_right - camera_limit.x): 46 | p.x = effective_limit_right - camera_limit.x 47 | 48 | if(p.y > effective_limit_bottom - camera_limit.y): 49 | p.y = effective_limit_bottom - camera_limit.y 50 | 51 | if(p.x < effective_limit_left - camera_limit.x): 52 | p.x = effective_limit_left - camera_limit.x 53 | 54 | if(p.y < effective_limit_top - camera_limit.y): 55 | p.y = effective_limit_top - camera_limit.y 56 | 57 | for camera_limit in camera_limits: 58 | if((p.x > effective_limit_right - camera_limit.x) or 59 | (p.y > effective_limit_bottom - camera_limit.y) or 60 | (p.x < effective_limit_left - camera_limit.x) or 61 | (p.y < effective_limit_top - camera_limit.y)): 62 | return false; 63 | 64 | position = p 65 | 66 | return true 67 | 68 | 69 | func _unhandled_input(e): 70 | if (e is InputEventMultiScreenDrag and movement_gesture == 2 71 | or e is InputEventSingleScreenDrag and movement_gesture == 1): 72 | _move(e) 73 | elif e is InputEventScreenTwist and rotation_gesture == 1: 74 | _rotate(e) 75 | elif e is InputEventScreenPinch and zoom_gesture == 1: 76 | _zoom(e) 77 | 78 | # Given a a position on the camera returns to the corresponding global position 79 | func camera2global(position): 80 | var camera_center = global_position 81 | var from_camera_center_pos = position - get_camera_center_offset() 82 | return camera_center + (from_camera_center_pos/zoom).rotated(rotation) 83 | 84 | func _move(event): 85 | set_camera_position(position - (event.relative/zoom).rotated(rotation)) 86 | 87 | func _zoom(event): 88 | var li = event.distance 89 | var lf = event.distance - event.relative 90 | 91 | var zi = zoom.x 92 | var zf = (li*zi)/lf 93 | var zd = zf - zi 94 | 95 | if zf <= MIN_ZOOM and sign(zd) < 0: 96 | zf = MIN_ZOOM 97 | zd = zf - zi 98 | elif zf >= MAX_ZOOM and sign(zd) > 0: 99 | zf = MAX_ZOOM 100 | zd = zf - zi 101 | 102 | zoom = zf*Vector2.ONE 103 | 104 | var from_camera_center_pos = event.position - get_camera_center_offset() 105 | var relative = (from_camera_center_pos*zd) / (zi*zf) 106 | if(!set_camera_position(position + relative.rotated(rotation))): 107 | zoom = zi*Vector2.ONE 108 | 109 | func _rotate(event): 110 | if ignore_rotation: return 111 | 112 | var fccp = event.position - get_camera_center_offset() 113 | var fccp_op_rot = -fccp.rotated(event.relative) 114 | 115 | rotation -= event.relative 116 | 117 | if(!set_camera_position(position - ((fccp_op_rot + fccp)/zoom).rotated(rotation-event.relative))): 118 | rotation += event.relative 119 | 120 | func get_camera_center_offset(): 121 | if anchor_mode == ANCHOR_MODE_FIXED_TOP_LEFT: 122 | return Vector2.ZERO 123 | elif anchor_mode == ANCHOR_MODE_DRAG_CENTER: 124 | return get_camera_size()/2 125 | 126 | func get_camera_size(): 127 | return get_viewport().get_visible_rect().size 128 | 129 | func _ready(): 130 | var limit_left_tmp = effective_limit_left 131 | effective_limit_left = limit_left 132 | limit_left = limit_left_tmp 133 | 134 | var limit_right_tmp = effective_limit_right 135 | effective_limit_right = limit_right 136 | limit_right = limit_right_tmp 137 | 138 | var limit_top_tmp = effective_limit_top 139 | effective_limit_top = limit_top 140 | limit_top = limit_top_tmp 141 | 142 | var limit_bottom_tmp = effective_limit_bottom 143 | effective_limit_bottom = limit_bottom 144 | limit_bottom = limit_bottom_tmp 145 | 146 | set_camera_position(position) 147 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Android" 4 | platform="Android" 5 | runnable=true 6 | custom_features="" 7 | export_filter="all_resources" 8 | include_filter="" 9 | exclude_filter="" 10 | export_path="../../GestureControlledCamera.apk" 11 | patch_list=PoolStringArray( ) 12 | script_export_mode=1 13 | script_encryption_key="" 14 | 15 | [preset.0.options] 16 | 17 | graphics/32_bits_framebuffer=true 18 | xr_features/xr_mode=0 19 | xr_features/degrees_of_freedom=0 20 | xr_features/hand_tracking=0 21 | one_click_deploy/clear_previous_install=false 22 | custom_template/debug="" 23 | custom_template/release="" 24 | custom_template/use_custom_build=false 25 | command_line/extra_args="" 26 | version/code=1 27 | version/name="1.0" 28 | package/unique_name="com.FedericoCiuffardi.GestureControlledCamera" 29 | package/name="GestureControlledCamera" 30 | package/signed=true 31 | screen/immersive_mode=true 32 | screen/orientation=0 33 | screen/support_small=true 34 | screen/support_normal=true 35 | screen/support_large=true 36 | screen/support_xlarge=true 37 | screen/opengl_debug=false 38 | launcher_icons/main_192x192="" 39 | launcher_icons/adaptive_foreground_432x432="" 40 | launcher_icons/adaptive_background_432x432="" 41 | keystore/debug="" 42 | keystore/debug_user="" 43 | keystore/debug_password="" 44 | keystore/release="" 45 | keystore/release_user="" 46 | keystore/release_password="" 47 | apk_expansion/enable=false 48 | apk_expansion/SALT="" 49 | apk_expansion/public_key="" 50 | architectures/armeabi-v7a=true 51 | architectures/arm64-v8a=true 52 | architectures/x86=false 53 | architectures/x86_64=false 54 | permissions/custom_permissions=PoolStringArray( ) 55 | permissions/access_checkin_properties=false 56 | permissions/access_coarse_location=false 57 | permissions/access_fine_location=false 58 | permissions/access_location_extra_commands=false 59 | permissions/access_mock_location=false 60 | permissions/access_network_state=false 61 | permissions/access_surface_flinger=false 62 | permissions/access_wifi_state=false 63 | permissions/account_manager=false 64 | permissions/add_voicemail=false 65 | permissions/authenticate_accounts=false 66 | permissions/battery_stats=false 67 | permissions/bind_accessibility_service=false 68 | permissions/bind_appwidget=false 69 | permissions/bind_device_admin=false 70 | permissions/bind_input_method=false 71 | permissions/bind_nfc_service=false 72 | permissions/bind_notification_listener_service=false 73 | permissions/bind_print_service=false 74 | permissions/bind_remoteviews=false 75 | permissions/bind_text_service=false 76 | permissions/bind_vpn_service=false 77 | permissions/bind_wallpaper=false 78 | permissions/bluetooth=false 79 | permissions/bluetooth_admin=false 80 | permissions/bluetooth_privileged=false 81 | permissions/brick=false 82 | permissions/broadcast_package_removed=false 83 | permissions/broadcast_sms=false 84 | permissions/broadcast_sticky=false 85 | permissions/broadcast_wap_push=false 86 | permissions/call_phone=false 87 | permissions/call_privileged=false 88 | permissions/camera=false 89 | permissions/capture_audio_output=false 90 | permissions/capture_secure_video_output=false 91 | permissions/capture_video_output=false 92 | permissions/change_component_enabled_state=false 93 | permissions/change_configuration=false 94 | permissions/change_network_state=false 95 | permissions/change_wifi_multicast_state=false 96 | permissions/change_wifi_state=false 97 | permissions/clear_app_cache=false 98 | permissions/clear_app_user_data=false 99 | permissions/control_location_updates=false 100 | permissions/delete_cache_files=false 101 | permissions/delete_packages=false 102 | permissions/device_power=false 103 | permissions/diagnostic=false 104 | permissions/disable_keyguard=false 105 | permissions/dump=false 106 | permissions/expand_status_bar=false 107 | permissions/factory_test=false 108 | permissions/flashlight=false 109 | permissions/force_back=false 110 | permissions/get_accounts=false 111 | permissions/get_package_size=false 112 | permissions/get_tasks=false 113 | permissions/get_top_activity_info=false 114 | permissions/global_search=false 115 | permissions/hardware_test=false 116 | permissions/inject_events=false 117 | permissions/install_location_provider=false 118 | permissions/install_packages=false 119 | permissions/install_shortcut=false 120 | permissions/internal_system_window=false 121 | permissions/internet=false 122 | permissions/kill_background_processes=false 123 | permissions/location_hardware=false 124 | permissions/manage_accounts=false 125 | permissions/manage_app_tokens=false 126 | permissions/manage_documents=false 127 | permissions/master_clear=false 128 | permissions/media_content_control=false 129 | permissions/modify_audio_settings=false 130 | permissions/modify_phone_state=false 131 | permissions/mount_format_filesystems=false 132 | permissions/mount_unmount_filesystems=false 133 | permissions/nfc=false 134 | permissions/persistent_activity=false 135 | permissions/process_outgoing_calls=false 136 | permissions/read_calendar=false 137 | permissions/read_call_log=false 138 | permissions/read_contacts=false 139 | permissions/read_external_storage=false 140 | permissions/read_frame_buffer=false 141 | permissions/read_history_bookmarks=false 142 | permissions/read_input_state=false 143 | permissions/read_logs=false 144 | permissions/read_phone_state=false 145 | permissions/read_profile=false 146 | permissions/read_sms=false 147 | permissions/read_social_stream=false 148 | permissions/read_sync_settings=false 149 | permissions/read_sync_stats=false 150 | permissions/read_user_dictionary=false 151 | permissions/reboot=false 152 | permissions/receive_boot_completed=false 153 | permissions/receive_mms=false 154 | permissions/receive_sms=false 155 | permissions/receive_wap_push=false 156 | permissions/record_audio=false 157 | permissions/reorder_tasks=false 158 | permissions/restart_packages=false 159 | permissions/send_respond_via_message=false 160 | permissions/send_sms=false 161 | permissions/set_activity_watcher=false 162 | permissions/set_alarm=false 163 | permissions/set_always_finish=false 164 | permissions/set_animation_scale=false 165 | permissions/set_debug_app=false 166 | permissions/set_orientation=false 167 | permissions/set_pointer_speed=false 168 | permissions/set_preferred_applications=false 169 | permissions/set_process_limit=false 170 | permissions/set_time=false 171 | permissions/set_time_zone=false 172 | permissions/set_wallpaper=false 173 | permissions/set_wallpaper_hints=false 174 | permissions/signal_persistent_processes=false 175 | permissions/status_bar=false 176 | permissions/subscribed_feeds_read=false 177 | permissions/subscribed_feeds_write=false 178 | permissions/system_alert_window=false 179 | permissions/transmit_ir=false 180 | permissions/uninstall_shortcut=false 181 | permissions/update_device_stats=false 182 | permissions/use_credentials=false 183 | permissions/use_sip=false 184 | permissions/vibrate=false 185 | permissions/wake_lock=false 186 | permissions/write_apn_settings=false 187 | permissions/write_calendar=false 188 | permissions/write_call_log=false 189 | permissions/write_contacts=false 190 | permissions/write_external_storage=false 191 | permissions/write_gservices=false 192 | permissions/write_history_bookmarks=false 193 | permissions/write_profile=false 194 | permissions/write_secure_settings=false 195 | permissions/write_settings=false 196 | permissions/write_sms=false 197 | permissions/write_social_stream=false 198 | permissions/write_sync_settings=false 199 | permissions/write_user_dictionary=false 200 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 centroid(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 Util.centroid(arr) 65 | 66 | func get_ends() -> Dictionary: 67 | var ends : Dictionary = {} 68 | 69 | for i in presses: 70 | ends[i] = presses[i].position 71 | 72 | for i in drags: 73 | ends[i] = drags[i].position 74 | 75 | for i in releases: 76 | ends[i] = releases[i].position 77 | 78 | return ends 79 | 80 | # Check for gesture consistency 81 | func is_consistent(diff_limit : float, length_limit : float = -1) -> bool: 82 | if length_limit == -1: length_limit = length_limit 83 | 84 | var ends : Dictionary = get_ends() 85 | 86 | var ends_centroid : Vector2 = Util.centroid(ends.values()) 87 | var starts_centroid : Vector2 = centroid("presses", "position") 88 | 89 | var valid : bool = true 90 | for i in ends: 91 | var start_relative_position : Vector2 = presses[i].position - starts_centroid 92 | var end_relative_position : Vector2 = ends[i] - ends_centroid 93 | 94 | valid = start_relative_position.length() < length_limit and \ 95 | end_relative_position.length() < length_limit and \ 96 | (end_relative_position - start_relative_position).length() < diff_limit 97 | 98 | if !valid: 99 | break 100 | 101 | return valid 102 | 103 | func rollback_relative(time : float) -> Array: 104 | return rollback_absolute(start_time+elapsed_time - time) 105 | 106 | func rollback_absolute(time : float) -> Array: 107 | var discarded_events : Array = [] 108 | var rg : RawGesture = copy() 109 | 110 | var latest_event_id : Array = rg.latest_event_id(time) 111 | while !latest_event_id.is_empty(): 112 | var latest_index : int = latest_event_id[0] 113 | var latest_type : String = latest_event_id[1] 114 | var latest_event = rg.history[latest_index][latest_type].pop_back() 115 | discarded_events.append(latest_event) 116 | if latest_type == "presses": 117 | rg.active_touches -= 1 118 | elif latest_type == "releases": 119 | rg.active_touches += 1 120 | if rg.history[latest_index][latest_type].is_empty(): 121 | rg.history[latest_index].erase(latest_type) 122 | if rg.history[latest_index].is_empty(): 123 | rg.history.erase(latest_index) 124 | latest_event_id = rg.latest_event_id(time) 125 | 126 | for index in rg.presses.keys(): 127 | if rg.history.has(index): 128 | if rg.history[index].has("presses"): 129 | var presses_history: Array = rg.history[index]["presses"] 130 | rg.presses[index] = presses_history.back() 131 | else: 132 | rg.presses.erase(index) 133 | 134 | if rg.history[index].has("releases"): 135 | var releases_history : Array = rg.history[index]["releases"] 136 | # !releases_history.empty() -> rg.presses.has(index) (touch precedes a release) 137 | if releases_history.back().time < rg.presses[index].time: 138 | rg.releases.erase(index) 139 | else: 140 | rg.releases[index] = releases_history.back() 141 | else: 142 | rg.releases.erase(index) 143 | 144 | if rg.history[index].has("drags"): 145 | var drags_history : Array = rg.history[index]["drags"] 146 | # rg.releases.has(index) -> rg.releases[index].time >= rg.presses[index].time -> 147 | # rg.releases[index] >= drags_history.back().time (drag should needs a new touch after the release) 148 | if rg.releases.has(index): 149 | rg.drags.erase(index) 150 | else: 151 | rg.drags[index] = drags_history.back() 152 | else: 153 | rg.drags.erase(index) 154 | else: 155 | rg.presses.erase(index) 156 | rg.releases.erase(index) 157 | rg.drags.erase(index) 158 | 159 | return [rg, discarded_events] 160 | 161 | func get_linear_event_history(): 162 | return rollback_absolute(0)[1] 163 | 164 | func copy() -> RawGesture: 165 | var rg : RawGesture = get_script().new() 166 | rg.presses = presses.duplicate(true) 167 | rg.releases = releases.duplicate(true) 168 | rg.drags = drags.duplicate(true) 169 | rg.history = history.duplicate(true) 170 | rg.active_touches = active_touches 171 | rg.start_time = start_time 172 | rg.elapsed_time = elapsed_time 173 | return rg 174 | 175 | func latest_event_id(latest_time : float = -1) -> Array: 176 | var res : Array = [] 177 | for index in history: 178 | for type in history[index]: 179 | var event_time = history[index][type].back().time 180 | if event_time >= latest_time: 181 | res = [index, type] 182 | latest_time = event_time 183 | return res 184 | 185 | func as_string() -> String: 186 | var txt = "presses: " 187 | for e in presses.values(): 188 | txt += "\n" + e.as_string() 189 | txt += "\ndrags: " 190 | for e in drags.values(): 191 | txt += "\n" + e.as_string() 192 | txt += "\nreleases: " 193 | for e in releases.values(): 194 | txt += "\n" + e.as_string() 195 | return txt 196 | 197 | func _update_screen_drag(event : InputEventScreenDrag, time : float = -1) -> void: 198 | if time < 0: 199 | time = Util.now() 200 | var drag : Drag = Drag.new() 201 | drag.position = event.position 202 | drag.relative = event.relative 203 | drag.velocity = event.velocity 204 | drag.index = event.index 205 | drag.time = time 206 | _add_history(event.index, "drags", drag) 207 | drags[event.index] = drag 208 | elapsed_time = time - start_time 209 | 210 | func _update_screen_touch(event : InputEventScreenTouch, time : float = -1) -> void: 211 | if time < 0: 212 | time = Util.now() 213 | var touch : Touch = Touch.new() 214 | touch.position = event.position 215 | touch.pressed = event.pressed 216 | touch.index = event.index 217 | touch.time = time 218 | if event.pressed: 219 | _add_history(event.index, "presses", touch) 220 | presses[event.index] = touch 221 | active_touches += 1 222 | releases.erase(event.index) 223 | drags.erase(event.index) 224 | if active_touches == 1: 225 | start_time = time 226 | else: 227 | _add_history(event.index, "releases", touch) 228 | releases[event.index] = touch 229 | active_touches -= 1 230 | drags.erase(event.index) 231 | elapsed_time = time - start_time 232 | 233 | func _add_history(index : int, type : String, value) -> void: 234 | if !history.has(index): 235 | history[index] = {} 236 | if !history[index].has(type): 237 | history[index][type] = [] 238 | history[index][type].append(value) 239 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | -------------------------------------------------------------------------------- /GDTIM-4.x-v2_1_2/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 | _add_timer(_drag_startup_timer, "_on_drag_startup_timer_timeout") 97 | _add_timer(_long_press_timer, "_on_long_press_timer_timeout") 98 | 99 | if DEFAULT_BINDIGS: 100 | _set_default_action("multi_swipe_up" , _native_key_event(KEY_I)) 101 | _set_default_action("multi_swipe_up_right" , _native_key_event(KEY_O)) 102 | _set_default_action("multi_swipe_right" , _native_key_event(KEY_L)) 103 | _set_default_action("multi_swipe_down_right", _native_key_event(KEY_PERIOD)) 104 | _set_default_action("multi_swipe_down" , _native_key_event(KEY_COMMA)) 105 | _set_default_action("multi_swipe_down_left" , _native_key_event(KEY_M)) 106 | _set_default_action("multi_swipe_left" , _native_key_event(KEY_J)) 107 | _set_default_action("multi_swipe_up_left" , _native_key_event(KEY_U)) 108 | 109 | _set_default_action("single_swipe_up" , _native_key_event(KEY_W)) 110 | _set_default_action("single_swipe_up_right" , _native_key_event(KEY_E)) 111 | _set_default_action("single_swipe_right" , _native_key_event(KEY_D)) 112 | _set_default_action("single_swipe_down_right", _native_key_event(KEY_C)) 113 | _set_default_action("single_swipe_down" , _native_key_event(KEY_X)) 114 | _set_default_action("single_swipe_down_left" , _native_key_event(KEY_Z)) 115 | _set_default_action("single_swipe_left" , _native_key_event(KEY_A)) 116 | _set_default_action("single_swipe_up_left" , _native_key_event(KEY_Q)) 117 | 118 | # _set_default_action("single_touch" , _native_mouse_button_event(MOUSE_BUTTON_LEFT)) 119 | _set_default_action("multi_touch" , _native_mouse_button_event(MOUSE_BUTTON_MIDDLE)) 120 | # _set_default_action("pinch" , _native_mouse_button_event(MOUSE_BUTTON_RIGHT)) # TODO 121 | _set_default_action("pinch_outward" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_UP)) 122 | _set_default_action("pinch_inward" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_DOWN)) 123 | _set_default_action("twist" , _native_mouse_button_event(MOUSE_BUTTON_RIGHT)) 124 | # _set_default_action("twist_clockwise" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_UP)) # TODO 125 | # _set_default_action("twist_counterclockwise" , _native_mouse_button_event(MOUSE_BUTTON_WHEEL_DOWN)) # TODO 126 | 127 | func _unhandled_input(event : InputEvent) -> void: 128 | if event is InputEventScreenDrag: 129 | _handle_screen_drag(event) 130 | elif event is InputEventScreenTouch: 131 | _handle_screen_touch(event) 132 | elif event is InputEventMouseMotion: 133 | _handle_mouse_motion(event) 134 | else: 135 | _handle_action(event) 136 | 137 | func _handle_mouse_motion(event : InputEventMouseMotion) -> void: 138 | if raw_gesture_data.size() == 1 and _mouse_event == Gesture.SINGLE_DRAG: 139 | _emit("drag", _native_drag_event(0, event.position, event.relative, event.velocity)) 140 | elif raw_gesture_data.size() == 2 and _mouse_event == Gesture.MULTI_DRAG: 141 | var offset = Vector2(5,5) 142 | var e0 = _native_drag_event(0, event.position-offset, event.relative, event.velocity) 143 | raw_gesture_data._update_screen_drag(e0) 144 | var e1 = _native_drag_event(1, event.position+offset, event.relative, event.velocity) 145 | raw_gesture_data._update_screen_drag(e1) 146 | _emit("multi_drag", InputEventMultiScreenDrag.new(raw_gesture_data,e0)) 147 | _emit("multi_drag", InputEventMultiScreenDrag.new(raw_gesture_data,e1)) 148 | elif _mouse_event == Gesture.TWIST: 149 | var rel1 = event.position - _mouse_event_press_position 150 | var rel2 = rel1 + event.relative 151 | var twist_event = InputEventScreenTwist.new() 152 | twist_event.position = _mouse_event_press_position 153 | twist_event.relative = rel1.angle_to(rel2) 154 | twist_event.fingers = 2 155 | _emit("twist", twist_event) 156 | 157 | func _handle_screen_touch(event : InputEventScreenTouch) -> void: 158 | if event.index < 0: 159 | _emit("cancel", InputEventScreenCancel.new(raw_gesture_data, event)) 160 | _end_gesture() 161 | return 162 | 163 | # ignore orphaned touch release events 164 | if !event.pressed and not event.index in raw_gesture_data.presses: 165 | return 166 | 167 | raw_gesture_data._update_screen_touch(event) 168 | _emit("raw_gesture", raw_gesture_data) 169 | var index : int = event.index 170 | if event.pressed: 171 | if raw_gesture_data.size() == 1: # First and only touch 172 | _long_press_timer.start(LONG_PRESS_TIME_THRESHOLD) 173 | _single_touch_cancelled = false 174 | _emit("single_touch", InputEventSingleScreenTouch.new(raw_gesture_data)) 175 | elif !_single_touch_cancelled : 176 | _single_touch_cancelled = true 177 | _cancel_single_drag() 178 | _emit("single_touch", InputEventSingleScreenTouch.new(raw_gesture_data)) 179 | else: 180 | var fingers : int = raw_gesture_data.size() 181 | if index == 0: 182 | _emit("single_touch", InputEventSingleScreenTouch.new(raw_gesture_data)) 183 | if !_single_touch_cancelled: 184 | var distance : float = (raw_gesture_data.releases[0].position - raw_gesture_data.presses[0].position).length() 185 | if raw_gesture_data.elapsed_time < TAP_TIME_LIMIT and distance <= TAP_DISTANCE_LIMIT: 186 | _emit("single_tap", InputEventSingleScreenTap.new(raw_gesture_data)) 187 | if raw_gesture_data.elapsed_time < SWIPE_TIME_LIMIT and distance > SWIPE_DISTANCE_THRESHOLD: 188 | _emit("single_swipe", InputEventSingleScreenSwipe.new(raw_gesture_data)) 189 | if raw_gesture_data.active_touches == 0: # last finger released 190 | if _single_touch_cancelled: 191 | var distance : float = (raw_gesture_data.centroid("releases","position") - raw_gesture_data.centroid("presses","position")).length() 192 | if raw_gesture_data.elapsed_time < TAP_TIME_LIMIT and distance <= TAP_DISTANCE_LIMIT and\ 193 | raw_gesture_data.is_consistent(TAP_DISTANCE_LIMIT, FINGER_SIZE*fingers) and\ 194 | _released_together(raw_gesture_data, MULTI_FINGER_RELEASE_THRESHOLD): 195 | _emit("multi_tap", InputEventMultiScreenTap.new(raw_gesture_data)) 196 | if raw_gesture_data.elapsed_time < SWIPE_TIME_LIMIT and distance > SWIPE_DISTANCE_THRESHOLD and\ 197 | raw_gesture_data.is_consistent(FINGER_SIZE, FINGER_SIZE*fingers) and\ 198 | _released_together(raw_gesture_data, MULTI_FINGER_RELEASE_THRESHOLD): 199 | _emit("multi_swipe", InputEventMultiScreenSwipe.new(raw_gesture_data)) 200 | _end_gesture() 201 | _cancel_single_drag() 202 | 203 | func _handle_screen_drag(event : InputEventScreenDrag) -> void: 204 | if event.index < 0: 205 | _emit("cancel", InputEventScreenCancel.new(raw_gesture_data, event)) 206 | _end_gesture() 207 | return 208 | 209 | raw_gesture_data._update_screen_drag(event) 210 | _emit("raw_gesture", raw_gesture_data) 211 | if raw_gesture_data.drags.size() > 1: 212 | _cancel_single_drag() 213 | var gesture : int = _identify_gesture(raw_gesture_data) 214 | if gesture == Gesture.PINCH: 215 | _emit("pinch", InputEventScreenPinch.new(raw_gesture_data, event)) 216 | elif gesture == Gesture.MULTI_DRAG: 217 | _emit("multi_drag", InputEventMultiScreenDrag.new(raw_gesture_data, event)) 218 | elif gesture == Gesture.TWIST: 219 | _emit("twist",InputEventScreenTwist.new(raw_gesture_data, event)) 220 | else: 221 | if _single_drag_enabled: 222 | _emit("single_drag", InputEventSingleScreenDrag.new(raw_gesture_data)) 223 | else: 224 | if _drag_startup_timer.is_stopped(): _drag_startup_timer.start(DRAG_STARTUP_TIME) 225 | 226 | func _handle_action(event : InputEvent) -> void: 227 | if InputMap.has_action("single_touch") and (event.is_action_pressed("single_touch") or event.is_action_released("single_touch")): 228 | _emit("touch", _native_touch_event(0, get_viewport().get_mouse_position(), event.pressed)) 229 | if event.pressed: 230 | _mouse_event = Gesture.SINGLE_DRAG 231 | else: 232 | _mouse_event = Gesture.NONE 233 | elif InputMap.has_action("multi_touch") and (event.is_action_pressed("multi_touch") or event.is_action_released("multi_touch")): 234 | _emit("touch", _native_touch_event(0, get_viewport().get_mouse_position(), event.pressed)) 235 | _emit("touch", _native_touch_event(1, get_viewport().get_mouse_position(), event.pressed)) 236 | if event.pressed: 237 | _mouse_event = Gesture.MULTI_DRAG 238 | else: 239 | _mouse_event = Gesture.NONE 240 | elif InputMap.has_action("twist") and (event.is_action_pressed("twist") or event.is_action_released("twist")): 241 | _mouse_event_press_position = get_viewport().get_mouse_position() 242 | if event.pressed: 243 | _mouse_event = Gesture.TWIST 244 | else: 245 | _mouse_event = Gesture.NONE 246 | 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")): 247 | var pinch_event = InputEventScreenPinch.new() 248 | pinch_event.fingers = 2 249 | pinch_event.position = get_viewport().get_mouse_position() 250 | pinch_event.distance = 400 251 | pinch_event.relative = 40 252 | if event.is_action_pressed("pinch_inward"): 253 | pinch_event.relative *= -1 254 | _emit("pinch", pinch_event) 255 | else: 256 | var swipe_emulation_dir : Vector2 = Vector2.ZERO 257 | var is_single_swipe : bool 258 | for swipe in swipe2dir: 259 | var dir = swipe2dir[swipe] 260 | if InputMap.has_action("single_"+swipe) and event.is_action_pressed("single_"+swipe): 261 | swipe_emulation_dir = dir 262 | is_single_swipe = true 263 | break 264 | if InputMap.has_action("multi_"+swipe) and event.is_action_pressed("multi_"+swipe): 265 | swipe_emulation_dir = dir 266 | is_single_swipe = false 267 | break 268 | 269 | if swipe_emulation_dir != Vector2.ZERO: 270 | var swipe_event 271 | if is_single_swipe: 272 | swipe_event = InputEventSingleScreenSwipe.new() 273 | else: 274 | swipe_event = InputEventMultiScreenSwipe.new() 275 | swipe_event.fingers = 2 276 | swipe_event.position = get_viewport().get_mouse_position() 277 | swipe_event.relative = swipe_emulation_dir*SWIPE_DISTANCE_THRESHOLD*2 278 | if is_single_swipe: 279 | _emit("single_swipe", swipe_event) 280 | else: 281 | _emit("multi_swipe", swipe_event) 282 | 283 | # Emits signal sig with the specified args 284 | func _emit(sig : String, val : InputEvent) -> void: 285 | if DEBUG: print(val.as_text()) 286 | emit_signal("any_gesture", sig, val) 287 | emit_signal(sig, val) 288 | Input.parse_input_event(val) 289 | 290 | 291 | # Disables drag and stops the drag enabling timer 292 | func _cancel_single_drag() -> void: 293 | _single_drag_enabled = false 294 | _drag_startup_timer.stop() 295 | 296 | func _released_together(_raw_gesture_data : RawGesture, threshold : float) -> bool: 297 | _raw_gesture_data = _raw_gesture_data.rollback_relative(threshold)[0] 298 | return _raw_gesture_data.size() == _raw_gesture_data.active_touches 299 | 300 | # Checks if the gesture is pinch 301 | func _identify_gesture(_raw_gesture_data : RawGesture) -> int: 302 | var center : Vector2 = _raw_gesture_data.centroid("drags","position") 303 | 304 | var sector : int = -1 305 | for e in _raw_gesture_data.drags.values(): 306 | var adjusted_position : Vector2 = center - e.position 307 | var raw_angle : float = fmod(adjusted_position.angle_to(e.relative) + (PI/4), TAU) 308 | var adjusted_angle : float = raw_angle if raw_angle >= 0 else raw_angle + TAU 309 | var e_sector : int = int(floor(adjusted_angle / (PI/2))) 310 | if sector == -1: 311 | sector = e_sector 312 | elif sector != e_sector: 313 | return Gesture.MULTI_DRAG 314 | 315 | if sector == 0 or sector == 2: 316 | return Gesture.PINCH 317 | else: # sector == 1 or sector == 3: 318 | return Gesture.TWIST 319 | 320 | func _on_drag_startup_timer_timeout() -> void: 321 | _single_drag_enabled = raw_gesture_data.drags.size() == 1 322 | 323 | func _on_long_press_timer_timeout() -> void: 324 | var ends_centroid : Vector2 = Util.centroid(raw_gesture_data.get_ends().values()) 325 | var starts_centroid : Vector2 = raw_gesture_data.centroid("presses", "position") 326 | var distance : float = (ends_centroid - starts_centroid).length() 327 | 328 | if raw_gesture_data.releases.is_empty() and distance <= LONG_PRESS_DISTANCE_LIMIT and\ 329 | raw_gesture_data.is_consistent(LONG_PRESS_DISTANCE_LIMIT, FINGER_SIZE*raw_gesture_data.size()): 330 | if _single_touch_cancelled: 331 | _emit("multi_long_press", InputEventMultiScreenLongPress.new(raw_gesture_data)) 332 | else: 333 | _emit("single_long_press", InputEventSingleScreenLongPress.new(raw_gesture_data)) 334 | 335 | 336 | func _end_gesture() -> void: 337 | _single_drag_enabled = false 338 | _long_press_timer.stop() 339 | raw_gesture_data = RawGesture.new() 340 | 341 | # create a native touch event 342 | func _native_touch_event(index : int, position : Vector2, pressed : bool) -> InputEventScreenTouch: 343 | var native_touch : InputEventScreenTouch = InputEventScreenTouch.new() 344 | native_touch.index = index 345 | native_touch.position = position 346 | native_touch.pressed = pressed 347 | return native_touch 348 | 349 | # create a native touch event 350 | func _native_drag_event(index : int, position : Vector2, relative : Vector2, velocity : Vector2) -> InputEventScreenDrag: 351 | var native_drag : InputEventScreenDrag = InputEventScreenDrag.new() 352 | native_drag.index = index 353 | native_drag.position = position 354 | native_drag.relative = relative 355 | native_drag.velocity = velocity 356 | return native_drag 357 | 358 | func _native_mouse_button_event(button : int) -> InputEventMouseButton: 359 | var ev = InputEventMouseButton.new() 360 | ev.button_index = button 361 | return ev 362 | 363 | func _native_key_event(key : int) -> InputEventKey: 364 | var ev = InputEventKey.new() 365 | ev.keycode = key 366 | return ev 367 | 368 | func _set_default_action(action : String, event : InputEvent) -> void: 369 | if !InputMap.has_action(action): 370 | InputMap.add_action(action) 371 | InputMap.action_add_event(action,event) 372 | 373 | # Macro to add a timer and connect it's timeout to func_name 374 | func _add_timer(timer : Timer, func_name : String) -> void: 375 | timer.one_shot = true 376 | if func_name: 377 | timer.connect("timeout", Callable(self, func_name)) 378 | self.add_child(timer) 379 | --------------------------------------------------------------------------------