diff --git a/#Scenes/MapUI.tscn b/#Scenes/MapUI.tscn new file mode 100644 index 00000000..b20815a3 --- /dev/null +++ b/#Scenes/MapUI.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=3 format=3 uid="uid://ki060lfrgty4"] + +[ext_resource type="Script" path="res://UI/MapUI.gd" id="1_enf2f"] +[ext_resource type="Script" path="res://addons/SmoothScroll/SmoothScrollContainer.gd" id="2_2gata"] + +[node name="MapUi" type="Control" node_paths=PackedStringArray("color_rect", "scroll_container", "room_container", "room_addition_node")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_enf2f") +color_rect = NodePath("ColorRect") +scroll_container = NodePath("SmoothScrollContainer") +room_container = NodePath("SmoothScrollContainer/RoomContainer") +room_addition_node = NodePath("SmoothScrollContainer/RoomContainer/RoomAdditionNode") + +[node name="ColorRect" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.309804) + +[node name="SmoothScrollContainer" type="ScrollContainer" parent="."] +layout_mode = 0 +offset_left = 223.0 +offset_top = 60.0 +offset_right = 1123.0 +offset_bottom = 660.0 +script = ExtResource("2_2gata") +speed = 1.0 +hide_scrollbar_over_time = true +scrollbar_hide_time = 0.5 + +[node name="RoomContainer" type="ColorRect" parent="SmoothScrollContainer"] +clip_contents = true +custom_minimum_size = Vector2(900, 600) +layout_mode = 2 +mouse_filter = 1 +color = Color(0.498039, 0.498039, 0.498039, 1) + +[node name="RoomAdditionNode" type="Control" parent="SmoothScrollContainer/RoomContainer"] +anchors_preset = 0 +offset_right = 40.0 +offset_bottom = 40.0 +mouse_filter = 1 + +[node name="Button" type="Button" parent="."] +layout_mode = 0 +offset_left = 4.0 +offset_top = 550.0 +offset_right = 65.0 +offset_bottom = 581.0 +text = "Return" + +[connection signal="pressed" from="Button" to="." method="_on_return_button_press"] diff --git a/#Scenes/TestingScene.tscn b/#Scenes/TestingScene.tscn index c457b82a..05c51d69 100644 --- a/#Scenes/TestingScene.tscn +++ b/#Scenes/TestingScene.tscn @@ -1,5 +1,6 @@ -[gd_scene load_steps=22 format=3 uid="uid://b60uabg68ra1l"] +[gd_scene load_steps=25 format=3 uid="uid://b60uabg68ra1l"] +[ext_resource type="Script" path="res://Map/MapManager.gd" id="2_50npk"] [ext_resource type="PackedScene" uid="uid://clmg3l3n28x38" path="res://Entity/Player/Player.tscn" id="3_4psp7"] [ext_resource type="PackedScene" uid="uid://dpjfy4pv0vxst" path="res://Cards/CardContainer.tscn" id="3_e7sws"] [ext_resource type="Resource" uid="uid://dxgoopi1roxu4" path="res://Cards/Resource/Card_Damage.tres" id="4_wvn3v"] @@ -19,6 +20,8 @@ [ext_resource type="Script" path="res://UI/EndTurnButton.gd" id="14_dpe64"] [ext_resource type="Texture2D" uid="uid://hqkt8t1v2f5h" path="res://Cards/deck_pile.png" id="18_b54hn"] [ext_resource type="Script" path="res://UI/DeckPileUISetter.gd" id="19_ucc6f"] +[ext_resource type="Texture2D" uid="uid://cjlatwiw7r80d" path="res://Map/map_icon.png" id="20_tiho6"] +[ext_resource type="Script" path="res://UI/MapButton.gd" id="22_druf4"] [sub_resource type="Resource" id="Resource_82ci8"] script = ExtResource("10_qowiy") @@ -39,6 +42,9 @@ metadata/_edit_vertical_guides_ = [1216.0] [node name="Battler" parent="." instance=ExtResource("8_qtw1k")] +[node name="TestMap" type="Node2D" parent="."] +script = ExtResource("2_50npk") + [node name="Player" parent="." instance=ExtResource("3_4psp7")] position = Vector2(595, 284) @@ -169,4 +175,19 @@ stretch_mode = 0 script = ExtResource("19_ucc6f") metadata/_edit_use_anchors_ = true +[node name="MapIcon" type="TextureButton" parent="CanvasLayer/UIControl"] +layout_mode = 0 +offset_left = 1174.0 +offset_top = 31.0 +offset_right = 1214.0 +offset_bottom = 71.0 +texture_normal = ExtResource("20_tiho6") +texture_pressed = ExtResource("20_tiho6") +texture_hover = ExtResource("20_tiho6") +texture_disabled = ExtResource("20_tiho6") +texture_focused = ExtResource("20_tiho6") +ignore_texture_size = true +stretch_mode = 0 +script = ExtResource("22_druf4") + [connection signal="pressed" from="CanvasLayer/UIControl/EndTurnButton" to="CanvasLayer/UIControl/EndTurnButton" method="_on_pressed"] diff --git a/Event/EventBase.gd b/Event/EventBase.gd index 7039b70a..9417ab99 100644 --- a/Event/EventBase.gd +++ b/Event/EventBase.gd @@ -15,3 +15,5 @@ func _load_scene() -> Node: var loadScene: PackedScene = load(packedScene) return loadScene.instantiate() +func get_room_abbreviation() -> String: + return "" diff --git a/Event/EventHeal.gd b/Event/EventHeal.gd index 5c9c9d63..285c6967 100644 --- a/Event/EventHeal.gd +++ b/Event/EventHeal.gd @@ -8,3 +8,7 @@ func _init() -> void: # @Override func _update_event() -> void: print("Updating Heal Event") + +# @Override +func get_room_abbreviation() -> String: + return "H" diff --git a/Event/EventMob.gd b/Event/EventMob.gd index 22039df8..a8a2c612 100644 --- a/Event/EventMob.gd +++ b/Event/EventMob.gd @@ -8,3 +8,7 @@ func _init() -> void: # @Override func _update_event() -> void: print("Update Mob") + +# @Override +func get_room_abbreviation() -> String: + return "M" diff --git a/Event/EventRandom.gd b/Event/EventRandom.gd index 9dc71de4..8285b383 100644 --- a/Event/EventRandom.gd +++ b/Event/EventRandom.gd @@ -8,3 +8,7 @@ func _init() -> void: # @Override func _update_event() -> void: print("Updating Random Event") + +# @Override +func get_room_abbreviation() -> String: + return "R" diff --git a/Event/EventShop.gd b/Event/EventShop.gd index c1bb4b57..fe49209f 100644 --- a/Event/EventShop.gd +++ b/Event/EventShop.gd @@ -8,3 +8,7 @@ func _init() -> void: # @Override func _update_event() -> void: print("Updating shop") + +# @Override +func get_room_abbreviation() -> String: + return "S" diff --git a/Map/MapManager.gd b/Map/MapManager.gd index 3d337933..b5fad223 100644 --- a/Map/MapManager.gd +++ b/Map/MapManager.gd @@ -1,8 +1,11 @@ extends Node2D ## Class to manage backend for rooms (generation and such) + +var current_map: MapBase +var map_width_array: Array[int] = [1, 3, 5, 7, 9, 11, 9, 7, 5, 3, 1] #map_floors_width changes the width of the map's floors -static func create_map(map_floors_width: Array[int]) -> MapBase: ## Generates and Populates a map with rooms that have random room types. More in depth algorithms will be added in the future +func create_map(map_floors_width: Array[int]) -> MapBase: ## Generates and Populates a map with rooms that have random room types. More in depth algorithms will be added in the future var _map: MapBase = MapBase.new() var _grid: Array[Array] = [] ## 2d array to return. this will be populated with rooms var _max_floor_size: int = map_floors_width.max() @@ -26,3 +29,9 @@ static func create_map(map_floors_width: Array[int]) -> MapBase: ## Generates an _grid[index_height].append_array(_padding) _map.rooms = _grid return _map as MapBase + +func _ready(): + current_map = create_map(map_width_array) + +func is_map_initialized() -> bool: + return current_map != null diff --git a/Map/RoomBase.gd b/Map/RoomBase.gd index 5998b6e5..36da8264 100644 --- a/Map/RoomBase.gd +++ b/Map/RoomBase.gd @@ -10,3 +10,6 @@ class_name RoomBase func _to_string() -> String: return "RoomBase" + +func get_room_abbreviation() -> String: + return room_event.get_room_abbreviation() diff --git a/Map/RoomUI.gd b/Map/RoomUI.gd new file mode 100644 index 00000000..4990b4fd --- /dev/null +++ b/Map/RoomUI.gd @@ -0,0 +1,7 @@ +extends Control + +var room: RoomBase +@export var label: Label + +func set_label(title: String): + label.set_text(title) diff --git a/Map/RoomUI.tscn b/Map/RoomUI.tscn new file mode 100644 index 00000000..b09ed9ba --- /dev/null +++ b/Map/RoomUI.tscn @@ -0,0 +1,27 @@ +[gd_scene load_steps=3 format=3 uid="uid://ck87o46d1kvpu"] + +[ext_resource type="Script" path="res://Map/RoomUI.gd" id="1_n442j"] +[ext_resource type="Texture2D" uid="uid://dxpg0rdhgt587" path="res://Map/room_icon.png" id="2_mdfia"] + +[node name="RoomUI" type="Control" node_paths=PackedStringArray("label")] +layout_mode = 3 +anchors_preset = 0 +size_flags_horizontal = 4 +size_flags_vertical = 4 +script = ExtResource("1_n442j") +label = NodePath("RoomEdge/Label") + +[node name="RoomEdge" type="TextureRect" parent="."] +layout_mode = 0 +offset_right = 50.0 +offset_bottom = 50.0 +texture = ExtResource("2_mdfia") +expand_mode = 1 + +[node name="Label" type="Label" parent="RoomEdge"] +layout_mode = 0 +offset_right = 50.0 +offset_bottom = 50.0 +text = "A" +horizontal_alignment = 1 +vertical_alignment = 1 diff --git a/Map/map_icon.png b/Map/map_icon.png new file mode 100644 index 00000000..9841c591 Binary files /dev/null and b/Map/map_icon.png differ diff --git a/Map/map_icon.png.import b/Map/map_icon.png.import new file mode 100644 index 00000000..b7592678 --- /dev/null +++ b/Map/map_icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjlatwiw7r80d" +path="res://.godot/imported/map_icon.png-ca4387eafbb70e74b0ffbb41a9cb9905.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Map/map_icon.png" +dest_files=["res://.godot/imported/map_icon.png-ca4387eafbb70e74b0ffbb41a9cb9905.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Map/room_icon.png b/Map/room_icon.png new file mode 100644 index 00000000..1614c338 Binary files /dev/null and b/Map/room_icon.png differ diff --git a/Map/room_icon.png.import b/Map/room_icon.png.import new file mode 100644 index 00000000..5b7ab584 --- /dev/null +++ b/Map/room_icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxpg0rdhgt587" +path="res://.godot/imported/room_icon.png-92b66821e87e400c614959fc9bbfa774.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Map/room_icon.png" +dest_files=["res://.godot/imported/room_icon.png-92b66821e87e400c614959fc9bbfa774.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Tests/test_map.gd b/Tests/test_map.gd index 0d5ae397..531472b6 100644 --- a/Tests/test_map.gd +++ b/Tests/test_map.gd @@ -1,10 +1,12 @@ extends GutTest ## Tests for MapManager, more will be added in the future var test_generator = load("res://Map/MapManager.gd") +var map_manager: MapManager = null func test_map_gen(): + map_manager = test_generator.new() var test_width: Array[int] = [1,3,5,3,1] - var test_map: MapBase = test_generator.create_map(test_width) + var test_map: MapBase = map_manager.create_map(test_width) var expected_null_array: Array[Array] = [[0,0], [0,1], [0,3],[0,4],[1,0],[1,4],[3,0],[3,4],[4,0],[4,1],[4,3],[4,4]] var expected_exists_array: Array[Array] = [[0,2],[1,1],[1,2],[1,3],[2,0],[2,1],[2,2],[2,3],[2,4],[3,1],[3,2],[3,3],[4,2]] for couple: Array[int] in expected_null_array: @@ -14,3 +16,6 @@ func test_map_gen(): for couple: Array[int] in expected_exists_array: var _room = test_map.rooms[couple[0]][couple[1]] assert_not_null(_room, "Expected not null at %s but got %s" % [couple, _room]) + +func after_each(): + map_manager.free() diff --git a/UI/CardScrollUI.gd b/UI/CardScrollUI.gd index 4381509c..b6e7d571 100644 --- a/UI/CardScrollUI.gd +++ b/UI/CardScrollUI.gd @@ -23,7 +23,7 @@ func _on_button_pressed() -> void: #deletes the root node CardScrollUI with the escape key func _input(_inputevent: InputEvent) -> void: - if Input.is_key_pressed(KEY_ESCAPE): + if (_inputevent.is_action_pressed("cancel_action")): queue_free() diff --git a/UI/MapButton.gd b/UI/MapButton.gd new file mode 100644 index 00000000..dfa0f6c3 --- /dev/null +++ b/UI/MapButton.gd @@ -0,0 +1,11 @@ +extends TextureButton +# Button to bring up the map UI + +@onready var map_scene: PackedScene = preload("res://#Scenes/MapUI.tscn") + +func _pressed(): + assert(MapManager.is_map_initialized(), "Please instantiate map") + var parent: Control = $".." + var map: Control = map_scene.instantiate() + + parent.add_child(map) diff --git a/UI/MapUI.gd b/UI/MapUI.gd new file mode 100644 index 00000000..a0658168 --- /dev/null +++ b/UI/MapUI.gd @@ -0,0 +1,104 @@ +extends Control + +var map_scene: PackedScene = preload("res://#Scenes/CardScrollUI.tscn") +var room_ui: PackedScene = load("res://Map/RoomUI.tscn") +var _padding_offset = 20 +var _MINIMUM_ROOM_WIDTH = 510 +var _MINIMUM_ROOM_HEIGHT = 490 + +@export var color_rect: ColorRect +@export var scroll_container: SmoothScrollContainer +@export var room_container: ColorRect +@export var room_addition_node: Control + +func _input(_inputevent: InputEvent) -> void: + if (_inputevent.is_action_pressed("cancel_action")): + queue_free() + + +func _on_return_button_press(): + queue_free() + + +func _ready(): + var current_map: MapBase = MapManager.current_map + + # Create New Room Object to append to the room container + var new_room: Control = room_ui.instantiate() + var new_room_texture_rect: TextureRect = Helpers.get_first_child_node_of_type(new_room, TextureRect) + var new_room_size: Vector2 = new_room_texture_rect.get_size() + + + var room_container_width: float = _get_combined_room_width(new_room_texture_rect) + # Set the max width between what we calculated above and the minimum room width constant + room_container_width = max(room_container_width, _MINIMUM_ROOM_WIDTH) + + var room_container_height: float = _get_combined_room_height(new_room_texture_rect) + # Set the max height between what we calculated above and the minimum room height constant + room_container_height = max(room_container_height, _MINIMUM_ROOM_HEIGHT) + + # Set the custom minimum size of the room container to allow scrolling + room_container.set_custom_minimum_size(Vector2(room_container_width, room_container_height)) + + # Set the size of the scroll container to be dynamic to the max numbers of rooms of a floor + scroll_container.set_size(Vector2(room_container_width, scroll_container.get_size().y)) + + # We're dynamically sizing the width of the map, as such we need to position it to center it on the screen. + # X position is calculated by getting half the width of the game screen, then subtracting that from + # half the width of the scroll_container + var scroll_container_position_x: float = color_rect.get_size().x / 2 - scroll_container.get_size().x / 2 + scroll_container.set_position(Vector2(scroll_container_position_x, scroll_container.position.y)) + + var scroll_container_bottom_y_position: float = scroll_container.position.y + scroll_container.get_size().y + + # Starting positions for the next room to be generated + # X = the Halfway position of room container - half the size of the longest floor to account for centering all of the rooms. + # Add a little padding to push it away from the right edge. + # Y = Height of the Room Container that was calculated in new_room_container_height - (Screen Height - scroll container's bottom y position) + var start_position_for_next_room_x: float = room_container.position.x + _padding_offset + var start_position_for_next_room_y: float = room_container.get_custom_minimum_size().y - (color_rect.get_size().y - scroll_container_bottom_y_position) + var position_for_next_room: Vector2 = Vector2(start_position_for_next_room_x, start_position_for_next_room_y) + + for floor_array: Array[RoomBase] in current_map.rooms: + # When we're done populating a floor and we go to the next index, reset the X start position + position_for_next_room.x = start_position_for_next_room_x + for room: RoomBase in floor_array: + if (room != null): + var room_display: Control = room_ui.instantiate() + room_addition_node.add_child(room_display) + room_display.set_label(room.get_room_abbreviation()) + room_display.position = position_for_next_room + # When we go through the array of a floor to put a room down, + # increase the X position to the new potential area the room will be displayed on. + position_for_next_room.x += new_room_size.x + _padding_offset + # When we finish the array for a floor we need to go to the next floor up, + # so increase the Y position to the new area the room will be displayed on. + position_for_next_room.y -= new_room_size.y + _padding_offset + + # Calculate the new position of where we should put the rooms after we populate the rooms out + # We want to position the rooms in the center of the room container, to do so: + # Get half the size of the room container and subtract it by half the size of the width of the combined rooms + # This is to account for in case the size of the rooms is smaller than the container we put it in + var new_room_position_x: float = room_container.get_custom_minimum_size().x / 2 - _get_combined_room_width(new_room_texture_rect) / 2 + + # If the height of the combined rooms is less than the minimum room height + # then calculate the position for it to be centered in the middle of the map: + # We again want to place the rooms in the center of the container, but the y position of where the rooms are is relative to the container + # Hence we subtract half the size of the container from half the size of the height of the combined rooms to get the center point + # then subtract it from the position of the container + var new_room_position_y: float = room_container.position.y + if (_get_combined_room_height(new_room_texture_rect) < _MINIMUM_ROOM_HEIGHT): + new_room_position_y = room_container.position.y - room_container.get_custom_minimum_size().y / 2 + _get_combined_room_height(new_room_texture_rect) / 2 + room_addition_node.set_position(Vector2(new_room_position_x, new_room_position_y)) + +# Get the width of room nodes, by getting the size of what a room is w/ some offset +# multiplying that by the max number in the map_width_array to get the width of the largest floor then add offset +# to account for the other end of the floor +func _get_combined_room_width(texture_rect: TextureRect) -> float: + return ((texture_rect.get_size().x + _padding_offset) * MapManager.map_width_array.max()) + _padding_offset + +# Calculate the height of the container where the rooms will reside in. This will be dynamic based on the map array that we have. +# The array we have in MapManager, each element will increase the height of the map display, +# multiply by the size of a room w/ some offset to dynamically set the size of the container of which we will be scrolling. +func _get_combined_room_height(texture_rect: TextureRect) -> float: + return MapManager.map_width_array.size() * (texture_rect.get_size().y + _padding_offset) + _padding_offset diff --git a/addons/SmoothScroll/SmoothScrollContainer.gd b/addons/SmoothScroll/SmoothScrollContainer.gd new file mode 100644 index 00000000..2e9a919f --- /dev/null +++ b/addons/SmoothScroll/SmoothScrollContainer.gd @@ -0,0 +1,695 @@ +## Smooth scroll functionality for ScrollContainer +## +## Applies velocity based momentum and "overdrag" +## functionality to a ScrollContainer +@tool +extends ScrollContainer +class_name SmoothScrollContainer + +## Drag impact for one scroll input +@export_range(0, 10, 0.01, "or_greater") +var speed := 5.0 +## Whether the content of this container should be allowed to overshoot at the ends +## before interpolating back to its bounds +@export +var allow_overdragging := true +## Softness of damping when "overdragging" with wheel button +@export_range(0, 1) +var damping_scroll := 0.1 +## Softness of damping when "overdragging" with dragging +@export_range(0, 1) +var damping_drag := 0.1 +## Scrolls to currently focused child element +@export +var follow_focus_ := true +## Margin of the currently focused element +@export_range(0, 50) +var follow_focus_margin := 20 +## Makes the container scrollable vertically +@export +var allow_vertical_scroll := true +## Makes the container scrollable horizontally +@export +var allow_horizontal_scroll := true +## Makes the container only scrollable where the content has overflow +@export +var auto_allow_scroll := true +## Friction when using mouse wheel +@export_range(0, 1) +var friction_scroll := 0.9 +## Friction when using touch +@export_range(0, 1) +var friction_drag := 0.9 +## Hides scrollbar as long as not hovered or interacted with +@export +var hide_scrollbar_over_time:= false: + set(val): hide_scrollbar_over_time = _set_hide_scrollbar_over_time(val) +## Time after scrollbar starts to fade out when 'hide_scrollbar_over_time' is true +@export +var scrollbar_hide_time := 5.0 +## Fadein time for scrollbar when 'hide_scrollbar_over_time' is true +@export +var scrollbar_fade_in_time := 0.2 +## Fadeout time for scrollbar when 'hide_scrollbar_over_time' is true +@export +var scrollbar_fade_out_time := 0.5 +## Adds debug information +@export +var debug_mode := false + +## Current velocity of the `content_node` +var velocity := Vector2(0,0) +## Below this value, velocity is set to `0` +var just_stop_under := 0.01 +## Below this value, snap content to boundary +var just_snap_under := 0.4 +## Control node to move when scrolling +var content_node : Control +## Current position of `content_node` +var pos := Vector2(0, 0) +## When true, `content_node`'s position is only set by dragging the h scroll bar +var h_scrollbar_dragging := false +## When true, `content_node`'s position is only set by dragging the v scroll bar +var v_scrollbar_dragging := false +## Current friction +var friction := 0.9 +## When ture, `content_node` follows drag position +var content_dragging := false +## Damping to use +var damping := 0.1 +## Distance between content_node's bottom and bottom of the scroll box +var bottom_distance := 0.0 +## Distance between content_node and top of the scroll box +var top_distance := 0.0 +## Distance between content_node's right and right of the scroll box +var right_distance := 0.0 +## Distance between content_node and left of the scroll box +var left_distance := 0.0 +## Content node position where dragging starts +var drag_start_pos := Vector2.ZERO +## Timer for hiding scroll bar +var scrollbar_hide_timer := Timer.new() +## Tween for hiding scroll bar +var scrollbar_hide_tween : Tween +## [0,1] Mouse or touch's relative movement accumulation when overdrag[br] +## [2,3,4,5] Top_distance, bottom_distance, left_distance, right_distance +var drag_temp_data := [] + +## If content is being scrolled +var is_scrolling := false: + set(val): + is_scrolling = val + if is_scrolling: + emit_signal("scroll_started") + else: + emit_signal("scroll_ended") + +## Last type of input used to scroll +enum SCROLL_TYPE {WHEEL, BAR, DRAG} +var last_scroll_type : SCROLL_TYPE + +#################### +##### Virtual functions + +func _ready() -> void: + if debug_mode: + setup_debug_drawing() + + get_v_scroll_bar().scrolling.connect(_on_VScrollBar_scrolling) + get_h_scroll_bar().scrolling.connect(_on_HScrollBar_scrolling) + get_v_scroll_bar().gui_input.connect(_scrollbar_input) + get_h_scroll_bar().gui_input.connect(_scrollbar_input) + get_viewport().gui_focus_changed.connect(_on_focus_changed) + + for c in get_children(): + if not c is ScrollBar: + content_node = c + + add_child(scrollbar_hide_timer) + scrollbar_hide_timer.timeout.connect(_scrollbar_hide_timer_timeout) + if hide_scrollbar_over_time: + scrollbar_hide_timer.start(scrollbar_hide_time) + get_tree().node_added.connect(_on_node_added) + +func _process(delta: float) -> void: + if Engine.is_editor_hint(): return + calculate_distance() + scroll(true, velocity.y, pos.y, delta) + scroll(false, velocity.x, pos.x, delta) + # Update vertical scroll bar + get_v_scroll_bar().set_value_no_signal(-pos.y) + get_v_scroll_bar().queue_redraw() + # Update horizontal scroll bar + get_h_scroll_bar().set_value_no_signal(-pos.x) + get_h_scroll_bar().queue_redraw() + # Update state + update_state() + + if debug_mode: + queue_redraw() + +# Forwarding scroll inputs from scrollbar +func _scrollbar_input(event: InputEvent) -> void: + if hide_scrollbar_over_time: + show_scrollbars() + scrollbar_hide_timer.start(scrollbar_hide_time) + + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_WHEEL_DOWN\ + or event.button_index == MOUSE_BUTTON_WHEEL_UP\ + or event.button_index == MOUSE_BUTTON_WHEEL_LEFT\ + or event.button_index == MOUSE_BUTTON_WHEEL_RIGHT: + _gui_input(event) + +func _gui_input(event: InputEvent) -> void: + if hide_scrollbar_over_time: + show_scrollbars() + scrollbar_hide_timer.start(scrollbar_hide_time) + + v_scrollbar_dragging = get_v_scroll_bar().has_focus() # != pressed => TODO + h_scrollbar_dragging = get_h_scroll_bar().has_focus() + + if event is InputEventMouseButton: + match event.button_index: + MOUSE_BUTTON_WHEEL_DOWN: + if event.pressed: + last_scroll_type = SCROLL_TYPE.WHEEL + if event.shift_pressed: + if should_scroll_horizontal(): + velocity.x -= speed + else: + if should_scroll_vertical(): + velocity.y -= speed + friction = friction_scroll + damping = damping_scroll + MOUSE_BUTTON_WHEEL_UP: + if event.pressed: + last_scroll_type = SCROLL_TYPE.WHEEL + if event.shift_pressed: + if should_scroll_horizontal(): + velocity.x += speed + else: + if should_scroll_vertical(): + velocity.y += speed + friction = friction_scroll + damping = damping_scroll + MOUSE_BUTTON_WHEEL_LEFT: + if event.pressed: + last_scroll_type = SCROLL_TYPE.WHEEL + if event.shift_pressed: + if should_scroll_vertical(): + velocity.y -= speed + else: + if should_scroll_horizontal(): + velocity.x += speed + friction = friction_scroll + damping = damping_scroll + MOUSE_BUTTON_WHEEL_RIGHT: + if event.pressed: + last_scroll_type = SCROLL_TYPE.WHEEL + if event.shift_pressed: + if should_scroll_vertical(): + velocity.y += speed + else: + if should_scroll_horizontal(): + velocity.x -= speed + friction = friction_scroll + damping = damping_scroll + MOUSE_BUTTON_LEFT: + if event.pressed: + content_dragging = true + last_scroll_type = SCROLL_TYPE.DRAG + friction = 0.0 + drag_start_pos = content_node.position + init_drag_temp_data() + else: + content_dragging = false + friction = friction_drag + damping = damping_drag + + if event is InputEventScreenDrag or event is InputEventMouseMotion: + if content_dragging: + is_scrolling = true + if should_scroll_horizontal(): + drag_temp_data[0] += event.relative.x + if should_scroll_vertical(): + drag_temp_data[1] += event.relative.y + remove_all_children_focus(self) + handle_content_dragging() + + if event is InputEventScreenTouch: + if event.pressed: + content_dragging = true + last_scroll_type = SCROLL_TYPE.DRAG + friction = 0.0 + drag_start_pos = content_node.position + init_drag_temp_data() + else: + content_dragging = false + friction = friction_drag + damping = damping_drag + # Handle input + get_tree().get_root().set_input_as_handled() + +# Scroll to new focused element +func _on_focus_changed(control: Control) -> void: + var is_child := false + if content_node.is_ancestor_of(control): + is_child = true + if not is_child: + return + if not follow_focus_: + return + + var focus_size_x = control.size.x + var focus_size_y = control.size.y + var focus_left = control.global_position.x - self.global_position.x + var focus_right = focus_left + focus_size_x + var focus_top = control.global_position.y - self.global_position.y + var focus_bottom = focus_top + focus_size_y + + if focus_top < 0.0: + scroll_y_to(content_node.position.y - focus_top + follow_focus_margin) + + if focus_bottom > self.size.y: + scroll_y_to(content_node.position.y - focus_bottom + self.size.y - follow_focus_margin) + + if focus_left < 0.0: + scroll_x_to(content_node.position.x - focus_left + follow_focus_margin) + + if focus_right > self.size.x: + scroll_x_to(content_node.position.x - focus_right + self.size.x - follow_focus_margin) + +func _on_VScrollBar_scrolling() -> void: + v_scrollbar_dragging = true + last_scroll_type = SCROLL_TYPE.BAR + +func _on_HScrollBar_scrolling() -> void: + h_scrollbar_dragging = true + last_scroll_type = SCROLL_TYPE.BAR + +func _draw() -> void: + if debug_mode: + draw_debug() + +# Sets default mouse filter for SmoothScroll children to MOUSE_FILTER_PASS +func _on_node_added(node) -> void: + if node is Control and Engine.is_editor_hint(): + if is_ancestor_of(node): + node.mouse_filter = Control.MOUSE_FILTER_PASS + +func _scrollbar_hide_timer_timeout() -> void: + if !any_scroll_bar_dragged(): + hide_scrollbars() + +func _set_hide_scrollbar_over_time(value) -> bool: + if value == false: + if scrollbar_hide_timer != null: + scrollbar_hide_timer.stop() + if scrollbar_hide_tween != null: + scrollbar_hide_tween.kill() + get_h_scroll_bar().modulate = Color.WHITE + get_v_scroll_bar().modulate = Color.WHITE + else: + if scrollbar_hide_timer != null and scrollbar_hide_timer.is_inside_tree(): + scrollbar_hide_timer.start(scrollbar_hide_time) + return value +##### Virtual functions +#################### + + +#################### +##### LOGIC + +func scroll(vertical : bool, axis_velocity : float, axis_pos : float, delta : float): + # If no scroll needed, don't apply forces + if vertical: + if not should_scroll_vertical(): + return + else: + if not should_scroll_horizontal(): + return + + # If velocity is too low, just set it to 0 + if abs(axis_velocity) <= just_stop_under: + axis_velocity = 0.0 + + # Applies counterforces when overdragging + if not content_dragging: + var result = handle_overdrag(vertical, axis_velocity, axis_pos) + axis_velocity = result[0] + axis_pos = result[1] + + # Move content node by applying velocity + axis_pos += axis_velocity * (pow(friction, delta*100) - 1) / log(friction) + axis_velocity *= pow(friction, delta*100) + + # If using scroll bar dragging, set the content_node's + # position by using the scrollbar position + if handle_scrollbar_drag(): + return + + if vertical: + if not allow_overdragging: + # Clamp if calculated position is beyond boundary + if is_outside_top_boundary(axis_pos): + axis_pos = 0.0 + axis_velocity = 0.0 + elif is_outside_bottom_boundary(axis_pos): + axis_pos = self.size.y - content_node.size.y + axis_velocity = 0.0 + + content_node.position.y = axis_pos + pos.y = axis_pos + velocity.y = axis_velocity + else: + if not allow_overdragging: + # Clamp if calculated position is beyond boundary + if is_outside_left_boundary(axis_pos): + axis_pos = 0.0 + axis_velocity = 0.0 + elif is_outside_right_boundary(axis_pos): + axis_pos = self.size.x - content_node.size.x + axis_velocity = 0.0 + + content_node.position.x = axis_pos + pos.x = axis_pos + velocity.x = axis_velocity + +func handle_overdrag(vertical : bool, axis_velocity : float, axis_pos : float) -> Array: + # Left/Right or Top/Bottom depending on x or y + var dist1 = top_distance if vertical else left_distance + var dist2 = bottom_distance if vertical else right_distance + + # Modify dist2 if content is smaller than container + if vertical: + var size_y = size.y + if get_h_scroll_bar().visible: + size_y -= get_h_scroll_bar().size.y + dist2 += max(size_y - content_node.size.y, 0) + else: + var size_x = content_node.size.x + if get_v_scroll_bar().visible: + size_x -= get_v_scroll_bar().size.x + dist2 += max(size_x - content_node.size.x, 0) + + var calculate = func(dist): + # Apply bounce force + axis_velocity = lerp(axis_velocity, -dist/8*get_process_delta_time()*100, damping) + # If it will be fast enough to scroll back next frame + # Apply a speed that will make it scroll back exactly + if will_stop_within(vertical, axis_velocity): + axis_velocity = -dist*(1-friction)/(1-pow(friction, stop_frame(axis_velocity))) + + return axis_velocity + + var result = [axis_velocity, axis_pos] + + if not (dist1 > 0 or dist2 < 0) or will_stop_within(vertical, axis_velocity): + return result + + # Overdrag on top or left + if dist1 > 0: + # Snap to boundary if close enough + if dist1 < just_snap_under and abs(axis_velocity) < just_snap_under: + result[0] = 0.0 + result[1] -= dist1 + else: + result[0] = calculate.call(dist1) + # Overdrag on bottom or right + elif dist2 < 0: + # Snap to boundary if close enough + if dist2 > -just_snap_under and abs(axis_velocity) < just_snap_under: + result[0] = 0.0 + result[1] -= dist2 + else: + result[0] = calculate.call(dist2) + + return result + +## Returns true when scrollbar was dragged +func handle_scrollbar_drag() -> bool: + if h_scrollbar_dragging: + velocity.x = 0.0 + pos.x = content_node.position.x + return true + + if v_scrollbar_dragging: + velocity.y = 0.0 + pos.y = content_node.position.y + return true + return false + +func handle_content_dragging() -> void: + var calculate_dest = func(delta: float, damping: float) -> float: + if delta >= 0.0: + return delta / (1 + delta * damping * 0.1) + else: + return delta + + var calculate_position = func( + temp_dist1: float, # Temp distance + temp_dist2: float, + temp_relative: float # Event's relative movement accumulation + ) -> float: + if temp_relative + temp_dist1 > 0.0: + var delta = min(temp_relative, temp_relative + temp_dist1) + var dest = calculate_dest.call(delta, damping_drag) + return dest - min(0.0, temp_dist1) + elif temp_relative + temp_dist2 < 0.0: + var delta = max(temp_relative, temp_relative + temp_dist2) + var dest = -calculate_dest.call(-delta, damping_drag) + return dest - max(0.0, temp_dist2) + else: return temp_relative + + if should_scroll_vertical(): + var y_pos = calculate_position.call( + drag_temp_data[2], # Temp top_distance + drag_temp_data[3], # Temp bottom_distance + drag_temp_data[1] # Temp y relative accumulation + ) + drag_start_pos.y + velocity.y = (y_pos - pos.y) / get_process_delta_time() / 100 + pos.y = y_pos + if should_scroll_horizontal(): + var x_pos = calculate_position.call( + drag_temp_data[4], # Temp left_distance + drag_temp_data[5], # Temp right_distance + drag_temp_data[0] # Temp x relative accumulation + ) + drag_start_pos.x + velocity.x = (x_pos - pos.x) / get_process_delta_time() / 100 + pos.x = x_pos + +func calculate_distance() -> void: + bottom_distance = content_node.position.y + content_node.size.y - self.size.y + top_distance = content_node.position.y + right_distance = content_node.position.x + content_node.size.x - self.size.x + left_distance = content_node.position.x + if get_v_scroll_bar().visible: + right_distance += get_v_scroll_bar().size.x + if get_h_scroll_bar().visible: + bottom_distance += get_h_scroll_bar().size.y + +func stop_frame(vel : float) -> float: + # How long it will take to stop scrolling + # 0.001 and 0.999 is to ensure that the denominator is not 0 + var stop_frame = log(just_stop_under/(abs(vel)+0.001))/log(friction*0.999) + # Clamp and floor + stop_frame = floor(max(stop_frame, 0.0)) + return stop_frame + +func will_stop_within(vertical : bool, vel : float) -> bool: + # Calculate stop frame + var stop_frame = stop_frame(vel) + # Distance it takes to stop scrolling + var stop_distance = vel*(1-pow(friction,stop_frame))/(1-friction) + # Position it will stop at + var stop_pos + if vertical: + stop_pos = pos.y + stop_distance + else: + stop_pos = pos.x + stop_distance + + var diff = self.size.y - content_node.size.y if vertical else self.size.x - content_node.size.x + + # Whether content node will stop inside the container + return stop_pos <= 0.0 and stop_pos >= min(diff, 0.0) + +func remove_all_children_focus(node : Node) -> void: + if node is Control: + var control = node as Control + control.release_focus() + + for child in node.get_children(): + remove_all_children_focus(child) + +func update_state() -> void: + if content_dragging\ + or v_scrollbar_dragging\ + or h_scrollbar_dragging\ + or velocity != Vector2.ZERO: + is_scrolling = true + else: + is_scrolling = false + +func init_drag_temp_data() -> void: + drag_temp_data = [0.0, 0.0, top_distance, bottom_distance, left_distance, right_distance] + +##### LOGIC +#################### + + +#################### +##### DEBUG DRAWING + +var debug_gradient = Gradient.new() + +func setup_debug_drawing() -> void: + debug_gradient.set_color(0.0, Color.GREEN) + debug_gradient.set_color(1.0, Color.RED) + +func draw_debug() -> void: + # Overdrag lines + # Top + Bottom + draw_line(Vector2(0.0, 0.0), Vector2(0.0, top_distance), debug_gradient.sample(clamp(top_distance / size.y, 0.0, 1.0)), 5.0) + draw_line(Vector2(0.0, size.y), Vector2(0.0, size.y+bottom_distance), debug_gradient.sample(clamp(-bottom_distance / size.y, 0.0, 1.0)), 5.0) + # Left + Right + draw_line(Vector2(0.0, size.y), Vector2(left_distance, size.y), debug_gradient.sample(clamp(left_distance / size.y, 0.0, 1.0)), 5.0) + draw_line(Vector2(size.x, size.y), Vector2(size.x+right_distance, size.y), debug_gradient.sample(clamp(-right_distance / size.y, 0.0, 1.0)), 5.0) + + # Velocity lines + var origin := Vector2(5.0, size.y/2) + draw_line(origin, origin + Vector2(0.0, velocity.y), debug_gradient.sample(clamp(velocity.y*2 / size.y, 0.0, 1.0)), 5.0) + draw_line(origin, origin + Vector2(0.0, velocity.x), debug_gradient.sample(clamp(velocity.x*2 / size.x, 0.0, 1.0)), 5.0) + +##### DEBUG DRAWING +#################### + + +#################### +##### API FUNCTIONS + +## Scrolls to specific x position +func scroll_x_to(x_pos: float, duration:float=0.5) -> void: + if not should_scroll_horizontal(): return + velocity.x = 0.0 + x_pos = clampf(x_pos, self.size.x-content_node.size.x, 0.0) + var tween = create_tween() + var tweener = tween.tween_property(self, "pos:x", x_pos, 0.5) + tweener.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUINT) + +## Scrolls to specific y position +func scroll_y_to(y_pos: float, duration:float=0.5) -> void: + if not should_scroll_vertical(): return + velocity.y = 0.0 + y_pos = clampf(y_pos, self.size.y-content_node.size.y, 0.0) + var tween = create_tween() + var tweener = tween.tween_property(self, "pos:y", y_pos, duration) + tweener.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUINT) + +## Scrolls up a page +func scroll_page_up(duration:float=0.5) -> void: + var destination = content_node.position.y + self.size.y + scroll_y_to(destination, duration) + +## Scrolls down a page +func scroll_page_down(duration:float=0.5) -> void: + var destination = content_node.position.y - self.size.y + scroll_y_to(destination, duration) + +## Scrolls left a page +func scroll_page_left(duration:float=0.5) -> void: + var destination = content_node.position.x + self.size.x + scroll_x_to(destination, duration) + +## Scrolls right a page +func scroll_page_right(duration:float=0.5) -> void: + var destination = content_node.position.x - self.size.x + scroll_x_to(destination, duration) + +## Adds velocity to the vertical scroll +func scroll_vertically(amount: float) -> void: + velocity.y -= amount + +## Adds velocity to the horizontal scroll +func scroll_horizontally(amount: float) -> void: + velocity.x -= amount + +## Scrolls to top +func scroll_to_top(duration:float=0.5) -> void: + scroll_y_to(0.0, duration) + +## Scrolls to bottom +func scroll_to_bottom(duration:float=0.5) -> void: + scroll_y_to(self.size.y - content_node.size.y, duration) + +## Scrolls to left +func scroll_to_left(duration:float=0.5) -> void: + scroll_x_to(0.0, duration) + +## Scrolls to right +func scroll_to_right(duration:float=0.5) -> void: + scroll_x_to(self.size.x - content_node.size.x, duration) + +func is_outside_top_boundary(y_pos: float = pos.y) -> bool: + return y_pos > 0.0 + +func is_outside_bottom_boundary(y_pos: float = pos.y) -> bool: + return y_pos < self.size.y - content_node.size.y + +func is_outside_left_boundary(x_pos: float = pos.x) -> bool: + return x_pos > 0.0 + +func is_outside_right_boundary(x_pos: float = pos.x) -> bool: + return x_pos < self.size.x - content_node.size.x + +## Returns true if any scroll bar is being dragged +func any_scroll_bar_dragged() -> bool: + if get_v_scroll_bar(): + if get_v_scroll_bar().has_focus(): + return true + if get_h_scroll_bar().has_focus(): + return true + return false + +## Returns true if there is enough content height to scroll +func should_scroll_vertical() -> bool: + var disable_scroll = content_node.size.y - self.size.y < 1 or not allow_vertical_scroll\ + if auto_allow_scroll else not allow_vertical_scroll + if disable_scroll: + velocity.y = 0.0 + return false + else: + return true + +## Returns true if there is enough content width to scroll +func should_scroll_horizontal() -> bool: + var disable_scroll = content_node.size.x - self.size.x < 1 or not allow_horizontal_scroll\ + if auto_allow_scroll else not allow_horizontal_scroll + if disable_scroll: + velocity.x = 0.0 + return false + else: + return true + +## Fades out scrollbars within given [param time].[br] +## Default for [param time] is current [member scrollbar_fade_out_time] +func hide_scrollbars(time: float = scrollbar_fade_out_time) -> void: + if scrollbar_hide_tween != null: + scrollbar_hide_tween.kill() + scrollbar_hide_tween = create_tween() + scrollbar_hide_tween.set_parallel(true) + scrollbar_hide_tween.tween_property(get_v_scroll_bar(), 'modulate', Color.TRANSPARENT, time) + scrollbar_hide_tween.tween_property(get_h_scroll_bar(), 'modulate', Color.TRANSPARENT, time) + +## Fades in scrollbars within given [param time].[br] +## Default for [param time] is current [member scrollbar_fade_in_time] +func show_scrollbars(time: float = scrollbar_fade_in_time) -> void: + if scrollbar_hide_tween != null: + scrollbar_hide_tween.kill() + scrollbar_hide_tween = create_tween() + scrollbar_hide_tween.set_parallel(true) + scrollbar_hide_tween.tween_property(get_v_scroll_bar(), 'modulate', Color.WHITE, time) + scrollbar_hide_tween.tween_property(get_h_scroll_bar(), 'modulate', Color.WHITE, time) + +##### API FUNCTIONS +######################## diff --git a/addons/SmoothScroll/class-icon.svg b/addons/SmoothScroll/class-icon.svg new file mode 100644 index 00000000..30862c19 --- /dev/null +++ b/addons/SmoothScroll/class-icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/addons/SmoothScroll/class-icon.svg.import b/addons/SmoothScroll/class-icon.svg.import new file mode 100644 index 00000000..488e0a44 --- /dev/null +++ b/addons/SmoothScroll/class-icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dorhyoghxkay6" +path="res://.godot/imported/class-icon.svg-c17de51589a7d30572bf401526524f64.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/SmoothScroll/class-icon.svg" +dest_files=["res://.godot/imported/class-icon.svg-c17de51589a7d30572bf401526524f64.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/addons/SmoothScroll/debug_font.tres b/addons/SmoothScroll/debug_font.tres new file mode 100644 index 00000000..3871a65a --- /dev/null +++ b/addons/SmoothScroll/debug_font.tres @@ -0,0 +1,3 @@ +[gd_resource type="SystemFont" format=3 uid="uid://cw8c0p3b5mv5y"] + +[resource] diff --git a/addons/SmoothScroll/icon.afdesign b/addons/SmoothScroll/icon.afdesign new file mode 100644 index 00000000..1b54598f Binary files /dev/null and b/addons/SmoothScroll/icon.afdesign differ diff --git a/addons/SmoothScroll/plugin.cfg b/addons/SmoothScroll/plugin.cfg new file mode 100644 index 00000000..f053bc52 --- /dev/null +++ b/addons/SmoothScroll/plugin.cfg @@ -0,0 +1,8 @@ +[plugin] + +name="SmoothScroll" +description="""This plugin adds a new scroll container class +with additional smooth scroll options.""" +author="Fabian Keßler (SpyrexDE)" +version="1.2" +script="plugin.gd" diff --git a/addons/SmoothScroll/plugin.gd b/addons/SmoothScroll/plugin.gd new file mode 100644 index 00000000..e487e521 --- /dev/null +++ b/addons/SmoothScroll/plugin.gd @@ -0,0 +1,10 @@ +@tool +extends EditorPlugin + + +func _enter_tree(): + add_custom_type("SmoothScrollContainer", "ScrollContainer", preload("SmoothScrollContainer.gd"), preload("class-icon.svg")) + + +func _exit_tree(): + remove_custom_type("SmoothScrollContainer") diff --git a/project.godot b/project.godot index 349b2da0..51632cb6 100644 --- a/project.godot +++ b/project.godot @@ -22,6 +22,7 @@ CardManager="*res://Managers/CardManager.gd" PhaseManager="*res://Managers/PhaseManager.gd" RandomGenerator="*res://Managers/RandomGenerator.gd" GlobalVar="*res://Global/GLOBAL_VAR.gd" +MapManager="*res://Map/MapManager.gd" [display] @@ -32,12 +33,12 @@ window/stretch/aspect="expand" [editor_plugins] -enabled=PackedStringArray("res://addons/gut/plugin.cfg") +enabled=PackedStringArray("res://addons/SmoothScroll/plugin.cfg", "res://addons/gut/plugin.cfg") [input] -escape={ +cancel_action={ "deadzone": 0.5, -"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194305,"key_label":0,"unicode":0,"echo":false,"script":null) +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194305,"physical_keycode":4194305,"key_label":4194305,"unicode":0,"echo":false,"script":null) ] }