Skip to content

Doesn't respect anchoring positioning #1

Description

@Elrinth

Here is fixed code (also now supports multitouch and touching two buttons at same time):

@tool
@icon("TouchScreenButtonControl.svg")
class_name TouchScreenButtonControl
extends TextureButton

const DefaultValues := {
	"expand": true,
	"ignore_texture_size": true,
	"stretch_mode": TextureButton.STRETCH_KEEP_ASPECT_CENTERED,
	"action_mode": TextureButton.ACTION_MODE_BUTTON_PRESS,
	"focus_mode": TextureButton.FOCUS_NONE,
}

@export var use_default_values := true
@export var touchscreen_only := false

# The actual variable that holds the data
var input_action: String = ""

# 2. MATCHED the property name here...
func _get_property_list() -> Array:
	var properties: Array = []
	
	if Engine.is_editor_hint():
		InputMap.load_from_project_settings()
	
	# Get actions and format them for the dropdown
	var actions = InputMap.get_actions()
	var action_list = ",".join(actions)
	
	properties.append({
		"name": "input_action",
		"type": TYPE_STRING,
		"hint": PROPERTY_HINT_ENUM_SUGGESTION,
		"hint_string": ",".join(actions),
		"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR
	})
	
	return properties

func _set(property: StringName, value: Variant) -> bool:
	if property == "input_action":
		input_action = value
		return true
	return false

func _get(property: StringName) -> Variant:
	if property == "input_action":
		return input_action
	return null

# Track multiple simultaneous touches for multitouch support
var active_touches := {} # Dictionary: touch_index -> bool (true if pressed)
var initial_press_touches := {} # Dictionary: touch_index -> bool (true if this button was pressed on initial touch)

func _init():
	if use_default_values:
		for k in DefaultValues.keys():
			self.set(k, DefaultValues.get(k))
	
	# Set mouse_filter to IGNORE so the button doesn't consume touch events
	# This allows multiple buttons to detect the same touch simultaneously
	mouse_filter = Control.MOUSE_FILTER_IGNORE
	
	if touchscreen_only and not DisplayServer.is_touchscreen_available():
		hide()

func press():
	var input_event: InputEvent = InputMap.action_get_events(input_action)[0]
	input_event.pressed = true
	Input.parse_input_event(input_event)

func release():
	var input_event: InputEvent = InputMap.action_get_events(input_action)[0]
	input_event.pressed = false
	Input.parse_input_event(input_event)

func is_in(pos: Vector2) -> bool:
	# Get the global rect of this Control node in screen coordinates
	var global_rect = get_global_rect()
	# Check if the touch position is within the button's global rect
	if pos.x >= global_rect.position.x and pos.x <= global_rect.position.x + global_rect.size.x:
		if pos.y >= global_rect.position.y and pos.y <= global_rect.position.y + global_rect.size.y:
			return true
	return false

func _input(event):
	# Use _input to receive all touch events
	# IMPORTANT: _input is called on ALL nodes in the scene tree, so multiple buttons
	# can receive the same event. We don't consume the event so others can too.
	if event is InputEventScreenTouch:
		if event.pressed:
			# Check if this touch is within this button's bounds
			if is_in(event.position):
				# Start tracking this touch for this button
				if event.index not in active_touches:
					active_touches[event.index] = true
					initial_press_touches[event.index] = true # Mark as initial press
					press()
		else:
			# Touch released - check if we were tracking it
			if event.index in active_touches:
				# This touch was being tracked by this button, release it
				active_touches.erase(event.index)
				initial_press_touches.erase(event.index)
				# Only release the action if no other touches are active for this button
				if active_touches.is_empty():
					release()
	elif event is InputEventScreenDrag:
		# Handle touch drag with smart re-activation logic
		if event.index in active_touches:
			# We're tracking this touch
			if initial_press_touches.has(event.index):
				# This was an initial press - keep it active even if touch moves outside
				# Don't release until touch ends
				pass
			else:
				# This was a re-activation (touch moved back into bounds)
				# Check if touch moved back out of bounds
				if not is_in(event.position):
					# Touch moved out of bounds, release this re-activated button
					active_touches.erase(event.index)
					if active_touches.is_empty():
						release()
		else:
			# We're not tracking this touch - check if it moved into our bounds
			# This allows "tapping" behavior - touch can re-enter and activate button
			if is_in(event.position):
				# Touch moved into bounds, start tracking (but not as initial press)
				active_touches[event.index] = true
				press()



Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions