# ############################################################################## #(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", Callable(self, "_on_time_timeout")) # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ 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() # ############################################################################## # # ############################################################################## 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 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 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 func _init(r = null): if r != null: add_receiver(r) _last_mouse_position = _default_mouse_position.duplicate() _tree_items_parent = Node.new() Engine.get_main_loop().root.add_child(_tree_items_parent) _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): 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 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 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: if r == Input: Input.parse_input_event(event) if _auto_flush_input: Input.flush_buffered_events() 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 _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() if _input_queue.size() == 0: _next_queue_item = null emit_signal("idle") else: _input_queue[0].start() # ------------------------------ # Public # ------------------------------ func add_receiver(obj): _receivers.append(obj) func get_receivers(): return _receivers 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): if typeof(t) == TYPE_STRING: var suffix = t.substr(t.length() - 1, 1) var val = t.rstrip("s").rstrip("f").to_float() if suffix.to_lower() == "s": wait_secs(val) elif suffix.to_lower() == "f": wait_frames(val) else: wait_secs(t) return self func clear(): _last_event = 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() _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(): 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 = 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 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 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 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 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 func mouse_motion(position, global_position = null): var event = _new_defaulted_mouse_motion_event(position, global_position) _send_or_record_event(event) return self func mouse_relative_motion(offset, speed = Vector2(0, 0)): 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 func mouse_set_position(position, global_position = null): 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: 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() 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 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