└── addons └── InstanceDock ├── InstanceDock.pot ├── InstanceDockPlugin.gd ├── InstanceDockPlugin.gd.uid ├── PluginUtils.gd ├── PluginUtils.gd.uid ├── Scenes ├── InstanceDock.tscn ├── InstanceSlot.tscn └── InstanceSlotText.tscn ├── Scripts ├── InstanceDock.gd ├── InstanceDock.gd.uid ├── InstancePropertyEdit.gd ├── InstancePropertyEdit.gd.uid ├── InstanceSlot.gd ├── InstanceSlot.gd.uid ├── PaintMode.gd └── PaintMode.gd.uid ├── Textures ├── Add.svg ├── Add.svg.import ├── Collapse.svg ├── Collapse.svg.import ├── GuiTabMenuHl.svg ├── GuiTabMenuHl.svg.import ├── Loading.png ├── Loading.png.import ├── Missing.png ├── Missing.png.import ├── Overrides.png ├── Overrides.png.import ├── Uncollapse.svg └── Uncollapse.svg.import ├── Translations └── pl.po └── plugin.cfg /addons/InstanceDock/InstanceDock.pot: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation for for the following files: 2 | # res://addons/InstanceDock/Scenes/InstanceDock.tscn 3 | # res://addons/InstanceDock/Scenes/InstanceSlot.tscn 4 | # res://addons/InstanceDock/Scenes/InstanceSlotText.tscn 5 | # res://addons/InstanceDock/Scripts/InstanceDock.gd 6 | # res://addons/InstanceDock/Scripts/InstancePropertyEdit.gd 7 | # res://addons/InstanceDock/Scripts/InstanceSlot.gd 8 | # res://addons/InstanceDock/Scripts/PaintMode.gd 9 | # res://addons/InstanceDock/InstanceDockPlugin.gd 10 | # 11 | # FIRST AUTHOR , YEAR. 12 | # 13 | #, fuzzy 14 | msgid "" 15 | msgstr "" 16 | "Project-Id-Version: \n" 17 | "MIME-Version: 1.0\n" 18 | "Content-Type: text/plain; charset=UTF-8\n" 19 | "Content-Transfer-Encoding: 8-bit\n" 20 | 21 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 22 | msgid "Filter Instances" 23 | msgstr "" 24 | 25 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 26 | msgid "Add tab to continue." 27 | msgstr "" 28 | 29 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 30 | msgid "Drag scene from file system on a slot to assign or right-click to Quick Load." 31 | msgstr "" 32 | 33 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 34 | msgid "Parent" 35 | msgstr "" 36 | 37 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 38 | msgid "Drop Node to assign" 39 | msgstr "" 40 | 41 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 42 | msgid "Paint Mode" 43 | msgstr "" 44 | 45 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 46 | msgid "Snap" 47 | msgstr "" 48 | 49 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 50 | msgid "This will permanently remove the tab. Continue?" 51 | msgstr "" 52 | 53 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 54 | msgid "Tab name" 55 | msgstr "" 56 | 57 | #: addons/InstanceDock/Scenes/InstanceSlot.tscn 58 | #: addons/InstanceDock/Scenes/InstanceSlotText.tscn 59 | msgid "Drop scene to assign" 60 | msgstr "" 61 | 62 | #: addons/InstanceDock/Scenes/InstanceSlot.tscn 63 | msgid "Instances" 64 | msgstr "" 65 | 66 | #: addons/InstanceDock/Scripts/InstanceDock.gd 67 | msgid "Icons" 68 | msgstr "" 69 | 70 | #: addons/InstanceDock/Scripts/InstanceDock.gd 71 | msgid "Text" 72 | msgstr "" 73 | 74 | #: addons/InstanceDock/Scripts/InstanceDock.gd 75 | msgid "Refresh All Previews" 76 | msgstr "" 77 | 78 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 79 | msgid "Open Scene" 80 | msgstr "" 81 | 82 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 83 | msgid "Override Properties" 84 | msgstr "" 85 | 86 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 87 | msgid "Remove" 88 | msgstr "" 89 | 90 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 91 | msgid "Remove Custom Icon" 92 | msgstr "" 93 | 94 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 95 | msgid "Refresh Icon" 96 | msgstr "" 97 | 98 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 99 | msgid "Quick Load..." 100 | msgstr "" 101 | 102 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 103 | msgid "Overrides:" 104 | msgstr "" 105 | 106 | #: addons/InstanceDock/Scripts/PaintMode.gd 107 | msgid "(Re)Select any CanvasItem to start" 108 | msgstr "" 109 | 110 | #: addons/InstanceDock/Scripts/PaintMode.gd 111 | msgid "Click instance slot to select" 112 | msgstr "" 113 | 114 | #: addons/InstanceDock/Scripts/PaintMode.gd 115 | msgid "Use LMB to paint instance" 116 | msgstr "" 117 | -------------------------------------------------------------------------------- /addons/InstanceDock/InstanceDockPlugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | const PluginUtils = preload("res://addons/InstanceDock/PluginUtils.gd") 5 | 6 | var dock: Control 7 | var paint_mode: Control 8 | 9 | func _init() -> void: 10 | var translator := PluginUtils.translate_plugin(self) 11 | translator.add_translations_from_directory("res://addons/InstanceDock/Translations") 12 | 13 | func _ready() -> void: 14 | dock = preload("res://addons/InstanceDock/Scenes/InstanceDock.tscn").instantiate() 15 | dock.plugin = self 16 | add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, dock) 17 | paint_mode = dock.paint_mode 18 | 19 | func _exit_tree(): 20 | remove_control_from_docks(dock) 21 | dock.free() 22 | 23 | func _handles(object: Object) -> bool: 24 | return paint_mode.enabled and object is CanvasItem 25 | 26 | func _edit(object: Object) -> void: 27 | paint_mode.edited_node = object 28 | 29 | func _forward_canvas_gui_input(event: InputEvent) -> bool: 30 | return paint_mode.paint_input(event) 31 | 32 | func _forward_canvas_draw_over_viewport(viewport_control: Control) -> void: 33 | paint_mode.paint_draw(viewport_control) 34 | 35 | func _get_window_layout(configuration: ConfigFile) -> void: 36 | var tabs: TabBar = dock.tabs 37 | if tabs.tab_count == 0: 38 | return 39 | 40 | tabs.set_tab_metadata(tabs.current_tab, dock.scroll.scroll_vertical) 41 | 42 | for i in tabs.tab_count: 43 | configuration.set_value("InstanceDock", "tab_%d_scroll" % i, tabs.get_tab_metadata(i)) 44 | 45 | func _set_window_layout(configuration: ConfigFile) -> void: 46 | var tabs: TabBar = dock.tabs 47 | for i in tabs.tab_count: 48 | tabs.set_tab_metadata(i, configuration.get_value("InstanceDock", "tab_%d_scroll" % i, 0)) 49 | -------------------------------------------------------------------------------- /addons/InstanceDock/InstanceDockPlugin.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dfstuid6lpnpo 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/PluginUtils.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | static func translate_plugin(plugin: EditorPlugin) -> PluginTranslator: 4 | var translator := PluginTranslator.new(plugin) 5 | return translator 6 | 7 | static func define_project_setting(setting: String, default_value: Variant, hint := PROPERTY_HINT_NONE, hint_string := "") -> Variant: 8 | var value: Variant 9 | if ProjectSettings.has_setting(setting): 10 | value = ProjectSettings.get_setting(setting) 11 | else: 12 | value = default_value 13 | ProjectSettings.set_setting(setting, default_value) 14 | 15 | ProjectSettings.set_initial_value(setting, default_value) 16 | if hint != PROPERTY_HINT_NONE: 17 | ProjectSettings.add_property_info({"name": setting, "type": typeof(default_value), "hint": hint, "hint_string": hint_string}) 18 | 19 | return value 20 | 21 | static func track_project_setting(setting: String, owner: Object, callback: Callable) -> void: 22 | var tracker := ProjectSettingTracker.new(owner, setting) 23 | tracker.callback = callback 24 | 25 | class PluginTranslator: 26 | var domain: TranslationDomain 27 | var translation_list: Array[Translation] 28 | 29 | func _init(for_plugin: EditorPlugin) -> void: 30 | domain = TranslationServer.get_or_add_domain(&"godot.editor") 31 | 32 | var existing = for_plugin.get_meta(&"_translator", false) 33 | if existing: 34 | push_error("The plugin is already translated.") 35 | return 36 | 37 | for_plugin.set_meta(&"_translator", self) 38 | for_plugin.tree_entered.connect(_submit_translations) 39 | for_plugin.tree_exited.connect(_revoke_translations) 40 | 41 | func add_translation(translation: Translation): 42 | translation_list.append(translation) 43 | 44 | func add_translations_from_directory(path: String): 45 | for file in ResourceLoader.list_directory(path): 46 | var translation := load(path.path_join(file)) as Translation 47 | if translation: 48 | translation_list.append(translation) 49 | 50 | func _submit_translations(): 51 | for translation in translation_list: 52 | domain.add_translation(translation) 53 | 54 | func _revoke_translations(): 55 | for translation in translation_list: 56 | domain.remove_translation(translation) 57 | 58 | class ProjectSettingTracker: 59 | class ActualTracker: 60 | var trackers: Array[ProjectSettingTracker] 61 | 62 | func _init() -> void: 63 | ProjectSettings.settings_changed.connect(_settings_changed) 64 | 65 | func _settings_changed(): 66 | for tracker in trackers: 67 | tracker._check_setting() 68 | 69 | var callback: Callable 70 | var tracked_setting: String 71 | var prev_value: Variant 72 | 73 | func _init(owner: Object, setting: String) -> void: 74 | var actual_tracker: ActualTracker 75 | if owner.has_meta(&"_settings_tracker"): 76 | actual_tracker = owner.get_meta(&"_settings_tracker") 77 | else: 78 | actual_tracker = ActualTracker.new() 79 | owner.set_meta(&"_settings_tracker", actual_tracker) 80 | 81 | tracked_setting = setting 82 | prev_value = ProjectSettings.get_setting(tracked_setting) 83 | actual_tracker.trackers.append(self) 84 | 85 | func _check_setting(): 86 | var new_value := ProjectSettings.get_setting(tracked_setting) 87 | if new_value != prev_value: 88 | callback.call(tracked_setting, new_value) 89 | prev_value = new_value 90 | -------------------------------------------------------------------------------- /addons/InstanceDock/PluginUtils.gd.uid: -------------------------------------------------------------------------------- 1 | uid://b50p4i2m862d5 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scenes/InstanceDock.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=3 uid="uid://d3a6r5br2nw5"] 2 | 3 | [ext_resource type="Script" uid="uid://c0f7ht031r3t8" path="res://addons/InstanceDock/Scripts/InstanceDock.gd" id="1_lolgq"] 4 | [ext_resource type="Texture2D" uid="uid://buv26pii10cx2" path="res://addons/InstanceDock/Textures/Add.svg" id="2_gdgx1"] 5 | [ext_resource type="Texture2D" uid="uid://doleo6g38lico" path="res://addons/InstanceDock/Textures/GuiTabMenuHl.svg" id="3_bps6t"] 6 | [ext_resource type="Texture2D" uid="uid://b81f1abox2e67" path="res://addons/InstanceDock/Textures/Uncollapse.svg" id="4_eqqv5"] 7 | [ext_resource type="Script" uid="uid://d16qd5wan2vm5" path="res://addons/InstanceDock/Scripts/PaintMode.gd" id="5_qgjgt"] 8 | 9 | [node name="Instances" type="MarginContainer"] 10 | anchors_preset = 9 11 | anchor_bottom = 1.0 12 | offset_right = 350.0 13 | grow_vertical = 2 14 | script = ExtResource("1_lolgq") 15 | 16 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 17 | layout_mode = 2 18 | 19 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] 20 | layout_mode = 2 21 | size_flags_horizontal = 3 22 | 23 | [node name="Tabs" type="TabBar" parent="VBoxContainer/HBoxContainer"] 24 | unique_name_in_owner = true 25 | auto_translate_mode = 2 26 | layout_mode = 2 27 | size_flags_horizontal = 3 28 | tab_close_display_policy = 1 29 | drag_to_rearrange_enabled = true 30 | 31 | [node name="Button" type="Button" parent="VBoxContainer/HBoxContainer"] 32 | layout_mode = 2 33 | icon = ExtResource("2_gdgx1") 34 | 35 | [node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"] 36 | layout_mode = 2 37 | 38 | [node name="FilterLineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer2"] 39 | unique_name_in_owner = true 40 | layout_mode = 2 41 | size_flags_horizontal = 3 42 | placeholder_text = "Filter Instances" 43 | clear_button_enabled = true 44 | 45 | [node name="ViewMenu" type="MenuButton" parent="VBoxContainer/HBoxContainer2"] 46 | unique_name_in_owner = true 47 | layout_mode = 2 48 | icon = ExtResource("3_bps6t") 49 | 50 | [node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] 51 | unique_name_in_owner = true 52 | layout_mode = 2 53 | size_flags_vertical = 3 54 | horizontal_scroll_mode = 0 55 | 56 | [node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"] 57 | layout_mode = 2 58 | size_flags_horizontal = 3 59 | size_flags_vertical = 3 60 | 61 | [node name="IconSlots" type="HFlowContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer"] 62 | unique_name_in_owner = true 63 | visible = false 64 | layout_mode = 2 65 | size_flags_horizontal = 3 66 | 67 | [node name="TextSlots" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/VBoxContainer"] 68 | unique_name_in_owner = true 69 | visible = false 70 | layout_mode = 2 71 | size_flags_horizontal = 3 72 | 73 | [node name="AddTabLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer"] 74 | unique_name_in_owner = true 75 | layout_mode = 2 76 | text = "Add tab to continue." 77 | autowrap_mode = 2 78 | 79 | [node name="DragLabel" type="Label" parent="VBoxContainer/ScrollContainer/VBoxContainer"] 80 | unique_name_in_owner = true 81 | visible = false 82 | layout_mode = 2 83 | text = "Drag scene from file system on a slot to assign or right-click to Quick Load." 84 | autowrap_mode = 2 85 | 86 | [node name="ExtrasToggle" type="Button" parent="VBoxContainer"] 87 | unique_name_in_owner = true 88 | layout_mode = 2 89 | icon = ExtResource("4_eqqv5") 90 | icon_alignment = 1 91 | 92 | [node name="Extras" type="VBoxContainer" parent="VBoxContainer"] 93 | unique_name_in_owner = true 94 | layout_mode = 2 95 | 96 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Extras"] 97 | layout_mode = 2 98 | 99 | [node name="Label" type="Label" parent="VBoxContainer/Extras/HBoxContainer"] 100 | layout_mode = 2 101 | text = "Parent" 102 | 103 | [node name="ParentSelector" type="HBoxContainer" parent="VBoxContainer/Extras/HBoxContainer"] 104 | unique_name_in_owner = true 105 | layout_mode = 2 106 | size_flags_horizontal = 3 107 | 108 | [node name="ParentIcon" type="TextureRect" parent="VBoxContainer/Extras/HBoxContainer/ParentSelector"] 109 | unique_name_in_owner = true 110 | visible = false 111 | layout_mode = 2 112 | mouse_filter = 2 113 | expand_mode = 2 114 | stretch_mode = 3 115 | 116 | [node name="ParentName" type="LineEdit" parent="VBoxContainer/Extras/HBoxContainer/ParentSelector"] 117 | unique_name_in_owner = true 118 | layout_mode = 2 119 | size_flags_horizontal = 3 120 | focus_mode = 0 121 | mouse_filter = 2 122 | placeholder_text = "Drop Node to assign" 123 | editable = false 124 | 125 | [node name="HSeparator" type="HSeparator" parent="VBoxContainer/Extras"] 126 | layout_mode = 2 127 | 128 | [node name="CheckButton" type="CheckButton" parent="VBoxContainer/Extras"] 129 | layout_mode = 2 130 | size_flags_horizontal = 4 131 | text = "Paint Mode" 132 | 133 | [node name="PaintMode" type="VBoxContainer" parent="VBoxContainer/Extras"] 134 | unique_name_in_owner = true 135 | layout_mode = 2 136 | script = ExtResource("5_qgjgt") 137 | 138 | [node name="Status" type="Label" parent="VBoxContainer/Extras/PaintMode"] 139 | unique_name_in_owner = true 140 | layout_mode = 2 141 | horizontal_alignment = 1 142 | 143 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Extras/PaintMode"] 144 | layout_mode = 2 145 | 146 | [node name="SnapEnabled" type="CheckBox" parent="VBoxContainer/Extras/PaintMode/HBoxContainer"] 147 | unique_name_in_owner = true 148 | layout_mode = 2 149 | text = "Snap" 150 | 151 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Extras/PaintMode/HBoxContainer"] 152 | layout_mode = 2 153 | size_flags_horizontal = 3 154 | 155 | [node name="SnapX" type="SpinBox" parent="VBoxContainer/Extras/PaintMode/HBoxContainer/HBoxContainer"] 156 | unique_name_in_owner = true 157 | layout_mode = 2 158 | size_flags_horizontal = 3 159 | min_value = 1.0 160 | max_value = 512.0 161 | value = 8.0 162 | suffix = "px" 163 | select_all_on_focus = true 164 | 165 | [node name="Label2" type="Label" parent="VBoxContainer/Extras/PaintMode/HBoxContainer/HBoxContainer"] 166 | auto_translate_mode = 2 167 | layout_mode = 2 168 | text = "x" 169 | 170 | [node name="SnapY" type="SpinBox" parent="VBoxContainer/Extras/PaintMode/HBoxContainer/HBoxContainer"] 171 | unique_name_in_owner = true 172 | layout_mode = 2 173 | size_flags_horizontal = 3 174 | min_value = 1.0 175 | max_value = 512.0 176 | value = 8.0 177 | suffix = "px" 178 | select_all_on_focus = true 179 | 180 | [node name="Dialogs" type="Node" parent="."] 181 | 182 | [node name="DeleteConfirm" type="ConfirmationDialog" parent="Dialogs"] 183 | unique_name_in_owner = true 184 | size = Vector2i(391, 100) 185 | dialog_text = "This will permanently remove the tab. Continue?" 186 | 187 | [node name="AddTabConfirm" type="ConfirmationDialog" parent="Dialogs"] 188 | unique_name_in_owner = true 189 | size = Vector2i(200, 88) 190 | 191 | [node name="AddTabName" type="LineEdit" parent="Dialogs/AddTabConfirm"] 192 | unique_name_in_owner = true 193 | anchors_preset = 10 194 | anchor_right = 1.0 195 | offset_left = 8.0 196 | offset_top = 8.0 197 | offset_right = -8.0 198 | offset_bottom = 39.0 199 | placeholder_text = "Tab name" 200 | 201 | [node name="Viewport" type="SubViewport" parent="."] 202 | transparent_bg = true 203 | render_target_update_mode = 3 204 | 205 | [connection signal="active_tab_rearranged" from="VBoxContainer/HBoxContainer/Tabs" to="." method="on_rearrange"] 206 | [connection signal="tab_changed" from="VBoxContainer/HBoxContainer/Tabs" to="." method="on_tab_changed"] 207 | [connection signal="tab_close_pressed" from="VBoxContainer/HBoxContainer/Tabs" to="." method="on_tab_close_attempt"] 208 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/Button" to="." method="on_add_tab_pressed"] 209 | [connection signal="text_changed" from="VBoxContainer/HBoxContainer2/FilterLineEdit" to="." method="_on_filter_changed"] 210 | [connection signal="pressed" from="VBoxContainer/ExtrasToggle" to="." method="toggle_extras"] 211 | [connection signal="toggled" from="VBoxContainer/Extras/CheckButton" to="VBoxContainer/Extras/PaintMode" method="set_paint_mode_enabled"] 212 | [connection signal="toggled" from="VBoxContainer/Extras/CheckButton" to="VBoxContainer/Extras/PaintMode" method="set_visible"] 213 | [connection signal="pressed" from="VBoxContainer/Extras/PaintMode/HBoxContainer/SnapEnabled" to="VBoxContainer/Extras/PaintMode" method="update_overlays"] 214 | [connection signal="value_changed" from="VBoxContainer/Extras/PaintMode/HBoxContainer/HBoxContainer/SnapX" to="VBoxContainer/Extras/PaintMode" method="_on_snap_changed"] 215 | [connection signal="value_changed" from="VBoxContainer/Extras/PaintMode/HBoxContainer/HBoxContainer/SnapY" to="VBoxContainer/Extras/PaintMode" method="_on_snap_changed"] 216 | [connection signal="confirmed" from="Dialogs/DeleteConfirm" to="." method="remove_tab_confirm"] 217 | [connection signal="confirmed" from="Dialogs/AddTabConfirm" to="." method="add_tab_confirm"] 218 | [connection signal="text_submitted" from="Dialogs/AddTabConfirm/AddTabName" to="." method="add_tab_confirm"] 219 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scenes/InstanceSlot.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=14 format=3 uid="uid://ba54n4w4qb18j"] 2 | 3 | [ext_resource type="Script" uid="uid://cffy8p6erriue" path="res://addons/InstanceDock/Scripts/InstanceSlot.gd" id="1_uffwn"] 4 | [ext_resource type="Texture2D" uid="uid://dp0lseq583t66" path="res://addons/InstanceDock/Textures/Overrides.png" id="2_ehk6s"] 5 | [ext_resource type="Texture2D" uid="uid://rsllva5pmojk" path="res://addons/InstanceDock/Textures/Loading.png" id="3_8kfgm"] 6 | 7 | [sub_resource type="StyleBoxFlat" id="1"] 8 | content_margin_left = 2.0 9 | content_margin_top = 2.0 10 | content_margin_right = 2.0 11 | content_margin_bottom = 2.0 12 | bg_color = Color(0, 0, 0, 0.501961) 13 | corner_radius_top_left = 8 14 | corner_radius_top_right = 8 15 | corner_radius_bottom_right = 8 16 | corner_radius_bottom_left = 8 17 | 18 | [sub_resource type="StyleBoxFlat" id="4"] 19 | bg_color = Color(0, 0, 0, 0.501961) 20 | border_width_left = 2 21 | border_width_top = 2 22 | border_width_right = 2 23 | border_width_bottom = 2 24 | border_color = Color(1, 1, 1, 0.25098) 25 | corner_radius_top_left = 8 26 | corner_radius_top_right = 8 27 | corner_radius_bottom_right = 8 28 | corner_radius_bottom_left = 8 29 | 30 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1u6qi"] 31 | 32 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hom1a"] 33 | 34 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_au8j5"] 35 | draw_center = false 36 | border_width_left = 2 37 | border_width_top = 2 38 | border_width_right = 2 39 | border_width_bottom = 2 40 | border_color = Color(1, 0.431373, 0, 1) 41 | 42 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s7yid"] 43 | draw_center = false 44 | border_width_left = 2 45 | border_width_top = 2 46 | border_width_right = 2 47 | border_width_bottom = 2 48 | border_color = Color(1, 0, 0, 1) 49 | 50 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_khpyj"] 51 | 52 | [sub_resource type="Animation" id="2"] 53 | length = 0.001 54 | tracks/0/type = "value" 55 | tracks/0/imported = false 56 | tracks/0/enabled = true 57 | tracks/0/path = NodePath(".:rotation") 58 | tracks/0/interp = 1 59 | tracks/0/loop_wrap = true 60 | tracks/0/keys = { 61 | "times": PackedFloat32Array(0), 62 | "transitions": PackedFloat32Array(1), 63 | "update": 0, 64 | "values": [0.0] 65 | } 66 | 67 | [sub_resource type="Animation" id="3"] 68 | resource_name = "Rotate" 69 | loop_mode = 1 70 | tracks/0/type = "value" 71 | tracks/0/imported = false 72 | tracks/0/enabled = true 73 | tracks/0/path = NodePath(".:rotation") 74 | tracks/0/interp = 1 75 | tracks/0/loop_wrap = true 76 | tracks/0/keys = { 77 | "times": PackedFloat32Array(0, 1), 78 | "transitions": PackedFloat32Array(1, 1), 79 | "update": 0, 80 | "values": [0.0, 6.28319] 81 | } 82 | 83 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_u8ud1"] 84 | _data = { 85 | &"RESET": SubResource("2"), 86 | &"Rotate": SubResource("3") 87 | } 88 | 89 | [node name="InstanceSlot" type="PanelContainer"] 90 | offset_right = 64.0 91 | offset_bottom = 64.0 92 | theme_override_styles/panel = SubResource("1") 93 | script = ExtResource("1_uffwn") 94 | normal = SubResource("1") 95 | custom = SubResource("4") 96 | 97 | [node name="Icon" type="TextureRect" parent="."] 98 | unique_name_in_owner = true 99 | custom_minimum_size = Vector2(64, 64) 100 | layout_mode = 2 101 | mouse_filter = 2 102 | expand_mode = 1 103 | 104 | [node name="Label" type="Label" parent="Icon"] 105 | unique_name_in_owner = true 106 | z_as_relative = false 107 | layout_mode = 1 108 | anchors_preset = 15 109 | anchor_right = 1.0 110 | anchor_bottom = 1.0 111 | grow_horizontal = 2 112 | grow_vertical = 2 113 | mouse_filter = 1 114 | theme_override_constants/line_spacing = 0 115 | theme_override_font_sizes/font_size = 12 116 | text = "Drop scene to assign" 117 | horizontal_alignment = 1 118 | vertical_alignment = 1 119 | autowrap_mode = 2 120 | 121 | [node name="HasOverrides" type="TextureRect" parent="."] 122 | visible = false 123 | texture_filter = 1 124 | layout_mode = 2 125 | size_flags_horizontal = 8 126 | size_flags_vertical = 8 127 | texture = ExtResource("2_ehk6s") 128 | stretch_mode = 2 129 | 130 | [node name="PaintButton" type="Button" parent="."] 131 | visible = false 132 | layout_mode = 2 133 | focus_mode = 0 134 | theme_override_styles/focus = SubResource("StyleBoxEmpty_1u6qi") 135 | theme_override_styles/disabled = SubResource("StyleBoxEmpty_hom1a") 136 | theme_override_styles/hover = SubResource("StyleBoxFlat_au8j5") 137 | theme_override_styles/pressed = SubResource("StyleBoxFlat_s7yid") 138 | theme_override_styles/normal = SubResource("StyleBoxEmpty_khpyj") 139 | toggle_mode = true 140 | 141 | [node name="Loading" type="Sprite2D" parent="."] 142 | unique_name_in_owner = true 143 | visible = false 144 | position = Vector2(32, 32) 145 | texture = ExtResource("3_8kfgm") 146 | 147 | [node name="AnimationPlayer" type="AnimationPlayer" parent="Loading"] 148 | unique_name_in_owner = true 149 | libraries = { 150 | &"": SubResource("AnimationLibrary_u8ud1") 151 | } 152 | 153 | [node name="Timer" type="Timer" parent="."] 154 | wait_time = 0.5 155 | one_shot = true 156 | 157 | [node name="DockNameHackForPOT" type="Label" parent="."] 158 | visible = false 159 | layout_mode = 2 160 | text = "Instances" 161 | 162 | [connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] 163 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scenes/InstanceSlotText.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=14 format=3 uid="uid://cud5aqeotr1y0"] 2 | 3 | [ext_resource type="Script" uid="uid://cffy8p6erriue" path="res://addons/InstanceDock/Scripts/InstanceSlot.gd" id="1_hrh6k"] 4 | [ext_resource type="Texture2D" uid="uid://dp0lseq583t66" path="res://addons/InstanceDock/Textures/Overrides.png" id="2_24cxw"] 5 | [ext_resource type="Texture2D" uid="uid://rsllva5pmojk" path="res://addons/InstanceDock/Textures/Loading.png" id="3_3qp7n"] 6 | 7 | [sub_resource type="StyleBoxFlat" id="1"] 8 | content_margin_left = 2.0 9 | content_margin_top = 2.0 10 | content_margin_right = 2.0 11 | content_margin_bottom = 2.0 12 | bg_color = Color(0, 0, 0, 0.501961) 13 | corner_radius_top_left = 8 14 | corner_radius_top_right = 8 15 | corner_radius_bottom_right = 8 16 | corner_radius_bottom_left = 8 17 | 18 | [sub_resource type="StyleBoxFlat" id="4"] 19 | bg_color = Color(0, 0, 0, 0.501961) 20 | border_width_left = 2 21 | border_width_top = 2 22 | border_width_right = 2 23 | border_width_bottom = 2 24 | border_color = Color(1, 1, 1, 0.25098) 25 | corner_radius_top_left = 8 26 | corner_radius_top_right = 8 27 | corner_radius_bottom_right = 8 28 | corner_radius_bottom_left = 8 29 | 30 | [sub_resource type="Animation" id="2"] 31 | length = 0.001 32 | tracks/0/type = "value" 33 | tracks/0/imported = false 34 | tracks/0/enabled = true 35 | tracks/0/path = NodePath(".:rotation") 36 | tracks/0/interp = 1 37 | tracks/0/loop_wrap = true 38 | tracks/0/keys = { 39 | "times": PackedFloat32Array(0), 40 | "transitions": PackedFloat32Array(1), 41 | "update": 0, 42 | "values": [0.0] 43 | } 44 | 45 | [sub_resource type="Animation" id="3"] 46 | resource_name = "Rotate" 47 | loop_mode = 1 48 | tracks/0/type = "value" 49 | tracks/0/imported = false 50 | tracks/0/enabled = true 51 | tracks/0/path = NodePath(".:rotation") 52 | tracks/0/interp = 1 53 | tracks/0/loop_wrap = true 54 | tracks/0/keys = { 55 | "times": PackedFloat32Array(0, 1), 56 | "transitions": PackedFloat32Array(1, 1), 57 | "update": 0, 58 | "values": [0.0, 6.28319] 59 | } 60 | 61 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_u8ud1"] 62 | _data = { 63 | &"RESET": SubResource("2"), 64 | &"Rotate": SubResource("3") 65 | } 66 | 67 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_1u6qi"] 68 | 69 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hom1a"] 70 | 71 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_au8j5"] 72 | draw_center = false 73 | border_width_left = 2 74 | border_width_top = 2 75 | border_width_right = 2 76 | border_width_bottom = 2 77 | border_color = Color(1, 0.431373, 0, 1) 78 | 79 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s7yid"] 80 | draw_center = false 81 | border_width_left = 2 82 | border_width_top = 2 83 | border_width_right = 2 84 | border_width_bottom = 2 85 | border_color = Color(1, 0, 0, 1) 86 | 87 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_khpyj"] 88 | 89 | [node name="InstanceSlot" type="PanelContainer"] 90 | offset_right = 144.0 91 | offset_bottom = 21.0 92 | theme_override_styles/panel = SubResource("1") 93 | script = ExtResource("1_hrh6k") 94 | normal = SubResource("1") 95 | custom = SubResource("4") 96 | 97 | [node name="HBoxContainer" type="HBoxContainer" parent="."] 98 | layout_mode = 2 99 | 100 | [node name="Icon" type="TextureRect" parent="HBoxContainer"] 101 | unique_name_in_owner = true 102 | custom_minimum_size = Vector2(16, 16) 103 | layout_mode = 2 104 | mouse_filter = 2 105 | expand_mode = 1 106 | 107 | [node name="Loading" type="Sprite2D" parent="HBoxContainer/Icon"] 108 | unique_name_in_owner = true 109 | visible = false 110 | position = Vector2(8, 8) 111 | texture = ExtResource("3_3qp7n") 112 | 113 | [node name="AnimationPlayer" type="AnimationPlayer" parent="HBoxContainer/Icon/Loading"] 114 | unique_name_in_owner = true 115 | libraries = { 116 | &"": SubResource("AnimationLibrary_u8ud1") 117 | } 118 | 119 | [node name="Label" type="Label" parent="HBoxContainer"] 120 | unique_name_in_owner = true 121 | z_as_relative = false 122 | layout_mode = 2 123 | mouse_filter = 1 124 | theme_override_constants/line_spacing = 0 125 | theme_override_font_sizes/font_size = 12 126 | text = "Drop scene to assign" 127 | horizontal_alignment = 1 128 | vertical_alignment = 1 129 | 130 | [node name="Path" type="Label" parent="HBoxContainer"] 131 | unique_name_in_owner = true 132 | visible = false 133 | layout_mode = 2 134 | size_flags_horizontal = 3 135 | text_overrun_behavior = 3 136 | 137 | [node name="HasOverrides" type="TextureRect" parent="."] 138 | visible = false 139 | texture_filter = 1 140 | layout_mode = 2 141 | size_flags_horizontal = 0 142 | size_flags_vertical = 8 143 | texture = ExtResource("2_24cxw") 144 | stretch_mode = 2 145 | 146 | [node name="PaintButton" type="Button" parent="."] 147 | visible = false 148 | layout_mode = 2 149 | focus_mode = 0 150 | theme_override_styles/focus = SubResource("StyleBoxEmpty_1u6qi") 151 | theme_override_styles/disabled = SubResource("StyleBoxEmpty_hom1a") 152 | theme_override_styles/hover = SubResource("StyleBoxFlat_au8j5") 153 | theme_override_styles/pressed = SubResource("StyleBoxFlat_s7yid") 154 | theme_override_styles/normal = SubResource("StyleBoxEmpty_khpyj") 155 | toggle_mode = true 156 | 157 | [node name="Timer" type="Timer" parent="."] 158 | wait_time = 0.5 159 | one_shot = true 160 | 161 | [connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"] 162 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/InstanceDock.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | const PluginUtils = preload("res://addons/InstanceDock/PluginUtils.gd") 5 | const PROJECT_SETTING_CONFIG = "addons/instance_dock/scene_data_file" 6 | const PROJECT_SETTING_LEGACY = "addons/instance_dock/scenes" 7 | const PROJECT_SETTING_PREVIEW = "addons/instance_dock/preview_resolution" 8 | const CACHE_PATH = "res://.godot/InstanceIconCache" 9 | 10 | var PREVIEW_SIZE = Vector2i(64, 64) 11 | var CONFIG_FILE = "res://InstanceDockSceneData.txt" 12 | 13 | enum {SLOT_MODE_ICONS, SLOT_MODE_TEXT, REFRESH_ALL_PREVIEWS} 14 | 15 | class InstanceDock_Data: 16 | class InstanceDock_Instance: 17 | var scene: String 18 | var custom_texture: String 19 | var overrides: Dictionary[StringName, Variant] 20 | 21 | class InstanceDock_Tab: 22 | var name: String 23 | var instances: Array[InstanceDock_Instance] 24 | 25 | var version: int 26 | var tab_data: Array[InstanceDock_Tab] 27 | 28 | func load_data(loaded): 29 | var dict: Dictionary 30 | if loaded is Dictionary: 31 | dict = loaded 32 | else: 33 | dict = {"tab_data": loaded} 34 | 35 | version = dict.get("version", -1) 36 | 37 | for tab_dict: Dictionary in dict["tab_data"]: 38 | var tab := InstanceDock_Data.InstanceDock_Tab.new() 39 | tab_data.append(tab) 40 | tab.name = tab_dict.get("name", "") 41 | 42 | for dict_instance: Dictionary in tab_dict.get("scenes", []): 43 | var instance := InstanceDock_Data.InstanceDock_Instance.new() 44 | tab.instances.append(instance) 45 | 46 | instance.scene = dict_instance.get("scene", "") 47 | if version == -1: 48 | var uid := ResourceLoader.get_resource_uid(instance.scene) 49 | if uid != ResourceUID.INVALID_ID: 50 | instance.scene = ResourceUID.id_to_text(uid) 51 | 52 | instance.custom_texture = dict_instance.get("custom_texture", "") 53 | if version == -1: 54 | var uid := ResourceLoader.get_resource_uid(instance.custom_texture) 55 | if uid != ResourceUID.INVALID_ID: 56 | instance.custom_texture = ResourceUID.id_to_text(uid) 57 | 58 | instance.overrides.assign(dict_instance.get("overrides", {})) 59 | 60 | version = 0 61 | 62 | func save_data() -> Dictionary: 63 | var data: Dictionary 64 | 65 | data["version"] = version 66 | 67 | var save_tabs: Array 68 | data["tab_data"] = save_tabs 69 | 70 | for tab in tab_data: 71 | var tab_dict: Dictionary 72 | save_tabs.append(tab_dict) 73 | tab_dict["name"] = tab.name 74 | 75 | var instances: Array 76 | for instance in tab.instances: 77 | var instance_dict: Dictionary 78 | instances.append(instance_dict) 79 | 80 | if instance: 81 | instance_dict["scene"] = instance.scene 82 | if not instance.custom_texture.is_empty(): 83 | instance_dict["custom_texture"] = instance.custom_texture 84 | if not instance.overrides.is_empty(): 85 | var untyped: Dictionary 86 | untyped.assign(instance.overrides) 87 | instance_dict["overrides"] = untyped 88 | 89 | if not instances.is_empty(): 90 | tab_dict["scenes"] = instances 91 | 92 | return data 93 | 94 | class ProcessedItem: 95 | var icon_path: String 96 | var icon: Texture2D 97 | var instance_path: String 98 | var instance: Node 99 | var slot: Control 100 | var overrides: Dictionary[StringName, Variant] 101 | 102 | @onready var tabs: TabBar = %Tabs 103 | @onready var tab_add_confirm := %AddTabConfirm 104 | @onready var tab_add_name := %AddTabName 105 | @onready var tab_delete_confirm := %DeleteConfirm 106 | @onready var filter_line_edit: LineEdit = %FilterLineEdit 107 | @onready var view_menu: MenuButton = %ViewMenu 108 | 109 | @onready var scroll := %ScrollContainer 110 | @onready var add_tab_label := %AddTabLabel 111 | @onready var drag_label := %DragLabel 112 | 113 | @onready var extras_toggle: Button = %ExtrasToggle 114 | @onready var extras: VBoxContainer = %Extras 115 | @onready var parent_selector: HBoxContainer = %ParentSelector 116 | @onready var parent_icon: TextureRect = %ParentIcon 117 | @onready var parent_name: LineEdit = %ParentName 118 | @onready var paint_mode: VBoxContainer = %PaintMode 119 | 120 | @onready var icon_generator := $Viewport 121 | 122 | var slot_container: Node 123 | var slot_scene: PackedScene 124 | 125 | var data: InstanceDock_Data 126 | var initialized: int 127 | 128 | var icon_cache: Dictionary 129 | var previous_tab: int 130 | var current_slot_mode: int = -1 131 | 132 | var tab_to_remove := -1 133 | var icon_queue: Array[ProcessedItem] 134 | var icon_progress: int 135 | var current_processed_item: ProcessedItem 136 | 137 | var default_parent: Node 138 | 139 | var plugin: EditorPlugin 140 | 141 | func _ready() -> void: 142 | set_process(false) 143 | if is_part_of_edited_scene(): 144 | return 145 | 146 | var popup := view_menu.get_popup() 147 | popup.add_radio_check_item("Icons", SLOT_MODE_ICONS) 148 | popup.add_radio_check_item("Text", SLOT_MODE_TEXT) 149 | popup.add_separator() 150 | popup.add_radio_check_item("Refresh All Previews", REFRESH_ALL_PREVIEWS) 151 | 152 | popup.set_item_checked(0, true) 153 | set_slot_mode(SLOT_MODE_ICONS) 154 | popup.id_pressed.connect(on_menu_option) 155 | 156 | DirAccess.make_dir_recursive_absolute(CACHE_PATH) 157 | if ProjectSettings.has_setting(PROJECT_SETTING_LEGACY): 158 | data = ProjectSettings.get_setting(PROJECT_SETTING_LEGACY) 159 | for tab in data: 160 | tab.erase("scroll") 161 | ProjectSettings.set_setting(PROJECT_SETTING_LEGACY, null) 162 | 163 | CONFIG_FILE = PluginUtils.define_project_setting(PROJECT_SETTING_CONFIG, CONFIG_FILE, PROPERTY_HINT_SAVE_FILE) 164 | load_data() 165 | 166 | PREVIEW_SIZE = PluginUtils.define_project_setting(PROJECT_SETTING_PREVIEW, PREVIEW_SIZE) 167 | icon_generator.size = PREVIEW_SIZE 168 | 169 | PluginUtils.track_project_setting(PROJECT_SETTING_CONFIG, self, _project_setting_changed) 170 | PluginUtils.track_project_setting(PROJECT_SETTING_PREVIEW, self, _project_setting_changed) 171 | 172 | for tab in data.tab_data: 173 | tabs.add_tab(tab.name) 174 | 175 | plugin.scene_changed.connect(on_scene_changed.unbind(1)) 176 | 177 | extras.hide() 178 | parent_selector.set_drag_forwarding(Callable(), _can_drop_node, _drop_node) 179 | 180 | func load_data(): 181 | data = InstanceDock_Data.new() 182 | 183 | var file := FileAccess.open(CONFIG_FILE, FileAccess.READ) 184 | if not file: 185 | #push_error("Failed loading Instance Dock scene data. Error loading file: %d." % FileAccess.get_open_error()) 186 | return 187 | 188 | var loaded = str_to_var(file.get_as_text()) 189 | if not loaded is Array and not loaded is Dictionary: # compat 190 | push_error("Failed loading Instance Dock scene data. File contains invalid data.") 191 | return 192 | 193 | data.load_data(loaded) 194 | 195 | func save_data(): 196 | var file := FileAccess.open(CONFIG_FILE, FileAccess.WRITE) 197 | if not file: 198 | push_error("Failed saving Instance Dock scene data. Error writing file: %d." % FileAccess.get_open_error()) 199 | return 200 | 201 | file.store_string(var_to_str(data.save_data())) 202 | 203 | func _project_setting_changed(setting: String, new_value: Variant): 204 | if setting == PROJECT_SETTING_PREVIEW: 205 | PREVIEW_SIZE = new_value 206 | icon_generator.size = PREVIEW_SIZE 207 | 208 | elif setting == PROJECT_SETTING_CONFIG: 209 | if FileAccess.file_exists(CONFIG_FILE): 210 | DirAccess.rename_absolute(CONFIG_FILE, new_value) 211 | CONFIG_FILE = new_value 212 | 213 | func _notification(what: int) -> void: 214 | if what == NOTIFICATION_DRAG_BEGIN: 215 | var drag_data = get_viewport().gui_get_drag_data() 216 | if drag_data is Dictionary and "instance_dock_overrides" in drag_data: 217 | get_tree().node_added.connect(node_added) 218 | elif what == NOTIFICATION_DRAG_END: 219 | if get_tree().node_added.is_connected(node_added): 220 | get_tree().node_added.disconnect(node_added) 221 | 222 | if initialized == 2: 223 | return 224 | 225 | if what == NOTIFICATION_ENTER_TREE: 226 | initialized = 1 227 | 228 | if what == NOTIFICATION_VISIBILITY_CHANGED: 229 | if is_visible_in_tree() and slot_container != null and initialized == 1: 230 | refresh_tab_contents() 231 | initialized = 2 232 | 233 | func node_added(node: Node): 234 | var scene := plugin.get_editor_interface().get_edited_scene_root() 235 | if not scene or not scene.is_ancestor_of(node): 236 | return 237 | 238 | var drag_data = get_viewport().gui_get_drag_data() 239 | if not "files" in drag_data or not node.scene_file_path in drag_data["files"]: 240 | return 241 | 242 | var overrides: Dictionary = drag_data["instance_dock_overrides"] 243 | for override in overrides: 244 | node.set(override, overrides[override]) 245 | 246 | if node.get_parent() == EditorInterface.get_edited_scene_root(): 247 | var parent := get_default_parent() 248 | 249 | if parent and node.get_parent() != parent: 250 | do_reparent.call_deferred(node, parent) 251 | 252 | func do_reparent(node: Node, to: Node): 253 | var undo_redo := plugin.get_undo_redo() 254 | undo_redo.create_action("InstanceDock reparent node") 255 | undo_redo.add_do_method(node, &"reparent", to) 256 | undo_redo.add_do_method(node, &"set_owner", node.owner) 257 | undo_redo.add_do_method(node, &"set_name", node.name) 258 | undo_redo.add_undo_method(node, &"reparent", node.get_parent()) 259 | undo_redo.add_undo_method(node, &"set_owner", node.owner) 260 | undo_redo.add_undo_method(node, &"set_name", node.name) 261 | undo_redo.commit_action() 262 | 263 | func on_add_tab_pressed() -> void: 264 | tab_add_name.text = "" # NO_TRANSLATE 265 | tab_add_confirm.reset_size() 266 | tab_add_confirm.popup_centered() 267 | tab_add_name.grab_focus.call_deferred() 268 | 269 | func add_tab_confirm(q = null) -> void: 270 | if q != null: 271 | tab_add_confirm.hide() 272 | 273 | tabs.add_tab(tab_add_name.text) 274 | var new_tab := InstanceDock_Data.InstanceDock_Tab.new() 275 | new_tab.name = tab_add_name.text 276 | data.tab_data.append(new_tab) 277 | save_data() 278 | 279 | if data.tab_data.size() == 1: 280 | refresh_tab_contents() 281 | 282 | func on_tab_close_attempt(tab: int) -> void: 283 | tab_to_remove = tab 284 | tab_delete_confirm.popup_centered() 285 | 286 | func remove_tab_confirm() -> void: 287 | if tab_to_remove != tabs.current_tab: 288 | tab_to_remove = -1 289 | data.tab_data.remove_at(tab_to_remove) 290 | tabs.remove_tab(tab_to_remove) 291 | save_data() 292 | 293 | if tabs.tab_count == 0: 294 | refresh_tab_contents() 295 | 296 | func on_tab_changed(tab: int) -> void: 297 | if tab_to_remove == -1 and data.tab_data.size() > 0: 298 | tabs.set_tab_metadata(previous_tab, scroll.scroll_vertical) 299 | tab_to_remove = -1 300 | previous_tab = tab 301 | 302 | if initialized == 2: 303 | icon_queue.clear() 304 | current_processed_item = null 305 | set_process(false) 306 | 307 | refresh_tab_contents() 308 | 309 | func refresh_tab_contents(): 310 | for c in slot_container.get_children(): 311 | c.free() 312 | 313 | if tabs.tab_count == 0: 314 | slot_container.hide() 315 | add_tab_label.show() 316 | drag_label.hide() 317 | filter_line_edit.clear() 318 | filter_line_edit.editable = false 319 | return 320 | else: 321 | slot_container.show() 322 | add_tab_label.hide() 323 | drag_label.show() 324 | filter_line_edit.editable = true 325 | 326 | if data.tab_data.size() > 0: 327 | var tab_data := data.tab_data[tabs.current_tab] 328 | var scenes := tab_data.instances 329 | 330 | adjust_slot_count() 331 | for i in slot_container.get_child_count(): 332 | if i < scenes.size(): 333 | slot_container.get_child(i).set_data(scenes[i]) 334 | else: 335 | slot_container.get_child(i).set_data(InstanceDock_Data.InstanceDock_Instance.new()) 336 | 337 | var scroll_value = tabs.get_tab_metadata(tabs.current_tab) 338 | await get_tree().process_frame 339 | if scroll_value is int: 340 | scroll.scroll_vertical = scroll_value 341 | 342 | if not filter_line_edit.text.is_empty(): 343 | _on_filter_changed(filter_line_edit.text) 344 | 345 | if paint_mode.enabled: 346 | paint_mode.set_paint_mode_enabled(true) 347 | 348 | func remove_scene(slot: int): 349 | var tab_scenes := data.tab_data[tabs.current_tab].instances 350 | tab_scenes[slot] = InstanceDock_Data.InstanceDock_Instance.new() 351 | while not tab_scenes.is_empty() and tab_scenes.back().is_empty(): 352 | tab_scenes.pop_back() 353 | 354 | func _process(delta: float) -> void: 355 | if icon_queue.is_empty() and not current_processed_item: 356 | set_process(false) 357 | return 358 | 359 | if not current_processed_item: 360 | get_item_from_queue() 361 | 362 | var slot := current_processed_item.slot 363 | if current_processed_item.icon: 364 | icon_cache[slot.get_hash()] = current_processed_item.icon 365 | slot.set_icon(current_processed_item.icon) 366 | get_item_from_queue() 367 | return 368 | 369 | var instance := current_processed_item.instance 370 | var overrides := current_processed_item.overrides 371 | for override in overrides: 372 | instance.set(override, overrides[override]) 373 | 374 | while not is_instance_valid(slot): 375 | icon_progress = 0 376 | instance.free() 377 | get_item_from_queue() 378 | 379 | if not current_processed_item: 380 | return 381 | else: 382 | instance = current_processed_item.instance 383 | slot = current_processed_item.slot 384 | 385 | match icon_progress: 386 | 0: 387 | icon_generator.add_child(instance) 388 | if instance is Node2D: 389 | instance.position = PREVIEW_SIZE / 2 390 | 3: 391 | var image = icon_generator.get_texture().get_image() 392 | image.save_png(CACHE_PATH.path_join("%s.png" % slot.get_hash())) 393 | var texture = ImageTexture.create_from_image(image) 394 | slot.set_icon(texture) 395 | icon_cache[slot.get_hash()] = texture 396 | instance.free() 397 | 398 | icon_progress = -1 399 | get_item_from_queue() 400 | 401 | icon_progress += 1 402 | 403 | func get_item_from_queue(): 404 | if icon_queue.is_empty(): 405 | current_processed_item = null 406 | return 407 | 408 | current_processed_item = icon_queue.pop_front() 409 | if not current_processed_item.icon_path.is_empty(): 410 | var texture := ImageTexture.create_from_image(Image.load_from_file(current_processed_item.icon_path)) 411 | current_processed_item.icon = texture 412 | else: 413 | current_processed_item.instance = load(current_processed_item.instance_path).instantiate() 414 | current_processed_item.overrides = current_processed_item.slot.data.overrides 415 | 416 | func assign_icon(scene_path: String, ignore_cache: bool, slot: Control): 417 | if not ignore_cache: 418 | var hash: int = slot.get_hash() 419 | var icon := icon_cache.get(hash) as Texture2D 420 | if icon: 421 | slot.set_icon(icon) 422 | return 423 | else: 424 | var cache_path := CACHE_PATH.path_join("%s.png" % hash) 425 | if FileAccess.file_exists(cache_path): 426 | var queued := ProcessedItem.new() 427 | queued.icon_path = cache_path 428 | queued.slot = slot 429 | icon_queue.append(queued) 430 | set_process(true) 431 | return 432 | generate_icon(scene_path, slot) 433 | 434 | func generate_icon(scene_path: String, slot: Control): 435 | var queued := ProcessedItem.new() 436 | queued.instance_path = scene_path 437 | queued.slot = slot 438 | icon_queue.append(queued) 439 | set_process(true) 440 | 441 | func add_slot() -> Control: 442 | var slot: Control = slot_scene.instantiate() 443 | slot_container.add_child(slot) 444 | slot.setup_button(paint_mode.buttons) 445 | slot.request_icon.connect(assign_icon.bind(slot)) 446 | slot.changed.connect(recreate_tab_data, CONNECT_DEFERRED) 447 | return slot 448 | 449 | func recreate_tab_data(): 450 | var tab_scenes := data.tab_data[tabs.current_tab].instances 451 | tab_scenes.clear() 452 | 453 | for slot in slot_container.get_children(): 454 | tab_scenes.append(slot.get_data()) 455 | 456 | while not tab_scenes.is_empty() and (not tab_scenes.back() or tab_scenes.back().scene.is_empty()): 457 | tab_scenes.pop_back() 458 | 459 | save_data() 460 | adjust_slot_count() 461 | 462 | func adjust_slot_count(): 463 | var tab_scenes := data.tab_data[tabs.current_tab].instances 464 | var desired_slots := tab_scenes.size() + 1 465 | 466 | while desired_slots > slot_container.get_child_count(): 467 | add_slot() 468 | 469 | while desired_slots < slot_container.get_child_count(): 470 | slot_container.get_child(slot_container.get_child_count() - 1).free() 471 | 472 | func on_rearrange(idx_to: int) -> void: 473 | var old_data := data.tab_data[previous_tab] 474 | data.tab_data[previous_tab] = data.tab_data[idx_to] 475 | data.tab_data[idx_to] = old_data 476 | previous_tab = idx_to 477 | save_data() 478 | 479 | func toggle_extras() -> void: 480 | extras.visible = not extras.visible 481 | if extras.visible: 482 | extras_toggle.icon = preload("res://addons/InstanceDock/Textures/Collapse.svg") 483 | else: 484 | extras_toggle.icon = preload("res://addons/InstanceDock/Textures/Uncollapse.svg") 485 | 486 | func set_default_parent(node: Node): 487 | if default_parent == node and not (default_parent and not node): 488 | return 489 | 490 | default_parent = node 491 | if node: 492 | parent_icon.show() 493 | parent_icon.texture = get_theme_icon(node.get_class(), &"EditorIcons") 494 | parent_name.text = node.name 495 | parent_selector.tooltip_text = EditorInterface.get_edited_scene_root().get_path_to(node) 496 | else: 497 | parent_icon.hide() 498 | parent_name.text = "" # NO_TRANSLATE 499 | parent_selector.tooltip_text = "" # NO_TRANSLATE 500 | 501 | func get_default_parent() -> Node: 502 | var parent := default_parent 503 | if is_instance_valid(parent): 504 | if not parent.is_inside_tree(): 505 | set_default_parent(null) 506 | else: 507 | return parent 508 | elif parent: 509 | set_default_parent(null) 510 | return null 511 | 512 | func set_slot_mode(new_slot_mode: int): 513 | if new_slot_mode == current_slot_mode: 514 | return 515 | 516 | if slot_container: 517 | for child in slot_container.get_children(): 518 | child.free() 519 | 520 | current_slot_mode = new_slot_mode 521 | if current_slot_mode == SLOT_MODE_TEXT: 522 | slot_container = %TextSlots 523 | slot_scene = preload("res://addons/InstanceDock/Scenes/InstanceSlotText.tscn") 524 | else: 525 | slot_container = %IconSlots 526 | slot_scene = preload("res://addons/InstanceDock/Scenes/InstanceSlot.tscn") 527 | 528 | refresh_tab_contents() 529 | 530 | func on_menu_option(id: int): 531 | var popup := view_menu.get_popup() 532 | if id == SLOT_MODE_ICONS or id == SLOT_MODE_TEXT: 533 | popup.set_item_checked(0, id == SLOT_MODE_ICONS) 534 | popup.set_item_checked(1, id == SLOT_MODE_TEXT) 535 | set_slot_mode(id) 536 | elif id == REFRESH_ALL_PREVIEWS: 537 | icon_cache.clear() 538 | 539 | var da := DirAccess.open(CACHE_PATH) 540 | for file in da.get_files(): 541 | da.remove(file) 542 | 543 | for slot in slot_container.get_children(): 544 | if slot.is_valid(): 545 | slot.menu_option(slot.MenuOption.REFRESH) 546 | 547 | func on_scene_changed(): 548 | set_default_parent(null) 549 | 550 | func _can_drop_node(at: Vector2, data: Variant) -> bool: 551 | if not data is Dictionary: 552 | return false 553 | 554 | if not data.get("type", "") == "nodes": 555 | return false 556 | 557 | if not "nodes" in data or not data["nodes"] is Array: 558 | return false 559 | 560 | if data["nodes"].size() != 1 or not data["nodes"][0] is NodePath: 561 | return false 562 | 563 | return true 564 | 565 | func _drop_node(at: Vector2, data: Variant): 566 | var node: Node = get_tree().root.get_node_or_null(data["nodes"][0]) 567 | if not node: 568 | return 569 | 570 | set_default_parent(node) 571 | 572 | func _on_filter_changed(new_text: String) -> void: 573 | new_text = new_text.to_lower() 574 | for slot in slot_container.get_children(): 575 | slot.filter(new_text) 576 | 577 | drag_label.visible = new_text.is_empty() 578 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/InstanceDock.gd.uid: -------------------------------------------------------------------------------- 1 | uid://c0f7ht031r3t8 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/InstancePropertyEdit.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | 3 | var instance: Node 4 | var overrides: Dictionary[StringName, Variant] 5 | 6 | signal changed 7 | 8 | func _get_property_list() -> Array[Dictionary]: 9 | var properties := instance.get_property_list() 10 | var ret: Array[Dictionary] 11 | 12 | for property in properties: 13 | if property["usage"] != PROPERTY_USAGE_DEFAULT and not property["usage"] & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_SUBGROUP): 14 | continue 15 | 16 | if property["name"] == "position" or property["name"] == "script": 17 | continue 18 | 19 | ret.append(property) 20 | 21 | return ret 22 | 23 | func _get(property: StringName) -> Variant: 24 | if property in overrides: 25 | return overrides[property] 26 | 27 | return instance.get(property) 28 | 29 | func _set(property: StringName, value: Variant) -> bool: 30 | if instance.get(property) == value: 31 | overrides.erase(property) 32 | else: 33 | overrides[property] = value 34 | 35 | changed.emit() 36 | return true 37 | 38 | func _property_can_revert(property: StringName) -> bool: 39 | return true 40 | 41 | func _property_get_revert(property: StringName) -> Variant: 42 | if property == &"script": 43 | return get_script() 44 | else: 45 | return instance.get(property) 46 | 47 | func _notification(what: int) -> void: 48 | if what == NOTIFICATION_PREDELETE: 49 | instance.free() 50 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/InstancePropertyEdit.gd.uid: -------------------------------------------------------------------------------- 1 | uid://dvi52w5jatrb4 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/InstanceSlot.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends PanelContainer 3 | 4 | const InstanceDockPropertyEdit = preload("res://addons/InstanceDock/Scripts/InstancePropertyEdit.gd") 5 | const InstanceDock = preload("res://addons/InstanceDock/Scripts/InstanceDock.gd") 6 | 7 | enum MenuOption { EDIT, MODIFY, REMOVE, REFRESH, CLEAR, QUICK_LOAD } 8 | 9 | @export var normal: StyleBox 10 | @export var custom: StyleBox 11 | 12 | @onready var icon: TextureRect = get_node_or_null(^"%Icon") 13 | @onready var path_label: Label = get_node_or_null(^"%Path") 14 | 15 | @onready var loading_icon: Sprite2D = %Loading 16 | @onready var loading_animator: AnimationPlayer = %AnimationPlayer 17 | @onready var timer: Timer = $Timer 18 | @onready var has_overrides: TextureRect = $HasOverrides 19 | @onready var text_label: Label = %Label 20 | @onready var paint_button: Button = $PaintButton 21 | 22 | var data: InstanceDock.InstanceDock_Data.InstanceDock_Instance 23 | var popup: PopupMenu 24 | var thread: Thread 25 | 26 | var filter_cache: String 27 | 28 | signal request_icon(instance, ignore_cache) 29 | signal changed 30 | 31 | func _ready() -> void: 32 | set_process(false) 33 | 34 | func _process(delta: float) -> void: 35 | if not thread.is_alive(): 36 | thread.wait_to_finish() 37 | thread = null 38 | set_process(false) 39 | 40 | func _can_drop_data(at_position: Vector2, drop_data) -> bool: 41 | if not drop_data is Dictionary or drop_data.get("type", "") != "files": 42 | return false 43 | 44 | if not "files" in drop_data or drop_data["files"].size() != 1: 45 | return false 46 | 47 | if drop_data["files"][0].get_extension() == "tscn" or drop_data["files"][0].get_extension() == "res": 48 | return true 49 | 50 | if is_texture(drop_data["files"][0]) and is_valid(): 51 | return true 52 | 53 | return false 54 | 55 | func _drop_data(at_position: Vector2, drop_data) -> void: 56 | var file: String = drop_data["files"][0] 57 | if is_texture(file) and is_valid(): 58 | data.custom_texture = file 59 | apply_data() 60 | changed.emit() 61 | elif file.get_extension() == "tscn": 62 | if "from_slot" in drop_data: 63 | var slot2: Control = get_parent().get_child(drop_data["from_slot"]) 64 | if slot2 == self: 65 | return 66 | 67 | var data2: InstanceDock.InstanceDock_Data.InstanceDock_Instance = slot2.data 68 | slot2.set_data(data) 69 | set_data(data2) 70 | else: 71 | set_scene(file) 72 | changed.emit() 73 | 74 | func is_texture(file: String) -> bool: 75 | return ClassDB.is_parent_class(EditorInterface.get_resource_filesystem().get_file_type(file), &"Texture2D") 76 | 77 | func _get_drag_data(position: Vector2): 78 | if not is_valid(): 79 | return null 80 | return {"files": [get_scene()], "type": "files", "from_slot": get_index(), "instance_dock_overrides": data.overrides } 81 | 82 | func set_icon(texture: Texture2D): 83 | icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED 84 | icon.texture = texture 85 | if texture and texture.get_width() <= icon.size.x: 86 | icon.texture_filter = TEXTURE_FILTER_NEAREST 87 | 88 | if loading_icon.visible: 89 | icon.modulate.a = 0 90 | thread = Thread.new() 91 | thread.start(check_if_transparent.bind(texture.get_image())) 92 | set_process(true) 93 | 94 | func check_if_transparent(data: Image): 95 | var is_valid: bool 96 | for x in data.get_width(): 97 | for y in data.get_height(): 98 | if data.get_pixel(x, y).a > 0: 99 | is_valid = true 100 | break 101 | 102 | if is_valid: 103 | break 104 | 105 | transparent_result.call_deferred(is_valid) 106 | 107 | func transparent_result(is_valid: bool): 108 | icon.modulate.a = 1 109 | loading_icon.hide() 110 | loading_animator.stop() 111 | 112 | if not is_valid: 113 | set_icon(preload("res://addons/InstanceDock/Textures/Missing.png")) 114 | icon.stretch_mode = TextureRect.STRETCH_KEEP_CENTERED 115 | 116 | func _gui_input(event: InputEvent) -> void: 117 | if event is InputEventMouseButton: 118 | if event.pressed and event.button_index == MOUSE_BUTTON_RIGHT: 119 | create_popup() 120 | popup.popup() 121 | popup.position = get_screen_transform() * event.position 122 | elif event.double_click and event.button_index == MOUSE_BUTTON_LEFT and is_valid(): 123 | menu_option(MenuOption.EDIT) 124 | 125 | func create_popup(): 126 | if not popup: 127 | popup = PopupMenu.new() 128 | popup.id_pressed.connect(menu_option) 129 | add_child(popup) 130 | 131 | popup.clear() 132 | 133 | if is_valid(): 134 | popup.add_item("Open Scene", MenuOption.EDIT) 135 | popup.add_item("Override Properties", MenuOption.MODIFY) 136 | popup.add_item("Remove", MenuOption.REMOVE) 137 | if data.custom_texture: 138 | popup.add_item("Remove Custom Icon", MenuOption.CLEAR) 139 | else: 140 | popup.add_item("Refresh Icon", MenuOption.REFRESH) 141 | 142 | popup.add_item("Quick Load...", MenuOption.QUICK_LOAD) 143 | 144 | popup.reset_size() 145 | 146 | func menu_option(id: int) -> void: 147 | match id: 148 | MenuOption.EDIT: 149 | EditorInterface.open_scene_from_path(data.scene) 150 | MenuOption.MODIFY: 151 | var editor := InstanceDockPropertyEdit.new() 152 | editor.instance = load(data.scene).instantiate() 153 | editor.overrides = data.overrides 154 | EditorInterface.inspect_object(editor, "", true) 155 | editor.changed.connect(timer.start) 156 | MenuOption.REMOVE: 157 | data = null 158 | unedit() 159 | apply_data() 160 | changed.emit() 161 | MenuOption.REFRESH: 162 | start_load() 163 | request_icon.emit(data.scene, true) 164 | MenuOption.CLEAR: 165 | data.custom_texture = "" 166 | apply_data() 167 | changed.emit() 168 | MenuOption.QUICK_LOAD: 169 | EditorInterface.popup_quick_open(func(scene: String): 170 | if not scene.is_empty(): 171 | set_scene(scene) 172 | changed.emit() 173 | , ["PackedScene"]) 174 | 175 | func get_data() -> InstanceDock.InstanceDock_Data.InstanceDock_Instance: 176 | return data 177 | 178 | func set_scene(scene: String): 179 | var uid := ResourceLoader.get_resource_uid(scene) 180 | if uid != ResourceUID.INVALID_ID: 181 | scene = ResourceUID.id_to_text(uid) 182 | 183 | data = InstanceDock.InstanceDock_Data.InstanceDock_Instance.new() 184 | data.scene = scene 185 | filter_cache = "" 186 | apply_data() 187 | 188 | func set_data(p_data: InstanceDock.InstanceDock_Data.InstanceDock_Instance): 189 | data = p_data 190 | filter_cache = "" 191 | apply_data() 192 | 193 | func set_text_label(vis : bool): 194 | text_label.visible = vis 195 | if path_label: 196 | path_label.visible = not vis 197 | 198 | func apply_data(): 199 | var text: PackedStringArray 200 | text.append(get_scene().get_file()) 201 | text.append(get_scene().get_base_dir()) 202 | 203 | if data and not data.overrides.is_empty(): 204 | text.append("") 205 | text.append(tr("Overrides:")) 206 | for override in data.overrides: 207 | text.append("%s: %s" % [override, data.overrides[override]]) 208 | tooltip_text = "\n".join(text) 209 | 210 | if path_label: 211 | path_label.text = get_scene().get_file() 212 | 213 | set_icon(null) 214 | set_text_label(false) 215 | add_theme_stylebox_override(&"panel", normal) 216 | 217 | if not is_valid(): 218 | set_icon(null) 219 | set_text_label(true) 220 | elif data.custom_texture.is_empty(): 221 | start_load() 222 | request_icon.emit(data.scene, false) 223 | else: 224 | set_icon(load(data.custom_texture)) 225 | add_theme_stylebox_override(&"panel", custom) 226 | 227 | paint_button.disabled = not is_valid() 228 | has_overrides.visible = data != null and not data.overrides.is_empty() 229 | 230 | func start_load(): 231 | loading_animator.play(&"Rotate") 232 | loading_icon.show() 233 | 234 | func _on_timer_timeout() -> void: 235 | has_overrides.visible = not data.overrides.is_empty() 236 | apply_data() 237 | menu_option(MenuOption.REFRESH) 238 | changed.emit() 239 | 240 | func get_scene() -> String: 241 | if not data: 242 | return "" 243 | 244 | var path := data.scene 245 | if path.begins_with("uid://"): 246 | path = ResourceUID.get_id_path(ResourceUID.text_to_id(path)) 247 | 248 | return path 249 | 250 | func is_valid() -> bool: 251 | return data != null and not data.scene.is_empty() 252 | 253 | func get_hash() -> int: 254 | if not data: 255 | return 0 256 | return str(data.scene, data.overrides).hash() 257 | 258 | func setup_button(group: ButtonGroup): 259 | paint_button.button_group = group 260 | 261 | func _exit_tree() -> void: 262 | unedit() 263 | if thread: 264 | thread.wait_to_finish() 265 | thread = null 266 | 267 | func unedit(): 268 | if EditorInterface.get_inspector().get_edited_object() is InstanceDockPropertyEdit: 269 | EditorInterface.edit_node(null) 270 | 271 | func filter(text: String): 272 | if filter_cache.is_empty(): 273 | filter_cache = get_scene().to_lower() 274 | 275 | visible = text.is_empty() or filter_cache.contains(text) 276 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/InstanceSlot.gd.uid: -------------------------------------------------------------------------------- 1 | uid://cffy8p6erriue 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/PaintMode.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Control 3 | 4 | const GRID_SIZE = 7 5 | 6 | @onready var plugin: EditorPlugin = owner.plugin 7 | 8 | @onready var status: Label = %Status 9 | @onready var snap_enabled: CheckBox = %SnapEnabled 10 | @onready var snap_x: SpinBox = %SnapX 11 | @onready var snap_y: SpinBox = %SnapY 12 | 13 | var buttons := ButtonGroup.new() 14 | var enabled: bool 15 | var continuous: Array[Vector2] 16 | 17 | var selected_scene: String 18 | var overrides: Dictionary[StringName, Variant] 19 | 20 | var edited_node: CanvasItem: 21 | set(n): 22 | edited_node = n 23 | update_preview() 24 | update_status() 25 | 26 | var preview: CanvasItem 27 | 28 | var font: Font 29 | var help: bool 30 | var rotating: bool 31 | var scaling: bool 32 | 33 | var transform_from: Variant 34 | var transform_from_point: Vector2 35 | 36 | func _ready() -> void: 37 | if not plugin: 38 | return 39 | 40 | hide() 41 | font = EditorInterface.get_editor_theme().get_font(&"main", &"EditorFonts") 42 | buttons.pressed.connect(on_button_pressed) 43 | 44 | var snap_size: Vector2i = EditorInterface.get_editor_settings().get_project_metadata("instance_dock", "snap_size", Vector2i(8, 8)) 45 | snap_x.set_value_no_signal(snap_size.x) 46 | snap_y.set_value_no_signal(snap_size.y) 47 | 48 | func set_paint_mode_enabled(toggled_on: bool) -> void: 49 | var was_enabled := enabled 50 | 51 | enabled = toggled_on 52 | for button in buttons.get_buttons(): 53 | button.visible = toggled_on and not button.disabled 54 | if not button.visible: 55 | button.button_pressed = false 56 | elif was_enabled: 57 | if button.owner.scene == selected_scene: 58 | button.set_pressed_no_signal(true) 59 | 60 | if not enabled: 61 | selected_scene = "" 62 | 63 | if preview: 64 | preview.queue_free() 65 | preview = null 66 | 67 | update_status() 68 | update_overlays() 69 | 70 | func on_button_pressed(button: BaseButton): 71 | selected_scene = button.owner.get_scene() 72 | if button.owner.data: 73 | overrides = button.owner.data.overrides 74 | else: 75 | overrides = {} 76 | 77 | update_preview() 78 | update_status() 79 | 80 | if not preview: 81 | push_error("Button does not provide CanvasItem scene.") 82 | button.button_pressed = false 83 | 84 | func on_scene_changed(): 85 | if enabled: 86 | update_preview() 87 | 88 | func update_preview(): 89 | if not is_instance_valid(preview): 90 | preview = null 91 | 92 | if preview and preview.scene_file_path != selected_scene: 93 | preview.queue_free() 94 | preview = null 95 | 96 | if selected_scene.is_empty(): 97 | return 98 | 99 | if not preview: 100 | var node: Node = load(selected_scene).instantiate() 101 | if not node: 102 | return 103 | 104 | preview = node as CanvasItem 105 | if not preview: 106 | node.queue_free() 107 | return 108 | else: 109 | preview.name = &"_InstanceDock_Preview_" 110 | RenderingServer.canvas_item_set_z_index(preview.get_canvas_item(), 4096) 111 | for override in overrides: 112 | preview.set(override, overrides[override]) 113 | 114 | preview.visible = (edited_node != null) 115 | 116 | var root := EditorInterface.get_edited_scene_root() 117 | 118 | if not root: 119 | return 120 | 121 | if not preview.get_parent(): 122 | root.add_child(preview) 123 | else: 124 | preview.reparent(root, false) 125 | 126 | func paint_input(event: InputEvent) -> bool: 127 | if not enabled: 128 | return false 129 | 130 | if not edited_node: 131 | EditorInterface.get_editor_toaster().push_toast("Can't paint on nothing.", EditorToaster.SEVERITY_WARNING) 132 | return false 133 | 134 | if selected_scene.is_empty(): 135 | return false 136 | 137 | if event is InputEventMouseMotion: 138 | if rotating: 139 | preview.rotation = transform_from_point.direction_to(get_local_mouse_position()).angle() 140 | update_overlays() 141 | elif scaling: 142 | preview.scale = (get_local_mouse_position() - transform_from_point) * 0.1 143 | update_overlays() 144 | else: 145 | var target_pos := edited_node.get_global_mouse_position() 146 | 147 | if snap_enabled.button_pressed: 148 | preview.global_position = target_pos.snapped(Vector2(snap_x.value, snap_y.value)) 149 | update_overlays() 150 | 151 | if event.button_mask & MOUSE_BUTTON_MASK_LEFT and not preview.get_global_transform().origin in continuous: 152 | paint() 153 | else: 154 | preview.global_position = target_pos 155 | 156 | if event is InputEventMouseButton: 157 | if event.pressed: 158 | if event.button_index == MOUSE_BUTTON_LEFT: 159 | paint() 160 | 161 | return true 162 | elif event.button_index == MOUSE_BUTTON_RIGHT: 163 | if rotating: 164 | preview.rotation = 0 165 | rotating = false 166 | update_overlays() 167 | return true 168 | elif scaling: 169 | preview.scale = Vector2.ONE 170 | scaling = false 171 | update_overlays() 172 | return true 173 | else: 174 | if event.button_index == MOUSE_BUTTON_LEFT: 175 | continuous.clear() 176 | 177 | if event is InputEventKey: 178 | if event.echo: 179 | return false 180 | 181 | if event.pressed: 182 | if event.keycode == KEY_H: 183 | help = not help 184 | update_overlays() 185 | return true 186 | 187 | if event.keycode == KEY_R: 188 | if scaling: 189 | return false 190 | 191 | rotating = event.pressed 192 | if rotating: 193 | transform_from = preview.rotation 194 | transform_from_point = get_local_mouse_position() 195 | 196 | update_overlays() 197 | return true 198 | 199 | if event.keycode == KEY_S: 200 | if rotating: 201 | return false 202 | 203 | scaling = event.pressed 204 | if scaling: 205 | transform_from = preview.scale 206 | transform_from_point = get_local_mouse_position() 207 | 208 | update_overlays() 209 | return true 210 | 211 | return false 212 | 213 | func paint(): 214 | if snap_enabled.button_pressed: 215 | continuous.append(preview.get_global_transform().origin) 216 | 217 | var parent: Node = owner.get_default_parent() 218 | if not parent: 219 | parent = edited_node 220 | 221 | var instance: CanvasItem = load(preview.scene_file_path).instantiate() 222 | for override in overrides: 223 | instance.set(override, overrides[override]) 224 | 225 | var undo_redo := EditorInterface.get_editor_undo_redo() 226 | undo_redo.create_action("InstanceDock paint instance", UndoRedo.MERGE_DISABLE, parent) 227 | undo_redo.add_do_reference(instance) 228 | undo_redo.add_do_method(self, &"add_instance", parent, EditorInterface.get_edited_scene_root(), instance, preview.get_global_transform()) 229 | undo_redo.add_undo_method(parent, &"remove_child", instance) 230 | undo_redo.commit_action() 231 | 232 | func add_instance(parent: Node, own: Node, instance: CanvasItem, trans: Transform2D): 233 | parent.add_child(instance, true) 234 | instance.owner = own 235 | instance.global_position = trans.origin 236 | instance.rotation = trans.get_rotation() 237 | instance.scale = trans.get_scale() 238 | 239 | func paint_draw(viewport_control: Control): 240 | if not enabled or not edited_node or not preview: 241 | return 242 | 243 | var font_pos_base := viewport_control.size * Vector2.DOWN + Vector2(40, -40) 244 | if help: 245 | viewport_control.draw_string(font, font_pos_base + Vector2.UP * 40, "Press H to toggle help") 246 | 247 | if rotating: 248 | viewport_control.draw_string(font, font_pos_base + Vector2.UP * 20, "Rotating: %0.2f°" % rad_to_deg(preview.rotation)) 249 | viewport_control.draw_string(font, font_pos_base, "Press Right Mouse Button to reset") 250 | elif not scaling: 251 | viewport_control.draw_string(font, font_pos_base + Vector2.UP * 20, "Hold R to rotate") 252 | 253 | if scaling: 254 | viewport_control.draw_string(font, font_pos_base + Vector2.UP * 20, "Scaling: %0.1f × %0.1f" % [preview.scale.x, preview.scale.y]) 255 | viewport_control.draw_string(font, font_pos_base, "Press Right Mouse Button to reset") 256 | elif not rotating: 257 | viewport_control.draw_string(font, font_pos_base, "Hold S to scale") 258 | else: 259 | viewport_control.draw_string(font, font_pos_base, "Press H to toggle help") 260 | 261 | if not snap_enabled.button_pressed: 262 | return 263 | 264 | for x in range(-GRID_SIZE / 2, GRID_SIZE / 2 + 1): 265 | for y in range(-GRID_SIZE / 2, GRID_SIZE / 2 + 1): 266 | var snap_vector := Vector2(snap_x.value, snap_y.value) 267 | var canvas_transform := edited_node.get_viewport().global_canvas_transform 268 | 269 | var pos := edited_node.get_global_mouse_position().snapped(snap_vector) 270 | pos += Vector2(x, y) * snap_vector 271 | pos = canvas_transform * pos 272 | viewport_control.draw_circle(pos, 2, Color(Color.WHITE, 1.0 - absf(x) * 0.2 - absf(y) * 0.2)) 273 | 274 | func _exit_tree() -> void: 275 | if is_instance_valid(preview): 276 | preview.queue_free() 277 | 278 | func update_status(): 279 | if not enabled: 280 | return 281 | elif not edited_node: 282 | status.text = "(Re)Select any CanvasItem to start" 283 | status.modulate = get_theme_color(&"warning_color", &"Editor") 284 | elif selected_scene.is_empty(): 285 | status.text = "Click instance slot to select" 286 | status.modulate = get_theme_color(&"warning_color", &"Editor") 287 | else: 288 | status.text = "Use LMB to paint instance" 289 | status.modulate = Color.WHITE 290 | 291 | func update_overlays() -> void: 292 | plugin.update_overlays() 293 | 294 | func _on_snap_changed(value: float) -> void: 295 | EditorInterface.get_editor_settings().set_project_metadata("instance_dock", "snap_size", Vector2i(snap_x.value, snap_y.value)) 296 | -------------------------------------------------------------------------------- /addons/InstanceDock/Scripts/PaintMode.gd.uid: -------------------------------------------------------------------------------- 1 | uid://d16qd5wan2vm5 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Add.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Add.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://buv26pii10cx2" 6 | path="res://.godot/imported/Add.svg-28aaa3510cf00626799739e2cebac218.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/Add.svg" 14 | dest_files=["res://.godot/imported/Add.svg-28aaa3510cf00626799739e2cebac218.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Collapse.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dpa3fyapmielg" 6 | path="res://.godot/imported/Collapse.svg-cad2901e024c468e999a4bc74b5112a7.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/Collapse.svg" 14 | dest_files=["res://.godot/imported/Collapse.svg-cad2901e024c468e999a4bc74b5112a7.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/GuiTabMenuHl.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/GuiTabMenuHl.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://doleo6g38lico" 6 | path="res://.godot/imported/GuiTabMenuHl.svg-ececdb8104447476ab69065d7ec21086.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/GuiTabMenuHl.svg" 14 | dest_files=["res://.godot/imported/GuiTabMenuHl.svg-ececdb8104447476ab69065d7ec21086.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoBeWi/Godot-Instance-Dock/d03847be2d8870653e851f4ad540992e831bab83/addons/InstanceDock/Textures/Loading.png -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Loading.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://rsllva5pmojk" 6 | path="res://.godot/imported/Loading.png-bbdaaa7edda5076b785759631b173c88.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/Loading.png" 14 | dest_files=["res://.godot/imported/Loading.png-bbdaaa7edda5076b785759631b173c88.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Missing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoBeWi/Godot-Instance-Dock/d03847be2d8870653e851f4ad540992e831bab83/addons/InstanceDock/Textures/Missing.png -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Missing.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://qhbx85wqyv0v" 6 | path="res://.godot/imported/Missing.png-73f7ee024944670303562ca1ed55a502.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/Missing.png" 14 | dest_files=["res://.godot/imported/Missing.png-73f7ee024944670303562ca1ed55a502.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Overrides.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KoBeWi/Godot-Instance-Dock/d03847be2d8870653e851f4ad540992e831bab83/addons/InstanceDock/Textures/Overrides.png -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Overrides.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dp0lseq583t66" 6 | path="res://.godot/imported/Overrides.png-090bd212cfc7269f12783dd04299318d.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/Overrides.png" 14 | dest_files=["res://.godot/imported/Overrides.png-090bd212cfc7269f12783dd04299318d.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Uncollapse.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /addons/InstanceDock/Textures/Uncollapse.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b81f1abox2e67" 6 | path="res://.godot/imported/Uncollapse.svg-5ec2ceb0db44495dffad3987515c4f5a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/InstanceDock/Textures/Uncollapse.svg" 14 | dest_files=["res://.godot/imported/Uncollapse.svg-5ec2ceb0db44495dffad3987515c4f5a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/InstanceDock/Translations/pl.po: -------------------------------------------------------------------------------- 1 | # LANGUAGE translation for for the following files: 2 | # res://addons/InstanceDock/Scenes/InstanceDock.tscn 3 | # res://addons/InstanceDock/Scenes/InstanceSlot.tscn 4 | # res://addons/InstanceDock/Scenes/InstanceSlotText.tscn 5 | # res://addons/InstanceDock/Scripts/InstanceDock.gd 6 | # res://addons/InstanceDock/Scripts/InstancePropertyEdit.gd 7 | # res://addons/InstanceDock/Scripts/InstanceSlot.gd 8 | # res://addons/InstanceDock/Scripts/PaintMode.gd 9 | # res://addons/InstanceDock/InstanceDockPlugin.gd 10 | # 11 | # FIRST AUTHOR , YEAR. 12 | # 13 | #, fuzzy 14 | msgid "" 15 | msgstr "" 16 | "Project-Id-Version: \n" 17 | "POT-Creation-Date: \n" 18 | "PO-Revision-Date: \n" 19 | "Last-Translator: \n" 20 | "Language-Team: \n" 21 | "Language: pl\n" 22 | "MIME-Version: 1.0\n" 23 | "Content-Type: text/plain; charset=UTF-8\n" 24 | "Content-Transfer-Encoding: 8bit\n" 25 | "X-Generator: Poedit 3.5\n" 26 | 27 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 28 | msgid "Filter Instances" 29 | msgstr "Filtruj instancje" 30 | 31 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 32 | msgid "Add tab to continue." 33 | msgstr "Dodaj kartę, by kontynuować." 34 | 35 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 36 | msgid "" 37 | "Drag scene from file system on a slot to assign or right-click to Quick Load." 38 | msgstr "" 39 | "Przeciągnij scenę z Systemu plików na slot lub kliknij prawym, by szybko " 40 | "wczytać." 41 | 42 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 43 | msgid "Parent" 44 | msgstr "Rodzic" 45 | 46 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 47 | msgid "Drop Node to assign" 48 | msgstr "Upuść węzeł, by przypisać" 49 | 50 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 51 | msgid "Paint Mode" 52 | msgstr "Tryb malowania" 53 | 54 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 55 | msgid "Snap" 56 | msgstr "Przyciąganie" 57 | 58 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 59 | msgid "This will permanently remove the tab. Continue?" 60 | msgstr "Karta zostanie bezpowrotnie usunięta. Kontynuować?" 61 | 62 | #: addons/InstanceDock/Scenes/InstanceDock.tscn 63 | msgid "Tab name" 64 | msgstr "Nazwa karty" 65 | 66 | #: addons/InstanceDock/Scenes/InstanceSlot.tscn 67 | #: addons/InstanceDock/Scenes/InstanceSlotText.tscn 68 | msgid "Drop scene to assign" 69 | msgstr "Upuść scenę, by przypisać" 70 | 71 | #: addons/InstanceDock/Scenes/InstanceSlot.tscn 72 | msgid "Instances" 73 | msgstr "Instancje" 74 | 75 | #: addons/InstanceDock/Scripts/InstanceDock.gd 76 | msgid "Icons" 77 | msgstr "Ikony" 78 | 79 | #: addons/InstanceDock/Scripts/InstanceDock.gd 80 | msgid "Text" 81 | msgstr "Tekst" 82 | 83 | #: addons/InstanceDock/Scripts/InstanceDock.gd 84 | msgid "Refresh All Previews" 85 | msgstr "Odśwież wszystkie podglądy" 86 | 87 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 88 | msgid "Open Scene" 89 | msgstr "Otwórz scenę" 90 | 91 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 92 | msgid "Override Properties" 93 | msgstr "Nadpisz właściwości" 94 | 95 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 96 | msgid "Remove" 97 | msgstr "Usuń" 98 | 99 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 100 | msgid "Remove Custom Icon" 101 | msgstr "Usuń własną ikonę" 102 | 103 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 104 | msgid "Refresh Icon" 105 | msgstr "Odśwież ikonę" 106 | 107 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 108 | msgid "Quick Load..." 109 | msgstr "Szybko wczytaj..." 110 | 111 | #: addons/InstanceDock/Scripts/InstanceSlot.gd 112 | msgid "Overrides:" 113 | msgstr "Nadpisane:" 114 | 115 | #: addons/InstanceDock/Scripts/PaintMode.gd 116 | msgid "(Re)Select any CanvasItem to start" 117 | msgstr "Wybierz (ponownie) jakiś CanvasItem, by rozpocząć" 118 | 119 | #: addons/InstanceDock/Scripts/PaintMode.gd 120 | msgid "Click instance slot to select" 121 | msgstr "Kliknij instancję, żeby wybrać" 122 | 123 | #: addons/InstanceDock/Scripts/PaintMode.gd 124 | msgid "Use LMB to paint instance" 125 | msgstr "Użyj LPM, by malować instancję" 126 | -------------------------------------------------------------------------------- /addons/InstanceDock/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Instance Dock" 4 | description="Adds a dedicated dock to store scenes, so you have them close at hand for instancing." 5 | author="KoBeWi" 6 | version="1.3.1" 7 | script="InstanceDockPlugin.gd" --------------------------------------------------------------------------------