diff --git a/.vscode/settings.json b/.vscode/settings.json
index c4a528b..9c95895 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -11,5 +11,6 @@
"color": "default"
}
],
- "VsCodeTaskButtons.showCounter": false
+ "VsCodeTaskButtons.showCounter": false,
+ "rust-analyzer.checkOnSave": true
}
diff --git a/Justfile b/Justfile
new file mode 100644
index 0000000..be05212
--- /dev/null
+++ b/Justfile
@@ -0,0 +1,5 @@
+test:
+ RUST_BACKTRACE=1 godot --path godot res://tests/test_runner.tscn -- --quiet-run
+
+bench:
+ RUST_BACKTRACE=1 godot --path godot res://tests/test_runner.tscn -- --bench
\ No newline at end of file
diff --git a/README.md b/README.md
index 693421e..bac81c6 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,9 @@
# godottest
messing around with godot and rust
+
+## run tests
+
+```sh
+RUST_BACKTRACE=1 godot --path godot res://tests/test_runner.tscn -- --quiet-run
+```
diff --git a/godot/Rust.gdextension b/godot/Rust.gdextension
index c640e38..6a12633 100644
--- a/godot/Rust.gdextension
+++ b/godot/Rust.gdextension
@@ -1,6 +1,7 @@
[configuration]
entry_symbol = "gdext_rust_init"
compatibility_minimum = 4.1
+reloadable = true
[libraries]
"android.debug" = "res://../rust/target/debug/godottest_rs.so"
@@ -65,6 +66,6 @@ compatibility_minimum = 4.1
"windows.editor.x86_64" = "res://../rust/target/x86_64-pc-windows-msvc/debug/godottest_rs.dll"
[icons]
-AsyncRuntime = "res://addons/rust/NodeRustFerris.svg"
-Player = "res://addons/rust/NodeRustFerris.svg"
Mappy = "res://addons/rust/NodeRustFerris.svg"
+Player = "res://addons/rust/NodeRustFerris.svg"
+AsyncRuntime = "res://addons/rust/NodeRustFerris.svg"
diff --git a/godot/Rust.gdextension.uid b/godot/Rust.gdextension.uid
index 92cc7f2..7b11879 100644
--- a/godot/Rust.gdextension.uid
+++ b/godot/Rust.gdextension.uid
@@ -1 +1 @@
-uid://dx7md3kauujl
+uid://yljs7ohq1dqw
diff --git a/godot/addons/panku_console/LICENSE b/godot/addons/panku_console/LICENSE
new file mode 100644
index 0000000..d66c0a7
--- /dev/null
+++ b/godot/addons/panku_console/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 Feo (k2kra) Wu
+
+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.
\ No newline at end of file
diff --git a/godot/addons/panku_console/README.md b/godot/addons/panku_console/README.md
new file mode 100644
index 0000000..b986276
--- /dev/null
+++ b/godot/addons/panku_console/README.md
@@ -0,0 +1 @@
+This repository is a mirror that tracks the latest version of [PankuConsole](https://github.com/Ark2000/PankuConsole), so you can add it as a submodule in you addons folder.
diff --git a/godot/addons/panku_console/common/buffered_rich_text.gd b/godot/addons/panku_console/common/buffered_rich_text.gd
new file mode 100644
index 0000000..db67648
--- /dev/null
+++ b/godot/addons/panku_console/common/buffered_rich_text.gd
@@ -0,0 +1,35 @@
+extends VBoxContainer
+
+# it is like, an infinite scroll game.
+
+# specifically, the first buffer will be cleared and sent to the last
+# when the last buffer is full.
+
+# with buffers, we can constanly output lots of fancy stuff while keeping a smooth experience.
+
+const BUFFER_MAX_PARAGRAPHS = 64
+const BUFFERS = 4
+
+var cur_label_idx:int = 0
+
+func add_text(text:String):
+ var cur_label:RichTextLabel = get_child(cur_label_idx)
+ cur_label.text += text
+ if cur_label.get_paragraph_count() > BUFFER_MAX_PARAGRAPHS:
+ cur_label_idx += 1
+ if cur_label_idx == BUFFERS:
+ cur_label_idx = BUFFERS - 1
+ var first_label:RichTextLabel = get_child(0)
+ first_label.text = ""
+ move_child(first_label, BUFFERS - 1)
+
+func _ready():
+ set("theme_override_constants/separation", 0)
+ for child in get_children():
+ child.queue_free()
+ for i in range(BUFFERS):
+ var new_buffer:RichTextLabel = RichTextLabel.new()
+ new_buffer.fit_content = true
+ new_buffer.bbcode_enabled = true
+ new_buffer.selection_enabled = true
+ add_child(new_buffer)
diff --git a/godot/addons/panku_console/common/buffered_rich_text.gd.uid b/godot/addons/panku_console/common/buffered_rich_text.gd.uid
new file mode 100644
index 0000000..b03aadc
--- /dev/null
+++ b/godot/addons/panku_console/common/buffered_rich_text.gd.uid
@@ -0,0 +1 @@
+uid://hxsdaekjx5k0
diff --git a/godot/addons/panku_console/common/config.gd b/godot/addons/panku_console/common/config.gd
new file mode 100644
index 0000000..ca85283
--- /dev/null
+++ b/godot/addons/panku_console/common/config.gd
@@ -0,0 +1,126 @@
+class_name PankuConfig
+
+const CONFIG_SECTION = "panku"
+const OPTIONS = {
+ # See https://github.com/Ark2000/PankuConsole/issues/170
+ DISABLE_ON_RELEASE = 'disable_on_release',
+ # See https://github.com/Ark2000/PankuConsole/issues/173
+ CUSTOM_DEFAULT_CONFIG = 'custom_default_config',
+}
+
+const INITIAL_DEFAULT_CONFIG_FILE_PATH = "res://addons/panku_console/default_panku_config.cfg"
+const USER_CONFIG_FILE_PATH = "user://panku_config.cfg"
+
+
+# Full option name in project settings.
+static func panku_option(option: String) -> String:
+ return CONFIG_SECTION + "/" + option
+
+
+# A helper function to add custom project settings
+# See https://dfaction.net/handling-custom-project-settings-using-gdscript/
+static func add_custom_project_setting(
+ name: String,
+ default_value,
+ type: int,
+ hint: int = PROPERTY_HINT_NONE, hint_string: String = ""
+ ) -> void:
+ if ProjectSettings.has_setting(name): return
+
+ var setting_info: Dictionary = {
+ "name": name,
+ "type": type,
+ "hint": hint,
+ "hint_string": hint_string
+ }
+
+ ProjectSettings.set_setting(name, default_value)
+ ProjectSettings.add_property_info(setting_info)
+ ProjectSettings.set_initial_value(name, default_value)
+ ProjectSettings.set_as_basic(name, true)
+
+
+static func init_all_project_settings() -> void:
+ # Seems we can't add descriptions to custom settings now.
+
+ # Disable Panku Console in release builds
+ add_custom_project_setting(
+ panku_option(OPTIONS.DISABLE_ON_RELEASE),
+ false,
+ TYPE_BOOL
+ )
+
+ # Path to the custom
+ # `res://` path default config file, useful if you are going to keep panku console in release builds.
+ add_custom_project_setting(
+ panku_option(OPTIONS.CUSTOM_DEFAULT_CONFIG),
+ INITIAL_DEFAULT_CONFIG_FILE_PATH,
+ TYPE_STRING,
+ PROPERTY_HINT_FILE,
+ "*.cfg"
+ )
+
+ # save_project_settings()
+
+
+static func clear_all_project_settings() -> void:
+ for option in OPTIONS.values():
+ var opt: String = panku_option(option)
+ if ProjectSettings.has_setting(opt):
+ ProjectSettings.clear(opt)
+
+ save_project_settings()
+
+
+static func save_project_settings() -> void:
+ var error: int = ProjectSettings.save()
+ if error != OK:
+ push_error("Encountered error %d when saving project settings." % error)
+
+
+# Get custom config file path from project settings
+static func get_custom_default_config_path() -> String:
+ return ProjectSettings.get_setting(panku_option(OPTIONS.CUSTOM_DEFAULT_CONFIG), INITIAL_DEFAULT_CONFIG_FILE_PATH)
+
+# Check if custom config file from project settings exist
+static func is_custom_default_config_exists() -> bool:
+ return FileAccess.file_exists(get_custom_default_config_path())
+
+
+# load config from file, always return a dictionary
+static func _get_config(file_path:String) -> Dictionary:
+ if FileAccess.file_exists(file_path):
+ var file = FileAccess.open(file_path, FileAccess.READ)
+ var content := file.get_as_text()
+ var config:Dictionary = str_to_var(content)
+ if config: return config
+ return {}
+
+
+# save user config to file
+static func set_config(config:Dictionary):
+ var file = FileAccess.open(USER_CONFIG_FILE_PATH, FileAccess.WRITE)
+ var content = var_to_str(config)
+ file.store_string(content)
+
+
+# get config, if user config exists, return user config, otherwise return default config configured by plugin user
+static func get_config() -> Dictionary:
+ var user_config:Dictionary = _get_config(USER_CONFIG_FILE_PATH)
+ if not user_config.is_empty():
+ return user_config
+ # if no user config, return default config, which is read-only
+ if is_custom_default_config_exists():
+ return _get_config(get_custom_default_config_path())
+
+ return _get_config(INITIAL_DEFAULT_CONFIG_FILE_PATH)
+
+
+static func get_value(key:String, default:Variant) -> Variant:
+ return get_config().get(key, default)
+
+
+static func set_value(key:String, val:Variant) -> void:
+ var config = _get_config(USER_CONFIG_FILE_PATH)
+ config[key] = val
+ set_config(config)
diff --git a/godot/addons/panku_console/common/config.gd.uid b/godot/addons/panku_console/common/config.gd.uid
new file mode 100644
index 0000000..9038732
--- /dev/null
+++ b/godot/addons/panku_console/common/config.gd.uid
@@ -0,0 +1 @@
+uid://bd0ob776pkvry
diff --git a/godot/addons/panku_console/common/gdexprenv.gd b/godot/addons/panku_console/common/gdexprenv.gd
new file mode 100644
index 0000000..61d03cd
--- /dev/null
+++ b/godot/addons/panku_console/common/gdexprenv.gd
@@ -0,0 +1,276 @@
+class_name PankuGDExprEnv
+
+const type_names = {
+ TYPE_NIL: "null",
+ TYPE_BOOL: "bool",
+ TYPE_INT: "int",
+ TYPE_FLOAT: "float",
+ TYPE_STRING: "String",
+ TYPE_VECTOR2: "Vector2",
+ TYPE_VECTOR2I: "Vector2i",
+ TYPE_RECT2: "Rect2",
+ TYPE_RECT2I: "Rect2i",
+ TYPE_VECTOR3: "Vector3",
+ TYPE_VECTOR3I: "Vector3i",
+ TYPE_TRANSFORM2D: "Transform2D",
+ TYPE_VECTOR4: "Vector4",
+ TYPE_VECTOR4I: "Vector4i",
+ TYPE_PLANE: "Plane",
+ TYPE_QUATERNION: "Quaternion",
+ TYPE_AABB: "AABB",
+ TYPE_BASIS: "Basis",
+ TYPE_TRANSFORM3D: "Transform3D",
+ TYPE_PROJECTION: "Projection",
+ TYPE_COLOR: "Color",
+ TYPE_STRING_NAME: "StringName",
+ TYPE_NODE_PATH: "NodePath",
+ TYPE_RID: "RID",
+ TYPE_OBJECT: "Object",
+ TYPE_CALLABLE: "Callable",
+ TYPE_SIGNAL: "Signal",
+ TYPE_DICTIONARY: "Dictionary",
+ TYPE_ARRAY: "Array",
+ TYPE_PACKED_BYTE_ARRAY: "PackedByteArray",
+ TYPE_PACKED_INT32_ARRAY: "PackedInt32Array",
+ TYPE_PACKED_INT64_ARRAY: "PackedInt64Array",
+ TYPE_PACKED_FLOAT32_ARRAY: "PackedFloat32Array",
+ TYPE_PACKED_FLOAT64_ARRAY: "PackedFloat64Array",
+ TYPE_PACKED_STRING_ARRAY: "PackedStringArray",
+ TYPE_PACKED_VECTOR2_ARRAY: "PackedVector2Array",
+ TYPE_PACKED_VECTOR3_ARRAY: "PackedVector3Array",
+ TYPE_PACKED_COLOR_ARRAY: "PackedColorArray",
+}
+
+var _envs = {}
+var _envs_info = {}
+var _expression = Expression.new()
+var _base_instance:Object
+
+func set_base_instance(base_instance:Object):
+ _base_instance = base_instance
+ #add info of base instance
+ var env_info = extract_info_from_script(_base_instance.get_script())
+ for k in env_info: _envs_info[k] = env_info[k]
+
+func get_base_instance():
+ return _base_instance
+
+## Register an environment that run expressions.
+## [br][code]env_name[/code]: the name of the environment
+## [br][code]env[/code]: The base instance that runs the expressions. For exmaple your player node.
+func register_env(env_name:String, env:Object):
+ _envs[env_name] = env
+# output("[color=green][Info][/color] [b]%s[/b] env loaded!"%env_name)
+ if env is Node:
+ env.tree_exiting.connect(
+ func(): remove_env(env_name)
+ )
+ if env.get_script():
+ var env_info = extract_info_from_script(env.get_script())
+ for k in env_info:
+ var keyword = "%s.%s" % [env_name, k]
+ _envs_info[keyword] = env_info[k]
+
+## Return the environment object or [code]null[/code] by its name.
+func get_env(env_name:String) -> Node:
+ return _envs.get(env_name)
+
+## Remove the environment named [code]env_name[/code]
+func remove_env(env_name:String):
+ if _envs.has(env_name):
+ _envs.erase(env_name)
+ for k in _envs_info.keys():
+ if k.begins_with(env_name + "."):
+ _envs_info.erase(k)
+
+#Execute an expression in a preset environment.
+func execute(exp:String) -> Dictionary:
+ return execute_exp(exp, _expression, _base_instance, _envs)
+
+# TODO: not used
+func get_available_export_objs() -> Array:
+ var result = []
+ for obj_name in _envs:
+ var obj = _envs[obj_name]
+ if !obj.get_script():
+ continue
+ var export_properties = get_export_properties_from_script(obj.get_script())
+ if export_properties.is_empty():
+ continue
+ result.push_back(obj_name)
+ return result
+
+func get_help_info(k:String) -> String:
+ return _envs_info[k]["help"]
+
+#TODO: refactor all those mess
+func parse_exp(exp:String, allow_empty:=false):
+ var result:Array
+ var empty_flag = allow_empty and exp.is_empty()
+
+ if empty_flag:
+ result = _envs_info.keys()
+ else:
+ result = search_and_sort_and_highlight(exp, _envs_info.keys())
+
+ var hints_bbcode = []
+ var hints_value = []
+
+ for r in result:
+ var keyword:String
+ var bbcode_main:String
+
+ if empty_flag:
+ keyword = r
+ bbcode_main = r
+ else:
+ keyword = r["keyword"]
+ bbcode_main = r["bbcode"]
+
+ var bbcode_postfix = _envs_info[keyword]["bbcode_postfix"]
+ var keyword_type = _envs_info[keyword]["type"]
+ hints_value.push_back(keyword)
+ hints_bbcode.push_back(bbcode_main + bbcode_postfix)
+ return {
+ "hints_bbcode": hints_bbcode,
+ "hints_value": hints_value
+ }
+
+static func search_and_sort_and_highlight(s:String, li:Array):
+ s = s.lstrip(" ").rstrip(" ")
+ var matched = []
+ if s == "": return matched
+ for k in li:
+ var start = k.find(s)
+ if start >= 0:
+ var similarity = 1.0 * s.length() / k.length()
+ matched.append({
+ "keyword": k,
+ "similarity": similarity,
+ "start": start,
+ "bbcode": ""
+ })
+
+ matched.sort_custom(
+ func(k1, k2):
+ if k1["start"] != k2["start"]:
+ return k1["start"] > k2["start"]
+ else:
+ return k1["similarity"] < k2["similarity"]
+ )
+
+ var line_format = "%s[color=green][b]%s[/b][/color]%s"
+
+ for m in matched:
+ var p = ["", "", ""]
+ if m["start"] < 0:
+ p[0] = m["keyword"]
+ else:
+ p[0] = m["keyword"].substr(0, m["start"])
+ p[1] = s
+ p[2] = m["keyword"].substr(m["start"] + s.length(), -1)
+
+ m["bbcode"] = line_format % p
+
+ return matched
+
+static func extract_info_from_script(script:Script):
+ var result = {}
+
+ var methods = []
+ var properties = []
+ var constants = []
+ var constants_bbcode_postfix = {}
+
+ for m in script.get_script_method_list():
+ if m["name"] != "" and m["name"].is_valid_identifier() and !m["name"].begins_with("_"):
+ var args = []
+ for a in m["args"]:
+ args.push_back("[color=cyan]%s[/color][color=gray]:[/color][color=orange]%s[/color]"%[a["name"], type_names[a["type"]]])
+ result[m["name"]] = {
+ "type": "method",
+ "bbcode_postfix": "(%s)"%("[color=gray], [/color]".join(PackedStringArray(args)))
+ }
+ for p in script.get_script_property_list():
+ if p["name"] != "" and !p["name"].begins_with("_") and p["name"].is_valid_identifier():
+ result[p["name"]] = {
+ "type": "property",
+ "bbcode_postfix":"[color=gray]:[/color][color=orange]%s[/color]"%type_names[p["type"]]
+ }
+
+ var constant_map = script.get_script_constant_map()
+ var help_info = {}
+ for c in constant_map:
+ if !c.begins_with("_"):
+ result[c] = {
+ "type": "constant",
+ "bbcode_postfix":"[color=gray]:[/color][color=orange]%s[/color]"%type_names[typeof(constant_map[c])]
+ }
+ elif c.begins_with("_HELP_") and c.length() > 6 and typeof(constant_map[c]) == TYPE_STRING:
+ var key = c.lstrip("_HELP_")
+ help_info[key] = constant_map[c]
+
+ for k in result:
+ if help_info.has(k):
+ result[k]["help"] = help_info[k]
+ else:
+ result[k]["help"] = "No help information provided."
+
+ #keyword -> {type, bbcode_postfix, help}
+ return result
+
+static func execute_exp(exp_str:String, expression:Expression, base_instance:Object, env:Dictionary):
+ var failed := false
+ var result = null
+
+ var error = expression.parse(exp_str, env.keys())
+ if error != OK:
+ failed = true
+ result = expression.get_error_text()
+ else:
+ result = expression.execute(env.values(), base_instance, true)
+ if expression.has_execute_failed():
+ failed = true
+ result = expression.get_error_text()
+
+ return {
+ "failed": failed,
+ "result": result
+ }
+
+static func get_export_properties_from_script(script:Script):
+ var result = []
+ var data = script.get_script_property_list()
+ for d in data:
+ if !(d.usage == PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_STORAGE):
+ continue
+ result.append(d)
+ return result
+
+static func generate_help_text_from_script(script:Script):
+ var result = ["[color=cyan][b]User script defined identifiers[/b][/color]: "]
+ var env_info = extract_info_from_script(script)
+ var keys = env_info.keys()
+ keys.sort()
+ for k in keys:
+ result.push_back("%s - [i]%s[/i]"%[k + env_info[k]["bbcode_postfix"], env_info[k]["help"]])
+ return "\n".join(PackedStringArray(result))
+
+#returns a string containing all public script properties of an object
+#please BE AWARE when using this function on an object with custom getters.
+static func get_object_outline(obj:Object) -> String:
+ var result := PackedStringArray()
+ if obj == null: return "null"
+ var script = obj.get_script()
+ if script == null:
+ return "this object has no script attached."
+ var properties = script.get_script_property_list()
+ for p in properties:
+ if p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE == 0:
+ continue
+ if p.name.begins_with("_"):
+ continue
+ result.append("%s: %s" % [p.name, str(obj.get(p.name))])
+ if result.is_empty():
+ return "this object has no public script variables."
+ return "\n".join(result)
diff --git a/godot/addons/panku_console/common/gdexprenv.gd.uid b/godot/addons/panku_console/common/gdexprenv.gd.uid
new file mode 100644
index 0000000..650c321
--- /dev/null
+++ b/godot/addons/panku_console/common/gdexprenv.gd.uid
@@ -0,0 +1 @@
+uid://xnce2n76k5p6
diff --git a/godot/addons/panku_console/common/lynx_window2/border.gd b/godot/addons/panku_console/common/lynx_window2/border.gd
new file mode 100644
index 0000000..53b25d6
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/border.gd
@@ -0,0 +1,9 @@
+extends Panel
+
+func hey_i_am_here():
+ modulate.a = 0.0
+ var t = create_tween()
+ t.set_speed_scale(1.0 / Engine.time_scale)
+ for i in range(2):
+ t.tween_property(self, "modulate:a", 0.3, 0.1)
+ t.tween_property(self, "modulate:a", 0.0, 0.1)
diff --git a/godot/addons/panku_console/common/lynx_window2/border.gd.uid b/godot/addons/panku_console/common/lynx_window2/border.gd.uid
new file mode 100644
index 0000000..b353266
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/border.gd.uid
@@ -0,0 +1 @@
+uid://b2oil1la5ccpf
diff --git a/godot/addons/panku_console/common/lynx_window2/lynx_window_2.gd b/godot/addons/panku_console/common/lynx_window2/lynx_window_2.gd
new file mode 100644
index 0000000..1933b0c
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/lynx_window_2.gd
@@ -0,0 +1,298 @@
+class_name PankuLynxWindow extends ColorRect
+
+#Do not connect the button node directly, use these signals to detect click event.
+signal title_btn_clicked
+signal window_closed
+
+const lynx_window_shader_material:ShaderMaterial = preload("./lynx_window_shader_material.tres")
+const OS_WINDOW_MARKER = "PankuOSWindow"
+
+@export var _window_title_container:HBoxContainer
+@export var _title_btn:PankuButton
+@export var _close_btn:PankuButton
+@export var _options_btn:PankuButton
+@export var _resize_btn:Button
+@export var _shadow_focus:Panel
+@export var _shadow:NinePatchRect
+@export var _container:Panel
+@export var _pop_btn:PankuButton
+
+@export var no_resize := false
+@export var no_resize_x := false
+@export var no_resize_y := false
+@export var no_move := false
+@export var no_snap := false
+
+@export var no_title := false:
+ set(v):
+ no_title = v
+ _window_title_container.visible = !v
+
+@export var queue_free_on_close := true
+@export var flicker := true
+
+var transform_interp_speed := 40.0
+var bounds_interp_speed := 50.0
+var anim_interp_speed := 10.0
+
+var _is_dragging := false
+var _drag_start_position:Vector2
+var _drag_start_position_global:Vector2
+var _is_resizing := false
+var _resize_start_position:Vector2
+var _os_window:Window
+var _content:Control
+var _size_before_folded:Vector2
+var _folded:bool = false
+var _size_animation:bool = false
+var _target_size:Vector2
+
+func add_options_button(callback:Callable):
+ _options_btn.show()
+ _options_btn.pressed.connect(callback)
+
+func get_layout_position(layout:Control.LayoutPreset) -> Vector2:
+ var window_rect = get_rect()
+ var screen_rect = get_viewport_rect()
+ var new_position = Vector2.ZERO
+ var end_position = screen_rect.size - window_rect.size
+ var center_position = end_position / 2
+ if layout == PRESET_TOP_LEFT:
+ pass
+ elif layout == PRESET_CENTER_TOP:
+ new_position.x = center_position.x
+ elif layout == PRESET_TOP_RIGHT:
+ new_position.x = end_position.x
+ elif layout == PRESET_CENTER_LEFT:
+ new_position.y = center_position.y
+ elif layout == PRESET_CENTER:
+ new_position = center_position
+ elif layout == PRESET_CENTER_RIGHT:
+ new_position.x = end_position.x
+ new_position.y = center_position.y
+ elif layout == PRESET_BOTTOM_LEFT:
+ new_position.y = end_position.y
+ elif layout == PRESET_CENTER_BOTTOM:
+ new_position.x = center_position.x
+ new_position.y = end_position.y
+ elif layout == PRESET_BOTTOM_RIGHT:
+ new_position = end_position
+ return new_position
+
+func get_content():
+ return _content
+
+func set_content(node:Control):
+ _content = node
+ if _os_window and _os_window.visible:
+ if _os_window.get_child_count() > 0:
+ push_error("Error: error in set_content")
+ return
+ _os_window.add_child(node)
+ return
+ if _container.get_child_count() > 0:
+ push_error("Error: error in set_content.")
+ return
+ _container.add_child(node)
+
+func highlight(v:bool):
+ _shadow_focus.visible = v
+
+func _init_os_window():
+ _os_window = Window.new()
+ _os_window.set_meta(OS_WINDOW_MARKER, true)
+ var color_rect = ColorRect.new()
+ color_rect.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ _os_window.add_child(color_rect)
+ get_tree().root.add_child(_os_window)
+ #destructor
+ tree_exiting.connect(
+ func():
+ _os_window.queue_free()
+ )
+ #switch back to embed window when os window close requested
+ _os_window.close_requested.connect(
+ func():
+ _os_window.remove_child(_content)
+ _os_window.hide()
+ set_content(_content)
+ show()
+ )
+ if get_parent().has_method("get_os_window_bg_color"):
+ color_rect.color = get_parent().get_os_window_bg_color()
+
+func switch_to_os_window():
+ if _content == null:
+ push_error("Error: No content. ")
+ return
+ if _os_window == null:
+ _init_os_window()
+ _container.remove_child(_content)
+ _os_window.add_child(_content)
+ _os_window.size = size
+ _os_window.title = _title_btn.text
+ _os_window.position = Vector2(DisplayServer.window_get_position(0)) + position
+ _content.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ _os_window.show()
+ hide()
+
+func show_window():
+ if _os_window and _os_window.visible:
+ return
+ show()
+ move_to_front()
+ modulate.a = 0.0
+ create_tween().tween_property(self, "modulate:a", 1.0, 0.2)
+
+func hide_window():
+ if _os_window and _os_window.visible:
+ _os_window.close_requested.emit()
+ hide()
+
+func toggle_window_visibility():
+ if _os_window.visible or visible:
+ hide_window()
+ else:
+ show_window()
+
+func set_window_visibility(b:bool):
+ if b: show_window()
+ else: hide_window()
+
+func get_window_visibility() -> bool:
+ return visible or _os_window.visible
+
+func set_window_title_text(text:String):
+ if _os_window and _os_window.visible:
+ _os_window.title = text
+ else:
+ _title_btn.text = " " + text
+
+func get_normal_window_size():
+ if _folded: return _size_before_folded
+ return size
+
+func get_title_bar_height():
+ return _window_title_container.size.y
+
+func _ready():
+ custom_minimum_size = _window_title_container.get_minimum_size()
+
+ _options_btn.visible = false
+
+ _title_btn.button_down.connect(
+ func():
+ _is_dragging = true
+ _drag_start_position = get_local_mouse_position()
+ _drag_start_position_global = get_global_mouse_position()
+ )
+ _title_btn.button_up.connect(
+ func():
+ _is_dragging = false
+ )
+ _resize_btn.button_down.connect(
+ func():
+ _is_resizing = true
+ _resize_start_position = _resize_btn.get_local_mouse_position()
+ )
+ _resize_btn.button_up.connect(
+ func():
+ _is_resizing = false
+ )
+ _close_btn.pressed.connect(
+ func():
+ window_closed.emit()
+ if queue_free_on_close:
+ queue_free()
+ else:
+ hide()
+ )
+
+ _title_btn.button.gui_input.connect(
+ func(e):
+ if e is InputEventMouseButton and !e.pressed:
+ if e.button_index != MOUSE_BUTTON_NONE:
+ if (get_global_mouse_position() - _drag_start_position_global).length_squared() < 4:
+ title_btn_clicked.emit()
+ )
+ visibility_changed.connect(
+ func():
+ if is_visible_in_tree() and flicker:
+ $Border.hey_i_am_here()
+ )
+
+ if flicker:
+ $Border.hey_i_am_here()
+
+ _pop_btn.pressed.connect(switch_to_os_window)
+
+ if _container.get_child_count() > 0:
+ _content = _container.get_child(0)
+
+ if get_parent().has_method("get_enable_os_popup_btns"):
+ _pop_btn.visible = get_parent().get_enable_os_popup_btns()
+
+ # feature: foldable window
+ title_btn_clicked.connect(
+ func():
+ if _folded:
+ _target_size = _size_before_folded
+ else:
+ if !_size_animation:
+ _size_before_folded = size
+ _target_size = _window_title_container.size
+ _size_animation = true
+ _folded = !_folded
+ _resize_btn.visible = !_folded
+ )
+
+func _input(e):
+ #release focus when you click outside of the window
+ if is_visible:
+ if e is InputEventMouseButton and e.pressed:
+ if !get_global_rect().has_point(get_global_mouse_position()):
+ var f = get_viewport().gui_get_focus_owner()
+ if f and is_ancestor_of(f):
+ f.release_focus()
+ if e is InputEventKey and e.keycode == KEY_ESCAPE and e.pressed and get_global_rect().has_point(get_global_mouse_position()):
+ window_closed.emit()
+ if queue_free_on_close:
+ queue_free()
+ else:
+ hide()
+
+func _process(delta: float) -> void:
+ if !no_move and _is_dragging:
+ var tp := position + get_local_mouse_position() - _drag_start_position
+ position = PankuUtils.interp(position, tp, transform_interp_speed, delta)
+ elif !no_resize and _is_resizing:
+ var ts := size + _resize_btn.get_local_mouse_position() - _resize_start_position
+ ts.x = min(ts.x, get_viewport_rect().size.x)
+ ts.y = min(ts.y, get_viewport_rect().size.y)
+ if !no_resize_x:
+ size.x = PankuUtils.interp(size.x, ts.x, transform_interp_speed, delta)
+ if !no_resize_y:
+ size.y = PankuUtils.interp(size.y, ts.y, transform_interp_speed, delta)
+ elif !no_snap:
+ var window_rect := get_rect()
+ var screen_rect := get_viewport_rect()
+ var target_position := window_rect.position
+ var target_size := window_rect.size.clamp(Vector2.ZERO, screen_rect.size)
+ if window_rect.position.y < 0:
+ target_position.y = 0
+ if window_rect.end.y > screen_rect.end.y:
+ target_position.y = screen_rect.end.y - window_rect.size.y
+ if window_rect.end.y > screen_rect.end.y + window_rect.size.y / 2:
+ target_position.y = screen_rect.end.y - get_title_bar_height()
+ if window_rect.position.x < 0:
+ target_position.x = 0
+ if window_rect.end.x > screen_rect.end.x:
+ target_position.x = screen_rect.end.x - window_rect.size.x
+ var current_position = window_rect.position
+ current_position = PankuUtils.interp(current_position, target_position, bounds_interp_speed, delta)
+ size = PankuUtils.interp(size, target_size, bounds_interp_speed, delta)
+ position = current_position
+ if _size_animation:
+ if _target_size.is_equal_approx(size):
+ _size_animation = false
+ size = PankuUtils.interp(size, _target_size, anim_interp_speed, delta)
diff --git a/godot/addons/panku_console/common/lynx_window2/lynx_window_2.gd.uid b/godot/addons/panku_console/common/lynx_window2/lynx_window_2.gd.uid
new file mode 100644
index 0000000..cdf383a
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/lynx_window_2.gd.uid
@@ -0,0 +1 @@
+uid://buw6hpyf4b2an
diff --git a/godot/addons/panku_console/common/lynx_window2/lynx_window_2.tscn b/godot/addons/panku_console/common/lynx_window2/lynx_window_2.tscn
new file mode 100644
index 0000000..56a65ac
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/lynx_window_2.tscn
@@ -0,0 +1,204 @@
+[gd_scene load_steps=19 format=3 uid="uid://s88loppa6gja"]
+
+[ext_resource type="Material" uid="uid://dyipeqsa8lcpc" path="res://addons/panku_console/common/lynx_window2/lynx_window_shader_material.tres" id="1_tvp6i"]
+[ext_resource type="Script" path="res://addons/panku_console/common/lynx_window2/lynx_window_2.gd" id="2_1ul5o"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="2_3fhqk"]
+[ext_resource type="Texture2D" uid="uid://dosm26riekruh" path="res://addons/panku_console/res/icons2/menu.svg" id="4_4dlyn"]
+[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="4_dnesi"]
+[ext_resource type="Texture2D" uid="uid://gav3m4qtvgje" path="res://addons/panku_console/res/icons2/pop-out-svgrepo-com.svg" id="4_im81u"]
+[ext_resource type="Texture2D" uid="uid://8g5afcuanbl6" path="res://addons/panku_console/res/icons2/close.svg" id="5_l4qpm"]
+[ext_resource type="Texture2D" uid="uid://dvr12fl5prm78" path="res://addons/panku_console/res/effect/square_shadow.png" id="6_mfp1h"]
+[ext_resource type="Texture2D" uid="uid://ciu5jiw4xmkq0" path="res://addons/panku_console/res/icons2/resize-svgrepo-com.svg" id="7_duwqn"]
+[ext_resource type="Script" path="res://addons/panku_console/common/lynx_window2/border.gd" id="8_gj3ji"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hv45g"]
+draw_center = false
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(1, 1, 1, 0.25098)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_6i67d"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+draw_center = false
+border_width_left = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(1, 1, 1, 0.25098)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lct0k"]
+draw_center = false
+shadow_color = Color(0, 0, 0, 0.0627451)
+shadow_size = 16
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_p3y6j"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_r0x7y"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_p7tml"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_5muk4"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uldta"]
+draw_center = false
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(1, 1, 1, 1)
+
+[node name="LynxWindow2" type="ColorRect" node_paths=PackedStringArray("_window_title_container", "_title_btn", "_close_btn", "_options_btn", "_resize_btn", "_shadow_focus", "_shadow", "_container", "_pop_btn")]
+material = ExtResource("1_tvp6i")
+offset_right = 413.0
+offset_bottom = 305.0
+theme = ExtResource("2_3fhqk")
+script = ExtResource("2_1ul5o")
+_window_title_container = NodePath("MainBody/VBoxContainer/Up")
+_title_btn = NodePath("MainBody/VBoxContainer/Up/TitleButton")
+_close_btn = NodePath("MainBody/VBoxContainer/Up/CloseButton")
+_options_btn = NodePath("MainBody/VBoxContainer/Up/MenuButton")
+_resize_btn = NodePath("Button")
+_shadow_focus = NodePath("Shadow2")
+_shadow = NodePath("Shadow")
+_container = NodePath("MainBody/VBoxContainer/Down")
+_pop_btn = NodePath("MainBody/VBoxContainer/Up/PopupButton")
+
+[node name="MainBody" type="Control" parent="."]
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="MainBody"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 0
+
+[node name="Up" type="HBoxContainer" parent="MainBody/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="TitleButton" parent="MainBody/VBoxContainer/Up" instance=ExtResource("4_dnesi")]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
+
+[node name="TextureRect" parent="MainBody/VBoxContainer/Up/TitleButton/HBoxContainer" index="0"]
+texture = null
+
+[node name="Label" parent="MainBody/VBoxContainer/Up/TitleButton/HBoxContainer" index="1"]
+text = "Window Title"
+
+[node name="PopupButton" parent="MainBody/VBoxContainer/Up" instance=ExtResource("4_dnesi")]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
+
+[node name="TextureRect" parent="MainBody/VBoxContainer/Up/PopupButton/HBoxContainer" index="0"]
+texture = ExtResource("4_im81u")
+
+[node name="Label" parent="MainBody/VBoxContainer/Up/PopupButton/HBoxContainer" index="1"]
+visible = false
+
+[node name="MenuButton" parent="MainBody/VBoxContainer/Up" instance=ExtResource("4_dnesi")]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
+
+[node name="TextureRect" parent="MainBody/VBoxContainer/Up/MenuButton/HBoxContainer" index="0"]
+texture = ExtResource("4_4dlyn")
+
+[node name="Label" parent="MainBody/VBoxContainer/Up/MenuButton/HBoxContainer" index="1"]
+visible = false
+
+[node name="CloseButton" parent="MainBody/VBoxContainer/Up" instance=ExtResource("4_dnesi")]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_hv45g")
+
+[node name="TextureRect" parent="MainBody/VBoxContainer/Up/CloseButton/HBoxContainer" index="0"]
+texture = ExtResource("5_l4qpm")
+
+[node name="Label" parent="MainBody/VBoxContainer/Up/CloseButton/HBoxContainer" index="1"]
+visible = false
+
+[node name="Down" type="Panel" parent="MainBody/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_6i67d")
+
+[node name="Shadow" type="NinePatchRect" parent="."]
+modulate = Color(1, 1, 1, 0.501961)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -64.0
+offset_top = -79.0
+offset_right = 63.0
+offset_bottom = 47.0
+grow_horizontal = 2
+grow_vertical = 2
+texture = ExtResource("6_mfp1h")
+draw_center = false
+region_rect = Rect2(0, 0, 512, 512)
+patch_margin_left = 64
+patch_margin_top = 80
+patch_margin_right = 64
+patch_margin_bottom = 48
+
+[node name="Shadow2" type="Panel" parent="."]
+visible = false
+layout_mode = 2
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_lct0k")
+
+[node name="Button" type="Button" parent="."]
+self_modulate = Color(1, 1, 1, 0.501961)
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -12.0
+offset_top = -12.0
+grow_horizontal = 0
+grow_vertical = 0
+mouse_default_cursor_shape = 12
+theme_override_styles/focus = SubResource("StyleBoxEmpty_p3y6j")
+theme_override_styles/hover = SubResource("StyleBoxEmpty_r0x7y")
+theme_override_styles/pressed = SubResource("StyleBoxEmpty_p7tml")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_5muk4")
+icon = ExtResource("7_duwqn")
+flat = true
+expand_icon = true
+
+[node name="Border" type="Panel" parent="."]
+modulate = Color(1, 1, 1, 0)
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_uldta")
+script = ExtResource("8_gj3ji")
+
+[editable path="MainBody/VBoxContainer/Up/TitleButton"]
+[editable path="MainBody/VBoxContainer/Up/PopupButton"]
+[editable path="MainBody/VBoxContainer/Up/MenuButton"]
+[editable path="MainBody/VBoxContainer/Up/CloseButton"]
diff --git a/godot/addons/panku_console/common/lynx_window2/lynx_window_shader_material.tres b/godot/addons/panku_console/common/lynx_window2/lynx_window_shader_material.tres
new file mode 100644
index 0000000..1ed1018
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/lynx_window_shader_material.tres
@@ -0,0 +1,8 @@
+[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://dyipeqsa8lcpc"]
+
+[ext_resource type="Shader" path="res://addons/panku_console/res/shader/simple_fast_blur.gdshader" id="1_3h55m"]
+
+[resource]
+shader = ExtResource("1_3h55m")
+shader_parameter/lod = 4.0
+shader_parameter/modulate = Color(0, 0, 0, 0.12549)
diff --git a/godot/addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd b/godot/addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd
new file mode 100644
index 0000000..3298c43
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd
@@ -0,0 +1,80 @@
+#A simple control node managing its child windows
+class_name PankuLynxWindowsManager extends Control
+
+const CFG_ENABLE_OS_WINDOW = "enable_os_window"
+const CFG_OS_WINDOW_BGCOLOR = "os_window_bg_color"
+
+@onready var console:PankuConsole = get_node(PankuConsole.SingletonPath)
+
+var os_popup_btn_enabled:bool
+var os_window_bg_color:Color
+
+func _ready():
+ load_data()
+
+func _input(e):
+ if e is InputEventMouseButton and e.pressed:
+ var flag = true
+ #traverse child windows in reverse order, use double shadow to highlight current active window.
+ for i in range(get_child_count() - 1, -1, -1):
+ var w:Control = get_child(i)
+ if w.visible and w.get_global_rect().has_point(get_global_mouse_position()):
+ var forefront = get_child(get_child_count() - 1)
+ if forefront.has_method("highlight"): forefront.highlight(false)
+ w.move_to_front()
+ forefront = get_child(get_child_count() - 1)
+ if forefront.has_method("highlight"): forefront.highlight(true)
+ flag = false
+ break
+ if flag and get_child_count() > 0:
+ var forefront = get_child(get_child_count() - 1)
+ if forefront.has_method("highlight"): forefront.highlight(false)
+
+func create_window(content:Control) -> PankuLynxWindow:
+ var new_window:PankuLynxWindow = preload("lynx_window_2.tscn").instantiate()
+ content.anchors_preset = Control.PRESET_FULL_RECT
+ new_window.set_content(content)
+ add_child(new_window)
+ new_window.show_window()
+ return new_window
+
+func enable_os_popup_btns(b:bool):
+ #note that this may affect your project
+ get_viewport().gui_embed_subwindows = !b
+ os_popup_btn_enabled = b
+ for w in get_children():
+ #maybe there's a better way to get node type
+ if !w.has_method("switch_to_os_window"):
+ continue
+ w._pop_btn.visible = b
+
+func get_enable_os_popup_btns() -> bool:
+ return os_popup_btn_enabled
+
+func set_os_window_bg_color(c:Color):
+ os_window_bg_color = c
+ for w in get_children():
+ #maybe there's a better way to get node type
+ if !w.has_method("switch_to_os_window"):
+ continue
+ if w._os_window != null:
+ w._os_window.get_child(0).color = c
+
+func get_os_window_bg_color() -> Color:
+ return os_window_bg_color
+
+func save_data():
+ var cfg = PankuConfig.get_config()
+ cfg[CFG_ENABLE_OS_WINDOW] = os_popup_btn_enabled
+ cfg[CFG_OS_WINDOW_BGCOLOR] = os_window_bg_color
+ PankuConfig.set_config(cfg)
+
+func load_data():
+ var cfg = PankuConfig.get_config()
+ enable_os_popup_btns(cfg.get(CFG_ENABLE_OS_WINDOW, false))
+ set_os_window_bg_color(cfg.get(CFG_OS_WINDOW_BGCOLOR, Color("#2b2e32")))
+
+func _notification(what):
+ #quit event
+ if what == NOTIFICATION_WM_CLOSE_REQUEST:
+ save_data()
diff --git a/godot/addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd.uid b/godot/addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd.uid
new file mode 100644
index 0000000..9830b20
--- /dev/null
+++ b/godot/addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd.uid
@@ -0,0 +1 @@
+uid://xjhlmbdx8fjf
diff --git a/godot/addons/panku_console/common/module_manager.gd b/godot/addons/panku_console/common/module_manager.gd
new file mode 100644
index 0000000..fbfe42e
--- /dev/null
+++ b/godot/addons/panku_console/common/module_manager.gd
@@ -0,0 +1,54 @@
+class_name PankuModuleManager
+
+var _modules:Array[PankuModule]
+var _modules_table:Dictionary
+var _core:PankuConsole
+
+func init_manager(_core:PankuConsole, _modules:Array[PankuModule]):
+ self._modules = _modules
+ self._core = _core
+ load_modules()
+
+func load_modules():
+
+ # The extra tree structure is purely used for avoiding using RefCounted which may cause uncessary leaked instance warnings.
+ var manager_node:Node = Node.new()
+ manager_node.name = "_Modules_"
+ _core.add_child(manager_node)
+
+ for _m in _modules:
+ var module:PankuModule = _m
+ _modules_table[module.get_module_name()] = module
+
+ module.name = module.get_module_name()
+ manager_node.add_child(module)
+
+ for _m in _modules:
+ var module:PankuModule = _m
+ module.core = _core
+ module._init_module()
+ #print("[info] %s module loaded!" % module.get_module_name())
+
+func update_modules(delta:float):
+ for _m in _modules:
+ var module:PankuModule = _m
+ module.update_module(delta)
+
+func get_module(module_name:String):
+ return _modules_table[module_name]
+
+func has_module(module_name:String):
+ return _modules_table.has(module_name)
+
+func get_module_option_objects():
+ var objects = []
+ for _m in _modules:
+ var module:PankuModule = _m
+ if module._opt != null:
+ objects.append(module._opt)
+ return objects
+
+func quit_modules():
+ for _m in _modules:
+ var module:PankuModule = _m
+ module.quit_module()
diff --git a/godot/addons/panku_console/common/module_manager.gd.uid b/godot/addons/panku_console/common/module_manager.gd.uid
new file mode 100644
index 0000000..bf46ffc
--- /dev/null
+++ b/godot/addons/panku_console/common/module_manager.gd.uid
@@ -0,0 +1 @@
+uid://rkefpsd8kk4q
diff --git a/godot/addons/panku_console/common/module_options.gd b/godot/addons/panku_console/common/module_options.gd
new file mode 100644
index 0000000..1910cfd
--- /dev/null
+++ b/godot/addons/panku_console/common/module_options.gd
@@ -0,0 +1,11 @@
+class_name ModuleOptions extends Resource
+
+var _module:PankuModule
+
+var _loaded := false
+
+#FIXME: Tricky part of saving data, needs to be reworked
+func update_setting(key: String, value: Variant):
+ self.set(key, value)
+ if _loaded and _module:
+ _module.save_module_data(key, value)
diff --git a/godot/addons/panku_console/common/module_options.gd.uid b/godot/addons/panku_console/common/module_options.gd.uid
new file mode 100644
index 0000000..cf3b91a
--- /dev/null
+++ b/godot/addons/panku_console/common/module_options.gd.uid
@@ -0,0 +1 @@
+uid://dhh5181ty0ng6
diff --git a/godot/addons/panku_console/common/panku_button.gd b/godot/addons/panku_console/common/panku_button.gd
new file mode 100644
index 0000000..7414df6
--- /dev/null
+++ b/godot/addons/panku_console/common/panku_button.gd
@@ -0,0 +1,39 @@
+class_name PankuButton extends Control
+
+signal pressed
+signal button_down
+signal button_up
+
+@export
+var button:Button
+
+@export
+var trect:TextureRect
+
+@export
+var label:Label
+
+var icon:
+ set(v):
+ trect.texture = v
+ get:
+ return trect.texture
+
+var text:
+ set(v):
+ label.text = v
+ get:
+ return label.text
+
+func _ready():
+
+ button.pressed.connect(
+ func():
+ pressed.emit()
+ )
+ button.button_down.connect(
+ func(): button_down.emit()
+ )
+ button.button_up.connect(
+ func(): button_up.emit()
+ )
diff --git a/godot/addons/panku_console/common/panku_button.gd.uid b/godot/addons/panku_console/common/panku_button.gd.uid
new file mode 100644
index 0000000..e04f6e0
--- /dev/null
+++ b/godot/addons/panku_console/common/panku_button.gd.uid
@@ -0,0 +1 @@
+uid://bdhu7gchsjvpf
diff --git a/godot/addons/panku_console/common/panku_button.tscn b/godot/addons/panku_console/common/panku_button.tscn
new file mode 100644
index 0000000..e404fdc
--- /dev/null
+++ b/godot/addons/panku_console/common/panku_button.tscn
@@ -0,0 +1,42 @@
+[gd_scene load_steps=5 format=3 uid="uid://drn5t13m088fb"]
+
+[ext_resource type="Script" path="res://addons/panku_console/common/panku_button.gd" id="1_7kf5f"]
+[ext_resource type="Texture2D" uid="uid://dchvk7qgfe37m" path="res://addons/panku_console/res/icons2/fold-svgrepo-com.svg" id="2_su653"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_v3kpx"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cwnaw"]
+content_margin_top = 4.0
+content_margin_bottom = 4.0
+
+[node name="PankuButton" type="PanelContainer" node_paths=PackedStringArray("button", "trect", "label")]
+editor_description = "Godot's Button can't handle scaling icons properly as descripted in https://github.com/godotengine/godot-proposals/issues/660, so I have to make a new one."
+self_modulate = Color(1, 1, 1, 0)
+offset_right = 112.0
+offset_bottom = 31.0
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxEmpty_v3kpx")
+script = ExtResource("1_7kf5f")
+button = NodePath("Button")
+trect = NodePath("HBoxContainer/TextureRect")
+label = NodePath("HBoxContainer/Label")
+
+[node name="Button" type="Button" parent="."]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/separation = 0
+
+[node name="TextureRect" type="TextureRect" parent="HBoxContainer"]
+layout_mode = 2
+mouse_filter = 2
+texture = ExtResource("2_su653")
+expand_mode = 2
+
+[node name="Label" type="Label" parent="HBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 1
+theme_override_styles/normal = SubResource("StyleBoxEmpty_cwnaw")
+vertical_alignment = 1
diff --git a/godot/addons/panku_console/common/panku_module.gd b/godot/addons/panku_console/common/panku_module.gd
new file mode 100644
index 0000000..d30ee7c
--- /dev/null
+++ b/godot/addons/panku_console/common/panku_module.gd
@@ -0,0 +1,101 @@
+class_name PankuModule extends Node
+# extends Node: A hacky way to avoid cyclic RefCounted verbose warnings which is uncessary to worry about.
+
+var core:PankuConsole
+
+var _env:RefCounted = null
+var _opt:ModuleOptions = null
+
+# dir name of the module
+func get_module_name() -> String:
+ return get_script().resource_path.get_base_dir().get_file()
+
+# called when the module is loaded
+func init_module():
+ pass
+
+# called when the module is unloaded (quit program)
+func quit_module():
+ if _opt:
+ _opt._loaded = false
+
+# called at the start of each physics frame
+func update_module(delta:float):
+ pass
+
+func save_module_data(key:String, value:Variant):
+ var cfg:Dictionary = PankuConfig.get_config()
+ var module_name:String = get_module_name()
+ if !cfg.has(module_name):
+ cfg[module_name] = {}
+ cfg[module_name][key] = value
+ PankuConfig.set_config(cfg)
+
+func load_module_data(key:String, default_value:Variant = null) -> Variant:
+ var cfg:Dictionary = PankuConfig.get_config()
+ var module_name:String = get_module_name()
+ var module_data = cfg.get(module_name, {})
+ return module_data.get(key, default_value)
+
+func has_module_data(key:String) -> bool:
+ var cfg:Dictionary = PankuConfig.get_config()
+ var module_name:String = get_module_name()
+ var module_data = cfg.get(module_name, {})
+ return module_data.has(key)
+
+func load_window_data(window:PankuLynxWindow):
+ window.position = load_module_data("window_position", window.get_layout_position([
+ Control.PRESET_TOP_LEFT,
+ Control.PRESET_CENTER_TOP,
+ Control.PRESET_TOP_RIGHT,
+ Control.PRESET_CENTER_LEFT,
+ Control.PRESET_CENTER,
+ Control.PRESET_CENTER_RIGHT,
+ Control.PRESET_BOTTOM_LEFT,
+ Control.PRESET_CENTER_BOTTOM,
+ Control.PRESET_BOTTOM_RIGHT,
+ ][randi()%9]))
+ window.size = load_module_data("window_size", window.get_normal_window_size())
+ window.set_window_visibility(load_module_data("window_visibility", false))
+
+func save_window_data(window:PankuLynxWindow):
+ _save_window_geometry(window)
+ save_module_data("window_visibility", window.visible)
+
+
+func _save_window_geometry(window:PankuLynxWindow):
+ save_module_data("window_position", window.position)
+ save_module_data("window_size", window.get_normal_window_size())
+
+
+# Add hook to window to auto save its geometry on close.
+func add_auto_save_hook(window: PankuLynxWindow) -> void:
+ # Here some global settings check can be implemented,
+ # if we decide to make "save on close" feature optional
+ window.window_closed.connect(_save_window_geometry.bind(window))
+
+
+func get_module_env() -> RefCounted:
+ return _env
+
+func get_module_opt() -> ModuleOptions:
+ return _opt
+
+func _init_module():
+ var module_script_dir:String = get_script().resource_path.get_base_dir()
+ var env_script_path = module_script_dir + "/env.gd"
+ var opt_script_path = module_script_dir + "/opt.gd"
+
+ if ResourceLoader.exists(env_script_path, "GDScript"):
+ _env = load(env_script_path).new()
+ _env._module = self
+ core.gd_exprenv.register_env(get_module_name(), _env)
+
+ if ResourceLoader.exists(opt_script_path, "GDScript"):
+ #print(opt_script_path)
+ _opt = load(opt_script_path).new() as ModuleOptions
+ _opt._module = self
+
+ init_module()
+ if _opt:
+ _opt._loaded = true
diff --git a/godot/addons/panku_console/common/panku_module.gd.uid b/godot/addons/panku_console/common/panku_module.gd.uid
new file mode 100644
index 0000000..8c0f8c6
--- /dev/null
+++ b/godot/addons/panku_console/common/panku_module.gd.uid
@@ -0,0 +1 @@
+uid://ce7g5hrgs5lql
diff --git a/godot/addons/panku_console/common/repl_base_instance.gd b/godot/addons/panku_console/common/repl_base_instance.gd
new file mode 100644
index 0000000..44d7ceb
--- /dev/null
+++ b/godot/addons/panku_console/common/repl_base_instance.gd
@@ -0,0 +1,23 @@
+var _core:PankuConsole
+
+const _HELP_help := "List all environment variables."
+var help:String:
+ get:
+ var result = ["Registered objects:\n"]
+ var colors = ["#7c3f58", "#eb6b6f", "#f9a875", "#fff6d3"]
+ var i = 0
+ for k in _core.gd_exprenv._envs:
+ var c = colors[i%4]
+ i = i + 1
+ result.push_back("[b][color=%s]%s[/color][/b] "%[c, k])
+ result.push_back("\n")
+ result.push_back("You can type [b]helpe(object)[/b] to get more information.")
+ return "".join(PackedStringArray(result))
+
+const _HELP_helpe := "Provide detailed information about one specific environment variable."
+func helpe(obj:Object) -> String:
+ if !obj:
+ return "Invalid!"
+ if !obj.get_script():
+ return "It has no attached script!"
+ return PankuGDExprEnv.generate_help_text_from_script(obj.get_script())
diff --git a/godot/addons/panku_console/common/repl_base_instance.gd.uid b/godot/addons/panku_console/common/repl_base_instance.gd.uid
new file mode 100644
index 0000000..5622b43
--- /dev/null
+++ b/godot/addons/panku_console/common/repl_base_instance.gd.uid
@@ -0,0 +1 @@
+uid://b8fo6s8tlpkh4
diff --git a/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.gd b/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.gd
new file mode 100644
index 0000000..40d071f
--- /dev/null
+++ b/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.gd
@@ -0,0 +1,57 @@
+extends PanelContainer
+
+@export var clip_container:Control
+
+@export var scrollbar:VScrollBar
+
+@export var follow_content:bool = true
+
+@onready var content:Control = clip_container.get_child(0)
+
+var scroll_progress:float = 0.0
+var prev_content_size_y:float = 0.0
+
+func init_progressbar() -> void:
+ scrollbar.min_value = 0.0
+ scrollbar.allow_greater = true
+ scrollbar.allow_lesser = true
+ scrollbar.value = 0.0
+
+func _gui_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton and event.is_pressed():
+ var step:float = clip_container.size.y / 8.0
+ if event.button_index == MOUSE_BUTTON_WHEEL_UP:
+ scrollbar.value -= step
+ if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
+ scrollbar.value += step
+
+func _process(delta: float) -> void:
+ # add a tiny optimization here
+ if not is_visible_in_tree(): return
+
+ # See https://github.com/Ark2000/PankuConsole/issues/183, looks quirky
+ # content.size = Vector2(clip_container.size.x, 0)
+ # content.position = Vector2.ZERO
+ content.size.x = clip_container.size.x
+ content.size.y = 0
+ content.position.y = 0
+ content.position.x = 0
+
+ scrollbar.max_value = content.size.y
+ var scrollbar_value_max = max(0, scrollbar.max_value - clip_container.size.y)
+ scrollbar.value = PankuUtils.interp(scrollbar.value, clampf(scrollbar.value, 0.0, scrollbar_value_max), 10, delta)
+ scrollbar.page = clip_container.size.y
+ scrollbar.visible = content.size.y > clip_container.size.y
+
+ scroll_progress = PankuUtils.interp(scroll_progress, scrollbar.value, 10, delta)
+ content.position.y = - scroll_progress
+
+ if !follow_content: return
+ if prev_content_size_y != content.size.y:
+ var should_follow:bool = (scrollbar.value + scrollbar.page) / prev_content_size_y > 0.99
+ prev_content_size_y = content.size.y
+ if should_follow:
+ scrollbar.value = scrollbar.max_value - scrollbar.page
+
+func _ready() -> void:
+ init_progressbar()
diff --git a/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.gd.uid b/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.gd.uid
new file mode 100644
index 0000000..5836452
--- /dev/null
+++ b/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.gd.uid
@@ -0,0 +1 @@
+uid://dtang12b2rua7
diff --git a/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.tscn b/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.tscn
new file mode 100644
index 0000000..013b157
--- /dev/null
+++ b/godot/addons/panku_console/common/smooth_scroll/smooth_scroll.tscn
@@ -0,0 +1,30 @@
+[gd_scene load_steps=3 format=3 uid="uid://dyq4rjkkjs55d"]
+
+[ext_resource type="Script" path="res://addons/panku_console/common/smooth_scroll/smooth_scroll.gd" id="1_ma8ku"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_pa7xs"]
+
+[node name="SmoothScrollContainer" type="PanelContainer" node_paths=PackedStringArray("clip_container", "scrollbar")]
+self_modulate = Color(1, 1, 1, 0)
+clip_contents = true
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_pa7xs")
+script = ExtResource("1_ma8ku")
+clip_container = NodePath("HBoxContainer/Control")
+scrollbar = NodePath("HBoxContainer/VScrollBar")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="Control" type="Control" parent="HBoxContainer"]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="VScrollBar" type="VScrollBar" parent="HBoxContainer"]
+layout_mode = 2
+page = 20.0
+value = 80.0
diff --git a/godot/addons/panku_console/common/utils.gd b/godot/addons/panku_console/common/utils.gd
new file mode 100644
index 0000000..7f3777e
--- /dev/null
+++ b/godot/addons/panku_console/common/utils.gd
@@ -0,0 +1,43 @@
+#@tool
+#extends EditorScript
+class_name PankuUtils
+
+# It will be included if you download the plugin from mirror repo(https://github.com/Ark2000/panku_console)
+const COMMIT_SHA_FILE_PATH = "res://addons/panku_console/COMMIT_SHA"
+
+static func get_plugin_version() -> String:
+ var error_result = "Unknown version"
+ #load version string from plugin.cfg
+ var cfg = ConfigFile.new()
+ if cfg.load("res://addons/panku_console/plugin.cfg") != OK:
+ return error_result
+ return cfg.get_value("plugin", "version", error_result)
+
+static func get_commit_sha() -> String:
+ if FileAccess.file_exists(COMMIT_SHA_FILE_PATH):
+ return FileAccess.get_file_as_string(COMMIT_SHA_FILE_PATH)
+ return ""
+
+static func get_commit_sha_short() -> String:
+ return get_commit_sha().substr(0, 7)
+
+static func get_commit_url() -> String:
+ var sha := get_commit_sha()
+ if sha != "":
+ return "https://github.com/Ark2000/PankuConsole/commit/" + sha
+ return ""
+
+# Framerate-independent interpolation.
+static func interp(from, to, lambda: float, delta: float):
+ if from is Vector2:
+ if abs(from.x - to.x) < 1.0: from.x = to.x
+ if abs(from.y - to.y) < 1.0: from.y = to.y
+ if from is float:
+ if abs(from - to) < 0.01: from = to
+ return lerp(from, to, 1.0 - exp(-lambda * delta))
+
+#func _run():
+# print("plugin_version: ", get_plugin_version())
+# print("commit_sha: ", get_commit_sha())
+# print("commit_sha_short: ", get_commit_sha_short())
+# print("commit_url: ", get_commit_url())
diff --git a/godot/addons/panku_console/common/utils.gd.uid b/godot/addons/panku_console/common/utils.gd.uid
new file mode 100644
index 0000000..9c48668
--- /dev/null
+++ b/godot/addons/panku_console/common/utils.gd.uid
@@ -0,0 +1 @@
+uid://yw3dk5jaii26
diff --git a/godot/addons/panku_console/console.gd b/godot/addons/panku_console/console.gd
new file mode 100644
index 0000000..8900e36
--- /dev/null
+++ b/godot/addons/panku_console/console.gd
@@ -0,0 +1,86 @@
+class_name PankuConsole extends CanvasLayer
+# `console.gd` is a global singleton that provides all modules with a common interface
+# you can also use some of its members to interact with the console
+
+signal interactive_shell_visibility_changed(visible:bool)
+signal new_expression_entered(expression:String, result)
+signal new_notification_created(bbcode:String, id:int)
+signal toggle_console_action_just_pressed()
+
+const SingletonName = "Panku"
+const SingletonPath = "/root/" + SingletonName
+const ToggleConsoleAction = "toggle_console"
+
+# create_data_controller(objs:Array[Object]) -> PankuLynxWindow
+var create_data_controller_window:Callable = func(objs:Array): return null
+
+var windows_manager:PankuLynxWindowsManager
+var module_manager:PankuModuleManager = PankuModuleManager.new()
+var gd_exprenv:PankuGDExprEnv = PankuGDExprEnv.new()
+var _shell_visibility := false
+
+# notification whose id>=0 will be fixed to the bottom of the notification list
+# useful for loop print
+# you can use `get_instance_id()` as notification's unique id
+func notify(any, id=-1) -> void:
+ var text = str(any)
+ new_notification_created.emit(text, id)
+
+func get_shell_visibility() -> bool:
+ return _shell_visibility
+
+func _input(event: InputEvent):
+ if event.is_action_pressed(ToggleConsoleAction):
+ toggle_console_action_just_pressed.emit()
+
+func _ready():
+ assert(get_tree().current_scene != self, "Do not run console.tscn as a scene!")
+
+ # Yep, seems like double check project settings in the main singleton
+ # is the only "correct" way to work with custom project setting
+ # https://github.com/godotengine/godot/issues/56598#issuecomment-1904100640
+ PankuConfig.init_all_project_settings()
+
+ if not PankuConfig.is_custom_default_config_exists():
+ push_warning("[Panku Console] Default config file not found. Using code-level default config.")
+
+ windows_manager = $LynxWindowsManager
+ var base_instance = preload("./common/repl_base_instance.gd").new()
+ base_instance._core = self
+ gd_exprenv.set_base_instance(base_instance)
+
+ # add default input action if not defined by user
+ if not InputMap.has_action(ToggleConsoleAction):
+ InputMap.add_action(ToggleConsoleAction)
+ var default_toggle_console_event = InputEventKey.new()
+ default_toggle_console_event.physical_keycode = KEY_QUOTELEFT
+ InputMap.action_add_event(ToggleConsoleAction, default_toggle_console_event)
+
+ # since panku console servers numerous purposes
+ # we use a module system to manage all different features
+ # modules are invisible to each other by design to avoid coupling
+ # you can add or remove any modules here as you wish
+ var modules:Array[PankuModule] = [
+ PankuModuleNativeLogger.new(),
+ PankuModuleScreenNotifier.new(),
+ PankuModuleSystemReport.new(),
+ PankuModuleHistoryManager.new(),
+ PankuModuleEngineTools.new(),
+ PankuModuleKeyboardShortcuts.new(),
+ PankuModuleCheckLatestRelease.new(),
+ PankuModuleInteractiveShell.new(),
+ PankuModuleGeneralSettings.new(),
+ PankuModuleDataController.new(),
+ PankuModuleScreenCrtEffect.new(),
+ PankuModuleExpressionMonitor.new(),
+ PankuModuleTextureViewer.new(),
+ PankuModuleVariableTracker.new(),
+ PankuModuleAbout.new(),
+ PankuModuleSnakeGame.new(),
+ ]
+ module_manager.init_manager(self, modules)
+
+func _notification(what):
+ # quit event
+ if what == NOTIFICATION_WM_CLOSE_REQUEST:
+ module_manager.quit_modules()
diff --git a/godot/addons/panku_console/console.gd.uid b/godot/addons/panku_console/console.gd.uid
new file mode 100644
index 0000000..5a70886
--- /dev/null
+++ b/godot/addons/panku_console/console.gd.uid
@@ -0,0 +1 @@
+uid://otltxiojr8qu
diff --git a/godot/addons/panku_console/console.tscn b/godot/addons/panku_console/console.tscn
new file mode 100644
index 0000000..2506b02
--- /dev/null
+++ b/godot/addons/panku_console/console.tscn
@@ -0,0 +1,22 @@
+[gd_scene load_steps=4 format=3 uid="uid://cyftuo4syatlv"]
+
+[ext_resource type="Script" path="res://addons/panku_console/console.gd" id="1_dohs1"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="2_wxhx6"]
+[ext_resource type="Script" path="res://addons/panku_console/common/lynx_window2/lynx_windows_manager_2.gd" id="10_f7qaq"]
+
+[node name="Console" type="CanvasLayer"]
+process_mode = 3
+layer = 128
+script = ExtResource("1_dohs1")
+
+[node name="LynxWindowsManager" type="Control" parent="."]
+z_index = 1
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme = ExtResource("2_wxhx6")
+script = ExtResource("10_f7qaq")
diff --git a/godot/addons/panku_console/default_panku_config.cfg b/godot/addons/panku_console/default_panku_config.cfg
new file mode 100644
index 0000000..e5deb54
--- /dev/null
+++ b/godot/addons/panku_console/default_panku_config.cfg
@@ -0,0 +1,80 @@
+{
+"enable_os_window": false,
+"engine_tools": {
+"time_scale": 1.0
+},
+"exp_history": [],
+"expression_monitor": {
+"monitor_data": [{
+"expressions": ["engine_tools.get_performance_info()"],
+"group_name": "default group"
+}],
+"window_position": Vector2(0, 49),
+"window_size": Vector2(85, 74),
+"window_visibility": false
+},
+"general_settings": {
+"enable_os_window": false,
+"lynx_window_base_color": Color(0, 0.0470588, 0.0941176, 0.501961),
+"lynx_window_blur_effect": true,
+"lynx_window_enable_os_window": false,
+"lynx_window_os_window_bg_color": Color(0, 0, 0, 0.658824),
+"os_window_bg_color": Color(0, 0, 0, 0.992157),
+"window_blur_effect": true,
+"window_position": Vector2(429.546, 94.1911),
+"window_size": Vector2(512.568, 478.128),
+"window_visibility": true
+},
+"history_manager": {
+"window_position": Vector2(317.728, 138.82),
+"window_size": Vector2(411.987, 339.537),
+"window_visibility": false
+},
+"interactive_shell": {
+"gui_mode": 0,
+"histories": [],
+"init_expr": "",
+"output_font_size": 14.0,
+"pause_if_input": false,
+"pause_if_popup": false,
+"show_side_menu": true,
+"unified_visibility": false,
+"unified_window_visibility": false,
+"window_position": Vector2(427.419, 75.3913),
+"window_size": Vector2(510.736, 410.437),
+"window_visibility": true
+},
+"keyboard_shortcuts": {
+"key_mapper": [],
+"window_position": Vector2(0, 49),
+"window_size": Vector2(85, 74),
+"window_visibility": false
+},
+"native_logger": {
+"font_size": 17.0,
+"logger_tags": ["[error]", "[warning]", "[info]"],
+"screen_overlay": 0,
+"screen_overlay_alpha": 0.44,
+"screen_overlay_font_shadow": true,
+"screen_overlay_font_size": 20.0,
+"show_timestamp": true,
+"window_position": Vector2(284.123, 124.547),
+"window_size": Vector2(483.998, 379.028),
+"window_visibility": false
+},
+"os_window_bg_color": Color(0, 0, 0, 0.992157),
+"os_window_bgcolor": Color(0, 0, 0, 0.658824),
+"snake": {
+"leader_board": [{
+"score": 40,
+"timestamp": "2024-01-29T16:00:00"
+}, {
+"score": 20,
+"timestamp": "2024-01-29T16:01:14"
+}]
+},
+"variable_tracker": {
+"tracking_delay": 0.5,
+"use_last_as_current": true
+}
+}
diff --git a/godot/addons/panku_console/logo.svg b/godot/addons/panku_console/logo.svg
new file mode 100644
index 0000000..41f564c
--- /dev/null
+++ b/godot/addons/panku_console/logo.svg
@@ -0,0 +1,25 @@
+
+
+
+
diff --git a/godot/addons/panku_console/logo.svg.import b/godot/addons/panku_console/logo.svg.import
new file mode 100644
index 0000000..79c2e5f
--- /dev/null
+++ b/godot/addons/panku_console/logo.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rkxm4c3bbf34"
+path="res://.godot/imported/logo.svg-5c6f042742ccac523c072414b9eb3caf.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/logo.svg"
+dest_files=["res://.godot/imported/logo.svg-5c6f042742ccac523c072414b9eb3caf.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/modules/about/about.gd b/godot/addons/panku_console/modules/about/about.gd
new file mode 100644
index 0000000..79b01f9
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/about.gd
@@ -0,0 +1,26 @@
+extends Control
+
+var _module:PankuModule
+
+@export var intro:Label
+@export var project_page_button:Button
+@export var check_update_button:Button
+
+# Called when the node enters the scene tree for the first time.
+func _ready():
+ var version:String = PankuUtils.get_plugin_version()
+ var sha:String = PankuUtils.get_commit_sha_short()
+ if sha == "": sha = "Unknown"
+
+ intro.text = intro.text.replace("", version)
+ intro.text = intro.text.replace("", sha)
+
+ project_page_button.pressed.connect(
+ func():
+ OS.shell_open("https://github.com/Ark2000/PankuConsole")
+ )
+
+ check_update_button.pressed.connect(
+ func():
+ _module.core.gd_exprenv.execute("check_latest_release.check()")
+ )
diff --git a/godot/addons/panku_console/modules/about/about.gd.uid b/godot/addons/panku_console/modules/about/about.gd.uid
new file mode 100644
index 0000000..fb3e65a
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/about.gd.uid
@@ -0,0 +1 @@
+uid://pkgdt4duvguc
diff --git a/godot/addons/panku_console/modules/about/about.tscn b/godot/addons/panku_console/modules/about/about.tscn
new file mode 100644
index 0000000..ecd50f3
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/about.tscn
@@ -0,0 +1,215 @@
+[gd_scene load_steps=10 format=3 uid="uid://cgwg3foes57mq"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/about/about.gd" id="1_kpix3"]
+[ext_resource type="PackedScene" uid="uid://dyq4rjkkjs55d" path="res://addons/panku_console/common/smooth_scroll/smooth_scroll.tscn" id="2_sev6e"]
+[ext_resource type="Texture2D" uid="uid://rkxm4c3bbf34" path="res://addons/panku_console/logo.svg" id="3_eycho"]
+[ext_resource type="PackedScene" uid="uid://cxmplwhfv5l88" path="res://addons/panku_console/modules/about/url_button.tscn" id="4_cqu2c"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_knn3o"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+bg_color = Color(0, 0, 0, 0.270588)
+
+[sub_resource type="FontVariation" id="FontVariation_v70xy"]
+variation_embolden = 0.5
+
+[sub_resource type="FontVariation" id="FontVariation_wgi7f"]
+variation_embolden = 0.5
+
+[sub_resource type="SystemFont" id="SystemFont_6on77"]
+font_names = PackedStringArray("Monospace")
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cqcex"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 0.25098)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[node name="about" type="Control" node_paths=PackedStringArray("intro", "project_page_button", "check_update_button")]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_kpix3")
+intro = NodePath("VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/PanelContainer/HBoxContainer/Label")
+project_page_button = NodePath("VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/ProjectPageButton")
+check_update_button = NodePath("VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer/CheckUpdateButton")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = -1
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 8.0
+offset_top = 8.0
+offset_right = -8.0
+offset_bottom = -8.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="SmoothScrollContainer" parent="VBoxContainer" instance=ExtResource("2_sev6e")]
+layout_mode = 2
+size_flags_vertical = 3
+follow_content = false
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control" index="0"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 995.0
+grow_horizontal = 2
+
+[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer"]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_knn3o")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/PanelContainer"]
+layout_mode = 2
+alignment = 1
+
+[node name="TextureRect" type="TextureRect" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/PanelContainer/HBoxContainer"]
+layout_mode = 2
+texture = ExtResource("3_eycho")
+expand_mode = 2
+
+[node name="Label" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/PanelContainer/HBoxContainer"]
+layout_mode = 2
+text = "Panku Console []
+© 2022-present Panku Console contributors.
+© 2022 Ark2000.
+"
+
+[node name="Label" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer"]
+layout_mode = 2
+text = "Panku Console is a feature-packed real-time debugging toolkit for Godot Engine."
+clip_text = true
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer"]
+layout_mode = 2
+
+[node name="ProjectPageButton" type="Button" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_fonts/font = SubResource("FontVariation_v70xy")
+text = "Project Page"
+
+[node name="CheckUpdateButton" type="Button" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+theme_override_fonts/font = SubResource("FontVariation_v70xy")
+text = "Check Update"
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer"]
+layout_mode = 2
+
+[node name="Label" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_fonts/font = SubResource("FontVariation_wgi7f")
+text = "Project Manager"
+
+[node name="Button" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "Feo Wu (Ark2000)"
+url = "https://github.com/Ark2000"
+
+[node name="Label2" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_fonts/font = SubResource("FontVariation_wgi7f")
+text = "Contributors"
+
+[node name="Button2" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "Feo Wu (Ark2000)"
+url = "https://github.com/Ark2000"
+
+[node name="Button3" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "Rafael Correa (scriptsengineer)"
+url = "https://github.com/scriptsengineer"
+
+[node name="Button4" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "worron"
+url = "https://github.com/worron"
+
+[node name="Button5" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "mieldepoche"
+url = "https://github.com/mieldepoche"
+
+[node name="Button6" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "Eggbertx"
+url = "https://github.com/Eggbertx"
+
+[node name="Button7" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "univeous"
+url = "https://github.com/univeous"
+
+[node name="Button8" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "CheapMeow"
+url = "https://github.com/CheapMeow"
+
+[node name="Button9" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "winston-yallow (Winston)"
+url = "https://github.com/winston-yallow"
+
+[node name="Label3" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_fonts/font = SubResource("FontVariation_wgi7f")
+text = "Icons"
+
+[node name="Button10" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer" instance=ExtResource("4_cqu2c")]
+layout_mode = 2
+text = "SVG Repo"
+url = "https://www.svgrepo.com/page/licensing/"
+
+[node name="Label4" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_fonts/font = SubResource("FontVariation_wgi7f")
+text = "License"
+
+[node name="Label5" type="Label" parent="VBoxContainer/SmoothScrollContainer/HBoxContainer/Control/VBoxContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_fonts/font = SubResource("SystemFont_6on77")
+theme_override_styles/normal = SubResource("StyleBoxFlat_cqcex")
+text = "The MIT License (MIT)
+
+Copyright (c) 2022-present Panku Console contributors.
+Copyright (c) 2022 Ark2000
+
+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."
+clip_text = true
+
+[editable path="VBoxContainer/SmoothScrollContainer"]
diff --git a/godot/addons/panku_console/modules/about/env.gd b/godot/addons/panku_console/modules/about/env.gd
new file mode 100644
index 0000000..e3e1d11
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/env.gd
@@ -0,0 +1,5 @@
+var _module:PankuModule
+
+const _HELP_open = "Open about window"
+func open() -> void:
+ _module.open_window()
diff --git a/godot/addons/panku_console/modules/about/env.gd.uid b/godot/addons/panku_console/modules/about/env.gd.uid
new file mode 100644
index 0000000..9a153ac
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/env.gd.uid
@@ -0,0 +1 @@
+uid://cbd0wepa77b0c
diff --git a/godot/addons/panku_console/modules/about/module.gd b/godot/addons/panku_console/modules/about/module.gd
new file mode 100644
index 0000000..9b4c915
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/module.gd
@@ -0,0 +1,15 @@
+class_name PankuModuleAbout extends PankuModule
+
+var window:PankuLynxWindow
+const ui = preload("./about.tscn")
+
+func open_window():
+ if window: return
+ var ui_instance = ui.instantiate()
+ ui_instance._module = self
+ window = core.windows_manager.create_window(ui_instance)
+ window.set_window_title_text("About")
+ window.show_window()
+ window.window_closed.connect(
+ func(): window = null
+ )
diff --git a/godot/addons/panku_console/modules/about/module.gd.uid b/godot/addons/panku_console/modules/about/module.gd.uid
new file mode 100644
index 0000000..2754617
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/module.gd.uid
@@ -0,0 +1 @@
+uid://bhcijrphn6uto
diff --git a/godot/addons/panku_console/modules/about/url_button.gd b/godot/addons/panku_console/modules/about/url_button.gd
new file mode 100644
index 0000000..97a4e1a
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/url_button.gd
@@ -0,0 +1,9 @@
+extends Button
+
+@export var url:String
+
+func _ready():
+ pressed.connect(
+ func():
+ OS.shell_open(url)
+ )
diff --git a/godot/addons/panku_console/modules/about/url_button.gd.uid b/godot/addons/panku_console/modules/about/url_button.gd.uid
new file mode 100644
index 0000000..da3bb33
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/url_button.gd.uid
@@ -0,0 +1 @@
+uid://btw5pitk8auv6
diff --git a/godot/addons/panku_console/modules/about/url_button.tscn b/godot/addons/panku_console/modules/about/url_button.tscn
new file mode 100644
index 0000000..72236a3
--- /dev/null
+++ b/godot/addons/panku_console/modules/about/url_button.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=3 format=3 uid="uid://cxmplwhfv5l88"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/about/url_button.gd" id="1_01blg"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ivnlg"]
+content_margin_left = 8.0
+bg_color = Color(0, 0, 0, 0.243137)
+corner_radius_top_left = 4
+corner_radius_top_right = 4
+corner_radius_bottom_right = 4
+corner_radius_bottom_left = 4
+
+[node name="Button" type="Button"]
+theme_override_styles/normal = SubResource("StyleBoxFlat_ivnlg")
+text = "Button Text"
+alignment = 0
+script = ExtResource("1_01blg")
diff --git a/godot/addons/panku_console/modules/check_latest_release/env.gd b/godot/addons/panku_console/modules/check_latest_release/env.gd
new file mode 100644
index 0000000..74e0a00
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/env.gd
@@ -0,0 +1,5 @@
+var _module:PankuModule
+
+const _HELP_check = "Fetch latest release information from Github"
+func check():
+ _module.check()
diff --git a/godot/addons/panku_console/modules/check_latest_release/env.gd.uid b/godot/addons/panku_console/modules/check_latest_release/env.gd.uid
new file mode 100644
index 0000000..eeb2680
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/env.gd.uid
@@ -0,0 +1 @@
+uid://dad7nd3c3hjqv
diff --git a/godot/addons/panku_console/modules/check_latest_release/module.gd b/godot/addons/panku_console/modules/check_latest_release/module.gd
new file mode 100644
index 0000000..e720ff7
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/module.gd
@@ -0,0 +1,32 @@
+class_name PankuModuleCheckLatestRelease extends PankuModule
+
+signal check_lasted_release_requested()
+signal check_lasted_release_responded(msg:Dictionary)
+
+func send_request() -> Node:
+ var node = preload("./network.gd").new()
+ core.add_child(node)
+ node.check_latest_release()
+ node.response_received.connect(
+ func(_v): node.queue_free()
+ )
+ return node
+
+func check_update():
+ send_request().response_received.connect(
+ func(msg:Dictionary):
+ check_lasted_release_responded.emit(msg)
+ )
+
+func check():
+ send_request().response_received.connect(
+ func(msg:Dictionary):
+ if !msg["success"]:
+ core.notify("[color=red][Error][/color] Failed! " + msg["msg"])
+ else:
+ core.notify("[color=green][info][/color] Latest: [%s] [url=%s]%s[/url]" % [msg["published_at"], msg["html_url"], msg["name"]])
+ )
+
+func init_module():
+ # implement core functions
+ check_lasted_release_requested.connect(check_update)
diff --git a/godot/addons/panku_console/modules/check_latest_release/module.gd.uid b/godot/addons/panku_console/modules/check_latest_release/module.gd.uid
new file mode 100644
index 0000000..e4a6b37
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/module.gd.uid
@@ -0,0 +1 @@
+uid://ba42o6b87vvna
diff --git a/godot/addons/panku_console/modules/check_latest_release/network.gd b/godot/addons/panku_console/modules/check_latest_release/network.gd
new file mode 100644
index 0000000..963718e
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/network.gd
@@ -0,0 +1,59 @@
+extends HTTPRequest
+
+signal response_received(msg:Dictionary)
+
+const LATEST_RELEASE_URL = "https://api.github.com/repos/Ark2000/PankuConsole/releases/latest"
+
+const REQUEST_RESULT = {
+ RESULT_SUCCESS: "SUCCESS",
+ RESULT_CHUNKED_BODY_SIZE_MISMATCH: "CHUNKED_BODY_SIZE_MISMATCH",
+ RESULT_CANT_CONNECT: "CANT_CONNECT",
+ RESULT_CANT_RESOLVE: "CANT_RESOLVE",
+ RESULT_CONNECTION_ERROR: "CONNECTION_ERROR",
+ RESULT_TLS_HANDSHAKE_ERROR: "TLS_HANDSHAKE_ERROR",
+ RESULT_NO_RESPONSE: "NO_RESPONSE",
+ RESULT_BODY_SIZE_LIMIT_EXCEEDED: "BODY_SIZE_LIMIT_EXCEEDED",
+ RESULT_BODY_DECOMPRESS_FAILED: "BODY_DECOMPRESS_FAILED",
+ RESULT_REQUEST_FAILED: "REQUEST_FAILED",
+ RESULT_DOWNLOAD_FILE_CANT_OPEN: "DOWNLOAD_FILE_CANT_OPEN",
+ RESULT_DOWNLOAD_FILE_WRITE_ERROR: "DOWNLOAD_FILE_WRITE_ERROR",
+ RESULT_REDIRECT_LIMIT_REACHED: "REDIRECT_LIMIT_REACHED",
+ RESULT_TIMEOUT: "TIMEOUT"
+}
+
+static func dget(dict:Dictionary, key, default_value=""):
+ return default_value if !dict.has(key) else dict[key]
+
+var is_busy := false
+
+func _ready():
+ request_completed.connect(_on_request_completed)
+
+func check_latest_release():
+ if is_busy: return
+ is_busy = true
+ var error = request(LATEST_RELEASE_URL)
+ if error != OK:
+ is_busy = false
+ response_received.emit({
+ "success": false,
+ "msg": "An error occurred in the HTTP request."
+ })
+
+func _on_request_completed(result:int, response_code:int, headers:PackedStringArray, body:PackedByteArray):
+ is_busy = false
+ if result != RESULT_SUCCESS:
+ response_received.emit({
+ "success": false,
+ "msg": REQUEST_RESULT[result]
+ })
+ return
+ var json = JSON.new()
+ json.parse(body.get_string_from_utf8())
+ var response:Dictionary = json.get_data()
+ response_received.emit({
+ "success": true,
+ "published_at": dget(response, "published_at", "???"),
+ "name": dget(response, "name", "???"),
+ "html_url": dget(response, "html_url", "???")
+ })
diff --git a/godot/addons/panku_console/modules/check_latest_release/network.gd.uid b/godot/addons/panku_console/modules/check_latest_release/network.gd.uid
new file mode 100644
index 0000000..7e2a57a
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/network.gd.uid
@@ -0,0 +1 @@
+uid://dojjr6xbfugxm
diff --git a/godot/addons/panku_console/modules/check_latest_release/opt.gd b/godot/addons/panku_console/modules/check_latest_release/opt.gd
new file mode 100644
index 0000000..9bd8384
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/opt.gd
@@ -0,0 +1,8 @@
+extends ModuleOptions
+
+@export_group("check_latest_release")
+
+@export var export_button_check_update := "Check Update"
+
+func check_update():
+ _module.check()
diff --git a/godot/addons/panku_console/modules/check_latest_release/opt.gd.uid b/godot/addons/panku_console/modules/check_latest_release/opt.gd.uid
new file mode 100644
index 0000000..f7b0d94
--- /dev/null
+++ b/godot/addons/panku_console/modules/check_latest_release/opt.gd.uid
@@ -0,0 +1 @@
+uid://dwah7jcf3nn6i
diff --git a/godot/addons/panku_console/modules/engine_tools/env.gd b/godot/addons/panku_console/modules/engine_tools/env.gd
new file mode 100644
index 0000000..33bfdd8
--- /dev/null
+++ b/godot/addons/panku_console/modules/engine_tools/env.gd
@@ -0,0 +1,30 @@
+var _module:PankuModule
+
+const _HELP_toggle_fullscreen = "Toggle [fullscreen / windowed] mode"
+func toggle_fullscreen() -> void:
+ _module.toggle_fullscreen()
+
+const _HELP_set_time_scale = "Equals to [color=green]Engine.time_scale[/color]"
+func set_time_scale(val:float) -> void:
+ _module.set_time_scale(val)
+
+const _HELP_get_performance_info = "Show performance info"
+func get_performance_info(count_nodes := false) -> String:
+ return _module.get_performance_info(count_nodes)
+
+const _HELP_take_screenshot = "Take a screenshot of current window"
+func take_screenshot() -> void:
+ _module.take_screenshot()
+
+const _HELP_quit = "Quit application"
+func quit() -> void:
+ _module.quit()
+
+const _HELP_toggle_2d_collision_shape_visibility = "Toggle visibility of 2D collision shapes, useful for debugging"
+func toggle_2d_collision_shape_visibility() -> void:
+ _module.toggle_2d_collision_shape_visibility()
+
+const _HELP_reload_current_scene = "Reload current scene"
+func reload_current_scene() -> void:
+ _module.reload_current_scene()
+
diff --git a/godot/addons/panku_console/modules/engine_tools/env.gd.uid b/godot/addons/panku_console/modules/engine_tools/env.gd.uid
new file mode 100644
index 0000000..69eda18
--- /dev/null
+++ b/godot/addons/panku_console/modules/engine_tools/env.gd.uid
@@ -0,0 +1 @@
+uid://b02ldrmvchylr
diff --git a/godot/addons/panku_console/modules/engine_tools/module.gd b/godot/addons/panku_console/modules/engine_tools/module.gd
new file mode 100644
index 0000000..512f650
--- /dev/null
+++ b/godot/addons/panku_console/modules/engine_tools/module.gd
@@ -0,0 +1,72 @@
+class_name PankuModuleEngineTools extends PankuModule
+
+func init_module():
+ get_module_opt().count_nodes = load_module_data("count_nodes", false)
+ super.init_module()
+
+func toggle_fullscreen() -> void:
+ if DisplayServer.window_get_mode() != DisplayServer.WINDOW_MODE_WINDOWED:
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
+ else:
+ DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
+ core.notify("Fullscreen: " + str(DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN))
+
+func set_time_scale(val:float) -> void:
+ Engine.time_scale = val
+
+class ClsCountNodesInTree:
+ func calc_children_count(core: PankuConsole, node_path: String) -> int:
+ var nd: Node = core.get_tree().current_scene.get_node(node_path)
+ return count_all_children(nd)
+ func count_all_children(nd: Node) -> int:
+ var count: int = nd.get_children().size()
+ for child: Node in nd.get_children():
+ count += count_all_children(child)
+ return count
+
+func get_performance_info(count_nodes:bool) -> String:
+ var result = "FPS: %d | Mem: %.2fMB | Objs: %d" % [
+ Engine.get_frames_per_second(),
+ OS.get_static_memory_usage()/1048576.0,
+ Performance.get_monitor(Performance.OBJECT_COUNT)
+ ]
+ if count_nodes:
+ var cls_count: ClsCountNodesInTree = ClsCountNodesInTree.new()
+ var root_count: int = cls_count.calc_children_count(core, "/root")
+ var panku_count: int = cls_count.calc_children_count(core, "/root/Panku")
+ result += " | Nodes: %d" % [root_count - panku_count]
+ return result
+
+func take_screenshot() -> void:
+ var image = core.get_viewport().get_texture().get_image()
+ var time = str(int(Time.get_unix_time_from_system() * 1000.0))
+ var file_name = "screenshot_%s.png" % time
+ var path = "user://".path_join(file_name)
+ var real_path = OS.get_user_data_dir().path_join(file_name)
+ image.save_png(path)
+ core.notify("[b]Screenshot[/b] saved at [color=green][url=%s]%s[/url][/color]" % [real_path, real_path])
+
+func quit() -> void:
+ core.get_tree().root.propagate_notification(core.NOTIFICATION_WM_CLOSE_REQUEST)
+ core.get_tree().quit()
+
+# Currently godot can't toggle visibility of 2D collision shapes at runtime, this is a workaround.
+# See https://github.com/godotengine/godot-proposals/issues/2072
+func toggle_2d_collision_shape_visibility() -> void:
+ var tree := core.get_tree()
+ tree.debug_collisions_hint = not tree.debug_collisions_hint
+
+ # Traverse tree to call queue_redraw on instances of
+ # CollisionShape2D and CollisionPolygon2D.
+ var node_stack: Array[Node] = [tree.get_root()]
+ while not node_stack.is_empty():
+ var node: Node = node_stack.pop_back()
+ if is_instance_valid(node):
+ if node is CollisionShape2D or node is CollisionPolygon2D:
+ node.queue_redraw()
+ node_stack.append_array(node.get_children())
+ core.notify("2D Debug Draw: " + str(tree.debug_collisions_hint))
+
+func reload_current_scene() -> void:
+ core.get_tree().reload_current_scene()
+ core.notify("Scene reloaded")
diff --git a/godot/addons/panku_console/modules/engine_tools/module.gd.uid b/godot/addons/panku_console/modules/engine_tools/module.gd.uid
new file mode 100644
index 0000000..2814df4
--- /dev/null
+++ b/godot/addons/panku_console/modules/engine_tools/module.gd.uid
@@ -0,0 +1 @@
+uid://dan217hmehfix
diff --git a/godot/addons/panku_console/modules/engine_tools/opt.gd b/godot/addons/panku_console/modules/engine_tools/opt.gd
new file mode 100644
index 0000000..63d1a77
--- /dev/null
+++ b/godot/addons/panku_console/modules/engine_tools/opt.gd
@@ -0,0 +1,40 @@
+extends ModuleOptions
+
+@export_group("engine_tools")
+
+@export var export_button_toggle_fullscreen := "Toggle Fullscreen"
+
+func toggle_fullscreen():
+ _module.toggle_fullscreen()
+
+@export var export_button_take_screenshot := "Take Screenshot"
+
+func take_screenshot():
+ _module.take_screenshot()
+
+@export var export_button_quit := "Quit"
+
+func quit():
+ _module.quit()
+
+@export var export_button_toggle_2d_debug_draw := "Toggle 2D Debug Draw"
+
+func toggle_2d_debug_draw():
+ _module.toggle_2d_collision_shape_visibility()
+
+@export var export_button_reload_current_scene := "Reload Current Scene"
+
+func reload_current_scene():
+ _module.reload_current_scene()
+
+@export_range(0.1, 10.0, 0.01) var time_scale := 1.0:
+ set(v):
+ time_scale = v
+ _module.set_time_scale(time_scale)
+
+@export var export_comment_count_nodes = "Show node count in performance info (can issue extra performance hit)."
+@export var count_nodes := false
+
+@export var readonly_performance_info:String:
+ get:
+ return _module.get_performance_info(count_nodes)
diff --git a/godot/addons/panku_console/modules/engine_tools/opt.gd.uid b/godot/addons/panku_console/modules/engine_tools/opt.gd.uid
new file mode 100644
index 0000000..f033914
--- /dev/null
+++ b/godot/addons/panku_console/modules/engine_tools/opt.gd.uid
@@ -0,0 +1 @@
+uid://v2u6b17gkyxf
diff --git a/godot/addons/panku_console/modules/expression_monitor/env.gd b/godot/addons/panku_console/modules/expression_monitor/env.gd
new file mode 100644
index 0000000..0ac9d50
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/env.gd
@@ -0,0 +1,4 @@
+var _module:PankuModule
+
+func open_window():
+ _module.open_window()
diff --git a/godot/addons/panku_console/modules/expression_monitor/env.gd.uid b/godot/addons/panku_console/modules/expression_monitor/env.gd.uid
new file mode 100644
index 0000000..9eb8888
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/env.gd.uid
@@ -0,0 +1 @@
+uid://cwk00j0krbxbk
diff --git a/godot/addons/panku_console/modules/expression_monitor/expression_item.gd b/godot/addons/panku_console/modules/expression_monitor/expression_item.gd
new file mode 100644
index 0000000..7be43d1
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/expression_item.gd
@@ -0,0 +1,33 @@
+extends Control
+
+signal expr_changed(new_expr:String)
+signal removing
+
+@export var x_btn:Button
+@export var ledit:LineEdit
+@export var label:Label
+
+func set_result(text:String):
+ label.text = text
+ label.get_parent().visible = (text != "")
+
+func set_expr(text:String):
+ ledit.text = text
+
+func get_expr() -> String:
+ return ledit.text
+
+func _ready():
+ x_btn.pressed.connect(
+ func():
+ removing.emit()
+ queue_free()
+ )
+ ledit.focus_exited.connect(
+ func():
+ expr_changed.emit(ledit.text)
+ )
+ ledit.text_submitted.connect(
+ func(new_text:String):
+ expr_changed.emit(new_text)
+ )
diff --git a/godot/addons/panku_console/modules/expression_monitor/expression_item.gd.uid b/godot/addons/panku_console/modules/expression_monitor/expression_item.gd.uid
new file mode 100644
index 0000000..714d5e9
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/expression_item.gd.uid
@@ -0,0 +1 @@
+uid://cncy7a8mdovjf
diff --git a/godot/addons/panku_console/modules/expression_monitor/expression_item.tscn b/godot/addons/panku_console/modules/expression_monitor/expression_item.tscn
new file mode 100644
index 0000000..21d307c
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/expression_item.tscn
@@ -0,0 +1,46 @@
+[gd_scene load_steps=4 format=3 uid="uid://om8bp40jo1e"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/expression_monitor/expression_item.gd" id="1_dnu23"]
+[ext_resource type="Texture2D" uid="uid://8g5afcuanbl6" path="res://addons/panku_console/res/icons2/close.svg" id="2_bc3ds"]
+[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="3_n03xa"]
+
+[node name="VBoxContainer" type="VBoxContainer" node_paths=PackedStringArray("x_btn", "ledit", "label")]
+anchors_preset = 10
+anchor_right = 1.0
+offset_bottom = 31.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+script = ExtResource("1_dnu23")
+x_btn = NodePath("expression_item/PankuButton/Button")
+ledit = NodePath("expression_item/LineEdit")
+label = NodePath("HBoxContainer/Label")
+
+[node name="expression_item" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="PankuButton" parent="expression_item" instance=ExtResource("3_n03xa")]
+layout_mode = 2
+
+[node name="TextureRect" parent="expression_item/PankuButton/HBoxContainer" index="0"]
+texture = ExtResource("2_bc3ds")
+
+[node name="LineEdit" type="LineEdit" parent="expression_item"]
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Input expression here..."
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+visible = false
+layout_mode = 2
+
+[node name="ColorRect" type="ColorRect" parent="HBoxContainer"]
+custom_minimum_size = Vector2(8, 0)
+layout_mode = 2
+color = Color(0.239216, 0.533333, 0.886275, 0.752941)
+
+[node name="Label" type="Label" parent="HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+autowrap_mode = 1
+
+[editable path="expression_item/PankuButton"]
diff --git a/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.gd b/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.gd
new file mode 100644
index 0000000..54902dc
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.gd
@@ -0,0 +1,24 @@
+extends Control
+
+@onready var monitor_groups_ui := $SmoothScrollContainer/HBoxContainer/Control/MonitorGroupsUI
+
+var _module:PankuModule
+
+func _physics_process(delta: float) -> void:
+ if Engine.get_physics_frames() % 10 != 1:
+ return
+ if !is_visible_in_tree():
+ return
+
+ var exprss = monitor_groups_ui.get_expressions_by_group()
+ var resultss = []
+ for exprs in exprss:
+ var results = []
+ for expr in exprs:
+ if expr == "":
+ continue
+ var eval_result_dict = _module.core.gd_exprenv.execute(expr)
+ var eval_result:String = str(eval_result_dict["result"])
+ results.append(eval_result)
+ resultss.append(results)
+ monitor_groups_ui.set_results_by_group(resultss)
diff --git a/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.gd.uid b/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.gd.uid
new file mode 100644
index 0000000..4eb2c21
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.gd.uid
@@ -0,0 +1 @@
+uid://jkmkx3lj1vj3
diff --git a/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.tscn b/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.tscn
new file mode 100644
index 0000000..abf8b5c
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/expression_monitor2.tscn
@@ -0,0 +1,30 @@
+[gd_scene load_steps=4 format=3 uid="uid://biuybfe60pr4u"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/expression_monitor/expression_monitor2.gd" id="1_ha27e"]
+[ext_resource type="PackedScene" uid="uid://dyq4rjkkjs55d" path="res://addons/panku_console/common/smooth_scroll/smooth_scroll.tscn" id="2_6rd5x"]
+[ext_resource type="PackedScene" uid="uid://ctgf7kewoa1cn" path="res://addons/panku_console/modules/expression_monitor/monitor_groups_ui.tscn" id="2_sa5u0"]
+
+[node name="expression_monitor2" type="Control"]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_ha27e")
+
+[node name="SmoothScrollContainer" parent="." instance=ExtResource("2_6rd5x")]
+layout_mode = 1
+
+[node name="MonitorGroupsUI" parent="SmoothScrollContainer/HBoxContainer/Control" index="0" instance=ExtResource("2_sa5u0")]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = 0.0
+offset_bottom = 0.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[editable path="SmoothScrollContainer"]
diff --git a/godot/addons/panku_console/modules/expression_monitor/module.gd b/godot/addons/panku_console/modules/expression_monitor/module.gd
new file mode 100644
index 0000000..7c5a827
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/module.gd
@@ -0,0 +1,29 @@
+class_name PankuModuleExpressionMonitor extends PankuModule
+
+var monitor
+var monitor_window:PankuLynxWindow
+
+func init_module():
+ init_monitor_window()
+ load_window_data(monitor_window)
+ monitor.monitor_groups_ui.load_persistent_data(load_module_data("monitor_data", [{
+ "group_name": "default group",
+ "expressions": []
+ }]))
+
+func quit_module():
+ super.quit_module()
+ save_window_data(monitor_window)
+ save_module_data("monitor_data", monitor.monitor_groups_ui.get_persistent_data())
+
+func init_monitor_window():
+ monitor = preload("./expression_monitor2.tscn").instantiate()
+ monitor._module = self
+# monitor.set_data(load_module_data("exprs", []))
+ monitor_window = core.windows_manager.create_window(monitor)
+ add_auto_save_hook(monitor_window)
+ monitor_window.queue_free_on_close = false
+ monitor_window.set_window_title_text("Expression Monitor")
+
+func open_window():
+ monitor_window.show_window()
diff --git a/godot/addons/panku_console/modules/expression_monitor/module.gd.uid b/godot/addons/panku_console/modules/expression_monitor/module.gd.uid
new file mode 100644
index 0000000..746767c
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/module.gd.uid
@@ -0,0 +1 @@
+uid://bk4d660m07xto
diff --git a/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.gd b/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.gd
new file mode 100644
index 0000000..ed14634
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.gd
@@ -0,0 +1,133 @@
+extends VBoxContainer
+
+signal group_move_up
+signal group_move_down
+
+const exp_item_ui_prefab:PackedScene = preload("./expression_item.tscn")
+const play_icon:Texture2D = preload("res://addons/panku_console/res/icons2/play-1001-svgrepo-com.svg")
+const pause_icon:Texture2D = preload("res://addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg")
+const expand_icon:Texture2D = preload("res://addons/panku_console/res/icons2/chevron_right.svg")
+const collapse_icon:Texture2D = preload("res://addons/panku_console/res/icons2/expand_more.svg")
+
+@onready var group_toggle_button:Button = $GroupManager/ToggleButton
+@onready var rename_line_edit:LineEdit = $GroupManager/RenameLineEdit
+@onready var state_control_button:PankuButton = $GroupManager/StateControlButton
+@onready var rename_button:PankuButton = $GroupManager/RenameButton
+@onready var confirm_rename_button:PankuButton = $GroupManager/ConfirmRenameButton
+@onready var cancel_rename_button:PankuButton = $GroupManager/CancelRenameButton
+@onready var move_up_button:PankuButton = $GroupManager/MoveUpButton
+@onready var move_down_button:PankuButton = $GroupManager/MoveDownButton
+
+@onready var exp_body_container:Control = $PanelContainer
+@onready var exp_container:Control = $PanelContainer/VBoxContainer/ExpressionContainer
+@onready var add_exp_button:Button = $PanelContainer/VBoxContainer/AddNewExpressionButton
+@onready var remove_this_group_button:PankuButton = $GroupManager/RemoveButton
+
+@onready var normal_ui_group:Array = [
+ group_toggle_button,
+ state_control_button,
+ rename_button
+]
+
+@onready var edit_ui_group:Array = [
+ rename_line_edit,
+ confirm_rename_button,
+ cancel_rename_button,
+ move_up_button,
+ move_down_button,
+ remove_this_group_button
+]
+
+func _ready():
+ edit_ui_group.map(func(item): item.hide())
+ normal_ui_group.map(func(item): item.show())
+ exp_body_container.hide()
+
+ group_toggle_button.toggle_mode = true
+ group_toggle_button.button_pressed = false
+ group_toggle_button.toggled.connect(
+ func(button_pressed:bool):
+ exp_body_container.visible = button_pressed
+ group_toggle_button.icon = collapse_icon if button_pressed else expand_icon
+ )
+
+ add_exp_button.pressed.connect(
+ func():
+ var exp_item = exp_item_ui_prefab.instantiate()
+ exp_container.add_child(exp_item)
+ )
+
+ state_control_button.button.toggle_mode = true
+ state_control_button.button.button_pressed = false
+ state_control_button.button.toggled.connect(
+ func(button_pressed:bool):
+ state_control_button.icon = pause_icon if button_pressed else play_icon
+ )
+
+ rename_button.pressed.connect(
+ func():
+ edit_ui_group.map(func(item): item.show())
+ normal_ui_group.map(func(item): item.hide())
+ rename_line_edit.text = group_toggle_button.text
+ )
+
+ cancel_rename_button.pressed.connect(
+ func():
+ edit_ui_group.map(func(item): item.hide())
+ normal_ui_group.map(func(item): item.show())
+ )
+
+ confirm_rename_button.pressed.connect(
+ func():
+ edit_ui_group.map(func(item): item.hide())
+ normal_ui_group.map(func(item): item.show())
+ group_toggle_button.text = rename_line_edit.text
+ )
+
+ move_up_button.pressed.connect(
+ func():
+ group_move_up.emit()
+ )
+
+ move_down_button.pressed.connect(
+ func():
+ group_move_down.emit()
+ )
+
+ remove_this_group_button.pressed.connect(queue_free)
+
+ load_persistent_data({
+ "group_name": "Unnamed Group",
+ "expressions": []
+ })
+
+func get_expressions(show_hidden := false) -> Array:
+ # optimization?
+ var exps = []
+ if state_control_button.button.button_pressed or show_hidden:
+ for child in exp_container.get_children():
+ exps.append(child.get_expr())
+ return exps
+
+func set_results(results:Array):
+ if results.size() > exp_container.get_child_count():
+ push_error("unexpected parameters.")
+ return
+ for i in range(results.size()):
+ var result:String = results[i]
+ exp_container.get_child(i).set_result(result)
+
+func get_persistent_data() -> Dictionary:
+ return {
+ "group_name": group_toggle_button.text,
+ "expressions": get_expressions(true)
+ }
+
+func load_persistent_data(data:Dictionary):
+ group_toggle_button.text = data["group_name"]
+ for child in exp_container.get_children():
+ child.queue_free()
+ for exp in data["expressions"]:
+ var exp_item = exp_item_ui_prefab.instantiate()
+ exp_container.add_child(exp_item)
+ exp_item.set_expr(exp)
diff --git a/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.gd.uid b/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.gd.uid
new file mode 100644
index 0000000..2ee52c6
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.gd.uid
@@ -0,0 +1 @@
+uid://pnr5pml8b4y6
diff --git a/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.tscn b/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.tscn
new file mode 100644
index 0000000..bceed67
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/monitor_group_ui.tscn
@@ -0,0 +1,145 @@
+[gd_scene load_steps=17 format=3 uid="uid://dlnkho5p7p2aq"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_5nvur"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/expression_monitor/monitor_group_ui.gd" id="1_b5egi"]
+[ext_resource type="Texture2D" uid="uid://ws58gucuygx1" path="res://addons/panku_console/res/icons2/chevron_right.svg" id="1_k5xby"]
+[ext_resource type="Texture2D" uid="uid://ecmevhk0tuas" path="res://addons/panku_console/res/icons2/play-1001-svgrepo-com.svg" id="2_wnb5s"]
+[ext_resource type="Texture2D" uid="uid://2cjvp0dp8ede" path="res://addons/panku_console/res/icons2/check-svgrepo-com.svg" id="3_h1h1v"]
+[ext_resource type="Texture2D" uid="uid://8g5afcuanbl6" path="res://addons/panku_console/res/icons2/close.svg" id="4_2d3kd"]
+[ext_resource type="Texture2D" uid="uid://brf6iwx4r6bdd" path="res://addons/panku_console/res/icons2/rename-svgrepo-com.svg" id="4_3tm8i"]
+[ext_resource type="Gradient" uid="uid://nr65cgweqh8n" path="res://addons/panku_console/res/green_gradient_1d.tres" id="4_gsqii"]
+[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="5_6smru"]
+[ext_resource type="Texture2D" uid="uid://d0k813mm5y0d5" path="res://addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg" id="5_xed2e"]
+[ext_resource type="Texture2D" uid="uid://cdxbns8lyctqp" path="res://addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg" id="6_168yw"]
+[ext_resource type="PackedScene" uid="uid://om8bp40jo1e" path="res://addons/panku_console/modules/expression_monitor/expression_item.tscn" id="9_2py4a"]
+[ext_resource type="Texture2D" uid="uid://cngs5d4uosvmt" path="res://addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg" id="10_jdwtu"]
+[ext_resource type="Texture2D" uid="uid://dprpfr0l5xvmu" path="res://addons/panku_console/res/icons2/add.svg" id="10_l2ajq"]
+
+[sub_resource type="GradientTexture1D" id="GradientTexture1D_c12ue"]
+gradient = ExtResource("4_gsqii")
+width = 128
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fdb5g"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+bg_color = Color(0, 0, 0, 0.12549)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.501961)
+
+[node name="MonitorGroupUI" type="VBoxContainer"]
+offset_right = 431.0
+offset_bottom = 124.0
+theme = ExtResource("1_5nvur")
+script = ExtResource("1_b5egi")
+
+[node name="GroupManager" type="HBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="ToggleButton" type="Button" parent="GroupManager"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Default Group"
+icon = ExtResource("1_k5xby")
+flat = true
+alignment = 0
+expand_icon = true
+
+[node name="TextureRect" type="TextureRect" parent="GroupManager/ToggleButton"]
+show_behind_parent = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+texture = SubResource("GradientTexture1D_c12ue")
+expand_mode = 1
+
+[node name="RenameLineEdit" type="LineEdit" parent="GroupManager"]
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Default Group"
+
+[node name="StateControlButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/StateControlButton/HBoxContainer" index="0"]
+texture = ExtResource("2_wnb5s")
+
+[node name="RenameButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/RenameButton/HBoxContainer" index="0"]
+texture = ExtResource("4_3tm8i")
+
+[node name="ConfirmRenameButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+visible = false
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/ConfirmRenameButton/HBoxContainer" index="0"]
+texture = ExtResource("3_h1h1v")
+
+[node name="CancelRenameButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+visible = false
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/CancelRenameButton/HBoxContainer" index="0"]
+texture = ExtResource("4_2d3kd")
+
+[node name="MoveUpButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+visible = false
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/MoveUpButton/HBoxContainer" index="0"]
+texture = ExtResource("5_xed2e")
+
+[node name="MoveDownButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+visible = false
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/MoveDownButton/HBoxContainer" index="0"]
+texture = ExtResource("6_168yw")
+
+[node name="RemoveButton" parent="GroupManager" instance=ExtResource("5_6smru")]
+layout_mode = 2
+
+[node name="TextureRect" parent="GroupManager/RemoveButton/HBoxContainer" index="0"]
+texture = ExtResource("10_jdwtu")
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_fdb5g")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer"]
+layout_mode = 2
+
+[node name="ExpressionContainer" type="VBoxContainer" parent="PanelContainer/VBoxContainer"]
+layout_mode = 2
+
+[node name="VBoxContainer" parent="PanelContainer/VBoxContainer/ExpressionContainer" instance=ExtResource("9_2py4a")]
+layout_mode = 2
+
+[node name="VBoxContainer2" parent="PanelContainer/VBoxContainer/ExpressionContainer" instance=ExtResource("9_2py4a")]
+layout_mode = 2
+
+[node name="AddNewExpressionButton" type="Button" parent="PanelContainer/VBoxContainer"]
+layout_mode = 2
+text = "Add New Expression"
+icon = ExtResource("10_l2ajq")
+alignment = 0
+expand_icon = true
+
+[editable path="GroupManager/StateControlButton"]
+[editable path="GroupManager/RenameButton"]
+[editable path="GroupManager/ConfirmRenameButton"]
+[editable path="GroupManager/CancelRenameButton"]
+[editable path="GroupManager/MoveUpButton"]
+[editable path="GroupManager/MoveDownButton"]
+[editable path="GroupManager/RemoveButton"]
diff --git a/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd b/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd
new file mode 100644
index 0000000..c7349e4
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd
@@ -0,0 +1,50 @@
+extends VBoxContainer
+
+const MonitorGroup:GDScript = preload("./monitor_group_ui.gd")
+const group_prefab:PackedScene = preload("./monitor_group_ui.tscn")
+
+@onready var groups_container:Control = $Groups
+@onready var add_group_button:Button = $AddGroupButton
+
+func _ready():
+ add_group_button.pressed.connect(add_group)
+
+func get_expressions_by_group() -> Array:
+ var result := []
+ for child in groups_container.get_children():
+ var group:MonitorGroup = child
+ result.append(group.get_expressions())
+ return result
+
+func set_results_by_group(results:Array):
+ if results.size() > groups_container.get_child_count():
+ push_error("unexpected parameters.")
+ return
+ for i in range(results.size()):
+ groups_container.get_child(i).set_results(results[i])
+
+func add_group() -> MonitorGroup:
+ var group_ui:Node = group_prefab.instantiate()
+ groups_container.add_child(group_ui)
+ group_ui.group_move_up.connect(
+ func():
+ groups_container.move_child(group_ui, group_ui.get_index() - 1)
+ )
+ group_ui.group_move_down.connect(
+ func():
+ groups_container.move_child(group_ui, group_ui.get_index() + 1)
+ )
+ return group_ui
+
+func get_persistent_data() -> Array:
+ var data := []
+ for child in groups_container.get_children():
+ data.append(child.get_persistent_data())
+ return data
+
+func load_persistent_data(data:Array):
+ for child in groups_container.get_children():
+ child.queue_free()
+ for group_data in data:
+ var group_ui = add_group()
+ group_ui.load_persistent_data(group_data)
diff --git a/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd.uid b/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd.uid
new file mode 100644
index 0000000..97b54cb
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd.uid
@@ -0,0 +1 @@
+uid://c7nca3l6nwgyw
diff --git a/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.tscn b/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.tscn
new file mode 100644
index 0000000..3f67e27
--- /dev/null
+++ b/godot/addons/panku_console/modules/expression_monitor/monitor_groups_ui.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=5 format=3 uid="uid://ctgf7kewoa1cn"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/expression_monitor/monitor_groups_ui.gd" id="1_8ojgn"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_gc4dk"]
+[ext_resource type="PackedScene" uid="uid://dlnkho5p7p2aq" path="res://addons/panku_console/modules/expression_monitor/monitor_group_ui.tscn" id="1_l15o5"]
+[ext_resource type="Texture2D" uid="uid://dprpfr0l5xvmu" path="res://addons/panku_console/res/icons2/add.svg" id="4_utrvo"]
+
+[node name="MonitorGroupsUI" type="VBoxContainer"]
+offset_right = 411.0
+offset_bottom = 309.0
+theme = ExtResource("1_gc4dk")
+script = ExtResource("1_8ojgn")
+
+[node name="Groups" type="VBoxContainer" parent="."]
+layout_mode = 2
+
+[node name="MonitorGroupUI" parent="Groups" instance=ExtResource("1_l15o5")]
+layout_mode = 2
+
+[node name="MonitorGroupUI2" parent="Groups" instance=ExtResource("1_l15o5")]
+layout_mode = 2
+
+[node name="MonitorGroupUI3" parent="Groups" instance=ExtResource("1_l15o5")]
+layout_mode = 2
+
+[node name="AddGroupButton" type="Button" parent="."]
+layout_mode = 2
+text = "Add New Group"
+icon = ExtResource("4_utrvo")
+alignment = 0
+expand_icon = true
diff --git a/godot/addons/panku_console/modules/general_settings/env.gd b/godot/addons/panku_console/modules/general_settings/env.gd
new file mode 100644
index 0000000..27c1092
--- /dev/null
+++ b/godot/addons/panku_console/modules/general_settings/env.gd
@@ -0,0 +1,4 @@
+var _module:PankuModule
+
+func open():
+ _module.open_settings_window()
diff --git a/godot/addons/panku_console/modules/general_settings/env.gd.uid b/godot/addons/panku_console/modules/general_settings/env.gd.uid
new file mode 100644
index 0000000..304b89e
--- /dev/null
+++ b/godot/addons/panku_console/modules/general_settings/env.gd.uid
@@ -0,0 +1 @@
+uid://b0m1kd1d2kjyc
diff --git a/godot/addons/panku_console/modules/general_settings/module.gd b/godot/addons/panku_console/modules/general_settings/module.gd
new file mode 100644
index 0000000..4d15ea7
--- /dev/null
+++ b/godot/addons/panku_console/modules/general_settings/module.gd
@@ -0,0 +1,68 @@
+class_name PankuModuleGeneralSettings extends PankuModule
+
+var window:PankuLynxWindow
+
+func open_settings_window():
+ if window: return
+ # create a new exporter window
+ window = core.create_data_controller_window.call(
+ core.module_manager.get_module_option_objects()
+ )
+ window.set_window_title_text("General Settings")
+ load_window_data(window)
+ window.show_window()
+ window.window_closed.connect(
+ func():
+ save_window_data(window)
+ window = null
+ )
+
+# Taken from https://github.com/godotengine/godot/blob/master/editor/editor_settings.cpp#L1539
+static func get_auto_display_scale() -> float:
+ var flag := false
+ match OS.get_name():
+ "macOS":
+ flag = true
+ "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD":
+ if DisplayServer.get_name() == "Wayland":
+ flag = true
+ "Android":
+ flag = true
+ "iOS":
+ flag = true
+ if flag:
+ return DisplayServer.screen_get_max_scale()
+
+ var screen := DisplayServer.window_get_current_screen()
+
+ if (DisplayServer.screen_get_size(screen) == Vector2i()):
+ # Invalid screen size, skip.
+ return 1.0
+
+ # Use the smallest dimension to use a correct display scale on portrait displays.
+ var smallest_dimension = min(DisplayServer.screen_get_size().x, DisplayServer.screen_get_size().y)
+ if DisplayServer.screen_get_dpi(screen) >= 192 and smallest_dimension >= 1400:
+ # hiDPI display.
+ return 2.0
+ elif smallest_dimension >= 1700:
+ # Likely a hiDPI display, but we aren't certain due to the returned DPI.
+ # Use an intermediate scale to handle this situation.
+ return 1.5
+ elif smallest_dimension <= 800:
+ # Small loDPI display. Use a smaller display scale so that editor elements fit more easily.
+ # Icons won't look great, but this is better than having editor elements overflow from its window.
+ return 0.75
+ return 1.0
+
+func init_module():
+ # load settings
+ get_module_opt().window_blur_effect = load_module_data("window_blur_effect", true)
+ get_module_opt().window_base_color = load_module_data("window_base_color", Color("#000c1880"))
+ get_module_opt().enable_os_window = load_module_data("enable_os_window", false)
+ get_module_opt().os_window_bg_color = load_module_data("os_window_bg_color", Color(0, 0, 0, 0))
+ get_module_opt().global_font_size = load_module_data("global_font_size", int(16 * get_auto_display_scale()))
+
+func quit_module():
+ super.quit_module()
+ if window:
+ save_window_data(window)
diff --git a/godot/addons/panku_console/modules/general_settings/module.gd.uid b/godot/addons/panku_console/modules/general_settings/module.gd.uid
new file mode 100644
index 0000000..555916f
--- /dev/null
+++ b/godot/addons/panku_console/modules/general_settings/module.gd.uid
@@ -0,0 +1 @@
+uid://bqx8byqbawedh
diff --git a/godot/addons/panku_console/modules/general_settings/opt.gd b/godot/addons/panku_console/modules/general_settings/opt.gd
new file mode 100644
index 0000000..4b89d96
--- /dev/null
+++ b/godot/addons/panku_console/modules/general_settings/opt.gd
@@ -0,0 +1,49 @@
+extends ModuleOptions
+
+@export_group("general_settings")
+
+@export var window_blur_effect:bool = true:
+ set(v):
+ PankuLynxWindow.lynx_window_shader_material.set("shader_parameter/lod", 4.0 if v else 0.0)
+ get:
+ return PankuLynxWindow.lynx_window_shader_material.get("shader_parameter/lod") > 0.0
+
+@export var window_base_color:Color = Color(0.0, 0.0, 0.0, 0.1):
+ set(v):
+ PankuLynxWindow.lynx_window_shader_material.set("shader_parameter/modulate", v)
+ get:
+ return PankuLynxWindow.lynx_window_shader_material.get("shader_parameter/modulate")
+
+@export var enable_os_window := false:
+ set(v):
+ _module.core.windows_manager.enable_os_popup_btns(v)
+ get:
+ return _module.core.windows_manager.os_popup_btn_enabled
+
+@export var os_window_bg_color:Color:
+ set(v):
+ _module.core.windows_manager.set_os_window_bg_color(v)
+ get:
+ return _module.core.windows_manager.os_window_bg_color
+
+@export var global_font_size:int:
+ set(v):
+ _module.core.windows_manager.theme.default_font_size = v
+ get:
+ return _module.core.windows_manager.theme.default_font_size
+
+@export var export_comment_auto_global_font_size = (
+ "Adjust global font size automatically according to your device DPI"
+)
+
+@export var export_button_auto_global_font_size := "Auto Global Font Size"
+func auto_global_font_size():
+ global_font_size = int(16 * _module.get_auto_display_scale())
+
+@export var export_button_report_bugs := "Report Bugs"
+func report_bugs():
+ OS.shell_open("https://github.com/Ark2000/PankuConsole/issues")
+
+@export var export_button_suggest_features := "Suggest Features"
+func suggest_features():
+ OS.shell_open("https://github.com/Ark2000/PankuConsole/issues")
diff --git a/godot/addons/panku_console/modules/general_settings/opt.gd.uid b/godot/addons/panku_console/modules/general_settings/opt.gd.uid
new file mode 100644
index 0000000..dd20c02
--- /dev/null
+++ b/godot/addons/panku_console/modules/general_settings/opt.gd.uid
@@ -0,0 +1 @@
+uid://bkxa6b0jf83y3
diff --git a/godot/addons/panku_console/modules/history_manager/env.gd b/godot/addons/panku_console/modules/history_manager/env.gd
new file mode 100644
index 0000000..dae4497
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/env.gd
@@ -0,0 +1,4 @@
+var _module:PankuModule
+
+func open() -> void:
+ _module.open_window()
diff --git a/godot/addons/panku_console/modules/history_manager/env.gd.uid b/godot/addons/panku_console/modules/history_manager/env.gd.uid
new file mode 100644
index 0000000..ff67f05
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/env.gd.uid
@@ -0,0 +1 @@
+uid://cr2kkri0xrsqv
diff --git a/godot/addons/panku_console/modules/history_manager/exp_history.gd b/godot/addons/panku_console/modules/history_manager/exp_history.gd
new file mode 100644
index 0000000..96926d3
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/exp_history.gd
@@ -0,0 +1,206 @@
+extends Control
+
+@onready var console:PankuConsole
+
+const exp_item_prefab = preload("./exp_history_item.tscn")
+const CFG_EXP_HISTORY = "exp_history"
+
+@export var item_container:VBoxContainer
+@export var copy_button:Button
+@export var monitor_button:Button
+@export var favorite_button:Button
+@export var delete_button:Button
+@export var reverse_select_button:Button
+@export var prev_page_button:Button
+@export var next_page_button:Button
+@export var first_page_button:Button
+@export var last_page_button:Button
+@export var page_ledit:LineEdit
+@export var page_label:Label
+
+var current_page := 1
+var all_pages := 1
+var items_per_page := 5
+#[checked:bool, fav:bool, exp:String]
+var item_data := []
+
+func _ready():
+ load_data()
+ copy_button.pressed.connect(copy_selected)
+ monitor_button.pressed.connect(monitor_selected)
+ favorite_button.pressed.connect(star_selected)
+ delete_button.pressed.connect(remove_selected)
+ reverse_select_button.pressed.connect(invert_selected)
+ prev_page_button.pressed.connect(
+ func():
+ current_page -= 1
+ reload()
+ )
+ next_page_button.pressed.connect(
+ func():
+ current_page += 1
+ reload()
+ )
+ first_page_button.pressed.connect(
+ func():
+ current_page = 1;
+ reload()
+ )
+ last_page_button.pressed.connect(
+ func():
+ current_page = 99999999;
+ reload()
+ )
+ page_ledit.text_submitted.connect(
+ func(_new_text:String):
+ current_page = page_ledit.text.to_int()
+ reload()
+ )
+ page_ledit.focus_exited.connect(
+ func():
+ current_page = page_ledit.text.to_int()
+ reload()
+ )
+ create_tween().set_loops().set_process_mode(Tween.TWEEN_PROCESS_PHYSICS).tween_callback(
+ func():
+ if !is_visible_in_tree(): return
+ if item_container.get_child_count() < 1: return
+ var item_height = item_container.get_child(0).size.y
+ var item_gap = item_container.get("theme_override_constants/separation")
+ var container_height = item_container.get_parent().size.y
+ var _items_per_page = floor((container_height + item_gap) / (item_height + item_gap))
+ _items_per_page = max(5, _items_per_page)
+ if _items_per_page != items_per_page:
+ items_per_page = _items_per_page
+ reload()
+ ).set_delay(0.1)
+
+ visibility_changed.connect(
+ func():
+ if is_visible_in_tree(): reload()
+ )
+
+func reload():
+ #calculate pages
+ all_pages = max(1, ceil(1.0 * item_data.size() / items_per_page))
+ current_page = clamp(current_page, 1, all_pages)
+ page_label.text = " / %d" % all_pages
+ page_ledit.text = str(current_page)
+
+ #remove existing list items
+ var start_idx := (current_page - 1) * items_per_page
+ var end_idx := mini(start_idx + items_per_page, item_data.size())
+ for c in item_container.get_children():
+ c.queue_free()
+
+ #add new list items
+ for i in range(start_idx, end_idx):
+ var list_item = exp_item_prefab.instantiate()
+ list_item.checkbox.button_pressed = item_data[i][0]
+ list_item.fav_icon.visible = item_data[i][1]
+ list_item.line_edit.text = item_data[i][2]
+ list_item.checkbox.toggled.connect(
+ func(val:bool): item_data[i][0] = val
+ )
+ list_item.line_edit.text_changed.connect(
+ func(val:String): item_data[i][2] = val
+ )
+ item_container.add_child(list_item)
+
+func copy_selected():
+ var result = combine_selected()
+ if result != "":
+ DisplayServer.clipboard_set(result)
+ console.notify("Combined expression has beed added to clipboard!")
+ clear_selected()
+
+func monitor_selected():
+ var result = combine_selected()
+ if result != "":
+ pass
+ # console.add_monitor_window(result, 0.1).centered()
+ clear_selected()
+
+func combine_selected() -> String:
+ var selected_exp = []
+ var combined_exp = ""
+ for d in item_data:
+ if !d[0]: continue
+ selected_exp.append(d[2])
+ if selected_exp.size() > 8:
+ console.notify("Maximum 8 items!")
+ return ""
+ if selected_exp.size() == 0:
+ console.notify("Nothing to copy!")
+ return ""
+ elif selected_exp.size() == 1:
+ combined_exp = selected_exp[0]
+ else:
+ for i in range(selected_exp.size()):
+ selected_exp[i] = "'%s: ' + str(%s)" % [selected_exp[i], selected_exp[i]]
+ combined_exp = " + '\\n' + ".join(PackedStringArray(selected_exp))
+ return combined_exp
+
+func clear_selected():
+ for d in item_data:
+ d[0] = false
+ reload()
+
+func invert_selected():
+ for d in item_data:
+ d[0] = !d[0]
+ reload()
+
+func star_selected():
+ var flag := true
+ for d in item_data:
+ if !d[0]: continue
+ d[1] = !d[1]
+ d[0] = false
+ flag = false
+ if flag:
+ console.notify("No selected items!")
+ return
+
+ #sort favorite
+ var fav_id := 0
+ var fav_items := []
+ var items := []
+ for d in item_data:
+ if d[1]: fav_items.append(d)
+ else: items.append(d)
+ item_data = fav_items + items
+ reload()
+
+func remove_selected():
+ var result = []
+ for d in item_data:
+ if d[0]: continue
+ result.append(d)
+ if item_data.size() == result.size():
+ console.notify("No selected items!")
+ return
+ console.notify("Removed %d items." % (item_data.size() - result.size()))
+ item_data = result
+ reload()
+
+func load_data():
+ #get saved data from cfg
+ var cfg = PankuConfig.get_config()
+ item_data = cfg.get(CFG_EXP_HISTORY, [])
+
+func save_data():
+ var cfg = PankuConfig.get_config()
+ cfg[CFG_EXP_HISTORY] = item_data
+ PankuConfig.set_config(cfg)
+
+func _notification(what):
+ if what == NOTIFICATION_WM_CLOSE_REQUEST:
+ save_data()
+
+func add_history(exp:String):
+ #ignore consecutive same
+ if item_data.size() > 0 and exp == item_data.back()[2]:
+ return
+ item_data.append([false, false, exp])
+ if is_visible_in_tree(): reload()
diff --git a/godot/addons/panku_console/modules/history_manager/exp_history.gd.uid b/godot/addons/panku_console/modules/history_manager/exp_history.gd.uid
new file mode 100644
index 0000000..876b83d
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/exp_history.gd.uid
@@ -0,0 +1 @@
+uid://nsybsat5vf4e
diff --git a/godot/addons/panku_console/modules/history_manager/exp_history.tscn b/godot/addons/panku_console/modules/history_manager/exp_history.tscn
new file mode 100644
index 0000000..2c485ef
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/exp_history.tscn
@@ -0,0 +1,149 @@
+[gd_scene load_steps=4 format=3 uid="uid://fladd1y6sa1j"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_q7hjy"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/history_manager/exp_history.gd" id="2_kgk33"]
+[ext_resource type="PackedScene" uid="uid://dhsvbbxqlb1xy" path="res://addons/panku_console/modules/history_manager/exp_history_item.tscn" id="3_wac0s"]
+
+[node name="ExpHistory" type="Control" node_paths=PackedStringArray("item_container", "copy_button", "monitor_button", "favorite_button", "delete_button", "reverse_select_button", "prev_page_button", "next_page_button", "first_page_button", "last_page_button", "page_ledit", "page_label")]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 0
+offset_left = 227.0
+offset_top = 149.0
+offset_right = 634.0
+offset_bottom = 437.0
+theme = ExtResource("1_q7hjy")
+script = ExtResource("2_kgk33")
+item_container = NodePath("VBoxContainer2/Control/ItemContainer")
+copy_button = NodePath("VBoxContainer2/HBoxContainer/CopyButton")
+monitor_button = NodePath("VBoxContainer2/HBoxContainer/MonitorButton")
+favorite_button = NodePath("VBoxContainer2/HBoxContainer/FavoriteButton")
+delete_button = NodePath("VBoxContainer2/HBoxContainer/DeleteButton")
+reverse_select_button = NodePath("VBoxContainer2/HBoxContainer/InvertSelect")
+prev_page_button = NodePath("VBoxContainer2/HBoxContainer2/PrevPageButton")
+next_page_button = NodePath("VBoxContainer2/HBoxContainer2/NextPageButton")
+first_page_button = NodePath("VBoxContainer2/HBoxContainer2/FirstpageButton")
+last_page_button = NodePath("VBoxContainer2/HBoxContainer2/LastPageButton")
+page_ledit = NodePath("VBoxContainer2/HBoxContainer2/PageNumberEdit")
+page_label = NodePath("VBoxContainer2/HBoxContainer2/TotalPageLabel")
+metadata/_edit_vertical_guides_ = [161.838]
+
+[node name="Panel" type="Panel" parent="."]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 4.0
+offset_top = 4.0
+offset_right = -4.0
+offset_bottom = -4.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer2"]
+layout_mode = 2
+
+[node name="CopyButton" type="Button" parent="VBoxContainer2/HBoxContainer"]
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Copy"
+
+[node name="MonitorButton" type="Button" parent="VBoxContainer2/HBoxContainer"]
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Monitor"
+
+[node name="FavoriteButton" type="Button" parent="VBoxContainer2/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Favorite"
+
+[node name="DeleteButton" type="Button" parent="VBoxContainer2/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Delete"
+
+[node name="InvertSelect" type="Button" parent="VBoxContainer2/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Invert Select"
+
+[node name="HSeparator2" type="HSeparator" parent="VBoxContainer2"]
+layout_mode = 2
+
+[node name="Control" type="Control" parent="VBoxContainer2"]
+clip_contents = true
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="ItemContainer" type="VBoxContainer" parent="VBoxContainer2/Control"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+theme_override_constants/separation = 2
+
+[node name="ExpItem" parent="VBoxContainer2/Control/ItemContainer" instance=ExtResource("3_wac0s")]
+layout_mode = 2
+
+[node name="ExpItem2" parent="VBoxContainer2/Control/ItemContainer" instance=ExtResource("3_wac0s")]
+layout_mode = 2
+
+[node name="ExpItem3" parent="VBoxContainer2/Control/ItemContainer" instance=ExtResource("3_wac0s")]
+layout_mode = 2
+
+[node name="HSeparator" type="HSeparator" parent="VBoxContainer2"]
+layout_mode = 2
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer2"]
+layout_mode = 2
+
+[node name="FirstpageButton" type="Button" parent="VBoxContainer2/HBoxContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "<<<"
+
+[node name="PrevPageButton" type="Button" parent="VBoxContainer2/HBoxContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "<"
+
+[node name="PageNumberEdit" type="LineEdit" parent="VBoxContainer2/HBoxContainer2"]
+layout_mode = 2
+text = "1"
+
+[node name="TotalPageLabel" type="Label" parent="VBoxContainer2/HBoxContainer2"]
+layout_mode = 2
+text = "/ 10"
+vertical_alignment = 1
+
+[node name="NextPageButton" type="Button" parent="VBoxContainer2/HBoxContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = ">"
+
+[node name="LastPageButton" type="Button" parent="VBoxContainer2/HBoxContainer2"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = ">>>"
+
+[node name="HFlowContainer" type="HFlowContainer" parent="."]
+layout_mode = 2
+offset_right = 399.0
+size_flags_vertical = 0
diff --git a/godot/addons/panku_console/modules/history_manager/exp_history_item.gd b/godot/addons/panku_console/modules/history_manager/exp_history_item.gd
new file mode 100644
index 0000000..23c6e27
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/exp_history_item.gd
@@ -0,0 +1,5 @@
+extends HBoxContainer
+
+@export var checkbox:CheckBox
+@export var fav_icon:TextureRect
+@export var line_edit:LineEdit
diff --git a/godot/addons/panku_console/modules/history_manager/exp_history_item.gd.uid b/godot/addons/panku_console/modules/history_manager/exp_history_item.gd.uid
new file mode 100644
index 0000000..0878ea8
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/exp_history_item.gd.uid
@@ -0,0 +1 @@
+uid://dy7ywh6we3xug
diff --git a/godot/addons/panku_console/modules/history_manager/exp_history_item.tscn b/godot/addons/panku_console/modules/history_manager/exp_history_item.tscn
new file mode 100644
index 0000000..a0e3378
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/exp_history_item.tscn
@@ -0,0 +1,27 @@
+[gd_scene load_steps=4 format=3 uid="uid://dhsvbbxqlb1xy"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_rsfup"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/history_manager/exp_history_item.gd" id="2_a3mfr"]
+[ext_resource type="Texture2D" uid="uid://hf6h8otb8qkv" path="res://addons/panku_console/res/icons2/favorite.svg" id="3_1odvn"]
+
+[node name="ExpItem" type="HBoxContainer" node_paths=PackedStringArray("checkbox", "fav_icon", "line_edit")]
+offset_right = 155.0
+offset_bottom = 20.0
+theme = ExtResource("1_rsfup")
+script = ExtResource("2_a3mfr")
+checkbox = NodePath("CheckBox")
+fav_icon = NodePath("TextureRect")
+line_edit = NodePath("LineEdit")
+
+[node name="CheckBox" type="CheckBox" parent="."]
+layout_mode = 2
+flat = true
+
+[node name="TextureRect" type="TextureRect" parent="."]
+layout_mode = 2
+texture = ExtResource("3_1odvn")
+stretch_mode = 3
+
+[node name="LineEdit" type="LineEdit" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
diff --git a/godot/addons/panku_console/modules/history_manager/module.gd b/godot/addons/panku_console/modules/history_manager/module.gd
new file mode 100644
index 0000000..24a7607
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/module.gd
@@ -0,0 +1,26 @@
+class_name PankuModuleHistoryManager extends PankuModule
+
+var window:PankuLynxWindow
+
+func init_module():
+ # setup ui
+ var ui = preload("./exp_history.tscn").instantiate()
+ ui.console = core
+ core.new_expression_entered.connect(
+ func(expression, result):
+ ui.add_history(expression)
+ )
+
+ # bind window
+ window = core.windows_manager.create_window(ui)
+ add_auto_save_hook(window)
+ window.queue_free_on_close = false
+ window.set_window_title_text("History Manager")
+ load_window_data(window)
+
+func quit_module():
+ super.quit_module()
+ save_window_data(window)
+
+func open_window():
+ window.show_window()
diff --git a/godot/addons/panku_console/modules/history_manager/module.gd.uid b/godot/addons/panku_console/modules/history_manager/module.gd.uid
new file mode 100644
index 0000000..beea25c
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/module.gd.uid
@@ -0,0 +1 @@
+uid://du280gsd10ts4
diff --git a/godot/addons/panku_console/modules/history_manager/opt.gd b/godot/addons/panku_console/modules/history_manager/opt.gd
new file mode 100644
index 0000000..0721510
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/opt.gd
@@ -0,0 +1,7 @@
+extends ModuleOptions
+
+@export_group("history_manager")
+
+@export var export_button_open_window := "Open Window"
+func open_window():
+ _module.open_window()
diff --git a/godot/addons/panku_console/modules/history_manager/opt.gd.uid b/godot/addons/panku_console/modules/history_manager/opt.gd.uid
new file mode 100644
index 0000000..d79ff21
--- /dev/null
+++ b/godot/addons/panku_console/modules/history_manager/opt.gd.uid
@@ -0,0 +1 @@
+uid://bf7hmiyq45wgk
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd b/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd
new file mode 100644
index 0000000..5010582
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd
@@ -0,0 +1,29 @@
+extends MarginContainer
+
+@export var rlabel:RichTextLabel
+
+#not sure about the performance
+const MAX_LOGS = 400
+
+func _ready():
+ rlabel.meta_clicked.connect(
+ func(meta):
+ OS.shell_open(meta)
+ )
+
+func add_log(bbcode:String):
+ rlabel.text += (bbcode + "\n")
+
+func clear():
+ rlabel.text = ""
+
+func set_font_size(sz:int):
+ rlabel.set("theme_override_font_sizes/normal_font_size", sz)
+ rlabel.set("theme_override_font_sizes/bold_font_size", sz)
+ rlabel.set("theme_override_font_sizes/italics_font_size", sz)
+ rlabel.set("theme_override_font_sizes/bold_italics_font_size", sz)
+ rlabel.set("theme_override_font_sizes/mono_font_size", sz)
+
+func get_font_size() -> int:
+ #return rlabel.get("theme_override_font_sizes/normal_font_size")
+ return 12
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd.uid b/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd.uid
new file mode 100644
index 0000000..9940ee6
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd.uid
@@ -0,0 +1 @@
+uid://bjf0ovt7iyqe8
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.tscn b/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.tscn
new file mode 100644
index 0000000..41232a5
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_logs/console_logs.tscn
@@ -0,0 +1,48 @@
+[gd_scene load_steps=4 format=3 uid="uid://nynkaa0igrh5"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/console_logs/console_logs.gd" id="1_3r4hk"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_b2qej"]
+[ext_resource type="Script" path="res://addons/panku_console/common/smooth_scroll/smooth_scroll.gd" id="2_46ya8"]
+
+[node name="ConsoleLogs" type="MarginContainer" node_paths=PackedStringArray("rlabel")]
+clip_contents = true
+offset_right = 365.0
+offset_bottom = 357.0
+theme = ExtResource("1_b2qej")
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
+script = ExtResource("1_3r4hk")
+rlabel = NodePath("SmoothScrollContainer/HBoxContainer/Control/RichTextLabel")
+
+[node name="SmoothScrollContainer" type="PanelContainer" parent="." node_paths=PackedStringArray("clip_container", "scrollbar")]
+self_modulate = Color(1, 1, 1, 0)
+layout_mode = 2
+script = ExtResource("2_46ya8")
+clip_container = NodePath("HBoxContainer/Control")
+scrollbar = NodePath("HBoxContainer/VScrollBar")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="SmoothScrollContainer"]
+layout_mode = 2
+
+[node name="Control" type="Control" parent="SmoothScrollContainer/HBoxContainer"]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="RichTextLabel" type="RichTextLabel" parent="SmoothScrollContainer/HBoxContainer/Control"]
+layout_mode = 1
+anchors_preset = 10
+anchor_right = 1.0
+grow_horizontal = 2
+focus_mode = 2
+bbcode_enabled = true
+fit_content = true
+context_menu_enabled = true
+selection_enabled = true
+
+[node name="VScrollBar" type="VScrollBar" parent="SmoothScrollContainer/HBoxContainer"]
+layout_mode = 2
+page = 20.0
+value = 80.0
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/help_bar.tscn b/godot/addons/panku_console/modules/interactive_shell/console_ui/help_bar.tscn
new file mode 100644
index 0000000..9582038
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/help_bar.tscn
@@ -0,0 +1,21 @@
+[gd_scene load_steps=2 format=3 uid="uid://cbijhl1nhy64n"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c7j4l"]
+bg_color = Color(0.180392, 0.32549, 0.403922, 1)
+
+[node name="HelpBar" type="PanelContainer"]
+anchors_preset = 12
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -50.0
+offset_bottom = -26.0
+grow_horizontal = 2
+grow_vertical = 0
+
+[node name="Label" type="Label" parent="."]
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxFlat_c7j4l")
+text = "[Help] This is hint!"
+vertical_alignment = 1
+clip_text = true
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd b/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd
new file mode 100644
index 0000000..f7e3942
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd
@@ -0,0 +1,54 @@
+extends Control
+
+@onready var _console_logs = $HBoxContainer/Control/VBoxContainer/ConsoleLogs
+@onready var _repl := $REPL
+@onready var side_button_separator:Node = $HBoxContainer/SideButtons/Space
+@onready var side_buttons:Control = $HBoxContainer/SideButtons
+@onready var side_separator:Control = $HBoxContainer/VSeparator
+
+const side_button_packed := preload("res://addons/panku_console/common/panku_button.tscn")
+const side_menu_config_file_path := "res://addons/panku_console/modules/interactive_shell/side_menu_config.json"
+
+var enable_side_menu := true
+
+func _ready() -> void:
+ _repl.output.connect(output)
+
+ var config_str := FileAccess.get_file_as_string(side_menu_config_file_path)
+ var cfg:Dictionary = JSON.parse_string(config_str)
+
+ for item in cfg["items.top"]:
+ add_side_button(item["command"], load(item["icon"]), item["text"])
+ for item in cfg["items.bottom"]:
+ add_side_button(item["command"], load(item["icon"]), item["text"], false)
+
+ resized.connect(
+ func():
+ var vis := side_buttons.get_minimum_size().y < size.y
+ vis = vis and enable_side_menu
+ side_buttons.visible = vis
+ side_separator.visible = vis
+ )
+
+## Output [code]any[/code] to the console
+func output(any):
+ var text = str(any)
+ _console_logs.add_log(text)
+
+func clear_output():
+ _console_logs.clear()
+
+func add_side_button(exp:String, icon:Texture2D, text:String, top:= true):
+ var new_button:PankuButton = side_button_packed.instantiate()
+ new_button.icon = icon
+ side_button_separator.get_parent().add_child(new_button)
+ side_button_separator.get_parent().move_child(
+ new_button,
+ side_button_separator.get_index() + int(!top)
+ )
+ new_button.pressed.connect(
+ func():
+ _repl._module.core.gd_exprenv.execute(exp)
+ )
+ new_button.set_meta("text", " " + text)
+ new_button.text = ""
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd.uid b/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd.uid
new file mode 100644
index 0000000..42c52a6
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd.uid
@@ -0,0 +1 @@
+uid://qh5xix4av8cw
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.tscn b/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.tscn
new file mode 100644
index 0000000..d3bc7b6
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.tscn
@@ -0,0 +1,111 @@
+[gd_scene load_steps=12 format=3 uid="uid://bqfm16y5vcgl3"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_8mhrg"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/console_ui/panku_console_ui.gd" id="1_aab8v"]
+[ext_resource type="PackedScene" uid="uid://nynkaa0igrh5" path="res://addons/panku_console/modules/interactive_shell/console_logs/console_logs.tscn" id="1_jb1ae"]
+[ext_resource type="PackedScene" uid="uid://bme8twac4ick5" path="res://addons/panku_console/modules/interactive_shell/input_field/input_area.tscn" id="2_w7j54"]
+[ext_resource type="PackedScene" uid="uid://b3jf18wonocnv" path="res://addons/panku_console/modules/interactive_shell/hints_list/hints_list.tscn" id="3_qw8p4"]
+[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="4_5x5gm"]
+[ext_resource type="PackedScene" uid="uid://ca0bkchv6nsrb" path="res://addons/panku_console/modules/interactive_shell/console_ui/repl.tscn" id="4_ihf0b"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd" id="5_2te5e"]
+[ext_resource type="PackedScene" uid="uid://cbijhl1nhy64n" path="res://addons/panku_console/modules/interactive_shell/console_ui/help_bar.tscn" id="10_0thy6"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd" id="11_epqvn"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i3l80"]
+bg_color = Color(0.6, 0.6, 0.6, 0.435294)
+border_width_left = 1
+border_color = Color(0.8, 0.8, 0.8, 0.352941)
+
+[node name="PankuConsoleUI" type="Control"]
+clip_contents = true
+custom_minimum_size = Vector2(0, 1)
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_8mhrg")
+script = ExtResource("1_aab8v")
+
+[node name="REPL" parent="." node_paths=PackedStringArray("_input_area", "_hints", "_helpbar", "_helpbar_label") instance=ExtResource("4_ihf0b")]
+_input_area = NodePath("../HBoxContainer/Control/VBoxContainer/InputArea")
+_hints = NodePath("../HBoxContainer/Control/Layer2/HintsList")
+_helpbar = NodePath("../HBoxContainer/Control/Layer2/HelpBar")
+_helpbar_label = NodePath("../HBoxContainer/Control/Layer2/HelpBar/Label")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 0
+
+[node name="SideButtons" type="VBoxContainer" parent="HBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="ControllerButton" parent="HBoxContainer/SideButtons" instance=ExtResource("4_5x5gm")]
+layout_mode = 2
+script = ExtResource("5_2te5e")
+
+[node name="Space" type="Control" parent="HBoxContainer/SideButtons"]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="VSeparator" type="VSeparator" parent="HBoxContainer"]
+layout_mode = 2
+theme_override_styles/separator = SubResource("StyleBoxFlat_i3l80")
+
+[node name="Control" type="Control" parent="HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/Control"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 0
+
+[node name="ConsoleLogs" parent="HBoxContainer/Control/VBoxContainer" instance=ExtResource("1_jb1ae")]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="InputArea" parent="HBoxContainer/Control/VBoxContainer" instance=ExtResource("2_w7j54")]
+layout_mode = 2
+
+[node name="Layer2" type="VBoxContainer" parent="HBoxContainer/Control"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_constants/separation = 0
+alignment = 2
+
+[node name="HintsList" parent="HBoxContainer/Control/Layer2" instance=ExtResource("3_qw8p4")]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+
+[node name="HelpBar" parent="HBoxContainer/Control/Layer2" instance=ExtResource("10_0thy6")]
+layout_mode = 2
+mouse_filter = 2
+
+[node name="RemoteControl" type="ColorRect" parent="HBoxContainer/Control/Layer2" node_paths=PackedStringArray("target_control")]
+layout_mode = 2
+mouse_filter = 2
+color = Color(1, 0, 0, 0)
+script = ExtResource("11_epqvn")
+target_control = NodePath("../../VBoxContainer/InputArea")
+
+[editable path="HBoxContainer/Control/VBoxContainer/InputArea"]
+[editable path="HBoxContainer/Control/VBoxContainer/InputArea/Button"]
+[editable path="HBoxContainer/Control/Layer2/HelpBar"]
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd b/godot/addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd
new file mode 100644
index 0000000..01534d5
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd
@@ -0,0 +1,15 @@
+extends Control
+
+@export
+var target_control:Control
+
+func _ready():
+ if not target_control:
+ return
+
+ target_control.resized.connect(
+ func():
+ custom_minimum_size = target_control.get_minimum_size()
+ )
+
+ target_control.resized.emit()
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd.uid b/godot/addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd.uid
new file mode 100644
index 0000000..1cef121
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/remote_control_node.gd.uid
@@ -0,0 +1 @@
+uid://bctccrx5gl50x
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.gd b/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.gd
new file mode 100644
index 0000000..f6a63e3
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.gd
@@ -0,0 +1,102 @@
+extends Node
+
+var _module:PankuModule:
+ set(v):
+ _module = v
+ _input_area.input.module = v
+
+@export_subgroup("Dependency")
+@export var _input_area:Control
+@export var _hints:Control
+@export var _helpbar:Control
+@export var _helpbar_label:Control
+
+@export_subgroup("Config")
+@export var show_all_hints_if_input_is_empty := false
+
+signal output(bbcode:String)
+signal output_echo(bbcode:String)
+signal output_result(bbcode:String)
+signal output_error(bbcode:String)
+
+var _current_hints := {}
+var _hint_idx := 0
+func _set_hint_idx(v):
+ _hint_idx = v
+ if _current_hints["hints_value"].size() > 0:
+ v = wrapi(v, 0, _current_hints["hints_value"].size())
+ var k = _current_hints["hints_value"][v]
+
+ #if the bbcode ends with ')',then we believe it is a method
+ #(I know maybe it's a bad practice, but the hinting system needs refactor)
+ var is_method = _current_hints["hints_bbcode"][v].ends_with(")")
+
+ _hint_idx = v
+ _hints.selected = v
+ _input_area.input.text = k + ("()" if is_method else "")
+ _input_area.input.caret_column = k.length() + (1 if is_method else 0)
+ _helpbar_label.text = "[Help] %s" % _module.core.gd_exprenv.get_help_info(k)
+
+func execute(exp:String):
+ exp = exp.lstrip(" ").rstrip(" ")
+ if exp.is_empty():
+ return
+ var echo:String = "[b][You][/b] " + exp
+ output.emit(echo)
+ output_echo.emit(echo)
+ var result = _module.core.gd_exprenv.execute(exp)
+ if !result["failed"]:
+ # ignore the expression result if it is null
+ if result["result"] != null:
+ var result_str:String = str(result["result"])
+ output.emit(result_str)
+ output_result.emit(result_str)
+ else:
+ var error_str:String = "[color=red]%s[/color]"%(result["result"])
+ output.emit(error_str)
+ output_error.emit(error_str)
+ _module.core.new_expression_entered.emit(exp, result)
+
+func _update_hints(exp:String):
+ _current_hints = _module.core.gd_exprenv.parse_exp(exp, show_all_hints_if_input_is_empty)
+ _hints.visible = _current_hints["hints_value"].size() > 0
+ _helpbar.visible = _hints.visible
+ _input_area.input.hints = _current_hints["hints_value"]
+ _hints.set_hints(_current_hints["hints_bbcode"])
+ _hint_idx = -1
+ _helpbar_label.text = "[Hint] Use TAB or up/down to autocomplete!"
+
+func _ready():
+ _input_area.visibility_changed.connect(
+ func():
+ #initialize all hints if is shown and the input is empty
+ if _input_area.visible and _input_area.input.text.is_empty() and !_hints.visible and show_all_hints_if_input_is_empty:
+ _update_hints("")
+ )
+ _input_area.submitted.connect(execute)
+ _input_area.update_hints.connect(_update_hints)
+ _input_area.next_hint.connect(
+ func():
+ _set_hint_idx(_hint_idx + 1)
+ )
+ _input_area.prev_hint.connect(
+ func():
+ if _hint_idx == -1:
+ _hint_idx = 0
+ _set_hint_idx(_hint_idx - 1)
+ )
+ _input_area.navigate_histories.connect(
+ func(histories, id):
+ if histories.size() > 0:
+ _hints.set_hints(histories)
+ _hints.selected = id
+ _hints.visible = true
+ else:
+ _hints.visible = false
+ _helpbar.visible = _hints.visible
+ _helpbar_label.text = "[Hint] Use up/down to navigate through submit histories!"
+
+ )
+
+ _helpbar.hide()
+ _hints.hide()
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.gd.uid b/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.gd.uid
new file mode 100644
index 0000000..43b74bc
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.gd.uid
@@ -0,0 +1 @@
+uid://ic51t4b4v6fl
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.tscn b/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.tscn
new file mode 100644
index 0000000..1e569af
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/repl.tscn
@@ -0,0 +1,6 @@
+[gd_scene load_steps=2 format=3 uid="uid://ca0bkchv6nsrb"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/console_ui/repl.gd" id="1_dk53o"]
+
+[node name="REPL" type="Node"]
+script = ExtResource("1_dk53o")
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd b/godot/addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd
new file mode 100644
index 0000000..d16d5c3
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd
@@ -0,0 +1,30 @@
+extends PankuButton
+
+func _ready():
+ super._ready()
+ set_meta("text", " Fold Menu")
+ text = ""
+ var img = Image.new()
+ img.copy_from(icon.get_image())
+ img.flip_x()
+ var unfold_icon = ImageTexture.create_from_image(img)
+ var fold_icon = icon
+
+ icon = unfold_icon
+
+ button.toggle_mode = true
+ button.toggled.connect(
+ func(button_pressed:bool):
+ if button_pressed:
+ icon = fold_icon
+ else:
+ icon = unfold_icon
+
+ for node in get_parent().get_children():
+ if not (node is PankuButton):
+ continue
+ if button_pressed:
+ node.text = node.get_meta("text")
+ else:
+ node.text = ""
+ )
diff --git a/godot/addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd.uid b/godot/addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd.uid
new file mode 100644
index 0000000..5b88497
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/console_ui/side_buttons_controller.gd.uid
@@ -0,0 +1 @@
+uid://dbknokoygt463
diff --git a/godot/addons/panku_console/modules/interactive_shell/env.gd b/godot/addons/panku_console/modules/interactive_shell/env.gd
new file mode 100644
index 0000000..550e482
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/env.gd
@@ -0,0 +1,11 @@
+var _module:PankuModule
+
+func open_window(): _module.open_window()
+
+func open_launcher(): _module.open_launcher()
+
+func set_unified_window_visibility(enabled:bool):
+ _module.set_unified_window_visibility(enabled)
+
+func set_pause_if_popup(enabled:bool):
+ _module.set_pause_if_popup(enabled)
diff --git a/godot/addons/panku_console/modules/interactive_shell/env.gd.uid b/godot/addons/panku_console/modules/interactive_shell/env.gd.uid
new file mode 100644
index 0000000..008e77a
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/env.gd.uid
@@ -0,0 +1 @@
+uid://dk268bsmvnisk
diff --git a/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.gd b/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.gd
new file mode 100644
index 0000000..1e3e7e6
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.gd
@@ -0,0 +1,8 @@
+extends PanelContainer
+
+@export var label:RichTextLabel
+@export var bg2:ColorRect
+
+func set_highlight(b:bool):
+ bg2.scale.x = 1.0 if b else 0.0
+ bg2.visible = b
diff --git a/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.gd.uid b/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.gd.uid
new file mode 100644
index 0000000..f47df9e
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.gd.uid
@@ -0,0 +1 @@
+uid://br60e27sllo1h
diff --git a/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.tscn b/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.tscn
new file mode 100644
index 0000000..4a9750a
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/hints_list/hint.tscn
@@ -0,0 +1,44 @@
+[gd_scene load_steps=5 format=3 uid="uid://dbtn0x604fx5o"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/hints_list/hint.gd" id="1_sa53g"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_56dbv"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6xc2u"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ganpp"]
+content_margin_left = 4.0
+content_margin_top = 4.0
+content_margin_right = 4.0
+content_margin_bottom = 4.0
+
+[node name="Hint" type="PanelContainer" node_paths=PackedStringArray("label", "bg2")]
+anchors_preset = 10
+anchor_right = 1.0
+grow_horizontal = 2
+theme_override_styles/panel = SubResource("StyleBoxEmpty_56dbv")
+script = ExtResource("1_sa53g")
+label = NodePath("RichTextLabel")
+bg2 = NodePath("Bg2")
+
+[node name="Blur" type="ColorRect" parent="."]
+layout_mode = 2
+color = Color(0.219608, 0.219608, 0.223529, 0.658824)
+
+[node name="Bg2" type="ColorRect" parent="."]
+visible = false
+layout_mode = 2
+color = Color(0.290196, 0.509804, 0.305882, 1)
+
+[node name="RichTextLabel" type="RichTextLabel" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+mouse_filter = 2
+theme_override_styles/focus = SubResource("StyleBoxEmpty_6xc2u")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_ganpp")
+bbcode_enabled = true
+text = "[color=cyan]w_button[/color][color=gray]([/color]display_name:[color=green]String[/color], env:[color=green]String[/color], exp:[color=green]String[/color][color=gray])[/color]"
+fit_content = true
+scroll_active = false
+autowrap_mode = 0
diff --git a/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd b/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd
new file mode 100644
index 0000000..e80acf8
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd
@@ -0,0 +1,54 @@
+extends ScrollContainer
+
+const hint_pck = preload("./hint.tscn")
+
+const MAX_HEIGHT = 400
+
+@export var auto_resize := false
+
+@export var container:VBoxContainer
+
+var hints_count = 0
+
+var selected:int = 0:
+ set(v):
+ if !container: return
+ if v < container.get_child_count():
+ if selected < container.get_child_count():
+ container.get_child(selected).set_highlight(false)
+ container.get_child(v).set_highlight(true)
+ selected = v
+
+ #follow selected
+ var bar = get_v_scroll_bar()
+ if bar.visible:
+ var a = bar.max_value
+ var b = bar.value
+ var c = bar.page
+ var d = bar.max_value / hints_count
+ var l = d * selected
+ var r = d * (selected + 1)
+ if b > l: b = l
+ if b + c < r: b = r - c
+ bar.value = b
+
+func set_hints(texts:Array):
+ hints_count = texts.size()
+ for i in range(texts.size()):
+ var h
+ if i < container.get_child_count():
+ h = container.get_child(i)
+ h.show()
+ else:
+ h = hint_pck.instantiate()
+ container.add_child(h)
+ h.set_meta("idx", i)
+ h.label.text = texts[i]
+ h.set_highlight(false)
+ if texts.size() < container.get_child_count():
+ for i in range(texts.size(), container.get_child_count()):
+ container.get_child(i).hide()
+
+ if auto_resize:
+ await get_tree().process_frame
+ custom_minimum_size.y = min(MAX_HEIGHT, container.size.y)
diff --git a/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd.uid b/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd.uid
new file mode 100644
index 0000000..3d35450
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd.uid
@@ -0,0 +1 @@
+uid://drcrjq1q1qqhd
diff --git a/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.tscn b/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.tscn
new file mode 100644
index 0000000..16bcdc8
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/hints_list/hints_list.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=2 format=3 uid="uid://b3jf18wonocnv"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/hints_list/hints_list.gd" id="1_yd1qq"]
+
+[node name="HintsList" type="ScrollContainer" node_paths=PackedStringArray("container")]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_yd1qq")
+container = NodePath("VBoxContainer")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 0
+alignment = 2
diff --git a/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.gd b/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.gd
new file mode 100644
index 0000000..721b374
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.gd
@@ -0,0 +1,34 @@
+extends HBoxContainer
+
+@onready var console:PankuConsole = get_node(PankuConsole.SingletonPath)
+
+signal submitted(exp:String)
+signal update_hints(exp:String)
+signal next_hint()
+signal prev_hint()
+signal navigate_histories(histories:Array, cur:int)
+
+@export var input:LineEdit
+@export var btn:PankuButton
+
+func _ready():
+ input.text_submitted.connect(
+ func(s):
+ submitted.emit(s)
+ )
+ input.text_changed.connect(
+ func(s):
+ update_hints.emit(s)
+ )
+ btn.pressed.connect(
+ func():
+ submitted.emit(input.text)
+ input.on_text_submitted(input.text)
+ )
+
+ #get focus automatically.
+ visibility_changed.connect(
+ func():
+ if is_visible_in_tree():
+ input.call_deferred("grab_focus")
+ )
diff --git a/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.gd.uid b/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.gd.uid
new file mode 100644
index 0000000..4579705
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.gd.uid
@@ -0,0 +1 @@
+uid://djjw21iltjovc
diff --git a/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.tscn b/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.tscn
new file mode 100644
index 0000000..15521f5
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/input_field/input_area.tscn
@@ -0,0 +1,41 @@
+[gd_scene load_steps=6 format=3 uid="uid://bme8twac4ick5"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_rocdy"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/input_field/input_area.gd" id="2_6g40s"]
+[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="4_6t5yf"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/input_field/input_field.gd" id="4_xjt2l"]
+[ext_resource type="Texture2D" uid="uid://b4jd6tqlie0wx" path="res://addons/panku_console/res/icons2/reply.svg" id="6_w88f1"]
+
+[node name="InputArea" type="HBoxContainer" node_paths=PackedStringArray("input", "btn")]
+offset_right = 277.0
+offset_bottom = 26.0
+theme = ExtResource("1_rocdy")
+theme_override_constants/separation = 0
+script = ExtResource("2_6g40s")
+input = NodePath("InputField")
+btn = NodePath("Button")
+
+[node name="InputField" type="LineEdit" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+focus_neighbor_left = NodePath(".")
+focus_neighbor_top = NodePath(".")
+focus_neighbor_right = NodePath(".")
+focus_neighbor_bottom = NodePath(".")
+focus_next = NodePath(".")
+focus_previous = NodePath(".")
+placeholder_text = "Input expression here..."
+clear_button_enabled = true
+caret_blink = true
+script = ExtResource("4_xjt2l")
+
+[node name="Button" parent="." instance=ExtResource("4_6t5yf")]
+layout_mode = 2
+
+[node name="TextureRect" parent="Button/HBoxContainer" index="0"]
+texture = ExtResource("6_w88f1")
+
+[node name="Label" parent="Button/HBoxContainer" index="1"]
+text = ""
+
+[editable path="Button"]
diff --git a/godot/addons/panku_console/modules/interactive_shell/input_field/input_field.gd b/godot/addons/panku_console/modules/interactive_shell/input_field/input_field.gd
new file mode 100644
index 0000000..8ba120f
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/input_field/input_field.gd
@@ -0,0 +1,45 @@
+extends LineEdit
+
+var module:PankuModuleInteractiveShell
+
+#up/down history
+var history_idx := 0
+
+var hints = []
+
+func _ready():
+ text_submitted.connect(on_text_submitted)
+
+func on_text_submitted(s:String):
+ var histories = module.get_histories()
+ if histories.size() > 0 and history_idx < histories.size() and text == histories[history_idx]:
+ pass
+ else:
+ module.add_history(s)
+ history_idx = histories.size()
+ clear()
+
+func _gui_input(e):
+ #navigate through histories
+ var histories = module.get_histories()
+ if hints.is_empty():
+ if e is InputEventKey and e.keycode == KEY_UP and e.pressed:
+ if !histories.is_empty() :
+ history_idx = wrapi(history_idx-1, 0, histories.size())
+ text = histories[history_idx]
+ get_parent().navigate_histories.emit(histories, history_idx)
+ await get_tree().process_frame
+ caret_column = text.length()
+ elif e is InputEventKey and e.keycode == KEY_DOWN and e.pressed:
+ if !histories.is_empty():
+ history_idx = wrapi(history_idx+1, 0, histories.size())
+ text = histories[history_idx]
+ get_parent().navigate_histories.emit(histories, history_idx)
+ await get_tree().process_frame
+ caret_column = text.length()
+ #navigate through hints
+ else:
+ if e is InputEventKey and e.keycode == KEY_UP and e.pressed:
+ get_parent().prev_hint.emit()
+ elif e is InputEventKey and (e.keycode in [KEY_DOWN, KEY_TAB]) and e.pressed:
+ get_parent().next_hint.emit()
diff --git a/godot/addons/panku_console/modules/interactive_shell/input_field/input_field.gd.uid b/godot/addons/panku_console/modules/interactive_shell/input_field/input_field.gd.uid
new file mode 100644
index 0000000..ac0255f
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/input_field/input_field.gd.uid
@@ -0,0 +1 @@
+uid://crmlcgjx0rruc
diff --git a/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.gd b/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.gd
new file mode 100644
index 0000000..c74eb05
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.gd
@@ -0,0 +1,13 @@
+extends VBoxContainer
+
+var console:PankuConsole
+
+@export var input_area:Node
+@export var repl:Node
+
+func _ready() -> void:
+ repl.output_result.connect(console.notify)
+ repl.output_error.connect(console.notify)
+ input_area.submitted.connect(
+ func(_s): hide()
+ )
diff --git a/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.gd.uid b/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.gd.uid
new file mode 100644
index 0000000..162e92a
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.gd.uid
@@ -0,0 +1 @@
+uid://e1chjcdql5bm
diff --git a/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.tscn b/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.tscn
new file mode 100644
index 0000000..34d48d5
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/mini_repl_2.tscn
@@ -0,0 +1,61 @@
+[gd_scene load_steps=10 format=3 uid="uid://7782tkm11uco"]
+
+[ext_resource type="PackedScene" uid="uid://bme8twac4ick5" path="res://addons/panku_console/modules/interactive_shell/input_field/input_area.tscn" id="1_cerpp"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/mini_repl_2.gd" id="1_qrxrm"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/interactive_shell/console_ui/repl.gd" id="1_tygtx"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_yfc5r"]
+[ext_resource type="PackedScene" uid="uid://cbijhl1nhy64n" path="res://addons/panku_console/modules/interactive_shell/console_ui/help_bar.tscn" id="2_2k26n"]
+[ext_resource type="Shader" path="res://addons/panku_console/res/shader/simple_fast_blur.gdshader" id="4_djxpe"]
+[ext_resource type="PackedScene" uid="uid://b3jf18wonocnv" path="res://addons/panku_console/modules/interactive_shell/hints_list/hints_list.tscn" id="5_i3ldx"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xty2u"]
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_kt6ig"]
+shader = ExtResource("4_djxpe")
+shader_parameter/lod = 4.0
+shader_parameter/modulate = Color(0, 0, 0, 0.25098)
+
+[node name="MiniREPL" type="VBoxContainer" node_paths=PackedStringArray("input_area", "repl")]
+z_index = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme = ExtResource("1_yfc5r")
+theme_override_constants/separation = 0
+alignment = 2
+script = ExtResource("1_qrxrm")
+input_area = NodePath("PanelContainer/InputArea")
+repl = NodePath("REPL")
+
+[node name="REPL" type="Node" parent="." node_paths=PackedStringArray("_input_area", "_hints", "_helpbar", "_helpbar_label")]
+script = ExtResource("1_tygtx")
+_input_area = NodePath("../PanelContainer/InputArea")
+_hints = NodePath("../HintsList")
+_helpbar = NodePath("../HelpBar")
+_helpbar_label = NodePath("../HelpBar/Label")
+
+[node name="HintsList" parent="." instance=ExtResource("5_i3ldx")]
+layout_mode = 2
+auto_resize = true
+
+[node name="HelpBar" parent="." instance=ExtResource("2_2k26n")]
+custom_minimum_size = Vector2(0, 24)
+layout_mode = 2
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxEmpty_xty2u")
+
+[node name="ColorRect" type="ColorRect" parent="PanelContainer"]
+material = SubResource("ShaderMaterial_kt6ig")
+layout_mode = 2
+
+[node name="InputArea" parent="PanelContainer" instance=ExtResource("1_cerpp")]
+custom_minimum_size = Vector2(0, 24)
+layout_mode = 2
+
+[editable path="HintsList"]
+[editable path="HelpBar"]
diff --git a/godot/addons/panku_console/modules/interactive_shell/module.gd b/godot/addons/panku_console/modules/interactive_shell/module.gd
new file mode 100644
index 0000000..2050319
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/module.gd
@@ -0,0 +1,171 @@
+class_name PankuModuleInteractiveShell extends PankuModule
+
+var window:PankuLynxWindow
+var interactive_shell:Control
+var simple_launcher:Control
+
+enum InputMode {
+ Window,
+ Launcher
+}
+
+var gui_mode:InputMode = InputMode.Window
+var pause_if_input:bool = true
+var unified_window_visibility:bool = false
+var init_expr:String = ""
+var _is_gui_open:bool = false
+var _was_tree_paused: bool = false
+var _previous_mouse_mode := Input.MOUSE_MODE_VISIBLE
+var _show_side_menu:bool
+
+func get_intro() -> String:
+ var intro:PackedStringArray = PackedStringArray()
+ intro.append("[color=#ffffff44][b][i]> Panku Console[/i][/b][/color]")
+ intro.append("[color=#ffffff44]Feature-Packed Runtime Debugging Toolkit for Godot[/color]")
+ intro.append("[color=#ffffff44]Version: %s([url=%s][color=#10a00c]%s[/color][/url]) | Visit [color=#3feeb6][url=https://github.com/Ark2000/PankuConsole]github repo[/url][/color] for more info[/color]" % [PankuUtils.get_plugin_version(), PankuUtils.get_commit_url(), PankuUtils.get_commit_sha_short()])
+ return "\n".join(intro)
+
+func init_module():
+ interactive_shell = preload("./console_ui/panku_console_ui.tscn").instantiate()
+ window = core.windows_manager.create_window(interactive_shell)
+ add_auto_save_hook(window)
+ interactive_shell._repl._module = self
+ window.queue_free_on_close = false
+ window.set_window_title_text("Interactive Shell V2")
+ load_window_data(window)
+ window.hide_window()
+
+ interactive_shell.output(get_intro())
+
+ simple_launcher = preload("./mini_repl_2.tscn").instantiate()
+ simple_launcher.console = core
+ core.add_child(simple_launcher)
+ simple_launcher.repl._module = self
+ simple_launcher.hide()
+
+ core.toggle_console_action_just_pressed.connect(
+ func():
+ if gui_mode == InputMode.Window:
+ if window.visible:
+ window.hide_window()
+ else:
+ window.show_window()
+ elif gui_mode == InputMode.Launcher:
+ simple_launcher.visible = not simple_launcher.visible
+ )
+
+ # Grab the mouse when the dev console is visible (e.g. FPS games)
+ window.visibility_changed.connect(
+ func():
+ # the mouse is grabbed when the window is visible
+ if window.visible:
+ _previous_mouse_mode = Input.mouse_mode
+ Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
+ # restore the mouse mode when the window is hidden
+ else:
+ Input.mouse_mode = _previous_mouse_mode
+ )
+
+ gui_mode = load_module_data("gui_mode", InputMode.Window)
+ pause_if_input = load_module_data("pause_if_popup", true)
+ unified_window_visibility = load_module_data("unified_visibility", false)
+ init_expr = load_module_data("init_expression", "print('Panku Console Loaded!')")
+
+ _show_side_menu = load_module_data("show_side_menu", true)
+ set_side_menu_visible(_show_side_menu)
+
+ window.visibility_changed.connect(update_gui_state)
+ simple_launcher.visibility_changed.connect(update_gui_state)
+ _is_gui_open = not (window.visible or simple_launcher.visible)
+ update_gui_state()
+
+ # TODO: this signal is emitted twice when the window is shown, investigate
+ # window.visibility_changed.connect(
+ # func():
+ # print("window visibility changed: ", window.visible)
+ # )
+
+ # execute init_expr after a short delay
+ if init_expr != "":
+ core.create_tween().tween_callback(
+ func():
+ var result = core.gd_exprenv.execute(init_expr)
+ core.new_expression_entered.emit(init_expr, result)
+ ).set_delay(0.1)
+
+ _input_histories = load_module_data("histories", [])
+
+func quit_module():
+ super.quit_module()
+ save_window_data(window)
+ save_module_data("gui_mode", gui_mode)
+ save_module_data("histories", _input_histories)
+ save_module_data("show_side_menu", _show_side_menu)
+
+func update_gui_state():
+ var is_gui_open = window.visible or simple_launcher.visible
+
+ if is_gui_open == _is_gui_open:
+ return
+
+ if _is_gui_open != is_gui_open:
+ core._shell_visibility = is_gui_open
+ core.interactive_shell_visibility_changed.emit(is_gui_open)
+ _is_gui_open = is_gui_open
+
+ if pause_if_input:
+ if _is_gui_open:
+ _was_tree_paused = core.get_tree().paused
+ core.get_tree().paused = true
+ else:
+ if core.get_tree().paused:
+ core.get_tree().paused = _was_tree_paused
+
+ if unified_window_visibility:
+ core.windows_manager.visible = _is_gui_open
+
+func open_window():
+ if gui_mode == InputMode.Window:
+ if not window.visible:
+ window.show_window()
+ else:
+ core.notify("The window is alreay opened.")
+ elif gui_mode == InputMode.Launcher:
+ gui_mode = InputMode.Window
+ simple_launcher.hide()
+ window.show_window()
+
+func open_launcher():
+ if gui_mode == InputMode.Window:
+ gui_mode = InputMode.Launcher
+ window.hide_window()
+ simple_launcher.show()
+ elif gui_mode == InputMode.Launcher:
+ if not simple_launcher.visible:
+ simple_launcher.show()
+ else:
+ core.notify("The launcher is alreay opened.")
+
+func set_side_menu_visible(enabled:bool):
+ _show_side_menu = enabled
+ interactive_shell.enable_side_menu = enabled
+ interactive_shell.resized.emit()
+
+func set_unified_window_visibility(enabled:bool):
+ unified_window_visibility = enabled
+ update_gui_state()
+
+func set_pause_if_popup(enabled:bool):
+ pause_if_input = enabled
+ update_gui_state()
+
+const MAX_HISTORY = 10
+var _input_histories := []
+
+func get_histories() -> Array:
+ return _input_histories
+
+func add_history(s:String) -> void:
+ _input_histories.append(s)
+ if _input_histories.size() > MAX_HISTORY:
+ _input_histories.remove_at(0)
diff --git a/godot/addons/panku_console/modules/interactive_shell/module.gd.uid b/godot/addons/panku_console/modules/interactive_shell/module.gd.uid
new file mode 100644
index 0000000..3ea5746
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/module.gd.uid
@@ -0,0 +1 @@
+uid://qyi713yh7kco
diff --git a/godot/addons/panku_console/modules/interactive_shell/opt.gd b/godot/addons/panku_console/modules/interactive_shell/opt.gd
new file mode 100644
index 0000000..a913952
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/opt.gd
@@ -0,0 +1,31 @@
+extends ModuleOptions
+
+@export_group("interactive_shell")
+
+@export var export_comment_show_side_menu = "config file at side_menu_config.json"
+@export var show_side_menu:bool:
+ get:
+ return _module._show_side_menu
+ set(v):
+ _module.set_side_menu_visible(v)
+
+@export var export_comment_unified_visibility = "unified_visibility will keep all windows' visibility the same as interactive shell"
+@export var unified_visibility:bool = false:
+ get:
+ return _module.unified_window_visibility
+ set(v):
+ _module.set_unified_window_visibility(v)
+
+@export var export_comment_pause_if_popup = "Whether the whole game should be paused when interactive shell is visible"
+@export var pause_if_popup:bool = false:
+ get:
+ return _module.pause_if_input
+ set(v):
+ _module.set_pause_if_popup(v)
+
+@export var export_comment_init_expression = "init_expression will be executed when the project starts"
+@export var init_expression:String = "":
+ get:
+ return _module.init_expr
+ set(v):
+ _module.init_expr = v
diff --git a/godot/addons/panku_console/modules/interactive_shell/opt.gd.uid b/godot/addons/panku_console/modules/interactive_shell/opt.gd.uid
new file mode 100644
index 0000000..9111062
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/opt.gd.uid
@@ -0,0 +1 @@
+uid://yh85n6ws8ne5
diff --git a/godot/addons/panku_console/modules/interactive_shell/side_menu_config.json b/godot/addons/panku_console/modules/interactive_shell/side_menu_config.json
new file mode 100644
index 0000000..e8af52e
--- /dev/null
+++ b/godot/addons/panku_console/modules/interactive_shell/side_menu_config.json
@@ -0,0 +1,36 @@
+{
+ "items.top":[
+ {
+ "text": "Debug Logs",
+ "icon": "res://addons/panku_console/res/icons2/info2.svg",
+ "command": "native_logger.open()"
+ },
+ {
+ "text": "Watch Exp",
+ "icon": "res://addons/panku_console/res/icons2/eye.svg",
+ "command": "expression_monitor.open_window()"
+ },
+ {
+ "text": "Shortcut",
+ "icon": "res://addons/panku_console/res/icons2/keyboard.svg",
+ "command": "keyboard_shortcuts.open()"
+ },
+ {
+ "text": "History",
+ "icon": "res://addons/panku_console/res/icons2/history.svg",
+ "command": "history_manager.open()"
+ }
+ ],
+ "items.bottom":[
+ {
+ "text": "Settings",
+ "icon": "res://addons/panku_console/res/icons2/gear.svg",
+ "command": "general_settings.open()"
+ },
+ {
+ "text": "About",
+ "icon": "res://addons/panku_console/res/icons2/question.svg",
+ "command": "about.open()"
+ }
+ ]
+}
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/env.gd b/godot/addons/panku_console/modules/keyboard_shortcuts/env.gd
new file mode 100644
index 0000000..dae4497
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/env.gd
@@ -0,0 +1,4 @@
+var _module:PankuModule
+
+func open() -> void:
+ _module.open_window()
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/env.gd.uid b/godot/addons/panku_console/modules/keyboard_shortcuts/env.gd.uid
new file mode 100644
index 0000000..793839d
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/env.gd.uid
@@ -0,0 +1 @@
+uid://c543ph1ee3se6
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd
new file mode 100644
index 0000000..8658b83
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd
@@ -0,0 +1,19 @@
+extends HBoxContainer
+
+signal exp_edit_submitted(new_exp:String)
+
+@export var exp_edit:LineEdit
+@export var remap_button:Button
+@export var delete_button:Button
+
+func _ready():
+ delete_button.pressed.connect(queue_free)
+ exp_edit.text_submitted.connect(
+ func(new_text:String):
+ exp_edit.release_focus()
+ exp_edit_submitted.emit(new_text)
+ )
+ exp_edit.focus_exited.connect(
+ func():
+ exp_edit_submitted.emit(exp_edit.text)
+ )
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd.uid b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd.uid
new file mode 100644
index 0000000..88f87fd
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd.uid
@@ -0,0 +1 @@
+uid://5ridrx8lft6u
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.tscn b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.tscn
new file mode 100644
index 0000000..d4c5b4a
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_item.tscn
@@ -0,0 +1,34 @@
+[gd_scene load_steps=5 format=3 uid="uid://dkw70e7xyrqxi"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_u320k"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/keyboard_shortcuts/exp_key_item.gd" id="2_7ijd8"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/keyboard_shortcuts/remap_button.gd" id="3_3bdj0"]
+[ext_resource type="Texture2D" uid="uid://dnexm7u6lq3km" path="res://addons/panku_console/res/icons2/remove.svg" id="4_vif73"]
+
+[node name="ExpkeyItem" type="HBoxContainer" node_paths=PackedStringArray("exp_edit", "remap_button", "delete_button")]
+offset_right = 280.0
+offset_bottom = 31.0
+theme = ExtResource("1_u320k")
+script = ExtResource("2_7ijd8")
+exp_edit = NodePath("ExpressionEdit")
+remap_button = NodePath("RemapButton")
+delete_button = NodePath("DeleteButton")
+
+[node name="ExpressionEdit" type="LineEdit" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Expression"
+clear_button_enabled = true
+
+[node name="RemapButton" type="Button" parent="."]
+layout_mode = 2
+toggle_mode = true
+text = "Unassigned"
+script = ExtResource("3_3bdj0")
+
+[node name="DeleteButton" type="Button" parent="."]
+custom_minimum_size = Vector2(27, 0)
+layout_mode = 2
+toggle_mode = true
+icon = ExtResource("4_vif73")
+expand_icon = true
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd
new file mode 100644
index 0000000..19d8158
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd
@@ -0,0 +1,92 @@
+extends Control
+
+signal key_binding_added(key: InputEventKey, expression: String)
+signal key_binding_changed(key: InputEventKey, expression: String)
+
+const INFO_STRING := "Yes, I know it."
+const exp_key_item := preload("./exp_key_item.tscn")
+
+var console: PankuConsole
+
+@export var add_btn: Button
+@export var container: VBoxContainer
+
+var mapping_data = []
+
+@onready var info_btn: Button = %InfoButton
+
+
+func _ready():
+ info_btn.pressed.connect(_console_notify.bind(INFO_STRING))
+
+ #when clicking the button, add a new exp key mapping item
+ add_btn.pressed.connect(
+ func():
+ var default_exp = ""
+ var default_event = null
+ add_item(default_exp, default_event)
+ mapping_data.push_back([default_exp, default_event])
+ )
+
+
+func _console_notify(txt: String) -> void:
+ if console:
+ console.notify(txt)
+
+
+#handle input here.
+func _unhandled_input(e):
+ if e is InputEventKey:
+ for i in range(len(mapping_data)):
+ var key_mapping = mapping_data[i]
+ var exp:String = key_mapping[0]
+ var event:InputEventKey = key_mapping[1]
+ if !event: continue
+ if e.keycode == event.keycode and e.pressed and !e.echo:
+ #execute the exp
+ var result = console.gd_exprenv.execute(exp)
+ if result.failed:
+ _console_notify("[color=red]%s[/color]" % result.result)
+ else:
+ #ignore null result
+ if result.result:
+ _console_notify(str(result.result))
+
+
+func add_item(exp:String, event:InputEventKey):
+ var item = exp_key_item.instantiate()
+ container.add_child(item)
+ container.move_child(item, container.get_child_count() - 2)
+ item.exp_edit.text = exp
+ item.remap_button.key_event = event
+
+ item.exp_edit_submitted.connect(
+ func(new_exp:String):
+ mapping_data[item.get_index()][0] = new_exp
+ if(key_binding_added.get_connections().size() > 0):
+ key_binding_added.emit(event, new_exp)
+ )
+ item.remap_button.key_event_changed.connect(
+ func(new_event:InputEventKey):
+ await get_tree().process_frame
+ mapping_data[item.get_index()][1] = new_event
+ if(key_binding_changed.get_connections().size() > 0):
+ key_binding_changed.emit(new_event, mapping_data[item.get_index()][0])
+ )
+ item.tree_exiting.connect(
+ func():
+ mapping_data.remove_at(item.get_index())
+ )
+
+func get_data() -> Array:
+ return mapping_data
+
+func load_data(data:Array):
+ mapping_data = data
+
+ #load data
+ for i in range(len(mapping_data)):
+ var key_mapping = mapping_data[i]
+ var exp:String = key_mapping[0]
+ var event:InputEventKey = key_mapping[1]
+ add_item(exp, event)
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd.uid b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd.uid
new file mode 100644
index 0000000..ed92246
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd.uid
@@ -0,0 +1 @@
+uid://gr6addua2s0a
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.tscn b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.tscn
new file mode 100644
index 0000000..7583903
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.tscn
@@ -0,0 +1,61 @@
+[gd_scene load_steps=6 format=3 uid="uid://c6hm8vweq0j4f"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_0xd5p"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/keyboard_shortcuts/exp_key_mapper_2.gd" id="2_vxcow"]
+[ext_resource type="Texture2D" uid="uid://dprpfr0l5xvmu" path="res://addons/panku_console/res/icons2/add.svg" id="3_0d2ct"]
+[ext_resource type="Texture2D" uid="uid://b6jt0ggmuoyeb" path="res://addons/panku_console/res/icons2/info2.svg" id="3_ofrgs"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_i7jk5"]
+content_margin_left = 8.0
+content_margin_top = 8.0
+content_margin_right = 8.0
+content_margin_bottom = 8.0
+
+[node name="ExpKeyMapper" type="Control" node_paths=PackedStringArray("add_btn", "container")]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_0xd5p")
+script = ExtResource("2_vxcow")
+add_btn = NodePath("ScrollContainer/PanelContainer/VBoxContainer2/VBoxContainer/Add")
+container = NodePath("ScrollContainer/PanelContainer/VBoxContainer2/VBoxContainer")
+
+[node name="ScrollContainer" type="ScrollContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+horizontal_scroll_mode = 0
+
+[node name="PanelContainer" type="PanelContainer" parent="ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxEmpty_i7jk5")
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="ScrollContainer/PanelContainer"]
+layout_mode = 2
+
+[node name="InfoButton" type="Button" parent="ScrollContainer/PanelContainer/VBoxContainer2"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Edit expression key bindings here."
+icon = ExtResource("3_ofrgs")
+alignment = 0
+expand_icon = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer/PanelContainer/VBoxContainer2"]
+layout_mode = 2
+
+[node name="Add" type="Button" parent="ScrollContainer/PanelContainer/VBoxContainer2/VBoxContainer"]
+layout_mode = 2
+text = " "
+icon = ExtResource("3_0d2ct")
+icon_alignment = 1
+expand_icon = true
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/module.gd b/godot/addons/panku_console/modules/keyboard_shortcuts/module.gd
new file mode 100644
index 0000000..db00dd2
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/module.gd
@@ -0,0 +1,34 @@
+class_name PankuModuleKeyboardShortcuts extends PankuModule
+
+var window:PankuLynxWindow
+var key_mapper
+
+func init_module():
+ # setup ui
+ key_mapper = preload("./exp_key_mapper_2.tscn").instantiate()
+ key_mapper.console = core
+
+ # bind window
+ window = core.windows_manager.create_window(key_mapper)
+ add_auto_save_hook(window)
+ window.queue_free_on_close = false
+ window.set_window_title_text("Keyboard Shortcuts")
+
+ load_window_data(window)
+ key_mapper.load_data(load_module_data("key_mapper", []))
+ key_mapper.key_binding_added.connect(
+ func(key: InputEventKey, expression: String):
+ save_module_data("key_mapper", key_mapper.get_data())
+ )
+ key_mapper.key_binding_changed.connect(
+ func(key: InputEventKey, expression: String):
+ save_module_data("key_mapper", key_mapper.get_data())
+ )
+
+func quit_module():
+ super.quit_module()
+ save_window_data(window)
+ save_module_data("key_mapper", key_mapper.get_data())
+
+func open_window():
+ window.show_window()
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/module.gd.uid b/godot/addons/panku_console/modules/keyboard_shortcuts/module.gd.uid
new file mode 100644
index 0000000..900200a
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/module.gd.uid
@@ -0,0 +1 @@
+uid://ciqyqor6o5wst
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/opt.gd b/godot/addons/panku_console/modules/keyboard_shortcuts/opt.gd
new file mode 100644
index 0000000..ac27bb8
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/opt.gd
@@ -0,0 +1,8 @@
+extends ModuleOptions
+
+@export_group("keyboard_shortcuts")
+
+@export var export_button_open_keyboard_shortcuts := "Open Keyboard Shortcuts"
+
+func open_keyboard_shortcuts():
+ _module.open_window()
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/opt.gd.uid b/godot/addons/panku_console/modules/keyboard_shortcuts/opt.gd.uid
new file mode 100644
index 0000000..cd7f7b0
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/opt.gd.uid
@@ -0,0 +1 @@
+uid://c3qb8rvua37fs
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/remap_button.gd b/godot/addons/panku_console/modules/keyboard_shortcuts/remap_button.gd
new file mode 100644
index 0000000..4391139
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/remap_button.gd
@@ -0,0 +1,27 @@
+extends Button
+
+signal key_event_changed(new_event:InputEventKey)
+
+var key_event:InputEventKey:
+ set(v):
+ key_event = v
+ var key_name = "unassigned"
+ if key_event:
+ key_name = "Key " + OS.get_keycode_string(key_event.keycode)
+ text = key_name
+ key_event_changed.emit(v)
+
+func _ready():
+ set_process_unhandled_key_input(false)
+ toggled.connect(
+ func(button_pressed:bool):
+ set_process_unhandled_key_input(button_pressed)
+ if button_pressed:
+ text = "Waiting..."
+ else:
+ release_focus()
+ )
+
+func _unhandled_key_input(event):
+ key_event = event
+ button_pressed = false
diff --git a/godot/addons/panku_console/modules/keyboard_shortcuts/remap_button.gd.uid b/godot/addons/panku_console/modules/keyboard_shortcuts/remap_button.gd.uid
new file mode 100644
index 0000000..5f06e73
--- /dev/null
+++ b/godot/addons/panku_console/modules/keyboard_shortcuts/remap_button.gd.uid
@@ -0,0 +1 @@
+uid://tvvj5uqe67ov
diff --git a/godot/addons/panku_console/modules/native_logger/env.gd b/godot/addons/panku_console/modules/native_logger/env.gd
new file mode 100644
index 0000000..aee7d7e
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/env.gd
@@ -0,0 +1,13 @@
+var _module:PankuModule
+
+const _HELP_open = "Open logger window"
+func open() -> void:
+ _module.open_window()
+
+const _HELP_toggle_overlay = "Toggle visibility of logger overlay"
+func toggle_overlay() -> void:
+ _module.toggle_overlay()
+
+const _HELP_clear = "Clear logs"
+func clear() -> void:
+ _module.logger_ui.clear_all()
diff --git a/godot/addons/panku_console/modules/native_logger/env.gd.uid b/godot/addons/panku_console/modules/native_logger/env.gd.uid
new file mode 100644
index 0000000..28b3ee3
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/env.gd.uid
@@ -0,0 +1 @@
+uid://dpi48sns7m7ch
diff --git a/godot/addons/panku_console/modules/native_logger/godot_log_monitor.gd b/godot/addons/panku_console/modules/native_logger/godot_log_monitor.gd
new file mode 100644
index 0000000..45cd568
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/godot_log_monitor.gd
@@ -0,0 +1,59 @@
+extends Node
+#Monitor built-in logs
+
+signal error_msg_received(msg:String)
+signal warning_msg_received(msg:String)
+signal info_msg_received(msg:String)
+
+const UPDATE_INTERVAL := 0.1
+const ERROR_MSG_PREFIX := "USER ERROR: "
+const WARNING_MSG_PREFIX := "USER WARNING: "
+#Any logs with three spaces at the beginning will be ignored.
+const IGNORE_PREFIX := " "
+
+var godot_log: FileAccess
+var godot_log_path: String
+
+
+func _ready():
+ if not _is_log_enabled():
+ push_warning("You have to enable file logging in order to use engine log monitor!")
+ return
+
+ godot_log_path = ProjectSettings.get("debug/file_logging/log_path")
+ if not FileAccess.file_exists(godot_log_path):
+ push_warning("Log file not fount by path " + godot_log_path)
+ return
+
+ _start_watching()
+
+
+func _start_watching() -> void:
+ godot_log = FileAccess.open(godot_log_path, FileAccess.READ)
+ create_tween().set_loops().tween_callback(_read_data).set_delay(UPDATE_INTERVAL)
+
+
+func _is_log_enabled() -> bool:
+
+ if ProjectSettings.get("debug/file_logging/enable_file_logging"):
+ return true
+
+ # this feels so weird and wrong
+ # what about other platforms?
+ if OS.has_feature("pc") and ProjectSettings.get("debug/file_logging/enable_file_logging.pc"):
+ return true
+
+ return false
+
+
+func _read_data():
+ while godot_log.get_position() < godot_log.get_length():
+ var new_line = godot_log.get_line()
+ if new_line.begins_with(IGNORE_PREFIX):
+ continue
+ if new_line.begins_with(ERROR_MSG_PREFIX):
+ error_msg_received.emit(new_line.trim_prefix(ERROR_MSG_PREFIX))
+ elif new_line.begins_with(WARNING_MSG_PREFIX):
+ warning_msg_received.emit(new_line.trim_prefix(WARNING_MSG_PREFIX))
+ else:
+ info_msg_received.emit(new_line)
diff --git a/godot/addons/panku_console/modules/native_logger/godot_log_monitor.gd.uid b/godot/addons/panku_console/modules/native_logger/godot_log_monitor.gd.uid
new file mode 100644
index 0000000..e260c54
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/godot_log_monitor.gd.uid
@@ -0,0 +1 @@
+uid://bpansextixugq
diff --git a/godot/addons/panku_console/modules/native_logger/log_overlay.tscn b/godot/addons/panku_console/modules/native_logger/log_overlay.tscn
new file mode 100644
index 0000000..16dfe9d
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/log_overlay.tscn
@@ -0,0 +1,21 @@
+[gd_scene load_steps=2 format=3 uid="uid://clwb00tc8ogtr"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_2qqxo"]
+
+[node name="LogOverlay" type="RichTextLabel"]
+modulate = Color(1, 1, 1, 0.501961)
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+mouse_filter = 2
+theme = ExtResource("1_2qqxo")
+bbcode_enabled = true
+text = "Output Area"
+scroll_active = false
+scroll_following = true
+autowrap_mode = 0
+shortcut_keys_enabled = false
diff --git a/godot/addons/panku_console/modules/native_logger/log_view_tag.gd b/godot/addons/panku_console/modules/native_logger/log_view_tag.gd
new file mode 100644
index 0000000..280b00b
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/log_view_tag.gd
@@ -0,0 +1,12 @@
+extends Control
+
+@export var tag_btn:Button
+@export var rm_btn:Button
+
+var tag_text:String
+var tag_number:int = 0
+
+func check(message:String):
+ if message.contains(tag_text):
+ tag_number += 1
+ tag_btn.text = "%s (%d)" % [tag_text, tag_number]
diff --git a/godot/addons/panku_console/modules/native_logger/log_view_tag.gd.uid b/godot/addons/panku_console/modules/native_logger/log_view_tag.gd.uid
new file mode 100644
index 0000000..ffc42e5
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/log_view_tag.gd.uid
@@ -0,0 +1 @@
+uid://d1cqmpqf4q3if
diff --git a/godot/addons/panku_console/modules/native_logger/log_view_tag.tscn b/godot/addons/panku_console/modules/native_logger/log_view_tag.tscn
new file mode 100644
index 0000000..66762cd
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/log_view_tag.tscn
@@ -0,0 +1,47 @@
+[gd_scene load_steps=6 format=3 uid="uid://dpurdc5me82ds"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_e0ejw"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/native_logger/log_view_tag.gd" id="2_ia8vx"]
+[ext_resource type="PackedScene" uid="uid://drn5t13m088fb" path="res://addons/panku_console/common/panku_button.tscn" id="3_cc2ep"]
+[ext_resource type="Texture2D" uid="uid://8g5afcuanbl6" path="res://addons/panku_console/res/icons2/close.svg" id="4_msugh"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jvhll"]
+bg_color = Color(1, 1, 1, 1)
+corner_radius_top_left = 2
+corner_radius_top_right = 2
+corner_radius_bottom_right = 2
+corner_radius_bottom_left = 2
+shadow_color = Color(0, 0, 0, 0.188235)
+shadow_size = 1
+shadow_offset = Vector2(1, 1)
+
+[node name="LoggerViewTag" type="PanelContainer" node_paths=PackedStringArray("tag_btn", "rm_btn")]
+self_modulate = Color(0.027451, 0.490196, 0.333333, 1)
+offset_right = 123.0
+offset_bottom = 31.0
+size_flags_vertical = 4
+theme = ExtResource("1_e0ejw")
+theme_override_styles/panel = SubResource("StyleBoxFlat_jvhll")
+script = ExtResource("2_ia8vx")
+tag_btn = NodePath("HBoxContainer/Button")
+rm_btn = NodePath("HBoxContainer/Button2/Button")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+theme_override_constants/separation = 0
+
+[node name="Button" type="Button" parent="HBoxContainer"]
+layout_mode = 2
+text = "[info] (428)"
+flat = true
+
+[node name="Button2" parent="HBoxContainer" instance=ExtResource("3_cc2ep")]
+layout_mode = 2
+
+[node name="Button" parent="HBoxContainer/Button2" index="0"]
+flat = true
+
+[node name="TextureRect" parent="HBoxContainer/Button2/HBoxContainer" index="0"]
+texture = ExtResource("4_msugh")
+
+[editable path="HBoxContainer/Button2"]
diff --git a/godot/addons/panku_console/modules/native_logger/logger_view.gd b/godot/addons/panku_console/modules/native_logger/logger_view.gd
new file mode 100644
index 0000000..7cd31d1
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/logger_view.gd
@@ -0,0 +1,150 @@
+extends Control
+
+signal content_updated(bbcode:String)
+
+var console:PankuConsole
+var _module:PankuModule
+
+const MAX_LOGS = 128
+
+@export var tag_prefab:PackedScene
+@export var search_box:LineEdit
+@export var search_btn:Button
+@export var pin_btn:Button
+@export var cls_btn:Button
+@export var tags_container2:ScrollContainer
+@export var tags_container:HBoxContainer
+@export var rlabel:RichTextLabel
+
+var current_filter:String = ""
+var logs:Array = []
+
+const CFG_LOGGER_TAGS = "logger_tags"
+const CFG_LOGGER_OUTPUT_FONT_SIZE = "logger_output_font_size"
+
+#level: #1.info 2.warning 3.error
+func add_log(message:String, level:int):
+ #add prefix
+ if level == 2:
+ message = "[bgcolor=yellow][color=black][warning][/color][/bgcolor] " + message
+ elif level == 3:
+ message = "[bgcolor=red][color=white][error][/color][/bgcolor] " + message
+
+ #update tags
+ for tag in tags_container.get_children():
+ tag.check(message)
+
+ if logs.size() > 0:
+ var last_log = logs.back()
+ if (last_log["message"] == message) and (Time.get_unix_time_from_system() - last_log["timestamp"] < 1.0):
+ last_log["count"] += 1
+ last_log["timestamp"] = Time.get_unix_time_from_system()
+ update_view()
+ return
+
+ logs.push_back({
+ "message": message,
+ "level": level,
+ "timestamp": Time.get_unix_time_from_system(),
+ "count": 1
+ })
+
+ #TODO: support more logs
+ if logs.size() >= MAX_LOGS:
+ logs = logs.slice(int(MAX_LOGS / 2))
+
+ update_view()
+
+func search(filter_string:String):
+ current_filter = filter_string
+ search_box.text = current_filter
+ update_view()
+
+func clear_all():
+ logs.clear()
+ update_view()
+
+func add_tag(filter_string:String):
+ if filter_string.trim_prefix(" ").trim_suffix(" ").is_empty():
+ return
+ var tag = tag_prefab.instantiate()
+ tag.tag_btn.text = filter_string
+ tag.tag_text = filter_string
+ tag.tag_btn.pressed.connect(
+ func():
+ search(filter_string)
+ )
+ tag.rm_btn.pressed.connect(
+ func():
+ if tags_container.get_child_count() == 1:
+ tags_container2.hide()
+ tag.queue_free()
+ )
+ #special treatment
+ if filter_string == "[warning]":
+ tag.self_modulate = Color("#f5c518")
+ tag.tag_btn.self_modulate = Color("#0a1014")
+ tag.rm_btn.self_modulate = Color("#0a1014")
+ elif filter_string == "[error]":
+ tag.self_modulate = Color("#d91f11")
+
+ tags_container.add_child(tag)
+ tags_container2.show()
+
+func update_view():
+ #TODO: optimization
+ var result:PackedStringArray = PackedStringArray()
+
+ for log in logs:
+ if !current_filter.is_empty() and !log["message"].contains(current_filter):
+ continue
+ var s = ""
+ if log["level"] == 1:
+ s = log["message"]
+ elif log["level"] == 2:
+ s = "[color=#e1ed96]%s[/color]" % log["message"]
+ elif log["level"] == 3:
+ s = "[color=#dd7085]%s[/color]" % log["message"]
+ if log["count"] > 1:
+ s = "[b](%d)[/b] %s" % [log["count"], s]
+ # add timestamp prefix
+ if _module.show_timestamp:
+ var time_str := Time.get_time_string_from_unix_time(log["timestamp"] + Time.get_time_zone_from_system()['bias'] * 60)
+ s = "[color=#a0a0a0][%s][/color] %s" % [time_str, s]
+ result.append(s)
+
+ var content:String = "\n".join(result)
+ #sync content
+ rlabel.text = content
+ content_updated.emit(content)
+
+func load_data(data:Array):
+ for item in data:
+ var text:String = item
+ add_tag(text)
+
+func get_data() -> Array:
+ var tags := PackedStringArray()
+ for tag in tags_container.get_children():
+ tags.push_back(tag.tag_text)
+ return tags
+
+func _ready():
+
+ #ui callbacks
+ search_btn.pressed.connect(
+ func():
+ search(search_box.text)
+ )
+ search_box.text_submitted.connect(
+ func(text:String):
+ search(search_box.text)
+ )
+ pin_btn.pressed.connect(
+ func():
+ add_tag(search_box.text)
+ search_box.clear()
+ )
+ cls_btn.pressed.connect(clear_all)
+
+ clear_all()
diff --git a/godot/addons/panku_console/modules/native_logger/logger_view.gd.uid b/godot/addons/panku_console/modules/native_logger/logger_view.gd.uid
new file mode 100644
index 0000000..587aeec
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/logger_view.gd.uid
@@ -0,0 +1 @@
+uid://duhsvwne7e1il
diff --git a/godot/addons/panku_console/modules/native_logger/logger_view.tscn b/godot/addons/panku_console/modules/native_logger/logger_view.tscn
new file mode 100644
index 0000000..5bb6e3c
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/logger_view.tscn
@@ -0,0 +1,136 @@
+[gd_scene load_steps=5 format=3 uid="uid://b1c075ic6oru7"]
+
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="1_gcj58"]
+[ext_resource type="PackedScene" uid="uid://dpurdc5me82ds" path="res://addons/panku_console/modules/native_logger/log_view_tag.tscn" id="2_06ga0"]
+[ext_resource type="Script" path="res://addons/panku_console/modules/native_logger/logger_view.gd" id="2_8mrs2"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ggi4i"]
+bg_color = Color(0.6, 0.6, 0.6, 0)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.639216, 0.639216, 0.639216, 0.501961)
+
+[node name="logger_view" type="Control" node_paths=PackedStringArray("search_box", "search_btn", "pin_btn", "cls_btn", "tags_container2", "tags_container", "rlabel")]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -882.0
+offset_bottom = -417.0
+grow_horizontal = 2
+grow_vertical = 2
+theme = ExtResource("1_gcj58")
+script = ExtResource("2_8mrs2")
+tag_prefab = ExtResource("2_06ga0")
+search_box = NodePath("VBoxContainer/HBoxContainer/LineEdit")
+search_btn = NodePath("VBoxContainer/HBoxContainer/Button")
+pin_btn = NodePath("VBoxContainer/HBoxContainer/Button2")
+cls_btn = NodePath("VBoxContainer/HBoxContainer/Button3")
+tags_container2 = NodePath("VBoxContainer/ScrollContainer")
+tags_container = NodePath("VBoxContainer/ScrollContainer/HBoxContainer/HBoxContainer2")
+rlabel = NodePath("VBoxContainer/PanelContainer/MarginContainer/RichTextLabel")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="Control" type="Control" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 2
+
+[node name="LineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "search..."
+clear_button_enabled = true
+
+[node name="Button" type="Button" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "OK"
+
+[node name="Button2" type="Button" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Pin"
+
+[node name="Button3" type="Button" parent="VBoxContainer/HBoxContainer"]
+layout_mode = 2
+text = "Clear All"
+
+[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
+visible = false
+custom_minimum_size = Vector2(0, 24)
+layout_mode = 2
+vertical_scroll_mode = 0
+
+[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/ScrollContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="Label" type="Label" parent="VBoxContainer/ScrollContainer/HBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 1
+text = " Tags: "
+vertical_alignment = 1
+
+[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/ScrollContainer/HBoxContainer"]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_ggi4i")
+
+[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/PanelContainer"]
+layout_mode = 2
+theme_override_constants/margin_left = 8
+theme_override_constants/margin_top = 8
+theme_override_constants/margin_right = 8
+theme_override_constants/margin_bottom = 8
+
+[node name="RichTextLabel" type="RichTextLabel" parent="VBoxContainer/PanelContainer/MarginContainer"]
+layout_mode = 2
+bbcode_enabled = true
+text = "Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here
+Hello
+Some text here"
+scroll_following = true
diff --git a/godot/addons/panku_console/modules/native_logger/module.gd b/godot/addons/panku_console/modules/native_logger/module.gd
new file mode 100644
index 0000000..eaeb555
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/module.gd
@@ -0,0 +1,99 @@
+class_name PankuModuleNativeLogger extends PankuModule
+
+var output_overlay:RichTextLabel
+var native_logs_monitor:Node
+var window:PankuLynxWindow
+var logger_ui:Node
+var output_overlay_display_mode:ScreenOverlayDisplayMode
+var show_timestamp:bool
+
+enum ScreenOverlayDisplayMode {
+ AlwaysShow,
+ ShowIfShellVisible,
+ NeverShow
+}
+
+func init_module():
+ # add godot log monitor
+ native_logs_monitor = preload("./godot_log_monitor.gd").new()
+ core.add_child(native_logs_monitor)
+
+ # add output overlay
+ output_overlay = preload("./log_overlay.tscn").instantiate()
+ output_overlay.clear()
+ core.add_child(output_overlay)
+
+ # add logger window
+ logger_ui = preload("./logger_view.tscn").instantiate()
+ logger_ui._module = self
+ logger_ui.console = core
+
+ window = core.windows_manager.create_window(logger_ui)
+ add_auto_save_hook(window)
+ window.queue_free_on_close = false
+ window.set_window_title_text("Native Logger")
+
+ native_logs_monitor.error_msg_received.connect(
+ func(msg:String):
+ logger_ui.add_log(msg, 3)
+ )
+ native_logs_monitor.warning_msg_received.connect(
+ func(msg:String):
+ logger_ui.add_log(msg, 2)
+ )
+ native_logs_monitor.info_msg_received.connect(
+ func(msg:String):
+ logger_ui.add_log(msg, 1)
+ )
+ logger_ui.content_updated.connect(
+ func(bbcode:String):
+ output_overlay.text = bbcode
+ )
+
+ core.interactive_shell_visibility_changed.connect(
+ func(v:bool):
+ if output_overlay_display_mode == ScreenOverlayDisplayMode.ShowIfShellVisible:
+ output_overlay.visible = v
+ )
+
+ # load data
+ load_window_data(window)
+ get_module_opt().screen_overlay = load_module_data("screen_overlay", ScreenOverlayDisplayMode.AlwaysShow)
+ get_module_opt().screen_overlay_alpha = load_module_data("screen_overlay_alpha", 0.3)
+ get_module_opt().screen_overlay_color = load_module_data("screen_overlay_color", Color(1, 1, 1, 0.878))
+ get_module_opt().screen_overlay_font_shadow = load_module_data("screen_overlay_font_shadow", false)
+ get_module_opt().screen_overlay_override_font_size = load_module_data("screen_overlay_override_font_size", 0)
+ get_module_opt().show_timestamp = load_module_data("show_timestamp", true)
+ logger_ui.load_data(load_module_data("logger_tags", ["[error]", "[warning]"]))
+
+func quit_module():
+ super.quit_module()
+ # properties defined in opt.gd will be automatically saved as soon as the value is changed
+ # we only need to manually save properties outside opt.gd
+ save_window_data(window)
+ save_module_data("logger_tags", logger_ui.get_data())
+
+func open_window():
+ window.show_window()
+
+func toggle_overlay():
+ var next = {
+ ScreenOverlayDisplayMode.AlwaysShow: ScreenOverlayDisplayMode.NeverShow,
+ ScreenOverlayDisplayMode.ShowIfShellVisible: ScreenOverlayDisplayMode.NeverShow,
+ ScreenOverlayDisplayMode.NeverShow: ScreenOverlayDisplayMode.AlwaysShow
+ }
+ output_overlay_display_mode = next[output_overlay_display_mode]
+ set_overlay_display_mode(output_overlay_display_mode)
+
+func set_overlay_display_mode(mode:ScreenOverlayDisplayMode):
+ output_overlay_display_mode = mode
+ if output_overlay_display_mode == ScreenOverlayDisplayMode.AlwaysShow:
+ output_overlay.visible = true
+ elif output_overlay_display_mode == ScreenOverlayDisplayMode.ShowIfShellVisible:
+ output_overlay.visible = core.get_shell_visibility()
+ elif output_overlay_display_mode == ScreenOverlayDisplayMode.NeverShow:
+ output_overlay.visible = false
+
+func set_show_timestamp(v:bool):
+ show_timestamp = v
+ logger_ui.update_view()
diff --git a/godot/addons/panku_console/modules/native_logger/module.gd.uid b/godot/addons/panku_console/modules/native_logger/module.gd.uid
new file mode 100644
index 0000000..1b1426f
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/module.gd.uid
@@ -0,0 +1 @@
+uid://bkaci1j5h5nwf
diff --git a/godot/addons/panku_console/modules/native_logger/opt.gd b/godot/addons/panku_console/modules/native_logger/opt.gd
new file mode 100644
index 0000000..4b0a339
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/opt.gd
@@ -0,0 +1,67 @@
+extends ModuleOptions
+
+@export_group("native_logger")
+
+@export var export_button_open_window := "Open Logger Window"
+func open_window():
+ _module.open_window()
+
+@export var export_comment_1 = "The logger is built upon the native engine file logging utility."
+
+@export var export_button_open_engine_log_folder:String = "Open Engine Logs Folder"
+
+@export_enum("Always Show", "Show If Shell Visible", "Never Show") var screen_overlay:int:
+ set(v):
+ _module.set_overlay_display_mode(v)
+ get:
+ return _module.output_overlay_display_mode
+
+@export var show_timestamp:bool = true:
+ set(v):
+ _module.set_show_timestamp(v)
+ get:
+ return _module.show_timestamp
+
+@export_range(0.0, 1.0, 0.01) var screen_overlay_alpha:float = 0.5:
+ set(v):
+ _module.output_overlay.modulate.a = v
+ get:
+ return _module.output_overlay.modulate.a
+
+@export var screen_overlay_color:Color:
+ set(v):
+ _module.output_overlay["theme_override_colors/default_color"] = v
+ get:
+ return _module.output_overlay["theme_override_colors/default_color"]
+
+@export var screen_overlay_override_font_size:int = 0:
+ set(v):
+ var overlay:RichTextLabel = _module.output_overlay
+ if (v <= 0):
+ overlay.remove_theme_font_size_override("normal_font_size")
+ overlay.remove_theme_font_size_override("bold_font_size")
+ overlay.remove_theme_font_size_override("italics_font_size")
+ overlay.remove_theme_font_size_override("bold_italics_font_size")
+ overlay.remove_theme_font_size_override("mono_font_size")
+ else:
+ overlay.add_theme_font_size_override("normal_font_size", v)
+ overlay.add_theme_font_size_override("bold_font_size", v)
+ overlay.add_theme_font_size_override("italics_font_size", v)
+ overlay.add_theme_font_size_override("bold_italics_font_size", v)
+ overlay.add_theme_font_size_override("mono_font_size", v)
+ get:
+ #return _module.output_overlay.theme.default_font_size
+ var overlay:RichTextLabel = _module.output_overlay
+ if overlay.has_theme_font_size_override("normal_font_size"):
+ return overlay.get("theme_override_font_sizes/normal_font_size")
+ return 0
+
+@export var screen_overlay_font_shadow:bool = false:
+ set(v):
+ var val = Color.BLACK if v else null
+ _module.output_overlay.set("theme_override_colors/font_shadow_color", val)
+ get:
+ return _module.output_overlay.get("theme_override_colors/font_shadow_color") != null
+
+func open_engine_log_folder():
+ OS.shell_open(ProjectSettings.globalize_path(ProjectSettings.get_setting("debug/file_logging/log_path").get_base_dir()))
diff --git a/godot/addons/panku_console/modules/native_logger/opt.gd.uid b/godot/addons/panku_console/modules/native_logger/opt.gd.uid
new file mode 100644
index 0000000..e680c9e
--- /dev/null
+++ b/godot/addons/panku_console/modules/native_logger/opt.gd.uid
@@ -0,0 +1 @@
+uid://ue3kbguiqdvt
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/crt_effect_layer.tscn b/godot/addons/panku_console/modules/screen_crt_effect/crt_effect_layer.tscn
new file mode 100644
index 0000000..201c93c
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/crt_effect_layer.tscn
@@ -0,0 +1,18 @@
+[gd_scene load_steps=3 format=3 uid="uid://c0hv8f6lk2d2d"]
+
+[ext_resource type="Shader" path="res://addons/panku_console/res/shader/mattias_crt.gdshader" id="1_ulpy0"]
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_p7j4u"]
+shader = ExtResource("1_ulpy0")
+
+[node name="CrtEffectLayer" type="CanvasLayer"]
+layer = 64
+
+[node name="ColorRect" type="ColorRect" parent="."]
+material = SubResource("ShaderMaterial_p7j4u")
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/env.gd b/godot/addons/panku_console/modules/screen_crt_effect/env.gd
new file mode 100644
index 0000000..32387fa
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/env.gd
@@ -0,0 +1,5 @@
+var _module:PankuModule
+
+const _HELP_toggle = "The good old days"
+func toggle() -> void:
+ _module.toggle_crt_effect()
\ No newline at end of file
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/env.gd.uid b/godot/addons/panku_console/modules/screen_crt_effect/env.gd.uid
new file mode 100644
index 0000000..511edba
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/env.gd.uid
@@ -0,0 +1 @@
+uid://bvru8qq3ixkex
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/module.gd b/godot/addons/panku_console/modules/screen_crt_effect/module.gd
new file mode 100644
index 0000000..edd5fe9
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/module.gd
@@ -0,0 +1,11 @@
+class_name PankuModuleScreenCrtEffect extends PankuModule
+
+var crt_effect_enabled := false
+var crt_effect_layer:CanvasLayer = null
+
+func toggle_crt_effect():
+ crt_effect_enabled = !crt_effect_enabled
+ if crt_effect_layer == null:
+ crt_effect_layer = preload("./crt_effect_layer.tscn").instantiate()
+ core.add_child(crt_effect_layer)
+ crt_effect_layer.visible = crt_effect_enabled
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/module.gd.uid b/godot/addons/panku_console/modules/screen_crt_effect/module.gd.uid
new file mode 100644
index 0000000..c38f4f1
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/module.gd.uid
@@ -0,0 +1 @@
+uid://c3brv46wbx5e3
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/opt.gd b/godot/addons/panku_console/modules/screen_crt_effect/opt.gd
new file mode 100644
index 0000000..1f6c8ef
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/opt.gd
@@ -0,0 +1,16 @@
+extends ModuleOptions
+
+@export_group("screen_crt_effect")
+
+@export var export_button_toggle_crt_effect:String = "Toggle Effect"
+
+func toggle_crt_effect():
+ _module.toggle_crt_effect()
+
+func set_unified_window_visibility(enabled:bool):
+ _module.unified_window_visibility = enabled
+ _module.update_gui_state()
+
+func set_pause_if_popup(enabled:bool):
+ _module.pause_if_input = enabled
+ _module.update_gui_state()
diff --git a/godot/addons/panku_console/modules/screen_crt_effect/opt.gd.uid b/godot/addons/panku_console/modules/screen_crt_effect/opt.gd.uid
new file mode 100644
index 0000000..261cb4e
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_crt_effect/opt.gd.uid
@@ -0,0 +1 @@
+uid://rp7jyp4t82se
diff --git a/godot/addons/panku_console/modules/screen_notifier/env.gd b/godot/addons/panku_console/modules/screen_notifier/env.gd
new file mode 100644
index 0000000..dada0ab
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/env.gd
@@ -0,0 +1,5 @@
+var _module:PankuModule
+
+const _HELP_notify = "Generate a notification"
+func notify(any):
+ _module.notify(str(any))
diff --git a/godot/addons/panku_console/modules/screen_notifier/env.gd.uid b/godot/addons/panku_console/modules/screen_notifier/env.gd.uid
new file mode 100644
index 0000000..f5c6be8
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/env.gd.uid
@@ -0,0 +1 @@
+uid://cs1wfvhlljx4k
diff --git a/godot/addons/panku_console/modules/screen_notifier/log_item.gd b/godot/addons/panku_console/modules/screen_notifier/log_item.gd
new file mode 100644
index 0000000..322bef1
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/log_item.gd
@@ -0,0 +1,54 @@
+extends HBoxContainer
+
+var life = 2.0
+
+var amount := 1:
+ set(v):
+ amount = v
+ play_amount_pop_animation()
+
+@export var content_label:RichTextLabel
+@export var amount_label:Label
+@export var amount_panel:PanelContainer
+
+@export var progress_a:Panel
+@export var progress_b:Control
+
+var amount_pop_tween:Tween
+var life_tween:Tween
+
+func play_amount_pop_animation():
+ if amount_pop_tween: amount_pop_tween.kill()
+ amount_pop_tween = create_tween()
+ amount_pop_tween.tween_property(amount_panel, "scale", Vector2(1, 1), 0.01)
+ amount_pop_tween.tween_property(amount_panel, "scale", Vector2(1.2, 1.2), 0.05)
+ amount_pop_tween.tween_property(amount_panel, "scale", Vector2(1, 1), 0.05)
+
+func fade_out():
+ var tween = create_tween()
+ tween.tween_property(self, "modulate:a", 0.0, 0.2)
+ tween.tween_callback(queue_free).set_delay(0.2)
+
+func set_progress(v:float):
+ progress_a.size_flags_stretch_ratio = v
+ progress_b.size_flags_stretch_ratio = 1.0 - v
+
+func play_lifespan_animation():
+ # interrupt and clear current tween animation
+ if life_tween: life_tween.kill()
+ life_tween = create_tween()
+ # create new tween animations
+ life_tween.tween_property(self, "modulate:a", 1.0, 0.2)
+ life_tween.set_parallel(true)
+ life_tween.tween_method(set_progress, 1.0, 0.0, life)
+ life_tween.set_parallel(false)
+ life_tween.tween_property(self, "modulate:a", 0.0, 0.2)
+ life_tween.tween_callback(queue_free).set_delay(0.2)
+
+
+func _ready():
+ content_label.meta_clicked.connect(
+ func(meta):
+ OS.shell_open(str(meta))
+ )
+ play_lifespan_animation()
diff --git a/godot/addons/panku_console/modules/screen_notifier/log_item.gd.uid b/godot/addons/panku_console/modules/screen_notifier/log_item.gd.uid
new file mode 100644
index 0000000..b7460be
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/log_item.gd.uid
@@ -0,0 +1 @@
+uid://b0t7xy10eu58i
diff --git a/godot/addons/panku_console/modules/screen_notifier/log_item.tscn b/godot/addons/panku_console/modules/screen_notifier/log_item.tscn
new file mode 100644
index 0000000..2fd60d8
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/log_item.tscn
@@ -0,0 +1,83 @@
+[gd_scene load_steps=4 format=3 uid="uid://c8rfpfel4mqtm"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/screen_notifier/log_item.gd" id="1_2q8bu"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1ons3"]
+bg_color = Color(0, 0, 0, 0.501961)
+border_color = Color(0.8, 1, 1, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_loh7i"]
+bg_color = Color(1, 1, 1, 0.12549)
+
+[node name="LogItem" type="HBoxContainer" node_paths=PackedStringArray("content_label", "amount_label", "amount_panel", "progress_a", "progress_b")]
+offset_right = 320.0
+offset_bottom = 26.0
+mouse_filter = 2
+theme_override_constants/separation = 2
+script = ExtResource("1_2q8bu")
+content_label = NodePath("Content/MarginContainer/RichTextLabel")
+amount_label = NodePath("Amount/MarginContainer/Label")
+amount_panel = NodePath("Amount")
+progress_a = NodePath("Content/HBoxContainer/A")
+progress_b = NodePath("Content/HBoxContainer/B")
+metadata/content_label = NodePath("Content/MarginContainer/RichTextLabel")
+metadata/amount_label = NodePath("Amount/MarginContainer/Label")
+metadata/amount_panel = NodePath("Amount")
+
+[node name="ColorRect" type="ColorRect" parent="."]
+custom_minimum_size = Vector2(4, 0)
+layout_mode = 2
+mouse_filter = 2
+color = Color(0, 0.752941, 0, 1)
+
+[node name="Content" type="PanelContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_1ons3")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Content"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/separation = 0
+
+[node name="A" type="Panel" parent="Content/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.4
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_loh7i")
+
+[node name="B" type="Control" parent="Content/HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.6
+mouse_filter = 2
+
+[node name="MarginContainer" type="MarginContainer" parent="Content"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 8
+
+[node name="RichTextLabel" type="RichTextLabel" parent="Content/MarginContainer"]
+layout_mode = 2
+mouse_filter = 2
+bbcode_enabled = true
+text = "yoooooooooo~"
+fit_content = true
+
+[node name="Amount" type="PanelContainer" parent="."]
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_1ons3")
+
+[node name="MarginContainer" type="MarginContainer" parent="Amount"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_constants/margin_left = 4
+theme_override_constants/margin_right = 4
+
+[node name="Label" type="Label" parent="Amount/MarginContainer"]
+layout_mode = 2
+text = "x1"
+metadata/amount = 1
diff --git a/godot/addons/panku_console/modules/screen_notifier/module.gd b/godot/addons/panku_console/modules/screen_notifier/module.gd
new file mode 100644
index 0000000..9a7d3a4
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/module.gd
@@ -0,0 +1,12 @@
+class_name PankuModuleScreenNotifier extends PankuModule
+
+var notifier_layer := preload("./resident_logs.tscn").instantiate()
+
+func notify(bbcode:String, id:=-1):
+ notifier_layer.add_log(bbcode, id)
+
+func init_module():
+ core.new_notification_created.connect(notify)
+
+ # setup ui
+ core.add_child(notifier_layer)
diff --git a/godot/addons/panku_console/modules/screen_notifier/module.gd.uid b/godot/addons/panku_console/modules/screen_notifier/module.gd.uid
new file mode 100644
index 0000000..f463e5f
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/module.gd.uid
@@ -0,0 +1 @@
+uid://c7up8ardl8443
diff --git a/godot/addons/panku_console/modules/screen_notifier/resident_logs.gd b/godot/addons/panku_console/modules/screen_notifier/resident_logs.gd
new file mode 100644
index 0000000..9b5b061
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/resident_logs.gd
@@ -0,0 +1,57 @@
+extends CanvasLayer
+
+const logitem2_proto := preload("./log_item.tscn")
+
+const MAX_LOGS = 10
+
+var prev_log = ""
+
+@export var named_container:VBoxContainer
+@export var unnamed_container:VBoxContainer
+
+# logs whose id >= 0
+var named_logs := {}
+
+
+func add_log(bbcode:String, id:=-1):
+ # logs whose id>=0 will be fixed to the bottom of the log list
+ # useful for loop print
+ # you can use `get_instance_id()` as logs's unique id
+ if id >= 0:
+ if !named_logs.has(id):
+ var new_node = logitem2_proto.instantiate()
+ new_node.amount_panel.hide()
+ new_node.life = 1.0
+ new_node.get_node("ColorRect").color = Color("#23aaf277")
+ named_container.add_child(new_node)
+ named_container.move_child(new_node, 0)
+ named_logs[id] = new_node
+ new_node.tree_exiting.connect(
+ func():
+ named_logs.erase(id)
+ )
+ var log_node = named_logs[id]
+ named_logs[id].content_label.text = bbcode
+ log_node.play_lifespan_animation()
+ return
+
+ #see the new log if can be combined with previous one
+ if prev_log == bbcode and unnamed_container.get_child_count() > 0:
+ var prev_node = unnamed_container.get_child(unnamed_container.get_child_count() - 1)
+ prev_node.amount += 1
+ prev_node.amount_label.text = "x" + str(prev_node.amount)
+ prev_node.amount_panel.show()
+ prev_node.play_lifespan_animation()
+ #create new log node
+ else:
+ if unnamed_container.get_child_count() >= MAX_LOGS:
+ unnamed_container.get_child(0).fade_out()
+ if get_tree():
+ var new_node = logitem2_proto.instantiate()
+ new_node.content_label.text = bbcode
+ new_node.amount_panel.hide()
+ new_node.get_node("ColorRect").color = Color(0.0, 0.75, 0.0, 0.5)
+ await get_tree().process_frame
+ unnamed_container.add_child(new_node)
+
+ prev_log = bbcode
diff --git a/godot/addons/panku_console/modules/screen_notifier/resident_logs.gd.uid b/godot/addons/panku_console/modules/screen_notifier/resident_logs.gd.uid
new file mode 100644
index 0000000..f97d599
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/resident_logs.gd.uid
@@ -0,0 +1 @@
+uid://bvgmltgih3fmw
diff --git a/godot/addons/panku_console/modules/screen_notifier/resident_logs.tscn b/godot/addons/panku_console/modules/screen_notifier/resident_logs.tscn
new file mode 100644
index 0000000..8d24856
--- /dev/null
+++ b/godot/addons/panku_console/modules/screen_notifier/resident_logs.tscn
@@ -0,0 +1,30 @@
+[gd_scene load_steps=3 format=3 uid="uid://ccr06ddwa73ca"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/screen_notifier/resident_logs.gd" id="1_uq2bh"]
+[ext_resource type="Theme" uid="uid://bk18yfu0d77wk" path="res://addons/panku_console/res/panku_console_theme.tres" id="2_npyp1"]
+
+[node name="ScreenNotifier" type="CanvasLayer" node_paths=PackedStringArray("named_container", "unnamed_container")]
+script = ExtResource("1_uq2bh")
+named_container = NodePath("ResidentLogs/Named")
+unnamed_container = NodePath("ResidentLogs/Unnamed")
+
+[node name="ResidentLogs" type="VBoxContainer" parent="."]
+anchors_preset = -1
+anchor_top = 0.25
+anchor_right = 0.5
+anchor_bottom = 0.75
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme = ExtResource("2_npyp1")
+alignment = 2
+
+[node name="Unnamed" type="VBoxContainer" parent="ResidentLogs"]
+layout_mode = 2
+mouse_filter = 2
+alignment = 2
+
+[node name="Named" type="VBoxContainer" parent="ResidentLogs"]
+layout_mode = 2
+mouse_filter = 2
+alignment = 2
diff --git a/godot/addons/panku_console/modules/snake/env.gd b/godot/addons/panku_console/modules/snake/env.gd
new file mode 100644
index 0000000..addf5b8
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/env.gd
@@ -0,0 +1,11 @@
+var _module:PankuModule
+
+const _HELP_execute = "Play Snake Game"
+func play() -> void:
+ _module.add_snake_window()
+
+func leader_board() -> String:
+ var content = ""
+ content += "== Learder Board ==\n"
+ content += str(_module.leader_board_arr)
+ return content
diff --git a/godot/addons/panku_console/modules/snake/env.gd.uid b/godot/addons/panku_console/modules/snake/env.gd.uid
new file mode 100644
index 0000000..7553b74
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/env.gd.uid
@@ -0,0 +1 @@
+uid://bf1jqeceg682p
diff --git a/godot/addons/panku_console/modules/snake/module.gd b/godot/addons/panku_console/modules/snake/module.gd
new file mode 100644
index 0000000..da74fe8
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/module.gd
@@ -0,0 +1,37 @@
+class_name PankuModuleSnakeGame extends PankuModule
+
+var leader_board_arr = []
+
+func init_module():
+ leader_board_arr = load_module_data("leader_board", [])
+
+func add_snake_window():
+ var snake_ui := preload("res://addons/panku_console/modules/snake/snake.tscn").instantiate()
+ var window:PankuLynxWindow = core.windows_manager.create_window(snake_ui)
+ window.queue_free_on_close = true
+ window.set_window_title_text("Snake Game")
+ window.position = window.get_layout_position(Control.PRESET_CENTER)
+ window.move_to_front()
+ window.size = Vector2(
+ snake_ui.snake_game.MAP_SIZE * snake_ui.CELL_SIZE, 0)
+ window.size.y = window.size.x + 24
+
+ core.get_tree().root.get_viewport().gui_release_focus()
+
+ snake_ui.snake_game.game_over.connect(
+ func():
+ var record = {
+ "timestamp": Time.get_datetime_string_from_system(),
+ "score": snake_ui.snake_game.get_snake_length()
+ }
+ leader_board_arr.append(record)
+ leader_board_arr.sort_custom(
+ func(a, b):
+ return a['score'] > b['score']
+ )
+
+ if leader_board_arr.size() > 10:
+ leader_board_arr.resize(10)
+
+ save_module_data("leader_board", leader_board_arr)
+ )
diff --git a/godot/addons/panku_console/modules/snake/module.gd.uid b/godot/addons/panku_console/modules/snake/module.gd.uid
new file mode 100644
index 0000000..7f05291
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/module.gd.uid
@@ -0,0 +1 @@
+uid://bclkgkqflmetk
diff --git a/godot/addons/panku_console/modules/snake/snake.gd b/godot/addons/panku_console/modules/snake/snake.gd
new file mode 100644
index 0000000..43b400d
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/snake.gd
@@ -0,0 +1,71 @@
+signal game_over
+
+const MAP_SIZE = 24
+
+const DIR_REV = {
+ Vector2.LEFT: Vector2.RIGHT,
+ Vector2.RIGHT: Vector2.LEFT,
+ Vector2.UP: Vector2.DOWN,
+ Vector2.DOWN: Vector2.UP,
+}
+
+var snake_dict:Dictionary
+var snake_arr:Array
+var apple:Vector2
+var move_dir:Vector2
+
+func _init() -> void:
+ init()
+
+func init() -> void:
+
+ # init snake. 0:head, -1:tail
+ snake_dict = {Vector2(9, 8):0, Vector2(8, 8):0}
+ snake_arr = snake_dict.keys()
+
+ # init apple
+ apple = spawn_apple()
+
+ move_dir = Vector2.RIGHT
+
+func get_snake_length() -> int:
+ return snake_arr.size()
+
+func spawn_apple() -> Vector2:
+ var x_range = range(MAP_SIZE)
+ x_range.shuffle()
+ var y_range = range(MAP_SIZE)
+ y_range.shuffle()
+ for y in y_range:
+ for x in x_range:
+ if !snake_dict.has(Vector2(x, y)):
+ return Vector2(x, y)
+ return Vector2(-1, -1)
+
+func tick(input_dir:Vector2):
+
+ # can't turn back
+ if DIR_REV[input_dir] != move_dir:
+ move_dir = input_dir
+
+ # create new head
+ var new_head:Vector2 = snake_arr[0] + move_dir
+ new_head.x = wrapi(new_head.x, 0, MAP_SIZE)
+ new_head.y = wrapi(new_head.y, 0, MAP_SIZE)
+
+ # check if collide with self
+ if snake_dict.has(new_head):
+ # game over, restart
+ game_over.emit()
+ init()
+ return
+
+ # add new head
+ snake_arr.push_front(new_head)
+ snake_dict[new_head] = 0
+
+ # remove tail if no apple
+ if new_head != apple:
+ snake_dict.erase(snake_arr.pop_back())
+ else:
+ apple = spawn_apple()
diff --git a/godot/addons/panku_console/modules/snake/snake.gd.uid b/godot/addons/panku_console/modules/snake/snake.gd.uid
new file mode 100644
index 0000000..24a0fd0
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/snake.gd.uid
@@ -0,0 +1 @@
+uid://bcksf7pgn01vt
diff --git a/godot/addons/panku_console/modules/snake/snake.tscn b/godot/addons/panku_console/modules/snake/snake.tscn
new file mode 100644
index 0000000..66f7b56
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/snake.tscn
@@ -0,0 +1,20 @@
+[gd_scene load_steps=2 format=3 uid="uid://du6m05vxpcubk"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/snake/snake_ui.gd" id="1_0yq81"]
+
+[node name="SnakeUI" type="Control" node_paths=PackedStringArray("tex_rect")]
+clip_contents = true
+layout_mode = 3
+anchors_preset = 0
+offset_right = 40.0
+offset_bottom = 40.0
+script = ExtResource("1_0yq81")
+tex_rect = NodePath("Snake")
+
+[node name="Snake" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
diff --git a/godot/addons/panku_console/modules/snake/snake_gradient.tres b/godot/addons/panku_console/modules/snake/snake_gradient.tres
new file mode 100644
index 0000000..b1ccdb3
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/snake_gradient.tres
@@ -0,0 +1,4 @@
+[gd_resource type="Gradient" format=3 uid="uid://chic0ft8frrr8"]
+
+[resource]
+colors = PackedColorArray(0.156863, 0.788235, 0, 1, 0, 0.658824, 0, 0.27451)
diff --git a/godot/addons/panku_console/modules/snake/snake_ui.gd b/godot/addons/panku_console/modules/snake/snake_ui.gd
new file mode 100644
index 0000000..f725ce4
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/snake_ui.gd
@@ -0,0 +1,66 @@
+extends Control
+
+const CELL_SIZE = 12
+const COLOR_EMPTY = Color(0.0, 0.0, 0.0, 0.0)
+const COLOR_APPLE = Color8(255, 112, 133)
+const COLOR_SNAKE = preload("./snake_gradient.tres")
+
+@export var tex_rect:TextureRect
+
+const KEY_SCHEME = {
+ KEY_UP: Vector2.UP,
+ KEY_DOWN: Vector2.DOWN,
+ KEY_LEFT: Vector2.LEFT,
+ KEY_RIGHT: Vector2.RIGHT,
+}
+
+var snake_game := preload("./snake.gd").new()
+var cached_input := Vector2.RIGHT
+var delay := 0.5
+var img_buffer:Image
+
+func _ready():
+ img_buffer = Image.create(
+ CELL_SIZE * snake_game.MAP_SIZE,
+ CELL_SIZE * snake_game.MAP_SIZE,
+ false, Image.FORMAT_RGBA8)
+ draw()
+
+func draw():
+ # draw background
+ img_buffer.fill(COLOR_EMPTY)
+
+ # draw snake
+ var gradient = GradientTexture1D.new()
+ for i in range(snake_game.snake_arr.size()):
+ var s = snake_game.snake_arr[i]
+ img_buffer.fill_rect(
+ Rect2i(s * CELL_SIZE, Vector2.ONE * CELL_SIZE),
+ COLOR_SNAKE.sample(1.0 * i / snake_game.snake_arr.size())
+ )
+
+ # draw apple
+ img_buffer.fill_rect(
+ Rect2i(snake_game.apple * CELL_SIZE, Vector2.ONE * CELL_SIZE),
+ COLOR_APPLE
+ )
+
+ # update texture
+ tex_rect.texture = ImageTexture.create_from_image(img_buffer)
+
+func _input(event:InputEvent):
+ if event is InputEventKey:
+ var key_event = event as InputEventKey
+ if key_event.pressed:
+ if KEY_SCHEME.has(key_event.keycode):
+ cached_input = KEY_SCHEME[key_event.keycode]
+ # if key is pressed, update immediately
+ delay = 0.0
+
+func _physics_process(delta):
+ delay -= delta
+ if delay <= 0.0:
+ snake_game.tick(cached_input)
+ draw()
+ # speed up as snake grows
+ delay = 2.0 / min(20, snake_game.get_snake_length() / 3.0 + 3)
diff --git a/godot/addons/panku_console/modules/snake/snake_ui.gd.uid b/godot/addons/panku_console/modules/snake/snake_ui.gd.uid
new file mode 100644
index 0000000..ca07530
--- /dev/null
+++ b/godot/addons/panku_console/modules/snake/snake_ui.gd.uid
@@ -0,0 +1 @@
+uid://bgc2g8djyaiu2
diff --git a/godot/addons/panku_console/modules/system_report/env.gd b/godot/addons/panku_console/modules/system_report/env.gd
new file mode 100644
index 0000000..2e86345
--- /dev/null
+++ b/godot/addons/panku_console/modules/system_report/env.gd
@@ -0,0 +1,9 @@
+var _os_report = preload("./os_report.gd").new()
+var _module:PankuModule
+
+const _HELP_execute = "Show detailed OS report"
+func execute() -> String:
+ _module.core.notify("Please wait, this may take a while...")
+ _os_report.inspect()
+ var report = "".join(_os_report.rtl)
+ return report
diff --git a/godot/addons/panku_console/modules/system_report/env.gd.uid b/godot/addons/panku_console/modules/system_report/env.gd.uid
new file mode 100644
index 0000000..5ff078b
--- /dev/null
+++ b/godot/addons/panku_console/modules/system_report/env.gd.uid
@@ -0,0 +1 @@
+uid://bnbehkvc4pl6t
diff --git a/godot/addons/panku_console/modules/system_report/module.gd b/godot/addons/panku_console/modules/system_report/module.gd
new file mode 100644
index 0000000..785d693
--- /dev/null
+++ b/godot/addons/panku_console/modules/system_report/module.gd
@@ -0,0 +1 @@
+class_name PankuModuleSystemReport extends PankuModule
diff --git a/godot/addons/panku_console/modules/system_report/module.gd.uid b/godot/addons/panku_console/modules/system_report/module.gd.uid
new file mode 100644
index 0000000..0a57e7f
--- /dev/null
+++ b/godot/addons/panku_console/modules/system_report/module.gd.uid
@@ -0,0 +1 @@
+uid://b2drqk3c8lmqe
diff --git a/godot/addons/panku_console/modules/system_report/os_report.gd b/godot/addons/panku_console/modules/system_report/os_report.gd
new file mode 100644
index 0000000..80fa3cc
--- /dev/null
+++ b/godot/addons/panku_console/modules/system_report/os_report.gd
@@ -0,0 +1,168 @@
+#reference: https://github.com/godotengine/godot-demo-projects/blob/4.0-dev/misc/os_test/os_test.gd
+
+var rtl := PackedStringArray()
+
+# Returns a human-readable string from a date and time, date, or time dictionary.
+func datetime_to_string(date):
+ if (
+ date.has("year")
+ and date.has("month")
+ and date.has("day")
+ and date.has("hour")
+ and date.has("minute")
+ and date.has("second")
+ ):
+ # Date and time.
+ return "{year}-{month}-{day} {hour}:{minute}:{second}".format({
+ year = str(date.year).pad_zeros(2),
+ month = str(date.month).pad_zeros(2),
+ day = str(date.day).pad_zeros(2),
+ hour = str(date.hour).pad_zeros(2),
+ minute = str(date.minute).pad_zeros(2),
+ second = str(date.second).pad_zeros(2),
+ })
+ elif date.has("year") and date.has("month") and date.has("day"):
+ # Date only.
+ return "{year}-{month}-{day}".format({
+ year = str(date.year).pad_zeros(2),
+ month = str(date.month).pad_zeros(2),
+ day = str(date.day).pad_zeros(2),
+ })
+ else:
+ # Time only.
+ return "{hour}:{minute}:{second}".format({
+ hour = str(date.hour).pad_zeros(2),
+ minute = str(date.minute).pad_zeros(2),
+ second = str(date.second).pad_zeros(2),
+ })
+
+
+func scan_midi_devices():
+ OS.open_midi_inputs()
+ var devices = ", ".join(OS.get_connected_midi_inputs())
+ OS.close_midi_inputs()
+ return devices
+
+
+func add_header(header):
+ rtl.append("\n[font_size=24][color=#6df]{header}[/color][/font_size]\n\n".format({
+ header = header,
+ }))
+
+
+func add_line(key, value):
+ rtl.append("[color=#adf]{key}:[/color] {value}\n".format({
+ key = key,
+ value = value if str(value) != "" else "[color=#fff8](empty)[/color]",
+ }))
+
+
+func inspect():
+ add_header("Audio")
+ add_line("Mix rate", "%d Hz" % AudioServer.get_mix_rate())
+ add_line("Output latency", "%f ms" % (AudioServer.get_output_latency() * 1000))
+ add_line("Output device list", ", ".join(AudioServer.get_output_device_list()))
+ add_line("Capture device list", ", ".join(AudioServer.get_input_device_list()))
+
+ add_header("Date")
+ add_line("Date and time (local)", Time.get_datetime_string_from_system(false, true))
+ add_line("Date and time (UTC)", Time.get_datetime_string_from_system(true, true))
+ add_line("Date (local)", Time.get_date_string_from_system(false))
+ add_line("Date (UTC)", Time.get_date_string_from_system(true))
+ add_line("Time (local)", Time.get_time_string_from_system(false))
+ add_line("Time (UTC)", Time.get_time_string_from_system(true))
+ add_line("Timezone", Time.get_time_zone_from_system())
+ add_line("UNIX time", Time.get_unix_time_from_system())
+
+ add_header("Display")
+ add_line("Screen count", DisplayServer.get_screen_count())
+ add_line("DPI", DisplayServer.screen_get_dpi())
+ add_line("Scale factor", DisplayServer.screen_get_scale())
+ add_line("Maximum scale factor", DisplayServer.screen_get_max_scale())
+ add_line("Startup screen position", DisplayServer.screen_get_position())
+ add_line("Startup screen size", DisplayServer.screen_get_size())
+ add_line("Startup screen refresh rate", ("%f Hz" % DisplayServer.screen_get_refresh_rate()) if DisplayServer.screen_get_refresh_rate() > 0.0 else "")
+ add_line("Usable (safe) area rectangle", DisplayServer.get_display_safe_area())
+ add_line("Screen orientation", [
+ "Landscape",
+ "Portrait",
+ "Landscape (reverse)",
+ "Portrait (reverse)",
+ "Landscape (defined by sensor)",
+ "Portrait (defined by sensor)",
+ "Defined by sensor",
+ ][DisplayServer.screen_get_orientation()])
+
+ add_header("Engine")
+ add_line("Version", Engine.get_version_info()["string"])
+ add_line("Command-line arguments", str(OS.get_cmdline_args()))
+ add_line("Is debug build", OS.is_debug_build())
+ add_line("Executable path", OS.get_executable_path())
+ add_line("User data directory", OS.get_user_data_dir())
+ add_line("Filesystem is persistent", OS.is_userfs_persistent())
+
+ add_header("Environment")
+ add_line("Value of `PATH`", OS.get_environment("PATH"))
+ add_line("Value of `path`", OS.get_environment("path"))
+
+ add_header("Hardware")
+ add_line("Model name", OS.get_model_name())
+ add_line("Processor name", OS.get_processor_name())
+ add_line("Processor count", OS.get_processor_count())
+ add_line("Device unique ID", OS.get_unique_id())
+
+ add_header("Input")
+ add_line("Device has touch screen", DisplayServer.is_touchscreen_available())
+ var has_virtual_keyboard = DisplayServer.has_feature(DisplayServer.FEATURE_VIRTUAL_KEYBOARD)
+ add_line("Device has virtual keyboard", has_virtual_keyboard)
+ if has_virtual_keyboard:
+ add_line("Virtual keyboard height", DisplayServer.virtual_keyboard_get_height())
+
+ add_header("Localization")
+ add_line("Locale", OS.get_locale())
+
+ add_header("Mobile")
+ add_line("Granted permissions", OS.get_granted_permissions())
+
+# add_header(".NET (C#)")
+# var csharp_enabled = ResourceLoader.exists("res://CSharpTest.cs")
+# add_line("Mono module enabled", "Yes" if csharp_enabled else "No")
+# if csharp_enabled:
+# csharp_test.set_script(load("res://CSharpTest.cs"))
+# add_line("Operating System", csharp_test.OperatingSystem())
+# add_line("Platform Type", csharp_test.PlatformType())
+
+ add_header("Software")
+ add_line("OS name", OS.get_name())
+ add_line("Process ID", OS.get_process_id())
+ add_line("System dark mode supported", DisplayServer.is_dark_mode_supported())
+ add_line("System dark mode enabled", DisplayServer.is_dark_mode())
+ add_line("System accent color", "#%s" % DisplayServer.get_accent_color().to_html())
+
+ add_header("System directories")
+ add_line("Desktop", OS.get_system_dir(OS.SYSTEM_DIR_DESKTOP))
+ add_line("DCIM", OS.get_system_dir(OS.SYSTEM_DIR_DCIM))
+ add_line("Documents", OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS))
+ add_line("Downloads", OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS))
+ add_line("Movies", OS.get_system_dir(OS.SYSTEM_DIR_MOVIES))
+ add_line("Music", OS.get_system_dir(OS.SYSTEM_DIR_MUSIC))
+ add_line("Pictures", OS.get_system_dir(OS.SYSTEM_DIR_PICTURES))
+ add_line("Ringtones", OS.get_system_dir(OS.SYSTEM_DIR_RINGTONES))
+
+ add_header("Video")
+ add_line("Adapter name", RenderingServer.get_video_adapter_name())
+ add_line("Adapter vendor", RenderingServer.get_video_adapter_vendor())
+ add_line("Adapter type", [
+ "Other (Unknown)",
+ "Integrated",
+ "Discrete",
+ "Virtual",
+ "CPU",
+ ][RenderingServer.get_video_adapter_type()])
+ add_line("Adapter graphics API version", RenderingServer.get_video_adapter_api_version())
+
+ var video_adapter_driver_info = OS.get_video_adapter_driver_info()
+ if video_adapter_driver_info.size() > 0:
+ add_line("Adapter driver name", video_adapter_driver_info[0])
+ if video_adapter_driver_info.size() > 1:
+ add_line("Adapter driver version", video_adapter_driver_info[1])
diff --git a/godot/addons/panku_console/modules/system_report/os_report.gd.uid b/godot/addons/panku_console/modules/system_report/os_report.gd.uid
new file mode 100644
index 0000000..18779f3
--- /dev/null
+++ b/godot/addons/panku_console/modules/system_report/os_report.gd.uid
@@ -0,0 +1 @@
+uid://cf6l16wrpj31l
diff --git a/godot/addons/panku_console/modules/texture_viewer/module.gd b/godot/addons/panku_console/modules/texture_viewer/module.gd
new file mode 100644
index 0000000..5088464
--- /dev/null
+++ b/godot/addons/panku_console/modules/texture_viewer/module.gd
@@ -0,0 +1,21 @@
+class_name PankuModuleTextureViewer extends PankuModule
+
+const texture_viewer_prefab = preload("./texture_viewer.tscn")
+
+func init_module():
+ core.new_expression_entered.connect(
+ func(exp:String, result):
+ if !result["failed"] and result["result"] is Texture2D:
+ add_texture_viewer_window(exp)
+ )
+
+func add_texture_viewer_window(expr:String):
+ #print("add_texture_viewer_window(%s)"%expr)
+ var texture_viewer := texture_viewer_prefab.instantiate()
+ texture_viewer.expr = expr
+ texture_viewer._module = self
+ var window:PankuLynxWindow = core.windows_manager.create_window(texture_viewer)
+ window.queue_free_on_close = true
+ window.set_window_title_text("Texture: " + expr)
+ window.position = window.get_layout_position(Control.PRESET_BOTTOM_LEFT)
+ window.move_to_front()
diff --git a/godot/addons/panku_console/modules/texture_viewer/module.gd.uid b/godot/addons/panku_console/modules/texture_viewer/module.gd.uid
new file mode 100644
index 0000000..5506ce9
--- /dev/null
+++ b/godot/addons/panku_console/modules/texture_viewer/module.gd.uid
@@ -0,0 +1 @@
+uid://cxcih636vv2rn
diff --git a/godot/addons/panku_console/modules/texture_viewer/texture_viewer.gd b/godot/addons/panku_console/modules/texture_viewer/texture_viewer.gd
new file mode 100644
index 0000000..bb47bb6
--- /dev/null
+++ b/godot/addons/panku_console/modules/texture_viewer/texture_viewer.gd
@@ -0,0 +1,13 @@
+extends Control
+
+@onready var trect:TextureRect = $TextureRect
+
+var _module:PankuModule
+var expr:String
+
+func _physics_process(delta: float) -> void:
+ if Engine.get_physics_frames() % 10 != 1:
+ return
+ var result = _module.core.gd_exprenv.execute(expr)["result"]
+ if result is Texture2D:
+ trect.texture = ImageTexture.create_from_image(result.get_image())
diff --git a/godot/addons/panku_console/modules/texture_viewer/texture_viewer.gd.uid b/godot/addons/panku_console/modules/texture_viewer/texture_viewer.gd.uid
new file mode 100644
index 0000000..8d025ad
--- /dev/null
+++ b/godot/addons/panku_console/modules/texture_viewer/texture_viewer.gd.uid
@@ -0,0 +1 @@
+uid://bs33mg5qx50it
diff --git a/godot/addons/panku_console/modules/texture_viewer/texture_viewer.tscn b/godot/addons/panku_console/modules/texture_viewer/texture_viewer.tscn
new file mode 100644
index 0000000..df593f4
--- /dev/null
+++ b/godot/addons/panku_console/modules/texture_viewer/texture_viewer.tscn
@@ -0,0 +1,21 @@
+[gd_scene load_steps=2 format=3 uid="uid://diohs08jsami2"]
+
+[ext_resource type="Script" path="res://addons/panku_console/modules/texture_viewer/texture_viewer.gd" id="1_vljdg"]
+
+[node name="TextureViewer" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_vljdg")
+
+[node name="TextureRect" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+expand_mode = 1
diff --git a/godot/addons/panku_console/modules/variable_tracker/module.gd b/godot/addons/panku_console/modules/variable_tracker/module.gd
new file mode 100644
index 0000000..e7283b3
--- /dev/null
+++ b/godot/addons/panku_console/modules/variable_tracker/module.gd
@@ -0,0 +1,172 @@
+class_name PankuModuleVariableTracker extends PankuModule
+## Module to register and update some common environments.
+##
+## On module startup current scene root node registered as 'current' environment var,
+## for more convenient access from interactive shell. Module constantly monitoring node
+## tree afterwards and auto rebind 'current' environment in case of current scene changed.
+## Also all user autoload singletons registered with its root node names.
+
+const PROJECT_AUTOLOAD_PREFIX := "autoload/"
+const CURRENT_SCENE_ENV := "current"
+const SHELL_MODULE_NAME := "interactive_shell"
+const DEFAULT_TRACKING_DELAY := 0.5
+
+const CURRENT_REGISTERED_TIP := "[tip] Node '%s' registered as current scene, you can access it by [b]%s[/b]."
+const CURRENT_REMOVED_TIP := "[tip] No current scene found, [b]%s[/b] keyword is no longer available."
+const USER_AUTOLOADS_TIP := "[tip] Accessible user singleton modules: [b]%s[/b]"
+
+var _raw_exceptions_string: String = ""
+var _nodes_exception_regexp: RegEx
+
+var _reverse_root_nodes_order: bool
+var _current_scene_root:Node
+var _user_singleton_files := []
+var _tween_loop:Tween
+var _loop_call_back:CallbackTweener
+
+
+func init_module():
+ get_module_opt().tracking_delay = load_module_data("tracking_delay", DEFAULT_TRACKING_DELAY)
+ _reverse_root_nodes_order = load_module_data("use_last_as_current", true)
+ _raw_exceptions_string = load_module_data("root_node_exceptions", _raw_exceptions_string)
+
+ await core.get_tree().process_frame # not sure if it is necessary
+
+ update_exceptions_regexp()
+ _update_project_singleton_files()
+ _setup_scene_root_tracker()
+ _check_autoloads()
+
+
+# Build root node exceptions regular expression
+func update_exceptions_regexp() -> void:
+ if _raw_exceptions_string.is_empty():
+ _nodes_exception_regexp = RegEx.new() # not valid expression
+ return
+
+ _nodes_exception_regexp = RegEx.create_from_string(_raw_exceptions_string)
+
+ if not _nodes_exception_regexp.is_valid():
+ push_error("Can't parse '%s' expression for variable tracker" % _raw_exceptions_string)
+
+
+# Parse project setting and collect and autoload files.
+func _update_project_singleton_files() -> void:
+ _user_singleton_files.clear()
+ for property in ProjectSettings.get_property_list():
+ if property.name.begins_with(PROJECT_AUTOLOAD_PREFIX):
+ _user_singleton_files.append(ProjectSettings.get_setting(property.name).trim_prefix("*"))
+
+
+# Check if given node is autoload singleton.
+func _is_singleton(node: Node) -> bool:
+ # Comparing scene file and script file with list of autoload files
+ # from project settings. I'm not sure that approach hundred percent perfect,
+ # but it works so far.
+ if node.scene_file_path in _user_singleton_files:
+ return true
+
+ var script = node.get_script()
+ if script and (script.get_path() in _user_singleton_files):
+ return true
+
+ return false
+
+
+# Setup monitoring loop for current scene root node.
+func _setup_scene_root_tracker() -> void:
+ _check_current_scene()
+ # The whole idea looping something in the background
+ # while dev console is not even opened does not feel so right.
+ # Have no idea how to make it more elegant way,
+ # so lets make loop interval user controllable at least.
+ var tracking_delay = get_module_opt().tracking_delay
+
+ _tween_loop = core.create_tween()
+ _loop_call_back = _tween_loop.set_loops().tween_callback(_check_current_scene).set_delay(tracking_delay)
+
+
+## Set current scene root node monitoring interval.
+func change_tracking_delay(delay: float) -> void:
+ if _loop_call_back:
+ _loop_call_back.set_delay(delay)
+
+
+# Update current scene root node environment.
+func _check_current_scene() -> void:
+ var scene_root_found: Node = get_scene_root()
+
+ if scene_root_found:
+ if scene_root_found != _current_scene_root:
+ core.gd_exprenv.register_env(CURRENT_SCENE_ENV, scene_root_found)
+ _print_to_interactive_shell(CURRENT_REGISTERED_TIP % [scene_root_found.name, CURRENT_SCENE_ENV])
+
+ else:
+ if _current_scene_root:
+ core.gd_exprenv.remove_env(CURRENT_SCENE_ENV)
+ _print_to_interactive_shell(CURRENT_REMOVED_TIP % CURRENT_SCENE_ENV)
+
+ _current_scene_root = scene_root_found
+
+
+## Find the root node of current active scene.
+func get_scene_root() -> Node:
+ # Assuming current scene is the first node in tree that is not autoload singleton.
+ for node in _get_valid_root_nodes():
+ if not _is_singleton(node):
+ return node
+
+ return null
+
+
+# Get list of tree root nodes filtered and sorted according module settings
+func _get_valid_root_nodes() -> Array:
+ var nodes: Array = core.get_tree().root.get_children().filter(_root_nodes_filter)
+
+ if _reverse_root_nodes_order:
+ nodes.reverse()
+
+ return nodes
+
+
+# Filter function for tree root nodes
+func _root_nodes_filter(node: Node) -> bool:
+ # skip panku plugin itself
+ if node.name == core.SingletonName:
+ return false
+
+ # skip os window
+ if node.get_meta(PankuLynxWindow.OS_WINDOW_MARKER, false):
+ return false
+
+ # skip user defined exceptions
+ if _nodes_exception_regexp.is_valid() and _nodes_exception_regexp.search(node.name):
+ return false
+
+ return true
+
+
+# Find all autoload singletons and bind its to environment vars.
+func _check_autoloads() -> void:
+ var _user_singleton_names := []
+
+ for node in _get_valid_root_nodes():
+ if _is_singleton(node):
+ # register user singleton
+ _user_singleton_names.append(node.name)
+ core.gd_exprenv.register_env(node.name, node)
+
+ if not _user_singleton_names.is_empty():
+ _print_to_interactive_shell(USER_AUTOLOADS_TIP % ",".join(_user_singleton_names))
+
+
+# Print a tip to interactive shell module, modules load order does matter.
+func _print_to_interactive_shell(message: String) -> void:
+ if core.module_manager.has_module(SHELL_MODULE_NAME):
+ var ishell = core.module_manager.get_module(SHELL_MODULE_NAME)
+ ishell.interactive_shell.output(message)
+
+
+func quit_module():
+ _tween_loop.kill()
+ super.quit_module()
diff --git a/godot/addons/panku_console/modules/variable_tracker/module.gd.uid b/godot/addons/panku_console/modules/variable_tracker/module.gd.uid
new file mode 100644
index 0000000..32d5777
--- /dev/null
+++ b/godot/addons/panku_console/modules/variable_tracker/module.gd.uid
@@ -0,0 +1 @@
+uid://dkpfrtlppo525
diff --git a/godot/addons/panku_console/modules/variable_tracker/opt.gd b/godot/addons/panku_console/modules/variable_tracker/opt.gd
new file mode 100644
index 0000000..57114fa
--- /dev/null
+++ b/godot/addons/panku_console/modules/variable_tracker/opt.gd
@@ -0,0 +1,32 @@
+extends ModuleOptions
+
+@export_group("variable_tracker")
+
+@export var export_comment_use_last_as_current = (
+ "Use last non singleton node as current scene. "
+ + "First node will be used if this option disabled."
+)
+@export var use_last_as_current: bool:
+ get:
+ return _module._reverse_root_nodes_order
+ set(value):
+ use_last_as_current = value
+ _module._reverse_root_nodes_order = value
+
+@export var export_comment_root_node_exceptions = (
+ "Top level nodes which will be ignored by variable tracker. "
+ + "Regular expressions can be used e.g. '(SignalBus|Game*)'."
+)
+@export var root_node_exceptions: String:
+ get:
+ return _module._raw_exceptions_string
+ set(value):
+ root_node_exceptions = value
+ _module._raw_exceptions_string = value
+ _module.update_exceptions_regexp()
+
+@export var export_comment_tracking_delay = "Current scene checking interval."
+@export_range(0.1, 2.0, 0.1) var tracking_delay := 0.5:
+ set(v):
+ tracking_delay = v
+ _module.change_tracking_delay(tracking_delay)
diff --git a/godot/addons/panku_console/modules/variable_tracker/opt.gd.uid b/godot/addons/panku_console/modules/variable_tracker/opt.gd.uid
new file mode 100644
index 0000000..632ff37
--- /dev/null
+++ b/godot/addons/panku_console/modules/variable_tracker/opt.gd.uid
@@ -0,0 +1 @@
+uid://hluoefbsmxj4
diff --git a/godot/addons/panku_console/plugin.cfg b/godot/addons/panku_console/plugin.cfg
new file mode 100644
index 0000000..68b1516
--- /dev/null
+++ b/godot/addons/panku_console/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="PankuConsole"
+description="All-in-One Godot Engine runtime debugging tool."
+author="Feo (k2kra) Wu"
+version="1.7.9"
+script="plugin.gd"
diff --git a/godot/addons/panku_console/plugin.gd b/godot/addons/panku_console/plugin.gd
new file mode 100644
index 0000000..f37ee49
--- /dev/null
+++ b/godot/addons/panku_console/plugin.gd
@@ -0,0 +1,89 @@
+@tool
+class_name PankuConsolePlugin
+extends EditorPlugin
+
+const SINGLETON_NAME = "Panku"
+const SINGLETON_PATH = "res://addons/panku_console/console.tscn"
+const SINGLETON_OPTION = "autoload/" + SINGLETON_NAME
+
+var exporter: PankuExporter
+
+
+# Custom export plugin to automatically disable console in release builds
+class PankuExporter extends EditorExportPlugin:
+ const NAME = "PankuReleaseExporter"
+ var owner: EditorPlugin
+ var need_restore_singleton: bool
+
+
+ func _get_name() -> String:
+ # Have no clue where this name will be used
+ # It just should be implemented according the docs
+ return NAME
+
+
+ func _export_begin(_features: PackedStringArray, is_debug: bool, _path: String, _flags: int) -> void:
+ need_restore_singleton = false
+ var disable_activated: bool = ProjectSettings.get_setting(
+ PankuConfig.panku_option(PankuConfig.OPTIONS.DISABLE_ON_RELEASE)
+ )
+
+ if not is_debug and disable_activated:
+ need_restore_singleton = ProjectSettings.has_setting(SINGLETON_OPTION)
+ owner.safe_remove_singleton()
+
+
+ func _export_end() -> void:
+ if need_restore_singleton:
+ owner.safe_add_singleton()
+
+
+func _enable_exporter() -> void:
+ if not exporter:
+ # See https://github.com/godotengine/godot/issues/73525
+ exporter = (PankuExporter as Variant).new()
+ exporter.owner = self
+ add_export_plugin(exporter)
+
+
+func _disable_exporter() -> void:
+ if exporter:
+ remove_export_plugin(exporter)
+
+
+# Adding singleton with preliminary check to avoid any conflicts.
+func safe_add_singleton() -> void:
+ if not ProjectSettings.has_setting(SINGLETON_OPTION):
+ add_autoload_singleton(SINGLETON_NAME, SINGLETON_PATH)
+
+
+# Removing singleton with preliminary check to avoid any conflicts.
+func safe_remove_singleton() -> void:
+ if ProjectSettings.has_setting(SINGLETON_OPTION):
+ remove_autoload_singleton(SINGLETON_NAME)
+
+
+func _enable_plugin() -> void:
+ safe_add_singleton()
+
+ print("[Panku Console] enabled.")
+
+
+func _disable_plugin() -> void:
+ safe_remove_singleton()
+ PankuConfig.clear_all_project_settings()
+
+ print("[Panku Console] disabled.")
+
+
+func _enter_tree() -> void:
+ PankuConfig.init_all_project_settings()
+ _enable_exporter()
+
+ print("[Panku Console] initialized! Project page: https://github.com/Ark2000/PankuConsole")
+
+
+func _exit_tree() -> void:
+ _disable_exporter()
+
+
diff --git a/godot/addons/panku_console/plugin.gd.uid b/godot/addons/panku_console/plugin.gd.uid
new file mode 100644
index 0000000..4b0295b
--- /dev/null
+++ b/godot/addons/panku_console/plugin.gd.uid
@@ -0,0 +1 @@
+uid://cfo1eom2dfg0e
diff --git a/godot/addons/panku_console/res/effect/square_shadow.png b/godot/addons/panku_console/res/effect/square_shadow.png
new file mode 100644
index 0000000..213fe2c
Binary files /dev/null and b/godot/addons/panku_console/res/effect/square_shadow.png differ
diff --git a/godot/addons/panku_console/res/effect/square_shadow.png.import b/godot/addons/panku_console/res/effect/square_shadow.png.import
new file mode 100644
index 0000000..1d6d561
--- /dev/null
+++ b/godot/addons/panku_console/res/effect/square_shadow.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dvr12fl5prm78"
+path="res://.godot/imported/square_shadow.png-0b7f91b74dd24247868957be13db72f4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/effect/square_shadow.png"
+dest_files=["res://.godot/imported/square_shadow.png-0b7f91b74dd24247868957be13db72f4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=true
+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/godot/addons/panku_console/res/green_gradient_1d.tres b/godot/addons/panku_console/res/green_gradient_1d.tres
new file mode 100644
index 0000000..b2e8aa1
--- /dev/null
+++ b/godot/addons/panku_console/res/green_gradient_1d.tres
@@ -0,0 +1,4 @@
+[gd_resource type="Gradient" format=3 uid="uid://nr65cgweqh8n"]
+
+[resource]
+colors = PackedColorArray(0.129412, 0.14902, 0.180392, 0.501961, 0.129412, 0.14902, 0.180392, 0)
diff --git a/godot/addons/panku_console/res/icons2/add.svg b/godot/addons/panku_console/res/icons2/add.svg
new file mode 100644
index 0000000..d0cbaf1
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/add.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/add.svg.import b/godot/addons/panku_console/res/icons2/add.svg.import
new file mode 100644
index 0000000..4332717
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/add.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dprpfr0l5xvmu"
+path="res://.godot/imported/add.svg-2a06068ce10412da423766fee4b6d4aa.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/add.svg"
+dest_files=["res://.godot/imported/add.svg-2a06068ce10412da423766fee4b6d4aa.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg
new file mode 100644
index 0000000..429e411
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg.import
new file mode 100644
index 0000000..64bc23d
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cdxbns8lyctqp"
+path="res://.godot/imported/arrow-down-svgrepo-com.svg-38ccb799fe0295b6eb8e51628325ac00.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/arrow-down-svgrepo-com.svg"
+dest_files=["res://.godot/imported/arrow-down-svgrepo-com.svg-38ccb799fe0295b6eb8e51628325ac00.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg
new file mode 100644
index 0000000..85982c5
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg.import
new file mode 100644
index 0000000..9c881ef
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d0k813mm5y0d5"
+path="res://.godot/imported/arrow-up-md-svgrepo-com.svg-0d7dc8f9b4626627fc89d656dd6522e5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/arrow-up-md-svgrepo-com.svg"
+dest_files=["res://.godot/imported/arrow-up-md-svgrepo-com.svg-0d7dc8f9b4626627fc89d656dd6522e5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg
new file mode 100644
index 0000000..8842fee
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg.import
new file mode 100644
index 0000000..c34228a
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cngs5d4uosvmt"
+path="res://.godot/imported/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg-b7f02697b5af6027969d40533290cf43.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg"
+dest_files=["res://.godot/imported/bin-cancel-delete-remove-trash-garbage-svgrepo-com.svg-b7f02697b5af6027969d40533290cf43.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg
new file mode 100644
index 0000000..e3b8168
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg.import
new file mode 100644
index 0000000..4770d4d
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://x2wmejhxundv"
+path="res://.godot/imported/bookmark-filled-svgrepo-com.svg-36e232dcc8b8fb5b9792fabd49d75fb9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/bookmark-filled-svgrepo-com.svg"
+dest_files=["res://.godot/imported/bookmark-filled-svgrepo-com.svg-36e232dcc8b8fb5b9792fabd49d75fb9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/bookmark-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/bookmark-svgrepo-com.svg
new file mode 100644
index 0000000..f7fae38
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/bookmark-svgrepo-com.svg
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/bookmark-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/bookmark-svgrepo-com.svg.import
new file mode 100644
index 0000000..6b944e5
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/bookmark-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://232vc3pkp4kt"
+path="res://.godot/imported/bookmark-svgrepo-com.svg-ff120b5ee0d289505ac4f825e8ec66d9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/bookmark-svgrepo-com.svg"
+dest_files=["res://.godot/imported/bookmark-svgrepo-com.svg-ff120b5ee0d289505ac4f825e8ec66d9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/check-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/check-svgrepo-com.svg
new file mode 100644
index 0000000..b0a18cf
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/check-svgrepo-com.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/check-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/check-svgrepo-com.svg.import
new file mode 100644
index 0000000..1b60ff7
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/check-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://2cjvp0dp8ede"
+path="res://.godot/imported/check-svgrepo-com.svg-034d6c5925c00dc6c3cf6966293ed643.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/check-svgrepo-com.svg"
+dest_files=["res://.godot/imported/check-svgrepo-com.svg-034d6c5925c00dc6c3cf6966293ed643.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/checkbox_checked.svg b/godot/addons/panku_console/res/icons2/checkbox_checked.svg
new file mode 100644
index 0000000..970edd0
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/checkbox_checked.svg
@@ -0,0 +1,22 @@
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/checkbox_checked.svg.import b/godot/addons/panku_console/res/icons2/checkbox_checked.svg.import
new file mode 100644
index 0000000..3752b27
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/checkbox_checked.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bua84a0uv8ntw"
+path="res://.godot/imported/checkbox_checked.svg-b5d0aaca03398eb49c2d410cf3c86d9a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/checkbox_checked.svg"
+dest_files=["res://.godot/imported/checkbox_checked.svg-b5d0aaca03398eb49c2d410cf3c86d9a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/checkbox_unchecked.svg b/godot/addons/panku_console/res/icons2/checkbox_unchecked.svg
new file mode 100644
index 0000000..609e889
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/checkbox_unchecked.svg
@@ -0,0 +1,19 @@
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/checkbox_unchecked.svg.import b/godot/addons/panku_console/res/icons2/checkbox_unchecked.svg.import
new file mode 100644
index 0000000..a659b48
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/checkbox_unchecked.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dkiyle8rjahmw"
+path="res://.godot/imported/checkbox_unchecked.svg-43408d4debc25d7804c4c806c8681c45.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/checkbox_unchecked.svg"
+dest_files=["res://.godot/imported/checkbox_unchecked.svg-43408d4debc25d7804c4c806c8681c45.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/chevron_right.svg b/godot/addons/panku_console/res/icons2/chevron_right.svg
new file mode 100644
index 0000000..650d8f2
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/chevron_right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/chevron_right.svg.import b/godot/addons/panku_console/res/icons2/chevron_right.svg.import
new file mode 100644
index 0000000..c8099a1
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/chevron_right.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ws58gucuygx1"
+path="res://.godot/imported/chevron_right.svg-2c66815dcd12ccc3ba2562028a236330.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/chevron_right.svg"
+dest_files=["res://.godot/imported/chevron_right.svg-2c66815dcd12ccc3ba2562028a236330.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/close.svg b/godot/addons/panku_console/res/icons2/close.svg
new file mode 100644
index 0000000..05fb420
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/close.svg.import b/godot/addons/panku_console/res/icons2/close.svg.import
new file mode 100644
index 0000000..cfded02
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/close.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://8g5afcuanbl6"
+path="res://.godot/imported/close.svg-3a85a46ed81631640c7e8cd4342c98f0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/close.svg"
+dest_files=["res://.godot/imported/close.svg-3a85a46ed81631640c7e8cd4342c98f0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/close_20x.svg b/godot/addons/panku_console/res/icons2/close_20x.svg
new file mode 100644
index 0000000..4aab7f5
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/close_20x.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/close_20x.svg.import b/godot/addons/panku_console/res/icons2/close_20x.svg.import
new file mode 100644
index 0000000..f9a6a2d
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/close_20x.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ttbgv1vr1xsc"
+path="res://.godot/imported/close_20x.svg-9ead6c9c7ae149729de0bac95295aaba.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/close_20x.svg"
+dest_files=["res://.godot/imported/close_20x.svg-9ead6c9c7ae149729de0bac95295aaba.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/expand_more.svg b/godot/addons/panku_console/res/icons2/expand_more.svg
new file mode 100644
index 0000000..61fa21d
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/expand_more.svg
@@ -0,0 +1 @@
+
diff --git a/godot/addons/panku_console/res/icons2/expand_more.svg.import b/godot/addons/panku_console/res/icons2/expand_more.svg.import
new file mode 100644
index 0000000..b7bd274
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/expand_more.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1qaq6vnyc2f2"
+path="res://.godot/imported/expand_more.svg-156eef34c01d1545ad526cf0292ef120.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/expand_more.svg"
+dest_files=["res://.godot/imported/expand_more.svg-156eef34c01d1545ad526cf0292ef120.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/eye.svg b/godot/addons/panku_console/res/icons2/eye.svg
new file mode 100644
index 0000000..171a612
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/eye.svg.import b/godot/addons/panku_console/res/icons2/eye.svg.import
new file mode 100644
index 0000000..4c56efd
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/eye.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cvli654t5ybne"
+path="res://.godot/imported/eye.svg-6814b908d03cc1398c9846342a0966fc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/eye.svg"
+dest_files=["res://.godot/imported/eye.svg-6814b908d03cc1398c9846342a0966fc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/favorite.svg b/godot/addons/panku_console/res/icons2/favorite.svg
new file mode 100644
index 0000000..4bd196b
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/favorite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/favorite.svg.import b/godot/addons/panku_console/res/icons2/favorite.svg.import
new file mode 100644
index 0000000..e6f17dc
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/favorite.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://hf6h8otb8qkv"
+path="res://.godot/imported/favorite.svg-94e3b968ddcfab82f19abf606a6cd9bb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/favorite.svg"
+dest_files=["res://.godot/imported/favorite.svg-94e3b968ddcfab82f19abf606a6cd9bb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/fold-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/fold-svgrepo-com.svg
new file mode 100644
index 0000000..0d9ed74
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/fold-svgrepo-com.svg
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/fold-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/fold-svgrepo-com.svg.import
new file mode 100644
index 0000000..23349a5
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/fold-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dchvk7qgfe37m"
+path="res://.godot/imported/fold-svgrepo-com.svg-6f951c0c7f0583013a7f3fa96d045e61.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/fold-svgrepo-com.svg"
+dest_files=["res://.godot/imported/fold-svgrepo-com.svg-6f951c0c7f0583013a7f3fa96d045e61.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/gear.svg b/godot/addons/panku_console/res/icons2/gear.svg
new file mode 100644
index 0000000..569706f
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/gear.svg
@@ -0,0 +1,10 @@
+
diff --git a/godot/addons/panku_console/res/icons2/gear.svg.import b/godot/addons/panku_console/res/icons2/gear.svg.import
new file mode 100644
index 0000000..42d75bb
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/gear.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ch214eu302abt"
+path="res://.godot/imported/gear.svg-bd633691cf7fd6c996b8a22361898045.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/gear.svg"
+dest_files=["res://.godot/imported/gear.svg-bd633691cf7fd6c996b8a22361898045.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/history.svg b/godot/addons/panku_console/res/icons2/history.svg
new file mode 100644
index 0000000..0d691fe
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/history.svg
@@ -0,0 +1,9 @@
+
diff --git a/godot/addons/panku_console/res/icons2/history.svg.import b/godot/addons/panku_console/res/icons2/history.svg.import
new file mode 100644
index 0000000..dbd6d5b
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/history.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://kkrqc0j1wdi6"
+path="res://.godot/imported/history.svg-a137f2f4fbb3649b91b01d8def6f6456.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/history.svg"
+dest_files=["res://.godot/imported/history.svg-a137f2f4fbb3649b91b01d8def6f6456.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/info.svg b/godot/addons/panku_console/res/icons2/info.svg
new file mode 100644
index 0000000..0e9ea7a
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/info.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/info.svg.import b/godot/addons/panku_console/res/icons2/info.svg.import
new file mode 100644
index 0000000..ecb56ba
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/info.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dxvodkcmn4pa8"
+path="res://.godot/imported/info.svg-2b1105336c45eeebd4acdf04c60c8bdb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/info.svg"
+dest_files=["res://.godot/imported/info.svg-2b1105336c45eeebd4acdf04c60c8bdb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/info2.svg b/godot/addons/panku_console/res/icons2/info2.svg
new file mode 100644
index 0000000..e3db00e
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/info2.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/info2.svg.import b/godot/addons/panku_console/res/icons2/info2.svg.import
new file mode 100644
index 0000000..aa5b413
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/info2.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b6jt0ggmuoyeb"
+path="res://.godot/imported/info2.svg-c9ea3dad2575e3684098e5112249010d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/info2.svg"
+dest_files=["res://.godot/imported/info2.svg-c9ea3dad2575e3684098e5112249010d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/keyboard.svg b/godot/addons/panku_console/res/icons2/keyboard.svg
new file mode 100644
index 0000000..6cdb9a1
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/keyboard.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/keyboard.svg.import b/godot/addons/panku_console/res/icons2/keyboard.svg.import
new file mode 100644
index 0000000..d1fcf08
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/keyboard.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bqxh6hk3op1wv"
+path="res://.godot/imported/keyboard.svg-65a8773013921e61a1cc3ef80ef82693.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/keyboard.svg"
+dest_files=["res://.godot/imported/keyboard.svg-65a8773013921e61a1cc3ef80ef82693.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/menu.svg b/godot/addons/panku_console/res/icons2/menu.svg
new file mode 100644
index 0000000..febcbbe
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/menu.svg.import b/godot/addons/panku_console/res/icons2/menu.svg.import
new file mode 100644
index 0000000..3a56540
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/menu.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dosm26riekruh"
+path="res://.godot/imported/menu.svg-15ef2ef76b3f6c0e7fc0168d1ac05d38.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/menu.svg"
+dest_files=["res://.godot/imported/menu.svg-15ef2ef76b3f6c0e7fc0168d1ac05d38.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/more_horiz.svg b/godot/addons/panku_console/res/icons2/more_horiz.svg
new file mode 100644
index 0000000..73560d0
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/more_horiz.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/more_horiz.svg.import b/godot/addons/panku_console/res/icons2/more_horiz.svg.import
new file mode 100644
index 0000000..9430815
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/more_horiz.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bbc41a7b3jre0"
+path="res://.godot/imported/more_horiz.svg-7ca01f2b54bfa3040d05158756af7968.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/more_horiz.svg"
+dest_files=["res://.godot/imported/more_horiz.svg-7ca01f2b54bfa3040d05158756af7968.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/open_with.svg b/godot/addons/panku_console/res/icons2/open_with.svg
new file mode 100644
index 0000000..d6c2bae
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/open_with.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/open_with.svg.import b/godot/addons/panku_console/res/icons2/open_with.svg.import
new file mode 100644
index 0000000..90ce2f3
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/open_with.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b22jdciwq07ic"
+path="res://.godot/imported/open_with.svg-4c7b4bea2cb5c0a1af405d61d3d85fa3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/open_with.svg"
+dest_files=["res://.godot/imported/open_with.svg-4c7b4bea2cb5c0a1af405d61d3d85fa3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg
new file mode 100644
index 0000000..6aba11b
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg
@@ -0,0 +1,19 @@
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg.import
new file mode 100644
index 0000000..812a930
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cpfnfg1aw6xso"
+path="res://.godot/imported/pause-1010-svgrepo-com.svg-8a4549394af5099adcc9e7d6754d8258.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/pause-1010-svgrepo-com.svg"
+dest_files=["res://.godot/imported/pause-1010-svgrepo-com.svg-8a4549394af5099adcc9e7d6754d8258.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/play-1001-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/play-1001-svgrepo-com.svg
new file mode 100644
index 0000000..7cebe14
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/play-1001-svgrepo-com.svg
@@ -0,0 +1,19 @@
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/play-1001-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/play-1001-svgrepo-com.svg.import
new file mode 100644
index 0000000..b9b56ee
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/play-1001-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ecmevhk0tuas"
+path="res://.godot/imported/play-1001-svgrepo-com.svg-6dda54bd1afb4351b91dc74d89d43c7f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/play-1001-svgrepo-com.svg"
+dest_files=["res://.godot/imported/play-1001-svgrepo-com.svg-6dda54bd1afb4351b91dc74d89d43c7f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/pop-out-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/pop-out-svgrepo-com.svg
new file mode 100644
index 0000000..e59c21a
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/pop-out-svgrepo-com.svg
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/pop-out-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/pop-out-svgrepo-com.svg.import
new file mode 100644
index 0000000..ad501b5
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/pop-out-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gav3m4qtvgje"
+path="res://.godot/imported/pop-out-svgrepo-com.svg-54bb73635d5759933a88bbec8ed6aca8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/pop-out-svgrepo-com.svg"
+dest_files=["res://.godot/imported/pop-out-svgrepo-com.svg-54bb73635d5759933a88bbec8ed6aca8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/question.svg b/godot/addons/panku_console/res/icons2/question.svg
new file mode 100644
index 0000000..f6a29dd
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/question.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/question.svg.import b/godot/addons/panku_console/res/icons2/question.svg.import
new file mode 100644
index 0000000..148ea93
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/question.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ca6btx2q3g7q8"
+path="res://.godot/imported/question.svg-b2322d216b84b418e5a3bac06c50c9e3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/question.svg"
+dest_files=["res://.godot/imported/question.svg-b2322d216b84b418e5a3bac06c50c9e3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/remove.svg b/godot/addons/panku_console/res/icons2/remove.svg
new file mode 100644
index 0000000..bee8c68
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/remove.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/remove.svg.import b/godot/addons/panku_console/res/icons2/remove.svg.import
new file mode 100644
index 0000000..55d2846
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/remove.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dnexm7u6lq3km"
+path="res://.godot/imported/remove.svg-76a0d91c4b08193a23093f3f0fc3dcaf.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/remove.svg"
+dest_files=["res://.godot/imported/remove.svg-76a0d91c4b08193a23093f3f0fc3dcaf.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/rename-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/rename-svgrepo-com.svg
new file mode 100644
index 0000000..5e80b7a
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/rename-svgrepo-com.svg
@@ -0,0 +1,114 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/rename-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/rename-svgrepo-com.svg.import
new file mode 100644
index 0000000..2aa845c
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/rename-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://brf6iwx4r6bdd"
+path="res://.godot/imported/rename-svgrepo-com.svg-5ec15595e7d5857527ce81e8c9082a28.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/rename-svgrepo-com.svg"
+dest_files=["res://.godot/imported/rename-svgrepo-com.svg-5ec15595e7d5857527ce81e8c9082a28.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/reply.svg b/godot/addons/panku_console/res/icons2/reply.svg
new file mode 100644
index 0000000..2f85918
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/reply.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/reply.svg.import b/godot/addons/panku_console/res/icons2/reply.svg.import
new file mode 100644
index 0000000..2286239
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/reply.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b4jd6tqlie0wx"
+path="res://.godot/imported/reply.svg-198ceccf73b6dc82a4737a0814707430.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/reply.svg"
+dest_files=["res://.godot/imported/reply.svg-198ceccf73b6dc82a4737a0814707430.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/resize-svgrepo-com.svg b/godot/addons/panku_console/res/icons2/resize-svgrepo-com.svg
new file mode 100644
index 0000000..c9a716d
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/resize-svgrepo-com.svg
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/godot/addons/panku_console/res/icons2/resize-svgrepo-com.svg.import b/godot/addons/panku_console/res/icons2/resize-svgrepo-com.svg.import
new file mode 100644
index 0000000..1f743ea
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/resize-svgrepo-com.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ciu5jiw4xmkq0"
+path="res://.godot/imported/resize-svgrepo-com.svg-cd0763e17be8b93faf6e99fda452ac2a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/resize-svgrepo-com.svg"
+dest_files=["res://.godot/imported/resize-svgrepo-com.svg-cd0763e17be8b93faf6e99fda452ac2a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/search.svg b/godot/addons/panku_console/res/icons2/search.svg
new file mode 100644
index 0000000..e14d7af
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/godot/addons/panku_console/res/icons2/search.svg.import b/godot/addons/panku_console/res/icons2/search.svg.import
new file mode 100644
index 0000000..e7910b4
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/search.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gtslyq1kvnxc"
+path="res://.godot/imported/search.svg-cbe7bc4d5cf71611ed1b364ee4407142.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/search.svg"
+dest_files=["res://.godot/imported/search.svg-cbe7bc4d5cf71611ed1b364ee4407142.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/icons2/swap_horiz.svg b/godot/addons/panku_console/res/icons2/swap_horiz.svg
new file mode 100644
index 0000000..4bf16b0
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/swap_horiz.svg
@@ -0,0 +1 @@
+
diff --git a/godot/addons/panku_console/res/icons2/swap_horiz.svg.import b/godot/addons/panku_console/res/icons2/swap_horiz.svg.import
new file mode 100644
index 0000000..c94e87b
--- /dev/null
+++ b/godot/addons/panku_console/res/icons2/swap_horiz.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bc2g8vv0x3d8y"
+path="res://.godot/imported/swap_horiz.svg-3fa7c06ee39a243e7a12b5915f37a894.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/panku_console/res/icons2/swap_horiz.svg"
+dest_files=["res://.godot/imported/swap_horiz.svg-3fa7c06ee39a243e7a12b5915f37a894.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+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/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+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/godot/addons/panku_console/res/panku_console_theme.tres b/godot/addons/panku_console/res/panku_console_theme.tres
new file mode 100644
index 0000000..69306e9
--- /dev/null
+++ b/godot/addons/panku_console/res/panku_console_theme.tres
@@ -0,0 +1,137 @@
+[gd_resource type="Theme" load_steps=26 format=3 uid="uid://bk18yfu0d77wk"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1q17k"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j0iw5"]
+bg_color = Color(0.160784, 0.160784, 0.160784, 0.12549)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.0627451)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2sv8t"]
+content_margin_left = 4.0
+content_margin_top = 2.0
+content_margin_right = 4.0
+content_margin_bottom = 2.0
+bg_color = Color(0, 0, 0, 0.12549)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.0627451)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oko07"]
+content_margin_left = 4.0
+content_margin_top = 2.0
+content_margin_right = 4.0
+content_margin_bottom = 2.0
+bg_color = Color(0.639216, 0.639216, 0.639216, 0.12549)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.0627451)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_66h6h"]
+bg_color = Color(1, 1, 1, 0.4)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_io0fu"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5a1x3"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_olfos"]
+content_margin_top = 8.0
+bg_color = Color(0.6, 0.6, 0.6, 0.12549)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.25098)
+expand_margin_top = 8.0
+
+[sub_resource type="ImageTexture" id="ImageTexture_p3ihr"]
+
+[sub_resource type="ImageTexture" id="ImageTexture_e0rko"]
+
+[sub_resource type="ImageTexture" id="ImageTexture_1il84"]
+
+[sub_resource type="ImageTexture" id="ImageTexture_amjqe"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_pc3w4"]
+bg_color = Color(1, 1, 1, 0.501961)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xodll"]
+bg_color = Color(1, 1, 1, 0.501961)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y3ihc"]
+content_margin_top = 23.0
+bg_color = Color(0, 0, 0, 0.25098)
+border_color = Color(0.6, 0.6, 0.6, 0.25098)
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_36yn6"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1hrnt"]
+content_margin_left = 8.0
+bg_color = Color(0, 0, 0, 0.12549)
+border_width_left = 2
+border_color = Color(0.8, 0.8, 0.8, 0.25098)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xot6g"]
+content_margin_left = 4.0
+draw_center = false
+border_width_left = 2
+border_color = Color(0.8, 0.8, 0.8, 0.12549)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bcc4x"]
+bg_color = Color(0.317647, 0.317647, 0.317647, 0.901961)
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_nqr1r"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_abx0a"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_37w0u"]
+bg_color = Color(1, 1, 1, 0.501961)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mhjmb"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_iamty"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jdo31"]
+content_margin_left = 8.0
+bg_color = Color(0.6, 0.6, 0.6, 0.12549)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.8, 0.8, 0.8, 0.25098)
+
+[resource]
+default_font_size = 16
+Button/styles/focus = SubResource("StyleBoxEmpty_1q17k")
+Button/styles/hover = SubResource("StyleBoxFlat_j0iw5")
+Button/styles/normal = SubResource("StyleBoxFlat_2sv8t")
+Button/styles/pressed = SubResource("StyleBoxFlat_oko07")
+HScrollBar/styles/grabber = SubResource("StyleBoxFlat_66h6h")
+HScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_io0fu")
+HScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_5a1x3")
+HScrollBar/styles/scroll = SubResource("StyleBoxFlat_olfos")
+HSlider/icons/grabber = SubResource("ImageTexture_p3ihr")
+HSlider/icons/grabber_disabled = SubResource("ImageTexture_e0rko")
+HSlider/icons/grabber_highlight = SubResource("ImageTexture_1il84")
+HSlider/icons/tick = SubResource("ImageTexture_amjqe")
+HSlider/styles/grabber_area = SubResource("StyleBoxFlat_pc3w4")
+HSlider/styles/grabber_area_highlight = SubResource("StyleBoxFlat_xodll")
+HSlider/styles/slider = SubResource("StyleBoxFlat_y3ihc")
+LineEdit/styles/focus = SubResource("StyleBoxEmpty_36yn6")
+LineEdit/styles/normal = SubResource("StyleBoxFlat_1hrnt")
+LineEdit/styles/read_only = SubResource("StyleBoxFlat_xot6g")
+PopupMenu/styles/panel = SubResource("StyleBoxFlat_bcc4x")
+RichTextLabel/colors/default_color = Color(1, 1, 1, 0.878431)
+RichTextLabel/styles/focus = SubResource("StyleBoxEmpty_nqr1r")
+RichTextLabel/styles/normal = SubResource("StyleBoxEmpty_abx0a")
+VScrollBar/styles/grabber = SubResource("StyleBoxFlat_37w0u")
+VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_mhjmb")
+VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_iamty")
+VScrollBar/styles/scroll = SubResource("StyleBoxFlat_jdo31")
diff --git a/godot/addons/panku_console/res/shader/mattias_crt.gdshader b/godot/addons/panku_console/res/shader/mattias_crt.gdshader
new file mode 100644
index 0000000..1251da1
--- /dev/null
+++ b/godot/addons/panku_console/res/shader/mattias_crt.gdshader
@@ -0,0 +1,63 @@
+// Source: https://www.shadertoy.com/view/Ms23DR
+// Loosely based on postprocessing shader by inigo quilez, License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
+
+shader_type canvas_item;
+
+uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
+
+vec2 curve(vec2 uv) {
+ uv = (uv - 0.5) * 2.0;
+ uv *= 1.1;
+ uv.x *= 1.0 + pow(abs(uv.y) / 5.0, 2.0);
+ uv.y *= 1.0 + pow(abs(uv.x) / 4.0, 2.0);
+ uv = (uv / 2.0) + 0.5;
+ uv = uv * 0.92 + 0.04;
+ return uv;
+}
+
+void fragment() {
+ vec2 fragCoord = FRAGCOORD.xy;
+ vec2 iResolution = 1.0 / SCREEN_PIXEL_SIZE;
+ float iTime = TIME;
+ vec2 q = fragCoord.xy / iResolution.xy;
+ vec2 uv = q;
+ uv = curve( uv );
+ vec3 oricol = texture( screen_texture, vec2(q.x,q.y) ).xyz;
+ vec3 col;
+ float x = sin(0.3*iTime+uv.y*21.0)*sin(0.7*iTime+uv.y*29.0)*sin(0.3+0.33*iTime+uv.y*31.0)*0.0017;
+
+ col.r = texture(screen_texture,vec2(x+uv.x+0.001,uv.y+0.001)).x+0.05;
+ col.g = texture(screen_texture,vec2(x+uv.x+0.000,uv.y-0.002)).y+0.05;
+ col.b = texture(screen_texture,vec2(x+uv.x-0.002,uv.y+0.000)).z+0.05;
+ col.r += 0.08*texture(screen_texture,0.75*vec2(x+0.025, -0.027)+vec2(uv.x+0.001,uv.y+0.001)).x;
+ col.g += 0.05*texture(screen_texture,0.75*vec2(x+-0.022, -0.02)+vec2(uv.x+0.000,uv.y-0.002)).y;
+ col.b += 0.08*texture(screen_texture,0.75*vec2(x+-0.02, -0.018)+vec2(uv.x-0.002,uv.y+0.000)).z;
+
+ col = clamp(col*0.6+0.4*col*col*1.0,0.0,1.0);
+
+ float vig = (0.0 + 1.0*16.0*uv.x*uv.y*(1.0-uv.x)*(1.0-uv.y));
+ col *= vec3(pow(vig,0.3));
+
+ col *= vec3(0.95,1.05,0.95);
+ col *= 2.8;
+
+ float scans = clamp( 0.35+0.35*sin(3.5*iTime+uv.y*iResolution.y*1.5), 0.0, 1.0);
+
+ float s = pow(scans,1.7);
+ col = col*vec3( 0.4+0.7*s) ;
+
+ col *= 1.0+0.01*sin(110.0*iTime);
+ if (uv.x < 0.0 || uv.x > 1.0)
+ col *= 0.0;
+ if (uv.y < 0.0 || uv.y > 1.0)
+ col *= 0.0;
+
+ col*=1.0-0.65*vec3(clamp((mod(fragCoord.x, 2.0)-1.0)*2.0,0.0,1.0));
+
+ float comp = smoothstep( 0.1, 0.9, sin(iTime) );
+
+ // Remove the next line to stop cross-fade between original and postprocess
+// col = mix( col, oricol, comp );
+
+ COLOR = vec4(col,1.0);
+}
diff --git a/godot/addons/panku_console/res/shader/mattias_crt.gdshader.uid b/godot/addons/panku_console/res/shader/mattias_crt.gdshader.uid
new file mode 100644
index 0000000..00bd283
--- /dev/null
+++ b/godot/addons/panku_console/res/shader/mattias_crt.gdshader.uid
@@ -0,0 +1 @@
+uid://b4q0v3umrhemp
diff --git a/godot/addons/panku_console/res/shader/simple_fast_blur.gdshader b/godot/addons/panku_console/res/shader/simple_fast_blur.gdshader
new file mode 100644
index 0000000..4667743
--- /dev/null
+++ b/godot/addons/panku_console/res/shader/simple_fast_blur.gdshader
@@ -0,0 +1,10 @@
+shader_type canvas_item;
+
+uniform float lod:hint_range(0.0, 5.0) = 0.0;
+uniform vec4 modulate:source_color = vec4(1.0);
+uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear_mipmap;
+
+void fragment() {
+ vec3 col = textureLod(screen_texture, SCREEN_UV, lod).rgb;
+ COLOR = vec4(mix(col, modulate.rgb, modulate.a), 1.0);
+}
diff --git a/godot/addons/panku_console/res/shader/simple_fast_blur.gdshader.uid b/godot/addons/panku_console/res/shader/simple_fast_blur.gdshader.uid
new file mode 100644
index 0000000..68a3854
--- /dev/null
+++ b/godot/addons/panku_console/res/shader/simple_fast_blur.gdshader.uid
@@ -0,0 +1 @@
+uid://b2ut3nil15x8o
diff --git a/godot/player.tscn b/godot/player.tscn
deleted file mode 100644
index 9a9540c..0000000
--- a/godot/player.tscn
+++ /dev/null
@@ -1,46 +0,0 @@
-[gd_scene load_steps=7 format=3 uid="uid://bx7xynpbusro6"]
-
-[ext_resource type="Texture2D" uid="uid://doyl1pfn8cean" path="res://art/playerGrey_up1.png" id="1_hqtel"]
-[ext_resource type="Texture2D" uid="uid://cxx2twmx2cov0" path="res://art/playerGrey_up2.png" id="2_sweqy"]
-[ext_resource type="Texture2D" uid="uid://y5fhjtmhqhdu" path="res://art/playerGrey_walk1.png" id="3_2hs0m"]
-[ext_resource type="Texture2D" uid="uid://cihvooq6h05er" path="res://art/playerGrey_walk2.png" id="4_1jxqw"]
-
-[sub_resource type="SpriteFrames" id="SpriteFrames_dw050"]
-animations = [{
-"frames": [{
-"duration": 1.0,
-"texture": ExtResource("1_hqtel")
-}, {
-"duration": 1.0,
-"texture": ExtResource("2_sweqy")
-}],
-"loop": true,
-"name": &"up",
-"speed": 5.0
-}, {
-"frames": [{
-"duration": 1.0,
-"texture": ExtResource("3_2hs0m")
-}, {
-"duration": 1.0,
-"texture": ExtResource("4_1jxqw")
-}],
-"loop": true,
-"name": &"walk",
-"speed": 5.0
-}]
-
-[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_b26j0"]
-radius = 27.0
-height = 68.0
-
-[node name="Player" type="Player"]
-metadata/_edit_group_ = true
-
-[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."]
-scale = Vector2(0.5, 0.5)
-sprite_frames = SubResource("SpriteFrames_dw050")
-animation = &"walk"
-
-[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
-shape = SubResource("CapsuleShape2D_b26j0")
diff --git a/godot/project.godot b/godot/project.godot
index 0fb1ea7..48ff521 100644
--- a/godot/project.godot
+++ b/godot/project.godot
@@ -30,7 +30,6 @@ config/icon="res://icon.svg"
[autoload]
-GLogging="*res://addons/glogging/glogging.gd"
Panku="*res://addons/panku_console/console.tscn"
[display]
@@ -39,7 +38,7 @@ window/stretch/mode="canvas_items"
[editor_plugins]
-enabled=PackedStringArray()
+enabled=PackedStringArray("res://addons/panku_console/plugin.cfg")
[godot_resource_groups]
@@ -71,3 +70,8 @@ move_down={
, 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":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
+toggle_console={
+"deadzone": 0.2,
+"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":96,"key_label":0,"unicode":96,"location":0,"echo":false,"script":null)
+]
+}
diff --git a/godot/tests/test_runner.tscn b/godot/tests/test_runner.tscn
new file mode 100644
index 0000000..69b6077
--- /dev/null
+++ b/godot/tests/test_runner.tscn
@@ -0,0 +1,5 @@
+[gd_scene format=3 uid="uid://dag477ig1al1u"]
+
+[node name="GdTestRunner" type="GdTestRunner"]
+disallow_focus = true
+run_benchmarks = false
diff --git a/godot/trymap.tscn b/godot/trymap.tscn
index 4b8e9a4..df95f7a 100644
--- a/godot/trymap.tscn
+++ b/godot/trymap.tscn
@@ -18,10 +18,11 @@ background_color = Color(1, 0, 0, 1)
[node name="Node3D" type="Node3D"]
[node name="Mappy" type="Mappy" parent="."]
-map_width = 10
-map_height = 10
+map_width = 1024
+map_height = 1024
noise_magnitude = 50.0
texture_atlas = ExtResource("1_qcwru")
+transform = Transform3D(128, 0, 0, 0, 128, 0, 0, 0, 128, 0, 0, 0)
[node name="Player" type="Player" parent="."]
@@ -32,4 +33,5 @@ mesh = SubResource("QuadMesh_yr18d")
[node name="Camera3D" type="Camera3D" parent="Player"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 2.78414)
environment = SubResource("Environment_bol8x")
+projection = 1
current = true
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index badee07..bde2e0c 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -8,7 +8,7 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
- "gimli",
+ "gimli 0.32.3",
]
[[package]]
@@ -17,6 +17,19 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -26,6 +39,24 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "ar"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
[[package]]
name = "autocfg"
version = "1.5.0"
@@ -42,16 +73,45 @@ dependencies = [
"cfg-if",
"libc",
"miniz_oxide",
- "object",
+ "object 0.37.3",
"rustc-demangle",
"windows-link",
]
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "bytes"
@@ -59,18 +119,222 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+[[package]]
+name = "camino"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-util-schemas"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830"
+dependencies = [
+ "semver",
+ "serde",
+ "serde-untagged",
+ "serde-value",
+ "thiserror",
+ "toml",
+ "unicode-xid",
+ "url",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cfca2aaa699835ba88faf58a06342a314a950d2b9686165e038286c30316868"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "cargo-util-schemas",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "derive_more"
+version = "0.99.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "elsa"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9abf33c656a7256451ebb7d0082c5a471820c31269e49d807c538c252352186e"
+dependencies = [
+ "indexmap",
+ "stable_deref_trait",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+[[package]]
+name = "erased-serde"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "259d404d09818dec19332e31d94558aeb442fea04c817006456c24b5460bbd4b"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fallible-iterator"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
+
+[[package]]
+name = "flate2"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "gd-rehearse"
+version = "0.2.1"
+source = "git+https://github.com/JoeyEamigh/gd-rehearse?branch=godot-4.5#1505d7b9ea0df99858a394fab7c366078eb88e2b"
+dependencies = [
+ "gd-rehearse-defs",
+ "gd-rehearse-macros",
+ "godot",
+]
+
+[[package]]
+name = "gd-rehearse-defs"
+version = "0.2.1"
+source = "git+https://github.com/JoeyEamigh/gd-rehearse?branch=godot-4.5#1505d7b9ea0df99858a394fab7c366078eb88e2b"
+dependencies = [
+ "godot",
+ "paste",
+]
+
+[[package]]
+name = "gd-rehearse-macros"
+version = "0.2.1"
+source = "git+https://github.com/JoeyEamigh/gd-rehearse?branch=godot-4.5#1505d7b9ea0df99858a394fab7c366078eb88e2b"
+dependencies = [
+ "gd-rehearse-defs",
+ "proc-macro2",
+ "quote",
+ "venial 0.5.0",
+]
+
[[package]]
name = "gdext-gen"
version = "0.1.1"
@@ -87,8 +351,30 @@ dependencies = [
[[package]]
name = "gdextension-api"
version = "0.3.0"
+source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=release-v0.3#f70baca72747869aaac7781a0cf139f53a2aac19"
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e25d88dabe9fdb2e064cb545312178ec4025eefb62d9a3bbce1769088e2c381d"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.7+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+dependencies = [
+ "fallible-iterator",
+ "indexmap",
+ "stable_deref_trait",
+]
[[package]]
name = "gimli"
@@ -96,6 +382,15 @@ version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
+[[package]]
+name = "glam"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945"
+dependencies = [
+ "libm",
+]
+
[[package]]
name = "glam"
version = "0.30.8"
@@ -111,8 +406,7 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "godot"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90b20e19a25e45460c4fd2b11b519beea3e9bcda929c1566f8d17a369b41dd82"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
dependencies = [
"godot-core",
"godot-macros",
@@ -121,8 +415,7 @@ dependencies = [
[[package]]
name = "godot-bindings"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "508d56d01018c16b67a906bda8bcd9ade7f265990e4be7c2dffecbfdebcc3992"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
dependencies = [
"gdextension-api",
]
@@ -130,14 +423,12 @@ dependencies = [
[[package]]
name = "godot-cell"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1794fbec9934ef375d717d02ec142d9e0c7d7482b24d7da463264b92bc14eaf"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
[[package]]
name = "godot-codegen"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "791e05ae1859028c3a910aaed83e4910ab7701ce32fa663d2dc7cd957287cdf5"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
dependencies = [
"godot-bindings",
"heck",
@@ -150,10 +441,9 @@ dependencies = [
[[package]]
name = "godot-core"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b1717ffba78bd2655c9ee1073752ac90d969b8a614800c469f00fc3ddc02439"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
dependencies = [
- "glam",
+ "glam 0.30.8",
"godot-bindings",
"godot-cell",
"godot-codegen",
@@ -163,8 +453,7 @@ dependencies = [
[[package]]
name = "godot-ffi"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "419e46ba92fba076da67f35ad96faaa830c1de4e8175db2bb4251aeb58b14b08"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
dependencies = [
"godot-bindings",
"godot-codegen",
@@ -175,24 +464,37 @@ dependencies = [
[[package]]
name = "godot-macros"
version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a464e26854e63825d36655759c24556c970a8777535466ebf4ecc7f8e87a4638"
+source = "git+https://github.com/JoeyEamigh/gdext#a31ea94b3cd8e2871edd2b87841410258f27797e"
dependencies = [
"godot-bindings",
"proc-macro2",
"quote",
- "venial",
+ "venial 0.6.1",
]
[[package]]
name = "godottest_rs"
version = "0.1.0"
dependencies = [
+ "bytemuck",
+ "gd-rehearse",
"gdext-gen",
"godot",
+ "spirv-builder",
"tokio",
]
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
+
[[package]]
name = "hashbrown"
version = "0.16.0"
@@ -205,6 +507,113 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+[[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
[[package]]
name = "indexmap"
version = "2.11.4"
@@ -212,26 +621,90 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
- "hashbrown",
+ "hashbrown 0.16.0",
]
+[[package]]
+name = "internal-iterator"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "969ee3fc68ec2e88eb21434ce4d9b7e1600d1ce92ff974560a6c4a304f5124b9"
+
[[package]]
name = "io-uring"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
- "bitflags",
+ "bitflags 2.9.4",
"cfg-if",
"libc",
]
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom",
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+
+[[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
[[package]]
name = "lock_api"
version = "0.4.13"
@@ -242,6 +715,27 @@ dependencies = [
"scopeguard",
]
+[[package]]
+name = "log"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+
+[[package]]
+name = "longest-increasing-subsequence"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86"
+
+[[package]]
+name = "matchers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
+dependencies = [
+ "regex-automata",
+]
+
[[package]]
name = "memchr"
version = "2.7.6"
@@ -255,6 +749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
+ "simd-adler32",
]
[[package]]
@@ -264,7 +759,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [
"libc",
- "wasi",
+ "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys",
]
@@ -283,13 +778,74 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a846cbc04412cf509efcd8f3694b114fc700a035fb5a37f21517f9fb019f1ebc"
+[[package]]
+name = "noisemap"
+version = "0.1.0"
+dependencies = [
+ "glam 0.24.2",
+ "libm",
+ "spirv-std",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "crc32fast",
+ "flate2",
+ "hashbrown 0.15.5",
+ "indexmap",
+ "memchr",
+ "ruzstd",
+]
+
[[package]]
name = "object"
version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
+ "crc32fast",
+ "hashbrown 0.15.5",
+ "indexmap",
"memchr",
+ "wasmparser",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
]
[[package]]
@@ -315,12 +871,33 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+[[package]]
+name = "potential_utf"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a"
+dependencies = [
+ "zerovec",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.101"
@@ -339,13 +916,25 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "raw-string"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0501e134c6905fee1f10fed25b0a7e1261bf676cffac9543a7d0730dec01af2"
+
[[package]]
name = "redox_syscall"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
- "bitflags",
+ "bitflags 2.9.4",
]
[[package]]
@@ -377,18 +966,138 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+[[package]]
+name = "rspirv"
+version = "0.12.0+sdk-1.3.268.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cf3a93856b6e5946537278df0d3075596371b1950ccff012f02b0f7eafec8d"
+dependencies = [
+ "rustc-hash",
+ "spirv",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_codegen_spirv"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+dependencies = [
+ "ahash",
+ "ar",
+ "bytemuck",
+ "either",
+ "indexmap",
+ "itertools 0.14.0",
+ "lazy_static",
+ "libc",
+ "log",
+ "object 0.37.3",
+ "regex",
+ "rspirv",
+ "rustc-demangle",
+ "rustc_codegen_spirv-target-specs",
+ "rustc_codegen_spirv-types",
+ "rustix",
+ "sanitize-filename",
+ "smallvec",
+ "spirt",
+ "spirv-std-types",
+ "spirv-tools",
+ "thorin-dwp",
+ "tracing",
+ "tracing-subscriber",
+ "tracing-tree",
+]
+
+[[package]]
+name = "rustc_codegen_spirv-target-specs"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+
+[[package]]
+name = "rustc_codegen_spirv-types"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+dependencies = [
+ "rspirv",
+ "serde",
+ "serde_json",
+ "spirv",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
+dependencies = [
+ "bitflags 2.9.4",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "ruzstd"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f"
+dependencies = [
+ "twox-hash",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "sanitize-filename"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d"
+dependencies = [
+ "regex",
+]
+
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
[[package]]
name = "serde"
version = "1.0.228"
@@ -399,6 +1108,28 @@ dependencies = [
"serde_derive",
]
+[[package]]
+name = "serde-untagged"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
[[package]]
name = "serde_core"
version = "1.0.228"
@@ -419,6 +1150,19 @@ dependencies = [
"syn",
]
+[[package]]
+name = "serde_json"
+version = "1.0.145"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+ "serde_core",
+]
+
[[package]]
name = "serde_spanned"
version = "0.6.9"
@@ -428,6 +1172,21 @@ dependencies = [
"serde",
]
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
[[package]]
name = "signal-hook-registry"
version = "1.4.6"
@@ -437,6 +1196,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
[[package]]
name = "slab"
version = "0.4.11"
@@ -448,6 +1213,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
[[package]]
name = "socket2"
@@ -459,6 +1227,114 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "spirt"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2d5968bd2a36466468aac637b355776f080edfb0c6f769b2b99b9708260c42a"
+dependencies = [
+ "arrayvec",
+ "bytemuck",
+ "derive_more",
+ "elsa",
+ "indexmap",
+ "internal-iterator",
+ "itertools 0.10.5",
+ "lazy_static",
+ "longest-increasing-subsequence",
+ "rustc-hash",
+ "serde",
+ "serde_json",
+ "smallvec",
+]
+
+[[package]]
+name = "spirv"
+version = "0.3.0+sdk-1.3.268.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
+dependencies = [
+ "bitflags 2.9.4",
+ "serde",
+]
+
+[[package]]
+name = "spirv-builder"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+dependencies = [
+ "cargo_metadata",
+ "log",
+ "memchr",
+ "raw-string",
+ "rustc_codegen_spirv",
+ "rustc_codegen_spirv-target-specs",
+ "rustc_codegen_spirv-types",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "spirv-std"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+dependencies = [
+ "bitflags 1.3.2",
+ "glam 0.24.2",
+ "libm",
+ "num-traits",
+ "spirv-std-macros",
+ "spirv-std-types",
+]
+
+[[package]]
+name = "spirv-std-macros"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "spirv-std-types",
+ "syn",
+]
+
+[[package]]
+name = "spirv-std-types"
+version = "0.9.0"
+source = "git+https://github.com/Rust-GPU/rust-gpu?rev=56cc13214566af452de71daf39f7b3f2c65551c2#56cc13214566af452de71daf39f7b3f2c65551c2"
+
+[[package]]
+name = "spirv-tools"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b10991c25e3adc32c018c528be2cdef16c0cdfaf8581fbeabb033b547a599ec"
+dependencies = [
+ "spirv-tools-sys",
+]
+
+[[package]]
+name = "spirv-tools-sys"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c78c1a8af381edef1b7ca7d7caea9d6454dccb4b00a35b7b968b44b8212f487"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "syn"
version = "2.0.104"
@@ -470,6 +1346,76 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thorin-dwp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e9c1e705f82a260173f3eec93f2ff6d7807f23ad5a8cc2e7316a891733ea7a1"
+dependencies = [
+ "gimli 0.31.1",
+ "hashbrown 0.15.5",
+ "object 0.36.7",
+ "tracing",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "tilemap"
+version = "0.1.0"
+dependencies = [
+ "glam 0.24.2",
+ "spirv-std",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
[[package]]
name = "tokio"
version = "1.47.1"
@@ -543,12 +1489,154 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-serde"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex-automata",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
+]
+
+[[package]]
+name = "tracing-tree"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac87aa03b6a4d5a7e4810d1a80c19601dbe0f8a837e9177f23af721c7ba7beec"
+dependencies = [
+ "nu-ansi-term",
+ "tracing-core",
+ "tracing-log",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "url"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "venial"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61584a325b16f97b5b25fcc852eb9550843a251057a5e3e5992d2376f3df4bb2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
[[package]]
name = "venial"
version = "0.6.1"
@@ -559,12 +1647,45 @@ dependencies = [
"quote",
]
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+[[package]]
+name = "wasi"
+version = "0.14.7+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
+dependencies = [
+ "wasip2",
+]
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.236.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7"
+dependencies = [
+ "bitflags 2.9.4",
+]
+
[[package]]
name = "windows-link"
version = "0.2.1"
@@ -652,3 +1773,113 @@ checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
dependencies = [
"memchr",
]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 97e0523..187cb9c 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -3,12 +3,26 @@ name = "godottest_rs"
version = "0.1.0"
edition = "2024"
+[workspace]
+resolver = "3"
+members = ["shaders/*"]
+
[lib]
crate-type = ["cdylib"]
+[features]
+default = ["gd_rehearse_tests"]
+gd_rehearse_tests = ["gd-rehearse"]
+
[dependencies]
-godot = { version = "0.4.0", features = ["api-4-5"] }
+godot = { git = "https://github.com/JoeyEamigh/gdext", features = ["api-4-5"] }
tokio = { version = "1.47.1", features = ["full"] }
+bytemuck = { version = "1.14", features = ["derive"] }
+gd-rehearse = { git = "https://github.com/JoeyEamigh/gd-rehearse", branch = "godot-4.5", optional = true }
[build-dependencies]
gdext-gen = { version = "0.1.1", features = ["dependencies", "find_icons"] }
+spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "56cc13214566af452de71daf39f7b3f2c65551c2", package = "spirv-builder" }
+
+[patch.crates-io]
+godot = { git = "https://github.com/JoeyEamigh/gdext", features = ["api-4-5"] }
diff --git a/rust/build.rs b/rust/build.rs
index 99ebad5..678d6b5 100644
--- a/rust/build.rs
+++ b/rust/build.rs
@@ -1,18 +1,26 @@
use gdext_gen::prelude::*;
-use std::io::Result;
+use spirv_builder::{MetadataPrintout, SpirvBuilder};
+
+fn main() -> Result<(), Box> {
+ SpirvBuilder::new("./shaders/tilemap", "spirv-unknown-spv1.6")
+ .print_metadata(MetadataPrintout::Full)
+ .build()?;
+
+ SpirvBuilder::new("./shaders/noisemap", "spirv-unknown-spv1.6")
+ .print_metadata(MetadataPrintout::Full)
+ .build()?;
-fn main() -> Result<()> {
// All your variable initialization and setup goes here.
generate_gdextension_file(
BaseDirectory::ProjectFolder,
Some("../rust/target".into()),
- Some("../godot/Rust.gdextension".into()),
+ Some("../godot/rust.gdextension".into()),
true,
Some(Configuration::new(
EntrySymbol::GodotRustDefault,
Some((4, 1)),
None,
- false,
+ true,
false,
)),
Some(WindowsABI::MSVC),
diff --git a/rust/rust-toolchain.toml b/rust/rust-toolchain.toml
new file mode 100644
index 0000000..1e4ccc6
--- /dev/null
+++ b/rust/rust-toolchain.toml
@@ -0,0 +1,13 @@
+[toolchain]
+channel = "nightly-2025-06-30"
+components = [
+ "rustc",
+ "cargo",
+ "rustfmt",
+ "clippy",
+ "rust-std",
+ "rust-analyzer",
+ "rust-src",
+ "llvm-tools",
+ "rustc-dev",
+]
diff --git a/rust/shaders/noisemap/Cargo.toml b/rust/shaders/noisemap/Cargo.toml
new file mode 100644
index 0000000..dcb0f6b
--- /dev/null
+++ b/rust/shaders/noisemap/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "noisemap"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+crate-type = ["dylib"]
+
+[dependencies]
+glam = { version = ">=0.22, <=0.30.7", default-features = false, features = [
+ "libm",
+] }
+spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "56cc13214566af452de71daf39f7b3f2c65551c2", package = "spirv-std" }
+libm = { version = "0.2", default-features = false }
diff --git a/rust/shaders/noisemap/src/lib.rs b/rust/shaders/noisemap/src/lib.rs
new file mode 100644
index 0000000..3dcdd94
--- /dev/null
+++ b/rust/shaders/noisemap/src/lib.rs
@@ -0,0 +1,111 @@
+#![no_std]
+// HACK(eddyb) can't easily see warnings otherwise from `spirv-builder` builds.
+#![deny(warnings)]
+#![allow(unexpected_cfgs)]
+
+use libm::{fabsf, floorf};
+use spirv_std::{RuntimeArray, glam::UVec3, spirv};
+
+#[repr(C)]
+pub struct NoiseParams {
+ pub map_width: u32,
+ pub map_height: u32,
+ pub seed: u32,
+ pub _pad0: u32,
+ pub noise_scale: f32,
+ pub noise_magnitude: f32,
+ pub _pad1: f32,
+ pub _pad2: f32,
+}
+
+#[inline(always)]
+fn fade(t: f32) -> f32 {
+ t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
+}
+
+#[inline(always)]
+fn lerp(a: f32, b: f32, t: f32) -> f32 {
+ a + (b - a) * t
+}
+
+#[inline(always)]
+fn hash(seed: u32, x: i32, y: i32) -> u32 {
+ let mut v = seed ^ (x as u32).wrapping_mul(0x27d4_eb2d);
+ v = v.wrapping_add((y as u32).wrapping_mul(0x1656_67b1));
+ v ^= v >> 15;
+ v = v.wrapping_mul(0x85eb_ca6b);
+ v ^= v >> 13;
+ v = v.wrapping_mul(0xc2b2_ae35);
+ v ^ (v >> 16)
+}
+
+#[inline(always)]
+fn grad(hash: u32, x: f32, y: f32) -> f32 {
+ let h = (hash & 0x7) as i32;
+ let u = if h < 4 { x } else { y };
+ let v = if h < 4 { y } else { x };
+ let mut result = if (h & 1) == 0 { u } else { -u };
+ result += if (h & 2) == 0 { v } else { -v };
+ result
+}
+
+#[inline(always)]
+fn clampf(value: f32, min: f32, max: f32) -> f32 {
+ if value < min {
+ min
+ } else if value > max {
+ max
+ } else {
+ value
+ }
+}
+
+#[spirv(compute(threads(16, 16, 1)))]
+pub fn main(
+ #[spirv(global_invocation_id)] id: UVec3,
+ #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] params: &NoiseParams,
+ #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] output_map: &mut RuntimeArray,
+) {
+ let x = id.x;
+ let y = id.y;
+
+ if x >= params.map_width || y >= params.map_height {
+ return;
+ }
+
+ let width = params.map_width;
+ let index = (y * width + x) as usize;
+
+ let scale = if params.noise_scale.abs() < 0.0001 {
+ 1.0
+ } else {
+ params.noise_scale
+ };
+ let sample_x = (x as f32 + 0.5) * scale;
+ let sample_y = (y as f32 + 0.5) * scale;
+
+ let ix = floorf(sample_x) as i32;
+ let iy = floorf(sample_y) as i32;
+ let fx = clampf(sample_x - ix as f32, 0.0, 1.0);
+ let fy = clampf(sample_y - iy as f32, 0.0, 1.0);
+
+ let wx = fade(fx);
+ let wy = fade(fy);
+
+ let v00 = grad(hash(params.seed, ix, iy), fx, fy);
+ let v10 = grad(hash(params.seed, ix + 1, iy), fx - 1.0, fy);
+ let v01 = grad(hash(params.seed, ix, iy + 1), fx, fy - 1.0);
+ let v11 = grad(hash(params.seed, ix + 1, iy + 1), fx - 1.0, fy - 1.0);
+
+ let nx0 = lerp(v00, v10, wx);
+ let nx1 = lerp(v01, v11, wx);
+ let value = clampf(lerp(nx0, nx1, wy) * 0.707_106_77, -1.0, 1.0);
+
+ let magnitude = fabsf(params.noise_magnitude);
+ let noise = fabsf(value) * magnitude;
+ let clamped = clampf(noise, 0.0, 2_147_483_647.0);
+
+ unsafe {
+ *output_map.index_mut(index) = floorf(clamped) as u32;
+ }
+}
diff --git a/rust/shaders/tilemap/Cargo.toml b/rust/shaders/tilemap/Cargo.toml
new file mode 100644
index 0000000..612f9ab
--- /dev/null
+++ b/rust/shaders/tilemap/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "tilemap"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+crate-type = ["dylib"]
+
+[dependencies]
+glam = { version = ">=0.22, <=0.30.7", default-features = false }
+spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu", rev = "56cc13214566af452de71daf39f7b3f2c65551c2", package = "spirv-std" }
diff --git a/rust/shaders/tilemap/src/lib.rs b/rust/shaders/tilemap/src/lib.rs
new file mode 100644
index 0000000..3d24c7a
--- /dev/null
+++ b/rust/shaders/tilemap/src/lib.rs
@@ -0,0 +1,89 @@
+#![no_std]
+// HACK(eddyb) can't easily see warnings otherwise from `spirv-builder` builds.
+#![deny(warnings)]
+#![allow(unexpected_cfgs)]
+
+use spirv_std::{RuntimeArray, glam::UVec3, spirv};
+
+#[repr(C)]
+pub struct TilemapParams {
+ pub out_width: u32,
+ pub out_height: u32,
+ pub tile_size: u32,
+ pub map_width: u32,
+ pub map_height: u32,
+ pub atlas_width: u32,
+ pub atlas_height: u32,
+}
+
+// 8x8 workgroup covers 64 pixels per dispatch group
+#[spirv(compute(threads(8, 8, 1)))]
+pub fn main(
+ #[spirv(global_invocation_id)] id: UVec3,
+ #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] params: &TilemapParams,
+ #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] map: &RuntimeArray,
+ #[spirv(storage_buffer, descriptor_set = 0, binding = 2)] atlas_rgba: &RuntimeArray,
+ #[spirv(storage_buffer, descriptor_set = 0, binding = 3)] output_rgba: &mut RuntimeArray,
+) {
+ let x = id.x;
+ let y = id.y;
+
+ if params.tile_size == 0 || x >= params.out_width || y >= params.out_height {
+ return;
+ }
+
+ let tile_size = params.tile_size;
+ let tile_x = x / tile_size;
+ let tile_y = y / tile_size;
+
+ if tile_x >= params.map_width || tile_y >= params.map_height {
+ return;
+ }
+
+ if params.atlas_width < tile_size {
+ return;
+ }
+
+ let atlas_tiles_per_row = params.atlas_width / tile_size;
+ if atlas_tiles_per_row == 0 {
+ return;
+ }
+
+ let map_index = tile_y * params.map_width + tile_x;
+ let map_capacity = params.map_width * params.map_height;
+ if map_index >= map_capacity {
+ return;
+ }
+
+ let tile_index = unsafe { *map.index(map_index as usize) };
+
+ let atlas_tile_x = tile_index % atlas_tiles_per_row;
+ let atlas_tile_y = tile_index / atlas_tiles_per_row;
+
+ let local_x = x % tile_size;
+ let local_y = y % tile_size;
+
+ let atlas_x = atlas_tile_x * tile_size + local_x;
+ let atlas_y = atlas_tile_y * tile_size + local_y;
+
+ if atlas_x >= params.atlas_width || atlas_y >= params.atlas_height {
+ return;
+ }
+
+ let atlas_width = params.atlas_width;
+ let dst_width = params.out_width;
+
+ let src_index = atlas_y * atlas_width + atlas_x;
+ let dst_index = y * dst_width + x;
+ let atlas_capacity = atlas_width * params.atlas_height;
+ let dst_capacity = params.out_width * params.out_height;
+
+ if src_index >= atlas_capacity || dst_index >= dst_capacity {
+ return;
+ }
+
+ let pixel = unsafe { *atlas_rgba.index(src_index as usize) };
+ unsafe {
+ *output_rgba.index_mut(dst_index as usize) = pixel;
+ }
+}
diff --git a/rust/src/Mappy.rs b/rust/src/Mappy.rs
deleted file mode 100644
index 2d9b327..0000000
--- a/rust/src/Mappy.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-use std::time::{SystemTime, UNIX_EPOCH};
-
-use godot::classes::base_material_3d::{ShadingMode, TextureParam};
-use godot::classes::image::Format;
-use godot::classes::mesh::ArrayType;
-use godot::classes::{
- BaseMaterial3D, Camera3D, CompressedTexture2D, FastNoiseLite, IMeshInstance3D, Image, ImageTexture, Input,
- MeshInstance3D, QuadMesh, StandardMaterial3D, Texture2D,
-};
-use godot::global::Key;
-use godot::meta::ByOption;
-use godot::prelude::*;
-
-#[derive(GodotClass)]
-#[class(base=MeshInstance3D)]
-struct Mappy {
- #[export]
- map_width: i32,
- #[export]
- map_height: i32,
- #[export]
- noise_scale: f32,
- #[export]
- noise_magnitude: f32,
- #[export]
- texture_atlas: OnEditor>,
-
- map: Array,
-
- is_generated: bool,
-
- // required
- base: Base,
-}
-
-#[godot_api]
-impl IMeshInstance3D for Mappy {
- fn init(base: Base) -> Self {
- let mut standard_array: Array = Array::new();
- let width: i32 = 32;
- let height: i32 = 32;
- standard_array.resize((width * height) as usize, 0);
-
- Self {
- map_width: width,
- map_height: height,
- noise_scale: 1.0,
- noise_magnitude: 5.0,
- texture_atlas: OnEditor::default(),
- map: standard_array,
- is_generated: false,
- base,
- }
- }
-
- fn ready(&mut self) {
- self.is_generated = false;
- self.generateMap();
- self.createTexture();
- }
-
- fn process(&mut self, delta: f32) {
- let input = Input::singleton();
- if (input.is_action_just_pressed("ui_accept")) {
- self.generateMap();
- self.createTexture();
- }
- }
-}
-
-impl Mappy {
- fn generateMap(&mut self) {
- let mut new_map: Array = Array::new();
-
- let mut noise = FastNoiseLite::new_gd();
- let now = SystemTime::now();
- let duration_since_epoch = now.duration_since(UNIX_EPOCH).expect("Time went backwards"); // Handle potential errors if the system clock is adjusted
-
- let milliseconds = duration_since_epoch.as_millis();
- noise.set_seed(milliseconds as i32);
- new_map.resize((self.map_width * self.map_height) as usize, 0);
- for x in 0..self.map_width {
- for y in 0..self.map_height {
- new_map.set(
- (x + y * self.map_height) as usize,
- (noise.get_noise_2d(x as f32 * self.noise_scale, y as f32 * self.noise_scale) * self.noise_magnitude).abs()
- as i32,
- );
- }
- }
- self.map = new_map;
- }
-
- fn createTexture(&mut self) {
- if (self.is_generated) {
- let old_node = self.base().get_node_as::("Map Quad");
- self.base_mut().remove_child(&old_node);
- old_node.free();
- }
-
- self.is_generated = true;
- let mut quad = QuadMesh::new_gd();
- quad.set_size(Vector2::new(1.0, 1.0));
- let mut mat = StandardMaterial3D::new_gd();
- mat.set_shading_mode(ShadingMode::UNSHADED);
-
- let use_mipmaps = false;
- let format = Format::RGBA8; // Or another desired format
-
- let mut image = Image::create(self.map_width * 16, self.map_height * 16, use_mipmaps, format)
- .expect("Couldn't create texture for map");
- let atlas = self.texture_atlas.get_image();
-
- // Example: Fill the image with a solid color
- for i in 0..self.map_width {
- for j in 0..self.map_height {
- let map_index = self.map.at((i + j * self.map_width) as usize);
- image.blit_rect(
- atlas.as_ref(),
- Rect2i::new(
- Vector2i::new(map_index % 16 * 16, (map_index / 16) * 16),
- Vector2i::new(16, 16),
- ),
- Vector2i::new(i * 16, j * 16),
- );
- }
- }
-
- let mut tex = ImageTexture::new_gd();
- tex.set_image(&image);
-
- mat.set_texture(TextureParam::ALBEDO, &tex);
- quad.set_material(&mat);
-
- let mut mesh = MeshInstance3D::new_alloc();
- mesh.set_name("Map Quad");
- mesh.set_mesh(&quad);
- self.base_mut().add_child(&mesh);
- }
-}
-
-#[derive(GodotClass)]
-#[class(base=Node3D)]
-struct Player {
- #[export]
- character_name: GString,
- #[export]
- velocity: Vector3,
- #[export]
- acceleration: Vector3,
- base: Base,
-}
-
-#[godot_api]
-impl INode3D for Player {
- fn init(base: Base) -> Self {
- Self {
- character_name: GString::from("Hello"),
- velocity: Vector3::ZERO,
- acceleration: Vector3::new(1.0, 1.0, 0.0),
- base,
- }
- }
-
- fn process(&mut self, delta: f32) {
- self.processInput(delta);
- self.updatePosition(delta);
- }
-}
-
-impl Player {
- fn processInput(&mut self, delta: f32) {
- let input = Input::singleton();
- if (input.is_key_pressed(Key::W)) {
- self.velocity.y += self.acceleration.y;
- }
- if (input.is_key_pressed(Key::A)) {
- self.velocity.x -= self.acceleration.x;
- }
- if (input.is_key_pressed(Key::S)) {
- self.velocity.y -= self.acceleration.y;
- }
- if (input.is_key_pressed(Key::D)) {
- self.velocity.x += self.acceleration.x;
- }
- }
-
- fn updatePosition(&mut self, delta: f32) {
- let before_move = self.base_mut().get_transform();
- let after_move = before_move.translated(self.velocity * delta);
- self.base_mut().set_transform(after_move);
- self.velocity *= 0.5;
- }
-}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index e325b3e..fc6de84 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -3,6 +3,7 @@ use godot::{classes::Engine, prelude::*};
mod runtime;
mod Mappy;
+mod player;
struct MyExtension;
diff --git a/rust/src/mappy.rs b/rust/src/mappy.rs
new file mode 100644
index 0000000..69dcfce
--- /dev/null
+++ b/rust/src/mappy.rs
@@ -0,0 +1,1133 @@
+use std::time::{Instant, SystemTime, UNIX_EPOCH};
+
+use bytemuck::{Pod, Zeroable, bytes_of, cast_slice};
+use godot::builtin::PackedByteArray;
+use godot::classes::base_material_3d::{ShadingMode, TextureParam};
+use godot::classes::image::Format;
+use godot::classes::rendering_device::{BufferCreationBits, ShaderStage, UniformType};
+use godot::classes::{
+ CompressedTexture2D, FastNoiseLite, IMeshInstance3D, Image, ImageTexture, Input, MeshInstance3D, QuadMesh,
+ RdShaderSpirv, RdUniform, RenderingDevice, RenderingServer, StandardMaterial3D,
+};
+use godot::prelude::*;
+
+const TILEMAP_SHADER: &[u8] = include_bytes!(env!("tilemap.spv"));
+const NOISE_SHADER: &[u8] = include_bytes!(env!("noisemap.spv"));
+const TILE_SIZE: u32 = 16;
+
+#[derive(GodotClass)]
+#[class(base=MeshInstance3D)]
+struct Mappy {
+ #[export]
+ map_width: i32,
+ #[export]
+ map_height: i32,
+ #[export]
+ noise_scale: f32,
+ #[export]
+ noise_magnitude: f32,
+ #[export]
+ texture_atlas: OnEditor>,
+
+ map: Array,
+
+ is_generated: bool,
+ map_mesh: Option>,
+ map_texture: Option>,
+ gpu_context: Option,
+
+ // required
+ base: Base,
+}
+
+#[godot_api]
+impl IMeshInstance3D for Mappy {
+ fn init(base: Base) -> Self {
+ let mut standard_array: Array = Array::new();
+ let width: i32 = 32;
+ let height: i32 = 32;
+ standard_array.resize((width * height) as usize, 0);
+
+ Self {
+ map_width: width,
+ map_height: height,
+ noise_scale: 1.0,
+ noise_magnitude: 5.0,
+ texture_atlas: OnEditor::default(),
+ map: standard_array,
+ is_generated: false,
+ map_mesh: None,
+ map_texture: None,
+ gpu_context: None,
+ base,
+ }
+ }
+
+ fn ready(&mut self) {
+ self.is_generated = false;
+ self.generate_map();
+ self.create_texture();
+ }
+
+ fn process(&mut self, _delta: f32) {
+ let input = Input::singleton();
+ if input.is_action_just_pressed("ui_accept") {
+ self.generate_map();
+ self.create_texture();
+ }
+ }
+}
+
+impl Mappy {
+ fn generate_map(&mut self) {
+ let now = SystemTime::now();
+ let duration_since_epoch = match now.duration_since(UNIX_EPOCH) {
+ Ok(duration) => duration,
+ Err(err) => {
+ godot_error!("Time went backwards during map generation: {}", err);
+ return;
+ }
+ };
+
+ let milliseconds = duration_since_epoch.as_millis();
+ let seed_u32 = (milliseconds & 0xFFFF_FFFF) as u32;
+ let seed_i32 = milliseconds as i128;
+ let seed_i32 = seed_i32 as i32;
+
+ godot_print!(
+ "Generating noise map {}x{} tiles (scale {:.2}, magnitude {:.2}, seed={})",
+ self.map_width,
+ self.map_height,
+ self.noise_scale,
+ self.noise_magnitude,
+ milliseconds
+ );
+
+ self.generate_map_cpu(seed_i32);
+ // if let Err(gpu_err) = self.generate_map_gpu(seed_u32) {
+ // godot_warn!("GPU noise generation failed: {}", gpu_err);
+ // self.gpu_context = None;
+ // self.generate_map_cpu(seed_i32);
+ // }
+ }
+
+ fn compute_context_mut(&mut self) -> Result<&mut GpuComputeContext, String> {
+ if self.gpu_context.is_none() {
+ self.gpu_context = Some(GpuComputeContext::new()?);
+ }
+ Ok(
+ self
+ .gpu_context
+ .as_mut()
+ .expect("GPU context must exist after initialization"),
+ )
+ }
+
+ fn generate_map_gpu(&mut self, seed: u32) -> Result<(), String> {
+ let map_width = self.map_width;
+ let map_height = self.map_height;
+ let noise_scale = self.noise_scale;
+ let noise_magnitude = self.noise_magnitude;
+
+ if map_width <= 0 || map_height <= 0 {
+ return Err("Map dimensions must be positive".to_string());
+ }
+
+ let tile_count = (map_width as usize)
+ .checked_mul(map_height as usize)
+ .ok_or_else(|| "Tile count overflow".to_string())?;
+
+ let params = NoiseGpuParams {
+ map_width: map_width as u32,
+ map_height: map_height as u32,
+ seed,
+ _pad0: 0,
+ noise_scale,
+ noise_magnitude: noise_magnitude.abs(),
+ _pad1: 0.0,
+ _pad2: 0.0,
+ };
+
+ let gpu_start = Instant::now();
+ let values = {
+ let context = self.compute_context_mut()?;
+ context.generate_noise(¶ms)?
+ };
+ if values.len() != tile_count {
+ return Err(format!(
+ "GPU noise output length mismatch. Expected {}, got {}",
+ tile_count,
+ values.len()
+ ));
+ }
+
+ let mut new_map: Array = Array::new();
+ new_map.resize(tile_count, 0);
+ for (index, value) in values.iter().enumerate() {
+ let clamped = (*value).min(i32::MAX as u32);
+ new_map.set(index, clamped as i32);
+ }
+ self.map = new_map;
+
+ let total_ms = gpu_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!(
+ "GPU noise map generation complete for {} tiles in {:.2} ms",
+ self.map.len(),
+ total_ms
+ );
+
+ Ok(())
+ }
+
+ fn generate_map_cpu(&mut self, seed: i32) {
+ let overall_start = Instant::now();
+ let mut new_map: Array = Array::new();
+ new_map.resize((self.map_width * self.map_height) as usize, 0);
+
+ let mut noise = FastNoiseLite::new_gd();
+ noise.set_seed(seed);
+
+ let fill_start = Instant::now();
+ for x in 0..self.map_width {
+ for y in 0..self.map_height {
+ new_map.set(
+ (x + y * self.map_height) as usize,
+ (noise.get_noise_2d(x as f32 * self.noise_scale, y as f32 * self.noise_scale) * self.noise_magnitude).abs()
+ as i32,
+ );
+ }
+ }
+ self.map = new_map;
+
+ let fill_ms = fill_start.elapsed().as_secs_f64() * 1000.0;
+ let total_ms = overall_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!(
+ "CPU noise generation complete for {} tiles in {:.2} ms (noise fill {:.2} ms)",
+ self.map.len(),
+ total_ms,
+ fill_ms
+ );
+ }
+
+ fn create_texture(&mut self) {
+ let overall_start = Instant::now();
+
+ if self.map_mesh.as_ref().is_some_and(|mesh| !mesh.is_instance_valid()) {
+ self.map_mesh = None;
+ }
+
+ if self
+ .map_texture
+ .as_ref()
+ .is_some_and(|texture| !texture.is_instance_valid())
+ {
+ self.map_texture = None;
+ }
+
+ let Some(mut atlas_image) = self.texture_atlas.get_image() else {
+ godot_error!("Texture atlas has no readable image data");
+ return;
+ };
+
+ let atlas_prepare_start = Instant::now();
+ if atlas_image.get_format() != Format::RGBA8 {
+ atlas_image.convert(Format::RGBA8);
+ }
+
+ let atlas_width = atlas_image.get_width();
+ let atlas_height = atlas_image.get_height();
+ godot_print!(
+ "Creating tilemap texture: map {}x{} tiles, tile_size {}, atlas {}x{} px",
+ self.map_width,
+ self.map_height,
+ TILE_SIZE,
+ atlas_width,
+ atlas_height
+ );
+ let atlas_prepare_ms = atlas_prepare_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!("Atlas fetch + convert finished in {:.2} ms", atlas_prepare_ms);
+
+ let map_clone = self.map.clone();
+ let map_len = map_clone.len();
+ let map_width = self.map_width;
+ let map_height = self.map_height;
+
+ let gpu_start = Instant::now();
+ let gpu_result = {
+ match self.compute_context_mut() {
+ Ok(context) => {
+ godot_print!("Attempting GPU tilemap generation for {} tiles", map_len);
+ Self::generate_texture_gpu(context, &map_clone, map_width, map_height, TILE_SIZE, &atlas_image)
+ }
+ Err(err) => Err(err),
+ }
+ };
+ let image = match gpu_result {
+ Ok(image) => {
+ let gpu_ms = gpu_start.elapsed().as_secs_f64() * 1000.0;
+ let width = image.get_width();
+ let height = image.get_height();
+ godot_print!(
+ "Generated tilemap texture on GPU in {:.2} ms ({}x{} px)",
+ gpu_ms,
+ width,
+ height
+ );
+ image
+ }
+ Err(gpu_err) => {
+ let gpu_ms = gpu_start.elapsed().as_secs_f64() * 1000.0;
+ godot_warn!("GPU tilemap generation failed after {:.2} ms: {}", gpu_ms, gpu_err);
+ godot_print!("Falling back to CPU tilemap generation path");
+ self.gpu_context = None;
+ let cpu_start = Instant::now();
+ match Self::create_texture_cpu(
+ &self.map,
+ self.map_width,
+ self.map_height,
+ TILE_SIZE,
+ &atlas_image,
+ Format::RGBA8,
+ ) {
+ Ok(image) => {
+ let cpu_ms = cpu_start.elapsed().as_secs_f64() * 1000.0;
+ let width = image.get_width();
+ let height = image.get_height();
+ godot_print!(
+ "Generated tilemap texture on CPU fallback in {:.2} ms ({}x{} px)",
+ cpu_ms,
+ width,
+ height
+ );
+ image
+ }
+ Err(cpu_err) => {
+ let cpu_ms = cpu_start.elapsed().as_secs_f64() * 1000.0;
+ godot_error!("CPU tilemap generation failed after {:.2} ms: {}", cpu_ms, cpu_err);
+ return;
+ }
+ }
+ }
+ };
+
+ let texture_handle = if let Some(tex) = self.map_texture.as_mut() {
+ tex.update(&image);
+ tex.clone()
+ } else {
+ let mut tex = ImageTexture::new_gd();
+ tex.set_image(&image);
+ let tex_clone = tex.clone();
+ self.map_texture = Some(tex);
+ tex_clone
+ };
+
+ let mut quad = QuadMesh::new_gd();
+ quad.set_size(Vector2::new(1.0, 1.0));
+ let mut mat = StandardMaterial3D::new_gd();
+ mat.set_shading_mode(ShadingMode::UNSHADED);
+ mat.set_texture(TextureParam::ALBEDO, &texture_handle);
+ quad.set_material(&mat);
+
+ if let Some(mesh) = self.map_mesh.as_mut() {
+ mesh.set_mesh(&quad);
+ } else {
+ let mut mesh = MeshInstance3D::new_alloc();
+ mesh.set_name("Map Quad");
+ mesh.set_mesh(&quad);
+ self.base_mut().add_child(&mesh);
+ self.map_mesh = Some(mesh);
+ }
+
+ self.is_generated = true;
+
+ let total_ms = overall_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!("create_texture() finished in {:.2} ms", total_ms);
+ }
+
+ fn generate_texture_gpu(
+ context: &mut GpuComputeContext,
+ map: &Array,
+ map_width: i32,
+ map_height: i32,
+ tile_size: u32,
+ atlas: &Gd,
+ ) -> Result, String> {
+ if tile_size == 0 {
+ return Err("Tile size must be positive".to_string());
+ }
+
+ if map_width <= 0 || map_height <= 0 {
+ return Err("Map dimensions must be positive".to_string());
+ }
+
+ let map_width_u32 = map_width as u32;
+ let map_height_u32 = map_height as u32;
+ let out_width = map_width_u32
+ .checked_mul(tile_size)
+ .ok_or_else(|| "Texture width overflow".to_string())?;
+ let out_height = map_height_u32
+ .checked_mul(tile_size)
+ .ok_or_else(|| "Texture height overflow".to_string())?;
+
+ if out_width == 0 || out_height == 0 {
+ return Err("Texture dimensions resolve to zero".to_string());
+ }
+
+ let atlas_width = atlas.get_width();
+ let atlas_height = atlas.get_height();
+ if atlas_width <= 0 || atlas_height <= 0 {
+ return Err("Atlas has invalid dimensions".to_string());
+ }
+
+ let atlas_width_u32 = atlas_width as u32;
+ let atlas_height_u32 = atlas_height as u32;
+
+ if !atlas_width_u32.is_multiple_of(tile_size) || !atlas_height_u32.is_multiple_of(tile_size) {
+ return Err("Atlas dimensions must be multiples of the tile size".to_string());
+ }
+
+ let atlas_bytes = atlas.get_data().to_vec();
+ let expected_atlas_bytes = (atlas_width as usize)
+ .checked_mul(atlas_height as usize)
+ .and_then(|v| v.checked_mul(4))
+ .ok_or_else(|| "Atlas size overflow".to_string())?;
+
+ if atlas_bytes.len() != expected_atlas_bytes {
+ return Err(format!(
+ "Atlas data size mismatch. Expected {}, got {}",
+ expected_atlas_bytes,
+ atlas_bytes.len()
+ ));
+ }
+
+ let map_vec = Self::map_to_vec(map, map_width_u32, map_height_u32)?;
+
+ let params = TilemapGpuParams {
+ out_width,
+ out_height,
+ tile_size,
+ map_width: map_width_u32,
+ map_height: map_height_u32,
+ atlas_width: atlas_width as u32,
+ atlas_height: atlas_height as u32,
+ };
+
+ let gpu_bytes = context.run_tilemap_compute(¶ms, &map_vec, &atlas_bytes)?;
+
+ Image::create_from_data(out_width as i32, out_height as i32, false, Format::RGBA8, &gpu_bytes)
+ .ok_or_else(|| "Failed to create image from GPU output".to_string())
+ }
+
+ fn create_texture_cpu(
+ map: &Array,
+ map_width: i32,
+ map_height: i32,
+ tile_size: u32,
+ atlas: &Gd,
+ format: Format,
+ ) -> Result, String> {
+ if tile_size == 0 {
+ return Err("Tile size must be positive".to_string());
+ }
+
+ let map_width_u32 = map_width as u32;
+ let map_height_u32 = map_height as u32;
+
+ let out_width = map_width_u32
+ .checked_mul(tile_size)
+ .ok_or_else(|| "CPU texture width overflow".to_string())?;
+ let out_height = map_height_u32
+ .checked_mul(tile_size)
+ .ok_or_else(|| "CPU texture height overflow".to_string())?;
+
+ if out_width == 0 || out_height == 0 {
+ return Err("CPU output dimensions resolve to zero".to_string());
+ }
+
+ let atlas_width = atlas.get_width();
+ let atlas_height = atlas.get_height();
+
+ if atlas_width <= 0 || atlas_height <= 0 {
+ return Err("Atlas has invalid dimensions".to_string());
+ }
+
+ let atlas_width_u32 = atlas_width as u32;
+ let atlas_height_u32 = atlas_height as u32;
+
+ if !atlas_width_u32.is_multiple_of(tile_size) || !atlas_height_u32.is_multiple_of(tile_size) {
+ return Err("Atlas dimensions must be multiples of the tile size".to_string());
+ }
+
+ let atlas_bytes = atlas.get_data().to_vec();
+ let expected_atlas_bytes = (atlas_width as usize)
+ .checked_mul(atlas_height as usize)
+ .and_then(|v| v.checked_mul(4))
+ .ok_or_else(|| "Atlas size overflow".to_string())?;
+
+ if atlas_bytes.len() != expected_atlas_bytes {
+ return Err(format!(
+ "Atlas data size mismatch. Expected {}, got {}",
+ expected_atlas_bytes,
+ atlas_bytes.len()
+ ));
+ }
+
+ let map_vec = Self::map_to_vec(map, map_width_u32, map_height_u32)?;
+
+ let output_size = (out_width as usize)
+ .checked_mul(out_height as usize)
+ .and_then(|v| v.checked_mul(4))
+ .ok_or_else(|| "CPU output size overflow".to_string())?;
+
+ let mut output = vec![0u8; output_size];
+
+ let tile_size_usize = tile_size as usize;
+ let atlas_width_usize = atlas_width_u32 as usize;
+ let out_width_usize = out_width as usize;
+ let atlas_tiles_per_row = atlas_width_usize / tile_size_usize;
+
+ if atlas_tiles_per_row == 0 {
+ return Err("Atlas cannot fit any tiles".to_string());
+ }
+
+ for tile_y in 0..map_height_u32 as usize {
+ for tile_x in 0..map_width_u32 as usize {
+ let tile_index = map_vec[tile_y * map_width_u32 as usize + tile_x] as usize;
+
+ let atlas_tile_x = tile_index % atlas_tiles_per_row;
+ let atlas_tile_y = tile_index / atlas_tiles_per_row;
+
+ for local_y in 0..tile_size_usize {
+ let dst_row = tile_y * tile_size_usize + local_y;
+ let src_row = atlas_tile_y * tile_size_usize + local_y;
+
+ let dst_offset = (dst_row * out_width_usize + tile_x * tile_size_usize) * 4;
+ let src_offset = (src_row * atlas_width_usize + atlas_tile_x * tile_size_usize) * 4;
+
+ let bytes_per_row = tile_size_usize * 4;
+
+ let dst_slice = dst_offset..dst_offset + bytes_per_row;
+ let src_slice = src_offset..src_offset + bytes_per_row;
+
+ if dst_slice.end > output.len() || src_slice.end > atlas_bytes.len() {
+ return Err("Tile copy exceeded buffer bounds".to_string());
+ }
+
+ output[dst_slice].copy_from_slice(&atlas_bytes[src_slice]);
+ }
+ }
+ }
+
+ let packed = PackedByteArray::from(output);
+
+ Image::create_from_data(out_width as i32, out_height as i32, false, format, &packed)
+ .ok_or_else(|| "Failed to create CPU image".to_string())
+ }
+
+ fn map_to_vec(map: &Array, map_width: u32, map_height: u32) -> Result, String> {
+ let expected = (map_width as usize)
+ .checked_mul(map_height as usize)
+ .ok_or_else(|| "Tilemap size overflow".to_string())?;
+
+ if map.len() != expected {
+ return Err(format!("Tilemap has {} entries but expected {}", map.len(), expected));
+ }
+
+ let mut result = Vec::with_capacity(expected);
+ for index in 0..expected {
+ let value = map.at(index);
+ result.push(if value < 0 { 0 } else { value as u32 });
+ }
+
+ Ok(result)
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+struct TilemapGpuParams {
+ out_width: u32,
+ out_height: u32,
+ tile_size: u32,
+ map_width: u32,
+ map_height: u32,
+ atlas_width: u32,
+ atlas_height: u32,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Pod, Zeroable)]
+struct NoiseGpuParams {
+ map_width: u32,
+ map_height: u32,
+ seed: u32,
+ _pad0: u32,
+ noise_scale: f32,
+ noise_magnitude: f32,
+ _pad1: f32,
+ _pad2: f32,
+}
+
+struct GpuComputeContext {
+ device: Gd,
+ tilemap_shader: Rid,
+ tilemap_pipeline: Rid,
+ noise_shader: Rid,
+ noise_pipeline: Rid,
+}
+
+impl GpuComputeContext {
+ fn new() -> Result {
+ let mut device = match RenderingServer::singleton().create_local_rendering_device() {
+ Some(device) => device,
+ None => RenderingServer::singleton()
+ .get_rendering_device()
+ .ok_or_else(|| "Unable to acquire RenderingDevice".to_string())?,
+ };
+
+ let (tilemap_shader, tilemap_pipeline) = Self::create_pipeline(&mut device, TILEMAP_SHADER)?;
+ let (noise_shader, noise_pipeline) = Self::create_pipeline(&mut device, NOISE_SHADER)?;
+
+ godot_print!("Initialized GPU compute context for tilemap + noise");
+
+ Ok(Self {
+ device,
+ tilemap_shader,
+ tilemap_pipeline,
+ noise_shader,
+ noise_pipeline,
+ })
+ }
+
+ fn create_pipeline(device: &mut Gd, spirv: &[u8]) -> Result<(Rid, Rid), String> {
+ if !spirv.len().is_multiple_of(4) {
+ return Err("SPIR-V blob size is not a multiple of 4".to_string());
+ }
+
+ let shader_bytes = PackedByteArray::from(spirv.to_vec());
+ let mut shader_spirv = RdShaderSpirv::new_gd();
+ shader_spirv.set_stage_bytecode(ShaderStage::COMPUTE, &shader_bytes);
+
+ let shader_rid = device.shader_create_from_spirv(&shader_spirv);
+ if !shader_rid.is_valid() {
+ return Err("Failed to create compute shader".to_string());
+ }
+
+ let pipeline_rid = device.compute_pipeline_create(shader_rid);
+ if !pipeline_rid.is_valid() {
+ device.free_rid(shader_rid);
+ return Err("Failed to create compute pipeline".to_string());
+ }
+
+ Ok((shader_rid, pipeline_rid))
+ }
+
+ fn run_tilemap_compute(
+ &mut self,
+ params: &TilemapGpuParams,
+ map_data: &[u32],
+ atlas_data: &[u8],
+ ) -> Result {
+ godot_print!(
+ "run_tilemap_compute: output {}x{} px, tile_size {}, map {}x{} tiles, atlas {}x{} px",
+ params.out_width,
+ params.out_height,
+ params.tile_size,
+ params.map_width,
+ params.map_height,
+ params.atlas_width,
+ params.atlas_height
+ );
+
+ if params.tile_size == 0 {
+ return Err("Tile size cannot be zero".to_string());
+ }
+
+ if params.map_width == 0 || params.map_height == 0 {
+ return Err("Map dimensions cannot be zero".to_string());
+ }
+
+ if params.atlas_width == 0 || params.atlas_height == 0 {
+ return Err("Atlas dimensions cannot be zero".to_string());
+ }
+
+ let expected_map_entries = (params.map_width as usize)
+ .checked_mul(params.map_height as usize)
+ .ok_or_else(|| "Map size overflow".to_string())?;
+
+ if map_data.len() != expected_map_entries {
+ return Err(format!(
+ "Map buffer length mismatch. Expected {}, got {}",
+ expected_map_entries,
+ map_data.len()
+ ));
+ }
+
+ let expected_atlas_bytes = (params.atlas_width as usize)
+ .checked_mul(params.atlas_height as usize)
+ .and_then(|v| v.checked_mul(4))
+ .ok_or_else(|| "Atlas byte size overflow".to_string())?;
+
+ if atlas_data.len() != expected_atlas_bytes {
+ return Err(format!(
+ "Atlas buffer length mismatch. Expected {}, got {}",
+ expected_atlas_bytes,
+ atlas_data.len()
+ ));
+ }
+
+ if !atlas_data.len().is_multiple_of(4) {
+ return Err("Atlas buffer length must be a multiple of 4".to_string());
+ }
+
+ let output_size = (params.out_width as usize)
+ .checked_mul(params.out_height as usize)
+ .and_then(|v| v.checked_mul(4))
+ .ok_or_else(|| "Output texture is too large".to_string())?;
+
+ if output_size == 0 {
+ return Err("Output texture is empty".to_string());
+ }
+
+ let mut device = self.device.clone();
+ godot_print!("Using cached RenderingDevice for compute dispatch");
+
+ let mut allocated: Vec = Vec::new();
+
+ let result = (|| -> Result {
+ let setup_start = Instant::now();
+
+ let params_raw = bytes_of(params);
+ let params_size =
+ u32::try_from(params_raw.len()).map_err(|_| "Parameter buffer exceeds supported size".to_string())?;
+ let params_bytes = PackedByteArray::from(params_raw.to_vec());
+ let params_buffer = device
+ .storage_buffer_create_ex(params_size)
+ .data(¶ms_bytes)
+ .creation_bits(BufferCreationBits::AS_STORAGE_BIT)
+ .done();
+ if !params_buffer.is_valid() {
+ return Err("Failed to allocate parameter buffer".to_string());
+ }
+ allocated.push(params_buffer);
+
+ let map_raw: &[u8] = cast_slice(map_data);
+ let map_size = u32::try_from(map_raw.len()).map_err(|_| "Map buffer exceeds supported size".to_string())?;
+ let map_bytes = PackedByteArray::from(map_raw.to_vec());
+ let map_buffer = device
+ .storage_buffer_create_ex(map_size)
+ .data(&map_bytes)
+ .creation_bits(BufferCreationBits::AS_STORAGE_BIT)
+ .done();
+ if !map_buffer.is_valid() {
+ return Err("Failed to allocate map buffer".to_string());
+ }
+ allocated.push(map_buffer);
+
+ let atlas_size =
+ u32::try_from(atlas_data.len()).map_err(|_| "Atlas buffer exceeds supported size".to_string())?;
+ let atlas_bytes = PackedByteArray::from(atlas_data.to_vec());
+ let atlas_buffer = device
+ .storage_buffer_create_ex(atlas_size)
+ .data(&atlas_bytes)
+ .creation_bits(BufferCreationBits::AS_STORAGE_BIT)
+ .done();
+ if !atlas_buffer.is_valid() {
+ return Err("Failed to allocate atlas buffer".to_string());
+ }
+ allocated.push(atlas_buffer);
+
+ let output_size_u32 =
+ u32::try_from(output_size).map_err(|_| "Output buffer exceeds supported size".to_string())?;
+ let output_init = PackedByteArray::from(vec![0u8; output_size]);
+ let output_buffer = device
+ .storage_buffer_create_ex(output_size_u32)
+ .data(&output_init)
+ .creation_bits(BufferCreationBits::AS_STORAGE_BIT)
+ .done();
+ if !output_buffer.is_valid() {
+ return Err("Failed to allocate output buffer".to_string());
+ }
+ allocated.push(output_buffer);
+
+ let mut uniform_nodes: Vec> = Vec::new();
+ for (binding, rid) in [
+ (0_i32, params_buffer),
+ (1_i32, map_buffer),
+ (2_i32, atlas_buffer),
+ (3_i32, output_buffer),
+ ] {
+ let mut uniform = RdUniform::new_gd();
+ uniform.set_uniform_type(UniformType::STORAGE_BUFFER);
+ uniform.set_binding(binding);
+ uniform.add_id(rid);
+ uniform_nodes.push(uniform);
+ }
+
+ let mut uniforms: Array> = Array::new();
+ for uniform in &uniform_nodes {
+ uniforms.push(uniform);
+ }
+
+ let uniform_set = device.uniform_set_create(&uniforms, self.tilemap_shader, 0);
+ if !uniform_set.is_valid() {
+ return Err("Failed to create uniform set".to_string());
+ }
+ allocated.push(uniform_set);
+
+ let setup_ms = setup_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!("GPU pipeline + buffer setup finished in {:.2} ms", setup_ms);
+
+ let compute_list = device.compute_list_begin();
+ if compute_list < 0 {
+ return Err("Failed to begin compute list".to_string());
+ }
+
+ device.compute_list_bind_compute_pipeline(compute_list, self.tilemap_pipeline);
+ device.compute_list_bind_uniform_set(compute_list, uniform_set, 0);
+
+ let groups_x = params.out_width.saturating_add(7) / 8;
+ let groups_y = params.out_height.saturating_add(7) / 8;
+ let dispatch_x = groups_x.max(1);
+ let dispatch_y = groups_y.max(1);
+
+ device.compute_list_dispatch(compute_list, dispatch_x, dispatch_y, 1);
+ device.compute_list_end();
+ godot_print!(
+ "Dispatched compute shader with workgroups {}x{} (covering {}x{} px)",
+ dispatch_x,
+ dispatch_y,
+ params.out_width,
+ params.out_height
+ );
+
+ let dispatch_start = Instant::now();
+ device.submit();
+ device.sync();
+ let dispatch_ms = dispatch_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!("GPU dispatch + sync finished in {:.2} ms", dispatch_ms);
+
+ let readback_start = Instant::now();
+ let output_data = device.buffer_get_data(output_buffer);
+ let readback_ms = readback_start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!("GPU readback finished in {:.2} ms", readback_ms);
+ godot_print!("Compute shader completed; output contains {} bytes", output_data.len());
+
+ Ok(output_data)
+ })();
+
+ for rid in allocated.into_iter().rev() {
+ if rid.is_valid() {
+ device.free_rid(rid);
+ }
+ }
+
+ result
+ }
+
+ fn generate_noise(&mut self, params: &NoiseGpuParams) -> Result, String> {
+ if params.map_width == 0 || params.map_height == 0 {
+ return Err("Noise map dimensions cannot be zero".to_string());
+ }
+
+ let tile_count = (params.map_width as usize)
+ .checked_mul(params.map_height as usize)
+ .ok_or_else(|| "Noise tile count overflow".to_string())?;
+
+ let output_size_bytes = tile_count
+ .checked_mul(std::mem::size_of::())
+ .ok_or_else(|| "Noise output exceeds supported size".to_string())?;
+
+ if output_size_bytes == 0 {
+ return Err("Noise output buffer is empty".to_string());
+ }
+
+ let mut device = self.device.clone();
+ let mut allocated: Vec = Vec::new();
+
+ let result = (|| -> Result, String> {
+ let params_raw = bytes_of(params);
+ let params_size =
+ u32::try_from(params_raw.len()).map_err(|_| "Noise parameter buffer exceeds supported size".to_string())?;
+ let params_bytes = PackedByteArray::from(params_raw.to_vec());
+ let params_buffer = device
+ .storage_buffer_create_ex(params_size)
+ .data(¶ms_bytes)
+ .creation_bits(BufferCreationBits::AS_STORAGE_BIT)
+ .done();
+ if !params_buffer.is_valid() {
+ return Err("Failed to allocate noise parameter buffer".to_string());
+ }
+ allocated.push(params_buffer);
+
+ let output_size_u32 =
+ u32::try_from(output_size_bytes).map_err(|_| "Noise output buffer exceeds supported size".to_string())?;
+ let output_init = PackedByteArray::from(vec![0u8; output_size_bytes]);
+ let output_buffer = device
+ .storage_buffer_create_ex(output_size_u32)
+ .data(&output_init)
+ .creation_bits(BufferCreationBits::AS_STORAGE_BIT)
+ .done();
+ if !output_buffer.is_valid() {
+ return Err("Failed to allocate noise output buffer".to_string());
+ }
+ allocated.push(output_buffer);
+
+ let mut uniform_nodes: Vec> = Vec::new();
+ for (binding, rid) in [(0_i32, params_buffer), (1_i32, output_buffer)] {
+ let mut uniform = RdUniform::new_gd();
+ uniform.set_uniform_type(UniformType::STORAGE_BUFFER);
+ uniform.set_binding(binding);
+ uniform.add_id(rid);
+ uniform_nodes.push(uniform);
+ }
+
+ let mut uniforms: Array> = Array::new();
+ for uniform in &uniform_nodes {
+ uniforms.push(uniform);
+ }
+
+ let uniform_set = device.uniform_set_create(&uniforms, self.noise_shader, 0);
+ if !uniform_set.is_valid() {
+ return Err("Failed to create noise uniform set".to_string());
+ }
+ allocated.push(uniform_set);
+
+ let compute_list = device.compute_list_begin();
+ if compute_list < 0 {
+ return Err("Failed to begin noise compute list".to_string());
+ }
+
+ device.compute_list_bind_compute_pipeline(compute_list, self.noise_pipeline);
+ device.compute_list_bind_uniform_set(compute_list, uniform_set, 0);
+
+ let groups_x = params.map_width.saturating_add(15) / 16;
+ let groups_y = params.map_height.saturating_add(15) / 16;
+ let dispatch_x = groups_x.max(1);
+ let dispatch_y = groups_y.max(1);
+
+ device.compute_list_dispatch(compute_list, dispatch_x, dispatch_y, 1);
+ device.compute_list_end();
+
+ device.submit();
+ device.sync();
+
+ let output_bytes = device.buffer_get_data(output_buffer);
+ if !output_bytes.len().is_multiple_of(4) {
+ return Err("Noise output byte count is not a multiple of 4".to_string());
+ }
+
+ let raw = output_bytes.to_vec();
+ let values: &[u32] = cast_slice(&raw);
+ Ok(values.to_vec())
+ })();
+
+ for rid in allocated.into_iter().rev() {
+ if rid.is_valid() {
+ device.free_rid(rid);
+ }
+ }
+
+ result
+ }
+}
+
+impl Drop for GpuComputeContext {
+ fn drop(&mut self) {
+ let mut device = self.device.clone();
+ if self.tilemap_pipeline.is_valid() {
+ device.free_rid(self.tilemap_pipeline);
+ }
+ if self.tilemap_shader.is_valid() {
+ device.free_rid(self.tilemap_shader);
+ }
+ if self.noise_pipeline.is_valid() {
+ device.free_rid(self.noise_pipeline);
+ }
+ if self.noise_shader.is_valid() {
+ device.free_rid(self.noise_shader);
+ }
+ }
+}
+
+#[cfg(feature = "gd_rehearse_tests")]
+mod gd_rehearse_tests {
+ use super::*;
+ use gd_rehearse::itest::*;
+ use std::time::Instant;
+
+ pub fn make_test_atlas() -> (Vec, u32, u32) {
+ let tile_size = 2u32;
+ let atlas_w = tile_size * 2; // two tiles horizontally
+ let atlas_h = tile_size; // one row
+
+ // Build atlas row-major across atlas_w
+ // Tile 0 = solid red, Tile 1 = solid green
+ let mut data = Vec::with_capacity((atlas_w * atlas_h * 4) as usize);
+ for _y in 0..atlas_h {
+ for x in 0..atlas_w {
+ if x < tile_size {
+ data.extend_from_slice(&[255u8, 0, 0, 255]);
+ } else {
+ data.extend_from_slice(&[0u8, 255, 0, 255]);
+ }
+ }
+ }
+
+ (data, atlas_w, atlas_h)
+ }
+
+ fn make_uniform_atlas(tile_size: u32, color: [u8; 4]) -> (Vec, u32, u32) {
+ let pixel_count = (tile_size * tile_size) as usize;
+ let mut data = Vec::with_capacity(pixel_count * 4);
+ for _ in 0..pixel_count {
+ data.extend_from_slice(&color);
+ }
+
+ (data, tile_size, tile_size)
+ }
+
+ fn run_tilemap_for_size(map_width: u32, map_height: u32, tile_size: u32) -> PackedByteArray {
+ godot_print!(
+ "Testing map {}x{} tiles at tile_size {} ({}x{} px output)",
+ map_width,
+ map_height,
+ tile_size,
+ map_width * tile_size,
+ map_height * tile_size
+ );
+
+ let (atlas_bytes, atlas_w, atlas_h) = make_uniform_atlas(tile_size, [0u8, 0u8, 255u8, 255u8]);
+
+ let params = TilemapGpuParams {
+ out_width: map_width * tile_size,
+ out_height: map_height * tile_size,
+ tile_size,
+ map_width,
+ map_height,
+ atlas_width: atlas_w,
+ atlas_height: atlas_h,
+ };
+
+ let tile_count = (map_width as usize)
+ .checked_mul(map_height as usize)
+ .expect("Tile count overflow in benchmark");
+ let map = vec![0u32; tile_count];
+ let start = Instant::now();
+ let mut context = GpuComputeContext::new().expect("Failed to create GPU context for benchmark");
+ let result = context
+ .run_tilemap_compute(¶ms, &map, &atlas_bytes)
+ .expect("GPU tilemap compute failed during size sweep benchmark");
+ let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!(
+ "Tilemap GPU compute finished in {:.2} ms for {} tiles ({} bytes output)",
+ elapsed_ms,
+ tile_count,
+ result.len()
+ );
+ result
+ }
+
+ fn expected_output_bytes(map_width: u32, map_height: u32, tile_size: u32) -> usize {
+ let out_width = map_width
+ .checked_mul(tile_size)
+ .expect("Output width overflow in expectation");
+ let out_height = map_height
+ .checked_mul(tile_size)
+ .expect("Output height overflow in expectation");
+
+ (out_width as usize)
+ .checked_mul(out_height as usize)
+ .and_then(|v| v.checked_mul(4))
+ .expect("Expected byte count overflow")
+ }
+
+ #[gditest(scene_path = "res://tests/test_runner.tscn")]
+ fn gpu_tilemap_produces_expected_pixels() {
+ let (atlas_bytes, atlas_w, atlas_h) = make_test_atlas();
+
+ let params = TilemapGpuParams {
+ out_width: 4,
+ out_height: 2,
+ tile_size: 2,
+ map_width: 2,
+ map_height: 1,
+ atlas_width: atlas_w,
+ atlas_height: atlas_h,
+ };
+
+ let map = vec![0u32, 1u32];
+ let start = Instant::now();
+ let mut context = GpuComputeContext::new().expect("Failed to create GPU context for sanity check");
+ let gpu_bytes = context
+ .run_tilemap_compute(¶ms, &map, &atlas_bytes)
+ .expect("GPU tilemap compute failed")
+ .to_vec();
+ let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0;
+ godot_print!("Sanity GPU tilemap test finished in {:.2} ms", elapsed_ms);
+
+ assert_eq!(gpu_bytes.len(), atlas_bytes.len(), "GPU output size mismatch");
+ assert_eq!(
+ gpu_bytes, atlas_bytes,
+ "GPU tilemap output differs from atlas expectation"
+ );
+ }
+
+ #[gditest(scene_path = "res://tests/test_runner.tscn")]
+ fn gpu_noise_generation_dimensions() {
+ let mut context = GpuComputeContext::new().expect("Failed to create GPU context for noise test");
+ let params = NoiseGpuParams {
+ map_width: 64,
+ map_height: 32,
+ seed: 1,
+ _pad0: 0,
+ noise_scale: 0.75,
+ noise_magnitude: 12.0,
+ _pad1: 0.0,
+ _pad2: 0.0,
+ };
+
+ let values = context.generate_noise(¶ms).expect("GPU noise generation failed");
+ let expected = (params.map_width as usize)
+ .checked_mul(params.map_height as usize)
+ .expect("Noise test tile count overflow");
+ assert_eq!(values.len(), expected, "Noise GPU output length mismatch");
+ let max_allowed = params.noise_magnitude.ceil() as u32;
+ assert!(
+ values.iter().all(|&value| value <= max_allowed),
+ "Noise GPU output exceeded magnitude limit"
+ );
+ }
+
+ #[gditest(scene_path = "res://tests/test_runner.tscn")]
+ fn gpu_tilemap_generation_small() {
+ let output = run_tilemap_for_size(128, 128, TILE_SIZE);
+ let expected = expected_output_bytes(128, 128, TILE_SIZE);
+ assert_eq!(output.len(), expected, "Small tilemap output byte size mismatch");
+ godot_print!("Small tilemap test validated {} bytes", output.len());
+ }
+
+ #[gditest(scene_path = "res://tests/test_runner.tscn")]
+ fn gpu_tilemap_generation_medium() {
+ let output = run_tilemap_for_size(256, 256, TILE_SIZE);
+ let expected = expected_output_bytes(256, 256, TILE_SIZE);
+ assert_eq!(output.len(), expected, "Medium tilemap output byte size mismatch");
+ godot_print!("Medium tilemap test validated {} bytes", output.len());
+ }
+
+ #[gditest(scene_path = "res://tests/test_runner.tscn")]
+ fn gpu_tilemap_generation_large() {
+ let output = run_tilemap_for_size(512, 512, TILE_SIZE);
+ let expected = expected_output_bytes(512, 512, TILE_SIZE);
+ assert_eq!(output.len(), expected, "Large tilemap output byte size mismatch");
+ godot_print!("Large tilemap test validated {} bytes", output.len());
+ }
+
+ #[gditest(scene_path = "res://tests/test_runner.tscn")]
+ fn gpu_tilemap_generation_xlarge() {
+ let output = run_tilemap_for_size(1024, 1024, TILE_SIZE);
+ let expected = expected_output_bytes(1024, 1024, TILE_SIZE);
+ assert_eq!(output.len(), expected, "XL tilemap output byte size mismatch");
+ godot_print!("XL tilemap test validated {} bytes", output.len());
+ }
+}
diff --git a/rust/src/player.rs b/rust/src/player.rs
new file mode 100644
index 0000000..8d7dc75
--- /dev/null
+++ b/rust/src/player.rs
@@ -0,0 +1,55 @@
+use godot::{classes::Input, global::Key, prelude::*};
+
+#[derive(GodotClass)]
+#[class(base=Node3D)]
+struct Player {
+ #[export]
+ character_name: GString,
+ #[export]
+ velocity: Vector3,
+ #[export]
+ acceleration: Vector3,
+ base: Base,
+}
+
+#[godot_api]
+impl INode3D for Player {
+ fn init(base: Base) -> Self {
+ Self {
+ character_name: GString::from("Hello"),
+ velocity: Vector3::ZERO,
+ acceleration: Vector3::new(1.0, 1.0, 0.0),
+ base,
+ }
+ }
+
+ fn process(&mut self, delta: f32) {
+ self.process_input(delta);
+ self.update_position(delta);
+ }
+}
+
+impl Player {
+ fn process_input(&mut self, _delta: f32) {
+ let input = Input::singleton();
+ if input.is_key_pressed(Key::W) {
+ self.velocity.y += self.acceleration.y;
+ }
+ if input.is_key_pressed(Key::A) {
+ self.velocity.x -= self.acceleration.x;
+ }
+ if input.is_key_pressed(Key::S) {
+ self.velocity.y -= self.acceleration.y;
+ }
+ if input.is_key_pressed(Key::D) {
+ self.velocity.x += self.acceleration.x;
+ }
+ }
+
+ fn update_position(&mut self, delta: f32) {
+ let before_move = self.base_mut().get_transform();
+ let after_move = before_move.translated(self.velocity * delta);
+ self.base_mut().set_transform(after_move);
+ self.velocity *= 0.5;
+ }
+}