diff options
Diffstat (limited to 'addons/gut/input_sender.gd')
| -rw-r--r-- | addons/gut/input_sender.gd | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/addons/gut/input_sender.gd b/addons/gut/input_sender.gd new file mode 100644 index 0000000..e9344d7 --- /dev/null +++ b/addons/gut/input_sender.gd @@ -0,0 +1,365 @@ +# ############################################################################## +#(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 + +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): + if(frame_delay > 0 and _delay_started): + _waited_frames += 1 + if(_waited_frames >= frame_delay): + emit_signal("event_ready") + + 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 + if(time_delay > 0): + var t = _delay_timer(time_delay) + t.connect("timeout", self, "_on_time_timeout") + +# ############################################################################## +# +# ############################################################################## +var _utils = load('res://addons/gut/utils.gd').get_instance() +var InputFactory = load("res://addons/gut/input_factory.gd") + +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 +# used by mouse_relative_motion. These use this instead of _last_event since +# it is logical to have a series of events happen between mouse motions. +var _last_mouse_motion = null +# used by hold_for and echo. +var _last_event = null + +# indexed by scancode, each entry contains a boolean value indicating the +# last emitted "pressed" value for that scancode. +var _pressed_keys = {} +var _pressed_actions = {} +var _pressed_mouse_buttons = {} + +signal idle + + +func _init(r=null): + if(r != null): + add_receiver(r) + + +func _send_event(event): + if(event is InputEventKey): + if((event.pressed and !event.echo) and is_key_pressed(event.scancode)): + _lgr.warn(str("InputSender: key_down called for ", event.as_text(), " when that key is already pressed. ", INPUT_WARN)) + _pressed_keys[event.scancode] = event.pressed + 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 + 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 + + for r in _receivers: + if(r == Input): + Input.parse_input_event(event) + else: + if(r.has_method("_input")): + r._input(event) + + if(r.has_method("_gui_input")): + r._gui_input(event) + + if(r.has_method("_unhandled_input")): + r._unhandled_input(event) + + +func _send_or_record_event(event): + _last_event = event + if(_next_queue_item != null): + _next_queue_item.events.append(event) + else: + _send_event(event) + + +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() + + if(_input_queue.size() == 0): + _next_queue_item = null + emit_signal("idle") + else: + _input_queue[0].start() + + +func _add_queue_item(item): + item.connect("event_ready", self, "_on_queue_item_ready", [item]) + _next_queue_item = item + _input_queue.append(item) + Engine.get_main_loop().root.add_child(item) + if(_input_queue.size() == 1): + item.start() + + +func add_receiver(obj): + _receivers.append(obj) + + +func get_receivers(): + return _receivers + + +func wait(t): + if(typeof(t) == TYPE_STRING): + var suffix = t.substr(t.length() -1, 1) + var val = float(t.rstrip('s').rstrip('f')) + + if(suffix.to_lower() == 's'): + wait_secs(val) + elif(suffix.to_lower() == 'f'): + wait_frames(val) + else: + wait_secs(t) + + return self + + +func wait_frames(num_frames): + var item = InputQueueItem.new(0, num_frames) + _add_queue_item(item) + return self + + +func wait_secs(num_secs): + var item = InputQueueItem.new(num_secs, 0) + _add_queue_item(item) + return self + + +# ------------------------------ +# 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(): + 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 + + +func action_up(which, strength=1.0): + var event = InputFactory.action_up(which, strength) + _send_or_record_event(event) + return self + + +func action_down(which, strength=1.0): + var event = InputFactory.action_down(which, strength) + _send_or_record_event(event) + return self + + +func mouse_left_button_down(position, global_position=null): + var event = InputFactory.mouse_left_button_down(position, global_position) + _send_or_record_event(event) + return self + + +func mouse_left_button_up(position, global_position=null): + var event = InputFactory.mouse_left_button_up(position, global_position) + _send_or_record_event(event) + return self + + +func mouse_double_click(position, global_position=null): + var event = InputFactory.mouse_double_click(position, global_position) + event.doubleclick = true + _send_or_record_event(event) + return self + + +func mouse_right_button_down(position, global_position=null): + var event = InputFactory.mouse_right_button_down(position, global_position) + _send_or_record_event(event) + return self + + +func mouse_right_button_up(position, global_position=null): + var event = InputFactory.mouse_right_button_up(position, global_position) + _send_or_record_event(event) + return self + + +func mouse_motion(position, global_position=null): + var event = InputFactory.mouse_motion(position, global_position) + _last_mouse_motion = event + _send_or_record_event(event) + return self + + +func mouse_relative_motion(offset, speed=Vector2(0, 0)): + var event = InputFactory.mouse_relative_motion(offset, _last_mouse_motion, speed) + _last_mouse_motion = event + _send_or_record_event(event) + return self + + +func mouse_set_position(position, global_position=null): + _last_mouse_motion = InputFactory.mouse_motion(position, global_position) + return self + + +func send_event(event): + _send_or_record_event(event) + return self + + +func release_all(): + for key in _pressed_keys: + if(_pressed_keys[key]): + _send_event(InputFactory.key_up(key)) + _pressed_keys.clear() + + for key in _pressed_actions: + 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() + if(event.pressed): + event.pressed = false + _send_event(event) + _pressed_mouse_buttons.clear() + + +func hold_for(duration): + if(_last_event != null and _last_event.pressed): + var next_event = _last_event.duplicate() + next_event.pressed = false + wait(duration) + send_event(next_event) + return self + + +func clear(): + pass + + _last_event = null + _last_mouse_motion = null + _next_queue_item = null + + for item in _input_queue: + item.free() + _input_queue.clear() + + _pressed_keys.clear() + _pressed_actions.clear() + _pressed_mouse_buttons.clear() + +func is_idle(): + return _input_queue.size() == 0 + +func is_key_pressed(which): + var event = InputFactory.key_up(which) + return _pressed_keys.has(event.scancode) and _pressed_keys[event.scancode] + +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] |
