godot-xterm/addons/gut/input_sender.gd

557 lines
15 KiB
GDScript
Raw Normal View History

# ##############################################################################
#(G)odot (U)nit (T)est class
#
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2020 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################
# Description
# -----------
# This class sends input to one or more recievers. The receivers' _input,
# _unhandled_input, and _gui_input are called sending InputEvent* events.
# InputEvents can be sent via the helper methods or a custom made InputEvent
# can be sent via send_event(...)
#
# ##############################################################################
#extends "res://addons/gut/input_factory.gd"
# Implemented InputEvent* convenience methods
# InputEventAction
# InputEventKey
# InputEventMouseButton
# InputEventMouseMotion
# Yet to implement InputEvents
# InputEventJoypadButton
# InputEventJoypadMotion
# InputEventMagnifyGesture
# InputEventMIDI
# InputEventPanGesture
# InputEventScreenDrag
# InputEventScreenTouch
2024-01-06 11:27:15 +01:00
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class InputQueueItem:
extends Node
var events = []
var time_delay = null
var frame_delay = null
var _waited_frames = 0
var _is_ready = false
var _delay_started = false
signal event_ready
# TODO should this be done in _physics_process instead or should it be
# configurable?
func _physics_process(delta):
2023-01-20 23:34:39 +01:00
if frame_delay > 0 and _delay_started:
_waited_frames += 1
2023-01-20 23:34:39 +01:00
if _waited_frames >= frame_delay:
emit_signal("event_ready")
2023-01-20 23:34:39 +01:00
func _init(t_delay, f_delay):
time_delay = t_delay
frame_delay = f_delay
_is_ready = time_delay == 0 and frame_delay == 0
func _on_time_timeout():
_is_ready = true
emit_signal("event_ready")
func _delay_timer(t):
return Engine.get_main_loop().root.get_tree().create_timer(t)
func is_ready():
return _is_ready
func start():
_delay_started = true
2023-01-20 23:34:39 +01:00
if time_delay > 0:
var t = _delay_timer(time_delay)
2023-01-20 23:34:39 +01:00
t.connect("timeout", Callable(self, "_on_time_timeout"))
2024-01-06 11:27:15 +01:00
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class MouseDraw:
extends Node2D
var down_color = Color(1, 1, 1, .25)
var up_color = Color(0, 0, 0, .25)
var line_color = Color(1, 0, 0)
var disabled = true:
get:
return disabled
set(val):
disabled = val
queue_redraw()
var _draw_at = Vector2(0, 0)
var _b1_down = false
var _b2_down = false
func draw_event(event):
if event is InputEventMouse:
_draw_at = event.position
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
_b1_down = event.pressed
elif event.button_index == MOUSE_BUTTON_RIGHT:
_b2_down = event.pressed
queue_redraw()
func _draw_cicled_cursor():
var r = 10
var b1_color = up_color
var b2_color = up_color
if _b1_down:
var pos = _draw_at - (Vector2(r * 1.5, 0))
draw_arc(pos, r / 2, 0, 360, 180, b1_color)
if _b2_down:
var pos = _draw_at + (Vector2(r * 1.5, 0))
draw_arc(pos, r / 2, 0, 360, 180, b2_color)
draw_arc(_draw_at, r, 0, 360, 360, line_color, 1)
draw_line(_draw_at - Vector2(0, r), _draw_at + Vector2(0, r), line_color)
draw_line(_draw_at - Vector2(r, 0), _draw_at + Vector2(r, 0), line_color)
func _draw_square_cursor():
var r = 10
var b1_color = up_color
var b2_color = up_color
if _b1_down:
b1_color = down_color
if _b2_down:
b2_color = down_color
var blen = r * .75
# left button rectangle
draw_rect(Rect2(_draw_at - Vector2(blen, blen), Vector2(blen, blen * 2)), b1_color)
# right button rectrangle
draw_rect(Rect2(_draw_at - Vector2(0, blen), Vector2(blen, blen * 2)), b2_color)
# Crosshair
draw_line(_draw_at - Vector2(0, r), _draw_at + Vector2(0, r), line_color)
draw_line(_draw_at - Vector2(r, 0), _draw_at + Vector2(r, 0), line_color)
func _draw():
if disabled:
return
_draw_square_cursor()
# ##############################################################################
#
# ##############################################################################
2023-01-20 23:34:39 +01:00
var _utils = load("res://addons/gut/utils.gd").get_instance()
var InputFactory = load("res://addons/gut/input_factory.gd")
2023-01-20 23:34:39 +01:00
const INPUT_WARN = "If using Input as a reciever it will not respond to *_down events until a *_up event is recieved. Call the appropriate *_up event or use hold_for(...) to automatically release after some duration."
var _lgr = _utils.get_logger()
var _receivers = []
var _input_queue = []
var _next_queue_item = null
2024-01-06 11:27:15 +01:00
# used by hold_for and echo.
var _last_event = null
# indexed by keycode, each entry contains a boolean value indicating the
# last emitted "pressed" value for that keycode.
var _pressed_keys = {}
var _pressed_actions = {}
var _pressed_mouse_buttons = {}
var _auto_flush_input = false
2024-01-06 11:27:15 +01:00
var _tree_items_parent = null
var _mouse_draw = null
var _default_mouse_position = {position = Vector2(0, 0), global_position = Vector2(0, 0)}
var _last_mouse_position = {}
var mouse_warp = false
var draw_mouse = true
signal idle
2023-01-20 23:34:39 +01:00
func _init(r = null):
if r != null:
add_receiver(r)
2024-01-06 11:27:15 +01:00
_last_mouse_position = _default_mouse_position.duplicate()
_tree_items_parent = Node.new()
Engine.get_main_loop().root.add_child(_tree_items_parent)
2024-01-06 11:27:15 +01:00
_mouse_draw = MouseDraw.new()
_tree_items_parent.add_child(_mouse_draw)
_mouse_draw.disabled = false
func _notification(what):
if what == NOTIFICATION_PREDELETE:
if is_instance_valid(_tree_items_parent):
_tree_items_parent.queue_free()
func _add_queue_item(item):
item.connect("event_ready", _on_queue_item_ready.bind(item))
_next_queue_item = item
_input_queue.append(item)
_tree_items_parent.add_child(item)
if _input_queue.size() == 1:
item.start()
func _handle_pressed_keys(event):
2023-01-20 23:34:39 +01:00
if event is InputEventKey:
if (event.pressed and !event.echo) and is_key_pressed(event.keycode):
_lgr.warn(
str(
"InputSender: key_down called for ",
event.as_text(),
" when that key is already pressed. ",
INPUT_WARN
)
)
_pressed_keys[event.keycode] = event.pressed
2023-01-20 23:34:39 +01:00
elif event is InputEventAction:
if event.pressed and is_action_pressed(event.action):
_lgr.warn(
str(
"InputSender: action_down called for ",
event.action,
" when that action is already pressed. ",
INPUT_WARN
)
)
_pressed_actions[event.action] = event.pressed
2023-01-20 23:34:39 +01:00
elif event is InputEventMouseButton:
if event.pressed and is_mouse_button_pressed(event.button_index):
_lgr.warn(
str(
"InputSender: mouse_button_down called for ",
event.button_index,
" when that mouse button is already pressed. ",
INPUT_WARN
)
)
_pressed_mouse_buttons[event.button_index] = event
2024-01-06 11:27:15 +01:00
func _handle_mouse_position(event):
if event is InputEventMouse:
_mouse_draw.disabled = !draw_mouse
_mouse_draw.draw_event(event)
if mouse_warp:
DisplayServer.warp_mouse(event.position)
func _send_event(event):
_handle_mouse_position(event)
_handle_pressed_keys(event)
for r in _receivers:
2023-01-20 23:34:39 +01:00
if r == Input:
Input.parse_input_event(event)
2023-01-20 23:34:39 +01:00
if _auto_flush_input:
Input.flush_buffered_events()
else:
2023-01-20 23:34:39 +01:00
if r.has_method("_input"):
r._input(event)
2023-01-20 23:34:39 +01:00
if r.has_method("_gui_input"):
r._gui_input(event)
2023-01-20 23:34:39 +01:00
if r.has_method("_unhandled_input"):
r._unhandled_input(event)
func _send_or_record_event(event):
_last_event = event
2023-01-20 23:34:39 +01:00
if _next_queue_item != null:
_next_queue_item.events.append(event)
else:
_send_event(event)
2024-01-06 11:27:15 +01:00
func _set_last_mouse_positions(event: InputEventMouse):
_last_mouse_position.position = event.position
_last_mouse_position.global_position = event.global_position
func _apply_last_position_and_set_last_position(event, position, global_position):
event.position = GutUtils.nvl(position, _last_mouse_position.position)
event.global_position = GutUtils.nvl(global_position, _last_mouse_position.global_position)
_set_last_mouse_positions(event)
func _new_defaulted_mouse_button_event(position, global_position):
var event = InputEventMouseButton.new()
_apply_last_position_and_set_last_position(event, position, global_position)
return event
func _new_defaulted_mouse_motion_event(position, global_position):
var event = InputEventMouseMotion.new()
_apply_last_position_and_set_last_position(event, position, global_position)
return event
# ------------------------------
# Events
# ------------------------------
func _on_queue_item_ready(item):
for event in item.events:
_send_event(event)
var done_event = _input_queue.pop_front()
done_event.queue_free()
2023-01-20 23:34:39 +01:00
if _input_queue.size() == 0:
_next_queue_item = null
emit_signal("idle")
else:
_input_queue[0].start()
2024-01-06 11:27:15 +01:00
# ------------------------------
# Public
# ------------------------------
func add_receiver(obj):
_receivers.append(obj)
func get_receivers():
return _receivers
2024-01-06 11:27:15 +01:00
func is_idle():
return _input_queue.size() == 0
func is_key_pressed(which):
var event = InputFactory.key_up(which)
return _pressed_keys.has(event.keycode) and _pressed_keys[event.keycode]
func is_action_pressed(which):
return _pressed_actions.has(which) and _pressed_actions[which]
func is_mouse_button_pressed(which):
return _pressed_mouse_buttons.has(which) and _pressed_mouse_buttons[which].pressed
func get_auto_flush_input():
return _auto_flush_input
func set_auto_flush_input(val):
_auto_flush_input = val
func wait(t):
2023-01-20 23:34:39 +01:00
if typeof(t) == TYPE_STRING:
var suffix = t.substr(t.length() - 1, 1)
var val = t.rstrip("s").rstrip("f").to_float()
2023-01-20 23:34:39 +01:00
if suffix.to_lower() == "s":
wait_secs(val)
2023-01-20 23:34:39 +01:00
elif suffix.to_lower() == "f":
wait_frames(val)
else:
wait_secs(t)
return self
2024-01-06 11:27:15 +01:00
func clear():
_last_event = null
_next_queue_item = null
2024-01-06 11:27:15 +01:00
for item in _input_queue:
item.free()
_input_queue.clear()
2024-01-06 11:27:15 +01:00
_pressed_keys.clear()
_pressed_actions.clear()
_pressed_mouse_buttons.clear()
_last_mouse_position = _default_mouse_position.duplicate()
# ------------------------------
# Event methods
# ------------------------------
func key_up(which):
var event = InputFactory.key_up(which)
_send_or_record_event(event)
return self
func key_down(which):
var event = InputFactory.key_down(which)
_send_or_record_event(event)
return self
func key_echo():
2023-01-20 23:34:39 +01:00
if _last_event != null and _last_event is InputEventKey:
var new_key = _last_event.duplicate()
new_key.echo = true
_send_or_record_event(new_key)
return self
2023-01-20 23:34:39 +01:00
func action_up(which, strength = 1.0):
var event = InputFactory.action_up(which, strength)
_send_or_record_event(event)
return self
2023-01-20 23:34:39 +01:00
func action_down(which, strength = 1.0):
var event = InputFactory.action_down(which, strength)
_send_or_record_event(event)
return self
2024-01-06 11:27:15 +01:00
func mouse_left_button_down(position = null, global_position = null):
var event = _new_defaulted_mouse_button_event(position, global_position)
event.pressed = true
event.button_index = MOUSE_BUTTON_LEFT
_send_or_record_event(event)
return self
2024-01-06 11:27:15 +01:00
func mouse_left_button_up(position = null, global_position = null):
var event = _new_defaulted_mouse_button_event(position, global_position)
event.pressed = false
event.button_index = MOUSE_BUTTON_LEFT
_send_or_record_event(event)
return self
2024-01-06 11:27:15 +01:00
func mouse_double_click(position = null, global_position = null):
var event = InputFactory.mouse_double_click(position, global_position)
event.double_click = true
_send_or_record_event(event)
return self
2024-01-06 11:27:15 +01:00
func mouse_right_button_down(position = null, global_position = null):
var event = _new_defaulted_mouse_button_event(position, global_position)
event.pressed = true
event.button_index = MOUSE_BUTTON_RIGHT
_send_or_record_event(event)
return self
2024-01-06 11:27:15 +01:00
func mouse_right_button_up(position = null, global_position = null):
var event = _new_defaulted_mouse_button_event(position, global_position)
event.pressed = false
event.button_index = MOUSE_BUTTON_RIGHT
_send_or_record_event(event)
return self
2023-01-20 23:34:39 +01:00
func mouse_motion(position, global_position = null):
2024-01-06 11:27:15 +01:00
var event = _new_defaulted_mouse_motion_event(position, global_position)
_send_or_record_event(event)
return self
2023-01-20 23:34:39 +01:00
func mouse_relative_motion(offset, speed = Vector2(0, 0)):
2024-01-06 11:27:15 +01:00
var last_event = _new_defaulted_mouse_motion_event(null, null)
var event = InputFactory.mouse_relative_motion(offset, last_event, speed)
_set_last_mouse_positions(event)
_send_or_record_event(event)
return self
2023-01-20 23:34:39 +01:00
func mouse_set_position(position, global_position = null):
2024-01-06 11:27:15 +01:00
var event = _new_defaulted_mouse_motion_event(position, global_position)
return self
func mouse_left_click_at(where, duration = "5f"):
wait_frames(1)
mouse_left_button_down(where)
hold_for(duration)
wait_frames(10)
return self
func send_event(event):
_send_or_record_event(event)
return self
func release_all():
for key in _pressed_keys:
2023-01-20 23:34:39 +01:00
if _pressed_keys[key]:
_send_event(InputFactory.key_up(key))
_pressed_keys.clear()
for key in _pressed_actions:
2023-01-20 23:34:39 +01:00
if _pressed_actions[key]:
_send_event(InputFactory.action_up(key))
_pressed_actions.clear()
for key in _pressed_mouse_buttons:
var event = _pressed_mouse_buttons[key].duplicate()
2023-01-20 23:34:39 +01:00
if event.pressed:
event.pressed = false
_send_event(event)
_pressed_mouse_buttons.clear()
return self
2024-01-06 11:27:15 +01:00
func wait_frames(num_frames):
var item = InputQueueItem.new(0, num_frames)
_add_queue_item(item)
return self
2023-01-20 23:34:39 +01:00
2024-01-06 11:27:15 +01:00
func wait_secs(num_secs):
var item = InputQueueItem.new(num_secs, 0)
_add_queue_item(item)
return self
2024-01-06 11:27:15 +01:00
func hold_for(duration):
if _last_event != null and _last_event.pressed:
var next_event = _last_event.duplicate()
next_event.pressed = false
2024-01-06 11:27:15 +01:00
wait(duration)
send_event(next_event)
return self