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()



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