mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
add a bottom dock (#44)
- moves the console to the new bottom dock - adds a deck variables inspector to the bottom dock that allows adding, removing and editing variables of the currently open deck - bottom dock can be opened with <kbd>N</kbd> closes #33 Reviewed-on: https://codeberg.org/Eroax/StreamGraph/pulls/44 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
parent
0ebe17399d
commit
b143c63dcc
19 changed files with 519 additions and 35 deletions
|
@ -44,6 +44,8 @@ signal node_added(node: DeckNode)
|
||||||
signal node_removed(node: DeckNode)
|
signal node_removed(node: DeckNode)
|
||||||
## Emitted when nodes have been disconnected
|
## Emitted when nodes have been disconnected
|
||||||
signal nodes_disconnected(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int)
|
signal nodes_disconnected(from_node_id: String, to_node_id: String, from_output_port: int, to_input_port: int)
|
||||||
|
## Emitted when the [member variable_stack] has been modified.
|
||||||
|
signal variables_updated()
|
||||||
|
|
||||||
#region group signals
|
#region group signals
|
||||||
signal node_added_to_group(node: DeckNode, assign_id: String, assign_to_self: bool, deck: Deck)
|
signal node_added_to_group(node: DeckNode, assign_id: String, assign_to_self: bool, deck: Deck)
|
||||||
|
@ -196,6 +198,21 @@ func remove_node(uuid: String, remove_connections: bool = false, force: bool = f
|
||||||
node_removed_from_group.emit(uuid, remove_connections, self)
|
node_removed_from_group.emit(uuid, remove_connections, self)
|
||||||
|
|
||||||
|
|
||||||
|
## Set a variable on this deck.
|
||||||
|
func set_variable(var_name: String, value: Variant) -> void:
|
||||||
|
variable_stack[var_name] = value
|
||||||
|
variables_updated.emit()
|
||||||
|
|
||||||
|
|
||||||
|
func update_variable(old_name: String, new_name: String, new_value: Variant) -> void:
|
||||||
|
variable_stack.erase(old_name)
|
||||||
|
variable_stack[new_name] = new_value
|
||||||
|
|
||||||
|
|
||||||
|
func remove_variable(name: String) -> void:
|
||||||
|
variable_stack.erase(name)
|
||||||
|
|
||||||
|
|
||||||
## Group the [param nodes_to_group] into a new deck and return it.
|
## Group the [param nodes_to_group] into a new deck and return it.
|
||||||
## Returns [code]null[/code] on failure.[br]
|
## Returns [code]null[/code] on failure.[br]
|
||||||
## Adds a group node to this deck, and adds group input and output nodes in the group.
|
## Adds a group node to this deck, and adds group input and output nodes in the group.
|
||||||
|
|
|
@ -34,7 +34,7 @@ func handle_delay(data):
|
||||||
|
|
||||||
|
|
||||||
#print("Delay over")
|
#print("Delay over")
|
||||||
send(0, data)
|
send.call_deferred(0, data)
|
||||||
|
|
||||||
|
|
||||||
func _notification(what):
|
func _notification(what):
|
||||||
|
|
|
@ -42,7 +42,8 @@ func _receive(to_input_port: int, _data: Variant, _extra_data: Array = []) -> vo
|
||||||
var var_name: String = resolve_input_port_value(0)
|
var var_name: String = resolve_input_port_value(0)
|
||||||
var var_value: Variant = resolve_input_port_value(1)
|
var var_value: Variant = resolve_input_port_value(1)
|
||||||
|
|
||||||
_belonging_to.variable_stack[var_name] = var_value
|
#_belonging_to.variable_stack[var_name] = var_value
|
||||||
|
_belonging_to.set_variable(var_name, var_value)
|
||||||
|
|
||||||
send(0, var_value)
|
send(0, var_value)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,14 @@ const GODOT_TYPES_MAP := {
|
||||||
Types.DICTIONARY: TYPE_DICTIONARY,
|
Types.DICTIONARY: TYPE_DICTIONARY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INVERSE_GODOT_TYPES_MAP := {
|
||||||
|
TYPE_BOOL: Types.BOOL,
|
||||||
|
TYPE_FLOAT: Types.NUMERIC,
|
||||||
|
TYPE_STRING: Types.STRING,
|
||||||
|
TYPE_ARRAY: Types.ARRAY,
|
||||||
|
TYPE_DICTIONARY: Types.DICTIONARY,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static func can_convert(from: Types, to: Types) -> bool:
|
static func can_convert(from: Types, to: Types) -> bool:
|
||||||
if from == to:
|
if from == to:
|
||||||
|
|
17
graph_node_renderer/bottom_dock.gd
Normal file
17
graph_node_renderer/bottom_dock.gd
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# (c) 2023-present Eroax
|
||||||
|
# (c) 2023-present Yagich
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
extends TabContainer
|
||||||
|
class_name BottomDock
|
||||||
|
|
||||||
|
@export var tab_names := ["Console", "Variables"]
|
||||||
|
@onready var variable_viewer: VariableViewer = %VariableViewer
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
for i in get_child_count():
|
||||||
|
set_tab_title(i, tab_names[i])
|
||||||
|
|
||||||
|
|
||||||
|
func rebuild_variable_tree(data: Dictionary) -> void:
|
||||||
|
variable_viewer.rebuild_variable_tree(data)
|
19
graph_node_renderer/bottom_dock.tscn
Normal file
19
graph_node_renderer/bottom_dock.tscn
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[gd_scene load_steps=4 format=3 uid="uid://dayri1ejk20bc"]
|
||||||
|
|
||||||
|
[ext_resource type="PackedScene" uid="uid://duvh3r740w2p5" path="res://graph_node_renderer/logger_renderer.tscn" id="1_6yuco"]
|
||||||
|
[ext_resource type="Script" path="res://graph_node_renderer/bottom_dock.gd" id="1_mu1hf"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://ovf5nt5pt0oj" path="res://graph_node_renderer/variable_viewer.tscn" id="3_vlkh8"]
|
||||||
|
|
||||||
|
[node name="BottomDock" type="TabContainer"]
|
||||||
|
offset_right = 122.0
|
||||||
|
offset_bottom = 95.0
|
||||||
|
script = ExtResource("1_mu1hf")
|
||||||
|
|
||||||
|
[node name="LoggerRenderer" parent="." instance=ExtResource("1_6yuco")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="VariableViewer" parent="." instance=ExtResource("3_vlkh8")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
|
@ -65,13 +65,14 @@ var _deck_to_save: WeakRef
|
||||||
|
|
||||||
@onready var obs_setup_dialog := $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog
|
@onready var obs_setup_dialog := $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog
|
||||||
@onready var twitch_setup_dialog := $Twitch_Setup_Dialog as TwitchSetupDialog
|
@onready var twitch_setup_dialog := $Twitch_Setup_Dialog as TwitchSetupDialog
|
||||||
@onready var logger_renderer: LoggerRenderer = %LoggerRenderer
|
@onready var bottom_dock: BottomDock = %BottomDock
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
get_tree().auto_accept_quit = false
|
get_tree().auto_accept_quit = false
|
||||||
tab_container.add_button_pressed.connect(add_empty_deck)
|
tab_container.add_button_pressed.connect(add_empty_deck)
|
||||||
tab_container.tab_changed.connect(_on_tab_container_tab_changed)
|
tab_container.tab_changed.connect(_on_tab_container_tab_changed)
|
||||||
|
tab_container.tab_about_to_change.connect(_on_tab_container_tab_about_to_change)
|
||||||
RendererPersistence.init_namespace(PERSISTENCE_NAMESPACE)
|
RendererPersistence.init_namespace(PERSISTENCE_NAMESPACE)
|
||||||
|
|
||||||
var embed_subwindows: bool = RendererPersistence.get_or_create(PERSISTENCE_NAMESPACE, "config", "embed_subwindows", true)
|
var embed_subwindows: bool = RendererPersistence.get_or_create(PERSISTENCE_NAMESPACE, "config", "embed_subwindows", true)
|
||||||
|
@ -113,12 +114,31 @@ func _ready() -> void:
|
||||||
file_popup_menu.set_item_shortcut(FileMenuId.SAVE, save_deck_shortcut)
|
file_popup_menu.set_item_shortcut(FileMenuId.SAVE, save_deck_shortcut)
|
||||||
file_popup_menu.set_item_shortcut(FileMenuId.SAVE_AS, save_deck_as_shortcut)
|
file_popup_menu.set_item_shortcut(FileMenuId.SAVE_AS, save_deck_as_shortcut)
|
||||||
file_popup_menu.set_item_shortcut(FileMenuId.CLOSE, close_deck_shortcut)
|
file_popup_menu.set_item_shortcut(FileMenuId.CLOSE, close_deck_shortcut)
|
||||||
|
|
||||||
|
bottom_dock.variable_viewer.top_field_edited.connect(
|
||||||
|
func(old_name: String, new_name: String, new_value: Variant) -> void:
|
||||||
|
get_active_deck().update_variable(old_name, new_name, new_value)
|
||||||
|
)
|
||||||
|
|
||||||
|
bottom_dock.variable_viewer.top_field_removed.connect(
|
||||||
|
func(field_name: String) -> void:
|
||||||
|
get_active_deck().remove_variable(field_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_tab_container_tab_about_to_change(previous_tab: int) -> void:
|
||||||
|
var deck := get_deck_at_tab(previous_tab)
|
||||||
|
if deck.variables_updated.is_connected(bottom_dock.rebuild_variable_tree):
|
||||||
|
deck.variables_updated.disconnect(bottom_dock.rebuild_variable_tree)
|
||||||
|
|
||||||
|
|
||||||
func _on_tab_container_tab_changed(tab: int) -> void:
|
func _on_tab_container_tab_changed(tab: int) -> void:
|
||||||
var is_group = tab_container.get_tab_metadata(tab, "group", false)
|
var is_group = tab_container.get_tab_metadata(tab, "group", false)
|
||||||
file_popup_menu.set_item_disabled(FileMenuId.SAVE, is_group)
|
file_popup_menu.set_item_disabled(FileMenuId.SAVE, is_group)
|
||||||
file_popup_menu.set_item_disabled(FileMenuId.SAVE_AS, is_group)
|
file_popup_menu.set_item_disabled(FileMenuId.SAVE_AS, is_group)
|
||||||
|
bottom_dock.rebuild_variable_tree(get_active_deck().variable_stack)
|
||||||
|
var deck := get_active_deck()
|
||||||
|
deck.variables_updated.connect(bottom_dock.rebuild_variable_tree.bind(deck.variable_stack))
|
||||||
|
|
||||||
|
|
||||||
## Called when the File button in the [MenuBar] is pressed with the [param id]
|
## Called when the File button in the [MenuBar] is pressed with the [param id]
|
||||||
|
@ -150,6 +170,7 @@ func add_empty_deck() -> void:
|
||||||
inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
||||||
inst.dirty_state_changed.connect(_on_deck_renderer_dirty_state_changed.bind(inst))
|
inst.dirty_state_changed.connect(_on_deck_renderer_dirty_state_changed.bind(inst))
|
||||||
tab_container.set_current_tab(tab)
|
tab_container.set_current_tab(tab)
|
||||||
|
bottom_dock.variable_viewer.enable_new_button()
|
||||||
|
|
||||||
## Closes the current tab in [member tab_container]
|
## Closes the current tab in [member tab_container]
|
||||||
func close_current_tab() -> void:
|
func close_current_tab() -> void:
|
||||||
|
@ -165,8 +186,10 @@ func close_tab(tab: int) -> void:
|
||||||
if tab_container.get_tab_metadata(c_tab, "id") == group:
|
if tab_container.get_tab_metadata(c_tab, "id") == group:
|
||||||
tab_container.close_tab(c_tab)
|
tab_container.close_tab(c_tab)
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
|
|
||||||
tab_container.close_tab(tab)
|
tab_container.close_tab(tab)
|
||||||
|
if tab_container.get_tab_count() == 0:
|
||||||
|
bottom_dock.variable_viewer.disable_new_button()
|
||||||
|
|
||||||
## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE]
|
## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE]
|
||||||
## as well as getting a weakref to the active [Deck]
|
## as well as getting a weakref to the active [Deck]
|
||||||
|
@ -234,14 +257,21 @@ func open_deck_at_path(path: String) -> void:
|
||||||
recent_path = path.get_base_dir()
|
recent_path = path.get_base_dir()
|
||||||
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_path", recent_path)
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_path", recent_path)
|
||||||
tab_container.set_current_tab(tab)
|
tab_container.set_current_tab(tab)
|
||||||
|
bottom_dock.variable_viewer.enable_new_button()
|
||||||
|
|
||||||
|
|
||||||
## Gets the currently active [Deck] from [member tab_container]
|
## Returns the current deck in the [member tab_container].
|
||||||
func get_active_deck() -> Deck:
|
func get_active_deck() -> Deck:
|
||||||
|
return get_deck_at_tab(tab_container.get_current_tab())
|
||||||
|
|
||||||
|
|
||||||
|
## Returns the deck at [param tab] in the [member tab_container].
|
||||||
|
func get_deck_at_tab(tab: int) -> Deck:
|
||||||
if tab_container.is_empty():
|
if tab_container.is_empty():
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
return (tab_container.get_content(tab) as DeckRendererGraphEdit).deck
|
||||||
|
|
||||||
return (tab_container.get_content(tab_container.get_current_tab()) as DeckRendererGraphEdit).deck
|
|
||||||
|
|
||||||
## Saves the active [Deck] in [member tab_container]
|
## Saves the active [Deck] in [member tab_container]
|
||||||
func save_active_deck() -> void:
|
func save_active_deck() -> void:
|
||||||
|
@ -445,8 +475,8 @@ func _on_unsaved_changes_dialog_confirmed() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _unhandled_input(event: InputEvent) -> void:
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
if event.is_action_pressed("toggle_console"):
|
if event.is_action_pressed("toggle_bottom_dock"):
|
||||||
logger_renderer.visible = !logger_renderer.visible
|
bottom_dock.visible = !bottom_dock.visible
|
||||||
accept_event()
|
accept_event()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
[ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"]
|
[ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"]
|
||||||
[ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"]
|
[ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"]
|
||||||
[ext_resource type="Theme" uid="uid://dqqdqscid2iem" path="res://graph_node_renderer/default_theme.tres" id="1_tgul2"]
|
[ext_resource type="Theme" uid="uid://dqqdqscid2iem" path="res://graph_node_renderer/default_theme.tres" id="1_tgul2"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://dayri1ejk20bc" path="res://graph_node_renderer/bottom_dock.tscn" id="4_gwnhy"]
|
||||||
[ext_resource type="Script" path="res://addons/no-obs-ws/NoOBSWS.gd" id="4_nu72u"]
|
[ext_resource type="Script" path="res://addons/no-obs-ws/NoOBSWS.gd" id="4_nu72u"]
|
||||||
[ext_resource type="PackedScene" uid="uid://duvh3r740w2p5" path="res://graph_node_renderer/logger_renderer.tscn" id="4_pvexk"]
|
|
||||||
[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"]
|
[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"]
|
||||||
[ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"]
|
[ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"]
|
[ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"]
|
||||||
|
@ -151,7 +151,7 @@ item_1/id = 1
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="LoggerRenderer" parent="MarginContainer/VSplitContainer" instance=ExtResource("4_pvexk")]
|
[node name="BottomDock" parent="MarginContainer/VSplitContainer" instance=ExtResource("4_gwnhy")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# (c) 2023-present Eroax
|
# (c) 2023-present Eroax
|
||||||
# (c) 2023-present Yagich
|
# (c) 2023-present Yagich
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
extends PanelContainer
|
extends VBoxContainer
|
||||||
class_name LoggerRenderer
|
class_name LoggerRenderer
|
||||||
|
|
||||||
@onready var output_label: RichTextLabel = %OutputLabel
|
@onready var output_label: RichTextLabel = %OutputLabel
|
||||||
|
|
|
@ -9,45 +9,35 @@ font_names = PackedStringArray("Monospace")
|
||||||
font_names = PackedStringArray("Monospace")
|
font_names = PackedStringArray("Monospace")
|
||||||
font_weight = 700
|
font_weight = 700
|
||||||
|
|
||||||
[node name="LoggerRenderer" type="PanelContainer"]
|
[node name="LoggerRenderer" type="VBoxContainer"]
|
||||||
anchors_preset = -1
|
anchors_preset = -1
|
||||||
anchor_right = 0.42016
|
anchor_right = 0.186632
|
||||||
anchor_bottom = 0.181704
|
anchor_bottom = 0.169753
|
||||||
offset_right = -0.0240021
|
grow_horizontal = 2
|
||||||
offset_bottom = 0.255997
|
grow_vertical = 2
|
||||||
script = ExtResource("1_82rlk")
|
script = ExtResource("1_82rlk")
|
||||||
metadata/_edit_use_anchors_ = true
|
metadata/_edit_use_anchors_ = true
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="."]
|
[node name="Label" type="Label" parent="."]
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 3
|
|
||||||
theme_override_constants/margin_top = 3
|
|
||||||
theme_override_constants/margin_right = 3
|
|
||||||
theme_override_constants/margin_bottom = 3
|
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Debug Console"
|
text = "Debug Console"
|
||||||
|
|
||||||
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
|
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
|
||||||
[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HBoxContainer/PanelContainer"]
|
[node name="MarginContainer" type="MarginContainer" parent="HBoxContainer/PanelContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/margin_left = 3
|
theme_override_constants/margin_left = 3
|
||||||
theme_override_constants/margin_top = 3
|
theme_override_constants/margin_top = 3
|
||||||
theme_override_constants/margin_right = 3
|
theme_override_constants/margin_right = 3
|
||||||
theme_override_constants/margin_bottom = 3
|
theme_override_constants/margin_bottom = 3
|
||||||
|
|
||||||
[node name="OutputLabel" type="RichTextLabel" parent="MarginContainer/VBoxContainer/HBoxContainer/PanelContainer/MarginContainer"]
|
[node name="OutputLabel" type="RichTextLabel" parent="HBoxContainer/PanelContainer/MarginContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
@ -63,17 +53,17 @@ selection_enabled = true
|
||||||
deselect_on_focus_loss_enabled = false
|
deselect_on_focus_loss_enabled = false
|
||||||
drag_and_drop_selection_enabled = false
|
drag_and_drop_selection_enabled = false
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HBoxContainer"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
alignment = 1
|
alignment = 1
|
||||||
|
|
||||||
[node name="CopyButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer"]
|
[node name="CopyButton" type="Button" parent="HBoxContainer/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Copy"
|
text = "Copy"
|
||||||
|
|
||||||
[node name="ClearButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer/VBoxContainer"]
|
[node name="ClearButton" type="Button" parent="HBoxContainer/VBoxContainer"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "Clear"
|
text = "Clear"
|
||||||
|
|
|
@ -21,6 +21,8 @@ class_name TabContainerCustom
|
||||||
signal add_button_pressed
|
signal add_button_pressed
|
||||||
## Emitted when the current tab in [member tab_bar] is changed.
|
## Emitted when the current tab in [member tab_bar] is changed.
|
||||||
signal tab_changed(tab: int)
|
signal tab_changed(tab: int)
|
||||||
|
## Emitted just before the current tab in [member tab_bar] is changed.s
|
||||||
|
signal tab_about_to_change(previous_tab: int)
|
||||||
## Emitted when a tab in [member tab_bar] has been closed.
|
## Emitted when a tab in [member tab_bar] has been closed.
|
||||||
## See [signal TabBar.tab_close_requested]
|
## See [signal TabBar.tab_close_requested]
|
||||||
signal tab_closed(tab: int)
|
signal tab_closed(tab: int)
|
||||||
|
@ -41,6 +43,8 @@ func _ready() -> void:
|
||||||
if _previous_active_tab == tab:
|
if _previous_active_tab == tab:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
tab_about_to_change.emit(_previous_active_tab)
|
||||||
|
|
||||||
if _previous_active_tab > -1:
|
if _previous_active_tab > -1:
|
||||||
content_container.get_child(_previous_active_tab).visible = false
|
content_container.get_child(_previous_active_tab).visible = false
|
||||||
content_container.get_child(tab).visible = true
|
content_container.get_child(tab).visible = true
|
||||||
|
|
1
graph_node_renderer/textures/add_icon.svg
Normal file
1
graph_node_renderer/textures/add_icon.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 149 B |
37
graph_node_renderer/textures/add_icon.svg.import
Normal file
37
graph_node_renderer/textures/add_icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://drxi5ks3mqbnk"
|
||||||
|
path="res://.godot/imported/add_icon.svg-1a070ac229fd6d9079fc5a70a6dc355a.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://graph_node_renderer/textures/add_icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/add_icon.svg-1a070ac229fd6d9079fc5a70a6dc355a.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
1
graph_node_renderer/textures/remove-icon.svg
Normal file
1
graph_node_renderer/textures/remove-icon.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m5 1v1h-4v2h14v-2h-4v-1zm-3 4v8a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-8zm1 2h2v6h-2zm4 0h2v6h-2zm4 0h2v6h-2z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 218 B |
37
graph_node_renderer/textures/remove-icon.svg.import
Normal file
37
graph_node_renderer/textures/remove-icon.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://cdhco73u4faf8"
|
||||||
|
path="res://.godot/imported/remove-icon.svg-39dfaea1590f79f30a4d86a2f83a7c3d.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://graph_node_renderer/textures/remove-icon.svg"
|
||||||
|
dest_files=["res://.godot/imported/remove-icon.svg-39dfaea1590f79f30a4d86a2f83a7c3d.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
280
graph_node_renderer/variable_viewer.gd
Normal file
280
graph_node_renderer/variable_viewer.gd
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
# (c) 2023-present Eroax
|
||||||
|
# (c) 2023-present Yagich
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
extends VBoxContainer
|
||||||
|
class_name VariableViewer
|
||||||
|
|
||||||
|
const REMOVE_ICON = preload("res://graph_node_renderer/textures/remove-icon.svg")
|
||||||
|
|
||||||
|
@onready var new_variable_button: Button = %NewVariableButton
|
||||||
|
@onready var variable_tree: Tree = %VariableTree
|
||||||
|
@onready var types_popup: PopupMenu = %TypesPopup
|
||||||
|
|
||||||
|
var root: TreeItem
|
||||||
|
var _old_name: String
|
||||||
|
|
||||||
|
signal top_field_edited(old_name: String, new_name: String, new_value: Variant)
|
||||||
|
signal top_field_removed(field_name: String)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
variable_tree.set_column_title(0, "Name")
|
||||||
|
variable_tree.set_column_title(1, "Value")
|
||||||
|
variable_tree.set_column_title(2, "Type")
|
||||||
|
variable_tree.set_column_expand(1, true)
|
||||||
|
variable_tree.set_column_expand_ratio(0, 15)
|
||||||
|
variable_tree.set_column_expand_ratio(1, 70)
|
||||||
|
variable_tree.set_column_expand_ratio(2, 15)
|
||||||
|
root = variable_tree.create_item()
|
||||||
|
rebuild_variable_tree()
|
||||||
|
variable_tree.item_activated.connect(
|
||||||
|
func():
|
||||||
|
var column := variable_tree.get_selected_column()
|
||||||
|
var item := variable_tree.get_selected()
|
||||||
|
|
||||||
|
# do nothing if this is an array and user is trying to edit index
|
||||||
|
# (TODO: proper reordering)
|
||||||
|
if column == 0 && item.get_meta(&"indexed"):
|
||||||
|
return
|
||||||
|
|
||||||
|
if column < 2:
|
||||||
|
_old_name = item.get_text(0)
|
||||||
|
|
||||||
|
variable_tree.edit_selected(true)
|
||||||
|
else:
|
||||||
|
var pos := get_global_mouse_position()
|
||||||
|
var r := Rect2i(Vector2i(pos), Vector2i(0, 100))
|
||||||
|
|
||||||
|
for i in types_popup.get_item_count():
|
||||||
|
types_popup.set_item_checked(i, false)
|
||||||
|
|
||||||
|
types_popup.set_item_checked(item.get_metadata(2), true)
|
||||||
|
types_popup.popup(r)
|
||||||
|
)
|
||||||
|
|
||||||
|
variable_tree.item_edited.connect(
|
||||||
|
func():
|
||||||
|
var item := variable_tree.get_selected()
|
||||||
|
var new_name := item.get_text(0)
|
||||||
|
var new_value
|
||||||
|
var type: DeckType.Types = item.get_metadata(2)
|
||||||
|
|
||||||
|
match type:
|
||||||
|
DeckType.Types.STRING:
|
||||||
|
new_value = item.get_text(1)
|
||||||
|
DeckType.Types.BOOL:
|
||||||
|
new_value = item.is_checked(1)
|
||||||
|
DeckType.Types.NUMERIC:
|
||||||
|
new_value = item.get_range(1)
|
||||||
|
_:
|
||||||
|
new_value = item.get_meta(&"value")
|
||||||
|
|
||||||
|
#item.set_meta(&"value", new_value)
|
||||||
|
|
||||||
|
if !item.has_meta(&"container"):
|
||||||
|
top_field_edited.emit(_old_name, new_name, new_value)
|
||||||
|
else:
|
||||||
|
var container = item.get_meta(&"container")
|
||||||
|
|
||||||
|
# array
|
||||||
|
if item.get_meta(&"indexed", false):
|
||||||
|
var index := item.get_index()
|
||||||
|
(container as Array)[index] = new_value
|
||||||
|
# dictionary
|
||||||
|
else:
|
||||||
|
var key := new_name
|
||||||
|
(container as Dictionary).erase(_old_name)
|
||||||
|
(container as Dictionary)[key] = new_value
|
||||||
|
|
||||||
|
set_item_value(item, new_value)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
variable_tree.nothing_selected.connect(variable_tree.deselect_all)
|
||||||
|
|
||||||
|
variable_tree.button_clicked.connect(_on_variable_tree_button_clicked)
|
||||||
|
|
||||||
|
for i in DeckType.Types.size() - 1:
|
||||||
|
types_popup.add_radio_check_item(DeckType.type_str(i))
|
||||||
|
|
||||||
|
types_popup.id_pressed.connect(_on_types_popup_id_pressed)
|
||||||
|
|
||||||
|
new_variable_button.pressed.connect(_on_new_variable_button_pressed)
|
||||||
|
|
||||||
|
disable_new_button()
|
||||||
|
|
||||||
|
|
||||||
|
func rebuild_variable_tree(data: Dictionary = {}) -> void:
|
||||||
|
#variable_tree.clear() # godot will raw dog a nullptr later if we clear the whole tree
|
||||||
|
for i in root.get_children():
|
||||||
|
i.free()
|
||||||
|
|
||||||
|
for i in data:
|
||||||
|
add_item(i, data[i])
|
||||||
|
|
||||||
|
|
||||||
|
func add_item(item_name: String, item_value: Variant, parent: TreeItem = root, container: Variant = null, indexed: bool = false) -> TreeItem:
|
||||||
|
# the container parameter is -1 instead of null because Object#get_meta() logs an error
|
||||||
|
# if the default parameter is null
|
||||||
|
var item := variable_tree.create_item(parent)
|
||||||
|
item.set_text(0, item_name)
|
||||||
|
|
||||||
|
var type: DeckType.Types = DeckType.INVERSE_GODOT_TYPES_MAP[typeof(item_value)]
|
||||||
|
item.set_meta(&"container", container)
|
||||||
|
item.set_meta(&"indexed", indexed)
|
||||||
|
|
||||||
|
set_item_value(item, item_value)
|
||||||
|
|
||||||
|
item.set_text(2, DeckType.type_str(type))
|
||||||
|
item.set_metadata(2, type)
|
||||||
|
if item_value is Dictionary:
|
||||||
|
for i in item_value:
|
||||||
|
add_item(i, item_value[i], item, item_value)
|
||||||
|
if item_value is Array:
|
||||||
|
for i in (item_value as Array).size():
|
||||||
|
add_item(str(i), item_value[i], item, item_value, true)
|
||||||
|
#item.collapsed = true
|
||||||
|
item.add_button(2, REMOVE_ICON)
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
func set_item_value(item: TreeItem, value: Variant) -> void:
|
||||||
|
item.set_meta(&"value", value)
|
||||||
|
var type: DeckType.Types = DeckType.INVERSE_GODOT_TYPES_MAP[typeof(value)]
|
||||||
|
match type:
|
||||||
|
DeckType.Types.NUMERIC:
|
||||||
|
item.set_cell_mode(1, TreeItem.CELL_MODE_RANGE)
|
||||||
|
item.set_range_config(1, -9999, 9999, 0.0001)
|
||||||
|
item.set_range(1, value)
|
||||||
|
DeckType.Types.BOOL:
|
||||||
|
item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
|
||||||
|
item.set_checked(1, value)
|
||||||
|
_:
|
||||||
|
item.set_text(1, str(value))
|
||||||
|
|
||||||
|
if item.has_meta(&"container"):
|
||||||
|
var container = item.get_meta(&"container")
|
||||||
|
if container is Array:
|
||||||
|
var index := item.get_index()
|
||||||
|
container[index] = value
|
||||||
|
else:
|
||||||
|
var key := item.get_text(0)
|
||||||
|
container[key] = value
|
||||||
|
|
||||||
|
if item.get_parent() != root:
|
||||||
|
refresh_item_value(item.get_parent())
|
||||||
|
|
||||||
|
|
||||||
|
func refresh_item_value(item: TreeItem) -> void:
|
||||||
|
var value = item.get_meta(&"value")
|
||||||
|
var type: DeckType.Types = DeckType.INVERSE_GODOT_TYPES_MAP[typeof(value)]
|
||||||
|
match type:
|
||||||
|
DeckType.Types.NUMERIC:
|
||||||
|
item.set_cell_mode(1, TreeItem.CELL_MODE_RANGE)
|
||||||
|
item.set_range_config(1, -9999, 9999, 0.0001)
|
||||||
|
item.set_range(1, value)
|
||||||
|
DeckType.Types.BOOL:
|
||||||
|
item.set_cell_mode(1, TreeItem.CELL_MODE_CHECK)
|
||||||
|
item.set_checked(1, value)
|
||||||
|
_:
|
||||||
|
item.set_cell_mode(1, TreeItem.CELL_MODE_STRING)
|
||||||
|
item.set_text(1, str(value))
|
||||||
|
|
||||||
|
|
||||||
|
func _on_variable_tree_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void:
|
||||||
|
if mouse_button_index != MOUSE_BUTTON_LEFT:
|
||||||
|
return
|
||||||
|
|
||||||
|
# we only have a delete button for now, so assume it is what's clicked
|
||||||
|
|
||||||
|
if !item.has_meta(&"container"):
|
||||||
|
var key := item.get_text(0)
|
||||||
|
top_field_removed.emit(key)
|
||||||
|
item.free()
|
||||||
|
else:
|
||||||
|
var container = item.get_meta(&"container")
|
||||||
|
var parent := item.get_parent()
|
||||||
|
# array
|
||||||
|
if item.get_meta(&"indexed", false):
|
||||||
|
var index := item.get_index()
|
||||||
|
(container as Array).remove_at(index)
|
||||||
|
item.free()
|
||||||
|
|
||||||
|
# go through the array and reset the index strings
|
||||||
|
for i in (container as Array).size():
|
||||||
|
parent.get_child(i).set_text(0, str(i))
|
||||||
|
|
||||||
|
# dictionary
|
||||||
|
else:
|
||||||
|
(container as Dictionary).erase(item.get_text(0))
|
||||||
|
item.free()
|
||||||
|
|
||||||
|
refresh_item_value(parent)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_types_popup_id_pressed(id: int) -> void:
|
||||||
|
var current_item := variable_tree.get_selected()
|
||||||
|
|
||||||
|
if current_item.get_metadata(2) == id:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_item.set_metadata(2, id)
|
||||||
|
|
||||||
|
var new_value: Variant
|
||||||
|
match id as DeckType.Types:
|
||||||
|
DeckType.Types.BOOL, DeckType.Types.STRING, DeckType.Types.NUMERIC:
|
||||||
|
# simple types that can generally be converted between each other
|
||||||
|
var target_type: Variant.Type = DeckType.GODOT_TYPES_MAP[id]
|
||||||
|
new_value = type_convert(current_item.get_meta(&"value"), target_type)
|
||||||
|
DeckType.Types.ARRAY:
|
||||||
|
new_value = []
|
||||||
|
for i in current_item.get_children():
|
||||||
|
i.free()
|
||||||
|
DeckType.Types.DICTIONARY:
|
||||||
|
new_value = {}
|
||||||
|
for i in current_item.get_children():
|
||||||
|
i.free()
|
||||||
|
|
||||||
|
set_item_value(current_item, new_value)
|
||||||
|
|
||||||
|
current_item.set_text(2, DeckType.type_str(id))
|
||||||
|
|
||||||
|
if !current_item.has_meta(&"container"):
|
||||||
|
var field_name := current_item.get_text(0)
|
||||||
|
top_field_edited.emit(field_name, field_name, new_value)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_new_variable_button_pressed() -> void:
|
||||||
|
var selected := variable_tree.get_selected()
|
||||||
|
# TODO: UX impr. - if selected is part of a container, add to that container instead
|
||||||
|
# (but if selected is a container, prioritize adding to that)
|
||||||
|
if selected == null || !(selected.get_meta(&"value") is Array || selected.get_meta(&"value") is Dictionary):
|
||||||
|
# top field
|
||||||
|
var var_name := "new_variable%s" % variable_tree.get_root().get_child_count()
|
||||||
|
var new_item := add_item(var_name, "")
|
||||||
|
top_field_edited.emit(var_name, var_name, "")
|
||||||
|
new_item.select(0)
|
||||||
|
else:
|
||||||
|
var container = selected.get_meta(&"value")
|
||||||
|
if container is Dictionary:
|
||||||
|
var var_name := "new_key%s" % (container as Dictionary).size()
|
||||||
|
var new_item := add_item(var_name, "", selected, container)
|
||||||
|
new_item.select(0)
|
||||||
|
container[var_name] = ""
|
||||||
|
else:
|
||||||
|
var index := (container as Array).size()
|
||||||
|
(container as Array).append("")
|
||||||
|
var new_item := add_item(str(index), "", selected, container, true)
|
||||||
|
new_item.select(0)
|
||||||
|
refresh_item_value(selected)
|
||||||
|
|
||||||
|
|
||||||
|
func enable_new_button() -> void:
|
||||||
|
new_variable_button.disabled = false
|
||||||
|
new_variable_button.tooltip_text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func disable_new_button() -> void:
|
||||||
|
new_variable_button.disabled = true
|
||||||
|
new_variable_button.tooltip_text = "There is no deck open, so you can not create a new variable.\nOpen a deck first."
|
37
graph_node_renderer/variable_viewer.tscn
Normal file
37
graph_node_renderer/variable_viewer.tscn
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
[gd_scene load_steps=3 format=3 uid="uid://ovf5nt5pt0oj"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://graph_node_renderer/variable_viewer.gd" id="1_b5gmj"]
|
||||||
|
[ext_resource type="Texture2D" uid="uid://drxi5ks3mqbnk" path="res://graph_node_renderer/textures/add_icon.svg" id="2_15lro"]
|
||||||
|
|
||||||
|
[node name="VariableViewer" type="VBoxContainer"]
|
||||||
|
anchors_preset = -1
|
||||||
|
anchor_right = 0.467014
|
||||||
|
anchor_bottom = 0.359568
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1_b5gmj")
|
||||||
|
metadata/_edit_use_anchors_ = true
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Deck Variables"
|
||||||
|
|
||||||
|
[node name="VariableTree" type="Tree" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
columns = 3
|
||||||
|
column_titles_visible = true
|
||||||
|
allow_rmb_select = true
|
||||||
|
allow_search = false
|
||||||
|
hide_root = true
|
||||||
|
|
||||||
|
[node name="TypesPopup" type="PopupMenu" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
|
||||||
|
[node name="NewVariableButton" type="Button" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
|
text = "New"
|
||||||
|
icon = ExtResource("2_15lro")
|
|
@ -44,7 +44,7 @@ rename_node={
|
||||||
"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":4194333,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194333,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
toggle_console={
|
toggle_bottom_dock={
|
||||||
"deadzone": 0.5,
|
"deadzone": 0.5,
|
||||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":78,"key_label":0,"unicode":110,"echo":false,"script":null)
|
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":78,"key_label":0,"unicode":110,"echo":false,"script":null)
|
||||||
]
|
]
|
||||||
|
|
5
script_templates/Object/empty_template_with_license.gd
Normal file
5
script_templates/Object/empty_template_with_license.gd
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# meta-description: An empty template with StreamGraph license header.
|
||||||
|
# (c) 2023-present Eroax
|
||||||
|
# (c) 2023-present Yagich
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
extends _BASE_
|
Loading…
Reference in a new issue