init
This commit is contained in:
commit
b99855351d
434 changed files with 50357 additions and 0 deletions
13
addons/inkgd/editor/panel/stories/empty_state_container.tscn
Normal file
13
addons/inkgd/editor/panel/stories/empty_state_container.tscn
Normal file
|
@ -0,0 +1,13 @@
|
|||
[gd_scene format=2]
|
||||
|
||||
[node name="EmptyStateContainer" type="CenterContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 100.0
|
||||
custom_minimum_size = Vector2( 0, 100 )
|
||||
|
||||
[node name="Label" type="Label" parent="."]
|
||||
offset_left = 879.0
|
||||
offset_top = 43.0
|
||||
offset_right = 1022.0
|
||||
offset_bottom = 57.0
|
||||
text = "No stories to compile."
|
153
addons/inkgd/editor/panel/stories/ink_story_configuration.gd
Normal file
153
addons/inkgd/editor/panel/stories/ink_story_configuration.gd
Normal file
|
@ -0,0 +1,153 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
|
||||
# Licensed under the MIT License.
|
||||
# See LICENSE in the project root for license information.
|
||||
# ############################################################################ #
|
||||
|
||||
extends VBoxContainer
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkStoryConfiguration
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
signal configuration_changed(story_configuration)
|
||||
|
||||
signal remove_button_pressed(story_configuration)
|
||||
|
||||
signal build_button_pressed(story_configuration)
|
||||
|
||||
signal source_file_button_pressed(story_configuration)
|
||||
|
||||
signal target_file_button_pressed(story_configuration)
|
||||
|
||||
signal watched_folder_button_pressed(story_configuration)
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface = null
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var story_label = find_child("StoryLabel")
|
||||
|
||||
@onready var remove_button = find_child("RemoveButton")
|
||||
@onready var build_button = find_child("BuildButton")
|
||||
|
||||
@onready var source_file_line_edit = find_child("SourceFileLineEdit")
|
||||
@onready var source_file_dialog_button = find_child("SourceFileDialogButton")
|
||||
|
||||
@onready var target_file_line_edit = find_child("TargetFileLineEdit")
|
||||
@onready var target_file_dialog_button = find_child("TargetFileDialogButton")
|
||||
|
||||
@onready var watched_folder_label = find_child("WatchedFolderLabel")
|
||||
@onready var watched_folder_container = find_child("WatchedFolderHBoxContainer")
|
||||
@onready var watched_folder_line_edit = find_child("WatchedFolderLineEdit")
|
||||
@onready var watched_folder_dialog_button = find_child("WatchedFolderDialogButton")
|
||||
|
||||
@onready var background_color_rect = find_child("BackgroundColorRect")
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null:
|
||||
return
|
||||
|
||||
_apply_custom_header_color()
|
||||
_set_button_icons()
|
||||
_connect_signals()
|
||||
|
||||
show_watched_folder(false)
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
func _configuration_entered(_new_text: String):
|
||||
_configuration_focus_exited()
|
||||
|
||||
|
||||
func _configuration_focus_exited():
|
||||
emit_signal("configuration_changed", self)
|
||||
|
||||
|
||||
func _remove_button_pressed():
|
||||
emit_signal("remove_button_pressed", self)
|
||||
|
||||
|
||||
func _build_button_pressed():
|
||||
emit_signal("build_button_pressed", self)
|
||||
|
||||
|
||||
func _source_file_button_pressed():
|
||||
emit_signal("source_file_button_pressed", self)
|
||||
|
||||
|
||||
func _target_file_button_pressed():
|
||||
emit_signal("target_file_button_pressed", self)
|
||||
|
||||
|
||||
func _watched_folder_button_pressed():
|
||||
emit_signal("watched_folder_button_pressed", self)
|
||||
|
||||
# ############################################################################ #
|
||||
# Public Methods
|
||||
# ############################################################################ #
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func show_watched_folder(show: bool):
|
||||
watched_folder_label.visible = show
|
||||
watched_folder_container.visible = show
|
||||
|
||||
func disable_all_buttons(disable: bool):
|
||||
remove_button.disabled = disable
|
||||
build_button.disabled = disable
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Methods
|
||||
# ############################################################################ #
|
||||
|
||||
func _apply_custom_header_color():
|
||||
var header_color = editor_interface.get_custom_header_color()
|
||||
if header_color != Color.TRANSPARENT:
|
||||
background_color_rect.color = header_color
|
||||
|
||||
|
||||
func _set_button_icons():
|
||||
var folder_icon = get_theme_icon("Folder", "EditorIcons")
|
||||
source_file_dialog_button.icon = folder_icon
|
||||
target_file_dialog_button.icon = folder_icon
|
||||
watched_folder_dialog_button.icon = folder_icon
|
||||
|
||||
var trash_icon = get_theme_icon("Remove", "EditorIcons")
|
||||
remove_button.icon = trash_icon
|
||||
|
||||
|
||||
func _connect_signals():
|
||||
source_file_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
|
||||
source_file_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
|
||||
|
||||
target_file_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
|
||||
target_file_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
|
||||
|
||||
source_file_dialog_button.connect("pressed", Callable(self, "_source_file_button_pressed"))
|
||||
target_file_dialog_button.connect("pressed", Callable(self, "_target_file_button_pressed"))
|
||||
watched_folder_dialog_button.connect("pressed", Callable(self, "_watched_folder_button_pressed"))
|
||||
|
||||
remove_button.connect("pressed", Callable(self, "_remove_button_pressed"))
|
||||
build_button.connect("pressed", Callable(self, "_build_button_pressed"))
|
189
addons/inkgd/editor/panel/stories/ink_story_configuration.tscn
Normal file
189
addons/inkgd/editor/panel/stories/ink_story_configuration.tscn
Normal file
|
@ -0,0 +1,189 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://addons/inkgd/editor/panel/stories/ink_story_configuration.gd" type="Script" id=1]
|
||||
|
||||
[sub_resource type="Image" id=3]
|
||||
data = {
|
||||
"data": PackedByteArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id=2]
|
||||
flags = 4
|
||||
flags = 4
|
||||
image = SubResource( 3 )
|
||||
size = Vector2( 16, 16 )
|
||||
|
||||
[node name="StoryConfiguration" type="VBoxContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 87.0
|
||||
theme_override_constants/separation = 5
|
||||
script = ExtResource( 1 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="HeaderMarginContainer" type="MarginContainer" parent="."]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 32.0
|
||||
theme_override_constants/margin_right = 0
|
||||
theme_override_constants/margin_top = 0
|
||||
theme_override_constants/margin_left = 0
|
||||
theme_override_constants/margin_bottom = 0
|
||||
|
||||
[node name="BackgroundColorRect" type="ColorRect" parent="HeaderMarginContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 32.0
|
||||
color = Color( 0.268314, 0.291712, 0.340784, 1 )
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="HeaderMarginContainer"]
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 32.0
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_left = 25
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="HeaderMarginContainer/MarginContainer"]
|
||||
offset_left = 25.0
|
||||
offset_top = 5.0
|
||||
offset_right = 1897.0
|
||||
offset_bottom = 27.0
|
||||
|
||||
[node name="StoryLabel" type="Label" parent="HeaderMarginContainer/MarginContainer/HBoxContainer"]
|
||||
offset_top = 4.0
|
||||
offset_right = 1711.0
|
||||
offset_bottom = 18.0
|
||||
size_flags_horizontal = 3
|
||||
text = "0"
|
||||
|
||||
[node name="RemoveButton" type="Button" parent="HeaderMarginContainer/MarginContainer/HBoxContainer"]
|
||||
offset_left = 1715.0
|
||||
offset_right = 1803.0
|
||||
offset_bottom = 22.0
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Remove"
|
||||
icon = SubResource( 2 )
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false,
|
||||
"_editor_description_": "Compile the story manually, based on the configuration below."
|
||||
}
|
||||
|
||||
[node name="BuildButton" type="Button" parent="HeaderMarginContainer/MarginContainer/HBoxContainer"]
|
||||
offset_left = 1807.0
|
||||
offset_right = 1872.0
|
||||
offset_bottom = 22.0
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "Compile"
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false,
|
||||
"_editor_description_": "Compile the story manually, based on the configuration below."
|
||||
}
|
||||
|
||||
[node name="ConfigurationMarginContainer" type="MarginContainer" parent="."]
|
||||
offset_top = 37.0
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 117.0
|
||||
theme_override_constants/margin_right = 0
|
||||
theme_override_constants/margin_top = 0
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_bottom = 0
|
||||
__meta__ = {
|
||||
"_edit_use_anchors_": false
|
||||
}
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="ConfigurationMarginContainer"]
|
||||
offset_left = 5.0
|
||||
offset_right = 1902.0
|
||||
offset_bottom = 80.0
|
||||
size_flags_horizontal = 3
|
||||
columns = 2
|
||||
|
||||
[node name="SourceFileLabel" type="Label" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
tooltip_text = "The input path, a.k.a the original ink story."
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Source File (*.ink)"
|
||||
|
||||
[node name="SourceFileHBoxContainer" type="HBoxContainer" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_left = 950.0
|
||||
offset_right = 1896.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SourceFileLineEdit" type="LineEdit" parent="ConfigurationMarginContainer/GridContainer/SourceFileHBoxContainer"]
|
||||
offset_right = 914.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SourceFileDialogButton" type="Button" parent="ConfigurationMarginContainer/GridContainer/SourceFileHBoxContainer"]
|
||||
offset_left = 918.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
icon = SubResource( 2 )
|
||||
|
||||
[node name="TargetFileLabel" type="Label" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_top = 28.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 52.0
|
||||
tooltip_text = "The output path, a.k.a the JSON file compiled from the original ink story."
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Target File (*.json)"
|
||||
|
||||
[node name="TargetFileHBoxContainer" type="HBoxContainer" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_left = 950.0
|
||||
offset_top = 28.0
|
||||
offset_right = 1896.0
|
||||
offset_bottom = 52.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TargetFileLineEdit" type="LineEdit" parent="ConfigurationMarginContainer/GridContainer/TargetFileHBoxContainer"]
|
||||
offset_right = 914.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TargetFileDialogButton" type="Button" parent="ConfigurationMarginContainer/GridContainer/TargetFileHBoxContainer"]
|
||||
offset_left = 918.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
icon = SubResource( 2 )
|
||||
|
||||
[node name="WatchedFolderLabel" type="Label" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_top = 56.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 80.0
|
||||
tooltip_text = "The directory to watch for changes in Ink sources.
|
||||
|
||||
This path is optional, leave it blank to disable automatic recompilation for that story."
|
||||
mouse_filter = 1
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
text = "Watched Folder"
|
||||
|
||||
[node name="WatchedFolderHBoxContainer" type="HBoxContainer" parent="ConfigurationMarginContainer/GridContainer"]
|
||||
offset_left = 950.0
|
||||
offset_top = 56.0
|
||||
offset_right = 1896.0
|
||||
offset_bottom = 80.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="WatchedFolderLineEdit" type="LineEdit" parent="ConfigurationMarginContainer/GridContainer/WatchedFolderHBoxContainer"]
|
||||
offset_right = 914.0
|
||||
offset_bottom = 24.0
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="WatchedFolderDialogButton" type="Button" parent="ConfigurationMarginContainer/GridContainer/WatchedFolderHBoxContainer"]
|
||||
offset_left = 918.0
|
||||
offset_right = 946.0
|
||||
offset_bottom = 24.0
|
||||
icon = SubResource( 2 )
|
524
addons/inkgd/editor/panel/stories/ink_story_panel.gd
Normal file
524
addons/inkgd/editor/panel/stories/ink_story_panel.gd
Normal file
|
@ -0,0 +1,524 @@
|
|||
@tool
|
||||
# ############################################################################ #
|
||||
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
|
||||
# Licensed under the MIT License.
|
||||
# See LICENSE in the project root for license information.
|
||||
# ############################################################################ #
|
||||
|
||||
extends Control
|
||||
|
||||
# Hiding this type to prevent registration of "private" nodes.
|
||||
# See https://github.com/godotengine/godot-proposals/issues/1047
|
||||
# class_name InkStoryPanel
|
||||
|
||||
# ############################################################################ #
|
||||
# Imports
|
||||
# ############################################################################ #
|
||||
|
||||
var InkConfiguration = load("res://addons/inkgd/editor/common/ink_configuration.gd")
|
||||
|
||||
var InkCompilationConfiguration = load("res://addons/inkgd/editor/common/executors/structures/ink_compilation_configuration.gd")
|
||||
var InkCompiler = load("res://addons/inkgd/editor/common/executors/ink_compiler.gd")
|
||||
|
||||
var InkRichDialog = load("res://addons/inkgd/editor/panel/common/ink_rich_dialog.tscn")
|
||||
var InkProgressDialog = load("res://addons/inkgd/editor/panel/common/ink_progress_dialog.tscn")
|
||||
var InkStoryConfigurationScene = load("res://addons/inkgd/editor/panel/stories/ink_story_configuration.tscn")
|
||||
var EmptyStateContainerScene = load("res://addons/inkgd/editor/panel/stories/empty_state_container.tscn")
|
||||
|
||||
# ############################################################################ #
|
||||
# Signals
|
||||
# ############################################################################ #
|
||||
|
||||
signal _compiled()
|
||||
|
||||
# ############################################################################ #
|
||||
# Enums
|
||||
# ############################################################################ #
|
||||
|
||||
enum FileDialogSelection {
|
||||
UNKNOWN,
|
||||
SOURCE_FILE,
|
||||
TARGET_FILE,
|
||||
WATCHED_FOLDER
|
||||
}
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var editor_interface: InkEditorInterface
|
||||
var configuration: InkConfiguration
|
||||
var progress_texture: AnimatedTexture
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private Properties
|
||||
# ############################################################################ #
|
||||
|
||||
var _scrollbar_max_value = -1
|
||||
|
||||
var _compilers: Dictionary = {}
|
||||
|
||||
var _file_dialog = EditorFileDialog.new()
|
||||
|
||||
## Configuration item for which the FileDialog is currently shown.
|
||||
##
|
||||
## Unknown by default.
|
||||
var _file_dialog_selection = FileDialogSelection.UNKNOWN
|
||||
|
||||
## The story index for which the FileDialog is currenlty shown.
|
||||
##
|
||||
## -1 by default or when the file dialog currently displayed doesn't
|
||||
## concern the stories source/target files.
|
||||
var _file_dialog_selection_story_index = -1
|
||||
|
||||
var _current_story_node = null
|
||||
|
||||
var _progress_dialog = null
|
||||
|
||||
# ############################################################################ #
|
||||
# Nodes
|
||||
# ############################################################################ #
|
||||
|
||||
@onready var _empty_state_container = EmptyStateContainerScene.instantiate()
|
||||
|
||||
@onready var _build_all_button = find_child("BuildAllButton")
|
||||
@onready var _add_new_story_button = find_child("AddNewStoryButton")
|
||||
|
||||
@onready var _story_configuration_container = find_child("StoryConfigurationVBoxContainer")
|
||||
@onready var _scroll_container = find_child("ScrollContainer")
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Overrides
|
||||
# ############################################################################ #
|
||||
|
||||
func _ready():
|
||||
# FIXME: This needs investigating.
|
||||
# Sanity check. It seems the editor instantiates tools script on their
|
||||
# own, probably to add them to its tree. In that case, they won't have
|
||||
# their dependencies injected, so we're not doing anything.
|
||||
if editor_interface == null || configuration == null || progress_texture == null:
|
||||
print("[inkgd] [INFO] Ink Stories Tab: dependencies not met, ignoring.")
|
||||
return
|
||||
|
||||
configuration.connect("compilation_mode_changed", Callable(self, "_compilation_mode_changed"))
|
||||
|
||||
editor_interface.editor_filesystem.connect("resources_reimported", Callable(self, "_resources_reimported"))
|
||||
|
||||
_story_configuration_container.add_child(_empty_state_container)
|
||||
add_child(_file_dialog)
|
||||
|
||||
var add_icon = get_theme_icon("Add", "EditorIcons")
|
||||
_add_new_story_button.icon = add_icon
|
||||
|
||||
_load_story_configurations()
|
||||
_connect_signals()
|
||||
|
||||
_compilation_mode_changed(configuration.compilation_mode)
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Signal Receivers
|
||||
# ############################################################################ #
|
||||
|
||||
func _resources_reimported(resources):
|
||||
call_deferred("_recompile_if_necessary", resources)
|
||||
|
||||
func _compilation_mode_changed(compilation_mode: int):
|
||||
var show_folder = (compilation_mode == InkConfiguration.BuildMode.AFTER_CHANGE)
|
||||
|
||||
for child in _story_configuration_container.get_children():
|
||||
child.show_watched_folder(show_folder)
|
||||
|
||||
func _source_file_button_pressed(node):
|
||||
_reset_file_dialog()
|
||||
|
||||
var index = _get_story_configuration_index(node)
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.SOURCE_FILE
|
||||
_file_dialog_selection_story_index = index
|
||||
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
var path = story_configuration.source_file_line_edit.text
|
||||
|
||||
_file_dialog.current_path = path
|
||||
_file_dialog.current_dir = path.get_base_dir()
|
||||
_file_dialog.current_file = path.get_file()
|
||||
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.add_filter("*.ink;Ink source file")
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _target_file_button_pressed(node):
|
||||
_reset_file_dialog()
|
||||
|
||||
var index = _get_story_configuration_index(node)
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.TARGET_FILE
|
||||
_file_dialog_selection_story_index = index
|
||||
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
var path = story_configuration.target_file_line_edit.text
|
||||
|
||||
_file_dialog.current_path = path
|
||||
_file_dialog.current_dir = path.get_base_dir()
|
||||
_file_dialog.current_file = path.get_file()
|
||||
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.add_filter("*.json;Compiled Ink story")
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
func _watched_folder_button_pressed(node):
|
||||
_reset_file_dialog()
|
||||
|
||||
var index = _get_story_configuration_index(node)
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.WATCHED_FOLDER
|
||||
_file_dialog_selection_story_index = index
|
||||
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
var path = story_configuration.watched_folder_line_edit.text
|
||||
|
||||
_file_dialog.current_path = path
|
||||
_file_dialog.current_dir = path.get_base_dir()
|
||||
_file_dialog.current_file = path.get_file()
|
||||
|
||||
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||||
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
|
||||
|
||||
|
||||
func _build_all_button_pressed():
|
||||
_compile_all_stories()
|
||||
|
||||
|
||||
func _add_new_story_button_pressed():
|
||||
_add_new_story_configuration()
|
||||
|
||||
|
||||
func _configuration_changed(_node):
|
||||
_persist_configuration()
|
||||
|
||||
|
||||
func _remove_button_pressed(node):
|
||||
var index = _get_story_configuration_index(node)
|
||||
configuration.remove_story_configuration_at_index(index)
|
||||
|
||||
# TODO: Rebuild from scratch instead.
|
||||
var parent = node.get_parent()
|
||||
if parent != null:
|
||||
parent.remove_child(node)
|
||||
node.queue_free()
|
||||
|
||||
if _story_configuration_container.get_child_count() == 0:
|
||||
_story_configuration_container.add_child(_empty_state_container)
|
||||
else:
|
||||
var i = 0
|
||||
for child in _story_configuration_container.get_children():
|
||||
# Not using "is InkStoryConfiguration", because it requires a type
|
||||
# declaration. Node Types register in the editor and we don't want
|
||||
# that. This is a bit hacky, but until the proposal is accepted,
|
||||
# it prevents cluttering the "Create new node" list.
|
||||
if "story_label" in child:
|
||||
child.story_label.text = "Story %d" % (i + 1)
|
||||
i += 1
|
||||
|
||||
_persist_configuration()
|
||||
|
||||
|
||||
func _build_button_pressed(node):
|
||||
var index = _get_story_configuration_index(node)
|
||||
var story_configuration = configuration.get_story_configuration_at_index(index)
|
||||
|
||||
if story_configuration == null:
|
||||
printerr("[inkgd] [ERROR] No configurations found for Story %d" % (index + 1))
|
||||
return
|
||||
|
||||
_compile_story(story_configuration, node)
|
||||
|
||||
|
||||
func _compile_all_stories():
|
||||
_disable_all_buttons(true)
|
||||
var number_of_stories = configuration.stories.size()
|
||||
var current_story_index = 0
|
||||
|
||||
_progress_dialog = InkProgressDialog.instantiate()
|
||||
add_child(_progress_dialog)
|
||||
|
||||
_progress_dialog.update_layout(editor_interface.scale)
|
||||
_progress_dialog.popup_centered(Vector2(600, 100) * editor_interface.scale)
|
||||
|
||||
for story_configuration in configuration.stories:
|
||||
var source_file_path: String = configuration.get_source_file_path(story_configuration)
|
||||
_progress_dialog.current_step_name = source_file_path.get_file()
|
||||
|
||||
_compile_story(story_configuration)
|
||||
await self._compiled
|
||||
|
||||
@warning_ignore("integer_division")
|
||||
_progress_dialog.progress = float(100 * (current_story_index + 1) / number_of_stories)
|
||||
current_story_index += 1
|
||||
|
||||
remove_child(_progress_dialog)
|
||||
_progress_dialog.queue_free()
|
||||
_progress_dialog = null
|
||||
_disable_all_buttons(false)
|
||||
|
||||
func _compile_story(story_configuration, node = null):
|
||||
var source_file_path = configuration.get_source_file_path(story_configuration)
|
||||
var target_file_path = configuration.get_target_file_path(story_configuration)
|
||||
|
||||
if node != null:
|
||||
_current_story_node = node
|
||||
|
||||
node.build_button.icon = progress_texture
|
||||
_disable_all_buttons(true)
|
||||
|
||||
var compiler_configuration = InkCompilationConfiguration.new(
|
||||
configuration,
|
||||
true,
|
||||
node != null,
|
||||
source_file_path,
|
||||
target_file_path
|
||||
)
|
||||
var compiler = InkCompiler.new(compiler_configuration)
|
||||
|
||||
_compilers[compiler.identifier] = compiler
|
||||
compiler.connect("story_compiled", Callable(self, "_handle_compilation"))
|
||||
compiler.compile_story()
|
||||
|
||||
|
||||
func _handle_compilation(result):
|
||||
if _current_story_node != null:
|
||||
var button = _current_story_node.build_button
|
||||
button.icon = null
|
||||
_disable_all_buttons(false)
|
||||
_current_story_node = null
|
||||
|
||||
if result.user_triggered:
|
||||
if result.success:
|
||||
if result.output && !result.output.is_empty():
|
||||
var dialog = InkRichDialog.instantiate()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Success!"
|
||||
dialog.message_text = "The story was successfully compiled."
|
||||
dialog.output_text = result.output
|
||||
dialog.update_layout(editor_interface.scale)
|
||||
|
||||
dialog.popup_centered(Vector2(700, 400) * editor_interface.scale)
|
||||
else:
|
||||
var dialog = AcceptDialog.new()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Success!"
|
||||
dialog.dialog_text = "The story was successfully compiled."
|
||||
|
||||
dialog.popup_centered()
|
||||
|
||||
_reimport_compiled_stories()
|
||||
else:
|
||||
var dialog = InkRichDialog.instantiate()
|
||||
add_child(dialog)
|
||||
|
||||
dialog.title = "Error"
|
||||
dialog.message_text = "The story could not be compiled. See inklecate's output below."
|
||||
dialog.output_text = result.output
|
||||
dialog.update_layout(editor_interface.scale)
|
||||
|
||||
dialog.popup_centered(Vector2(700, 400) * editor_interface.scale)
|
||||
else:
|
||||
_reimport_compiled_stories()
|
||||
|
||||
if _compilers.has(result.identifier):
|
||||
_compilers.erase(result.identifier)
|
||||
|
||||
emit_signal("_compiled")
|
||||
|
||||
|
||||
func _on_file_selected(path: String):
|
||||
var index = _file_dialog_selection_story_index
|
||||
|
||||
match _file_dialog_selection:
|
||||
FileDialogSelection.SOURCE_FILE:
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
if story_configuration == null:
|
||||
return
|
||||
|
||||
var localized_path = ProjectSettings.localize_path(path)
|
||||
var source_line_edit = story_configuration.source_file_line_edit
|
||||
|
||||
source_line_edit.text = localized_path
|
||||
source_line_edit.queue_redraw()
|
||||
|
||||
if story_configuration.target_file_line_edit.text.is_empty():
|
||||
var target_line_edit = story_configuration.target_file_line_edit
|
||||
target_line_edit.text = localized_path + ".json"
|
||||
target_line_edit.queue_redraw()
|
||||
|
||||
if story_configuration.watched_folder_line_edit.text.is_empty():
|
||||
var watched_folder_line_edit = story_configuration.watched_folder_line_edit
|
||||
watched_folder_line_edit.text = localized_path.get_base_dir()
|
||||
watched_folder_line_edit.queue_redraw()
|
||||
|
||||
_persist_configuration()
|
||||
|
||||
FileDialogSelection.TARGET_FILE:
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
if story_configuration == null:
|
||||
return
|
||||
|
||||
var localized_path = ProjectSettings.localize_path(path)
|
||||
var line_edit = story_configuration.target_file_line_edit
|
||||
|
||||
line_edit.text = localized_path
|
||||
line_edit.queue_redraw()
|
||||
_persist_configuration()
|
||||
|
||||
FileDialogSelection.WATCHED_FOLDER:
|
||||
var story_configuration = _get_story_configuration_at_index(index)
|
||||
if story_configuration == null:
|
||||
return
|
||||
|
||||
var localized_path = ProjectSettings.localize_path(path)
|
||||
var line_edit = story_configuration.watched_folder_line_edit
|
||||
|
||||
line_edit.text = localized_path
|
||||
line_edit.queue_redraw()
|
||||
_persist_configuration()
|
||||
|
||||
_:
|
||||
printerr("[inkgd] [ERROR] Unknown FileDialogSelection, failed to save FileDialog file.")
|
||||
|
||||
_file_dialog_selection = FileDialogSelection.UNKNOWN
|
||||
|
||||
|
||||
func _scrollbar_changed():
|
||||
var max_value = _scroll_container.get_v_scroll_bar().max_value
|
||||
|
||||
if _scrollbar_max_value == max_value && _scrollbar_max_value != -1:
|
||||
return
|
||||
|
||||
_scrollbar_max_value = max_value
|
||||
_scroll_container.scroll_vertical = max_value
|
||||
|
||||
|
||||
# ############################################################################ #
|
||||
# Private helpers
|
||||
# ############################################################################ #
|
||||
|
||||
func _reset_file_dialog():
|
||||
_file_dialog.current_path = "res://"
|
||||
_file_dialog.current_dir = "res://"
|
||||
_file_dialog.current_file = ""
|
||||
_file_dialog.clear_filters()
|
||||
|
||||
|
||||
func _persist_configuration():
|
||||
configuration.stories.clear()
|
||||
|
||||
if _empty_state_container.get_parent() == null:
|
||||
configuration.stories.clear()
|
||||
for node in _story_configuration_container.get_children():
|
||||
# Not using "is InkStoryConfiguration", because it requires a type
|
||||
# declaration. Node Types register in the editor and we don't want
|
||||
# that. This is a bit hacky, but until the proposal is accepted,
|
||||
# it prevents cluttering the "Create new node" list.
|
||||
if !("story_label" in node):
|
||||
continue
|
||||
|
||||
configuration.append_new_story_configuration(
|
||||
node.source_file_line_edit.text,
|
||||
node.target_file_line_edit.text,
|
||||
node.watched_folder_line_edit.text
|
||||
)
|
||||
|
||||
configuration.persist()
|
||||
|
||||
|
||||
func _load_story_configurations():
|
||||
for story_configuration in configuration.stories:
|
||||
var node = _add_new_story_configuration()
|
||||
|
||||
node.source_file_line_edit.text = configuration.get_source_file_path(story_configuration)
|
||||
node.target_file_line_edit.text = configuration.get_target_file_path(story_configuration)
|
||||
node.watched_folder_line_edit.text = configuration.get_watched_folder_path(story_configuration)
|
||||
|
||||
|
||||
func _add_new_story_configuration():
|
||||
var story_configuration = InkStoryConfigurationScene.instantiate()
|
||||
|
||||
story_configuration.editor_interface = editor_interface
|
||||
|
||||
story_configuration.connect("configuration_changed", Callable(self, "_configuration_changed"))
|
||||
story_configuration.connect("remove_button_pressed", Callable(self, "_remove_button_pressed"))
|
||||
story_configuration.connect("build_button_pressed", Callable(self, "_build_button_pressed"))
|
||||
story_configuration.connect("source_file_button_pressed", Callable(self, "_source_file_button_pressed"))
|
||||
story_configuration.connect("target_file_button_pressed", Callable(self, "_target_file_button_pressed"))
|
||||
story_configuration.connect("watched_folder_button_pressed", Callable(self, "_watched_folder_button_pressed"))
|
||||
|
||||
if _empty_state_container.get_parent() != null:
|
||||
_story_configuration_container.remove_child(_empty_state_container)
|
||||
|
||||
_story_configuration_container.add_child(story_configuration)
|
||||
|
||||
var count = _story_configuration_container.get_child_count()
|
||||
story_configuration.story_label.text = "Story %d" % count
|
||||
|
||||
var show_folder = (configuration.compilation_mode == InkConfiguration.BuildMode.AFTER_CHANGE)
|
||||
story_configuration.show_watched_folder(show_folder)
|
||||
|
||||
return story_configuration
|
||||
|
||||
|
||||
func _reimport_compiled_stories():
|
||||
editor_interface.scan_file_system()
|
||||
|
||||
|
||||
func _get_story_configuration_index(node) -> int:
|
||||
return _story_configuration_container.get_children().find(node)
|
||||
|
||||
|
||||
func _get_story_configuration_at_index(index: int):
|
||||
if index >= 0 && _story_configuration_container.get_child_count():
|
||||
return _story_configuration_container.get_children()[index]
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func _recompile_if_necessary(resources: PackedStringArray):
|
||||
# Making sure the resources have been imported before recompiling.
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
for story_configuration in configuration.stories:
|
||||
var watched_folder_path: String = configuration.get_watched_folder_path(story_configuration)
|
||||
|
||||
if watched_folder_path.is_empty():
|
||||
return
|
||||
|
||||
for resource in resources:
|
||||
if resource.begins_with(watched_folder_path):
|
||||
_compile_story(story_configuration)
|
||||
break
|
||||
|
||||
|
||||
func _disable_all_buttons(disable: bool):
|
||||
_add_new_story_button.disabled = disable
|
||||
_build_all_button.disabled = disable
|
||||
for child in _story_configuration_container.get_children():
|
||||
child.disable_all_buttons(disable)
|
||||
|
||||
|
||||
func _connect_signals():
|
||||
_build_all_button.connect("pressed", Callable(self, "_build_all_button_pressed"))
|
||||
_add_new_story_button.connect("pressed", Callable(self, "_add_new_story_button_pressed"))
|
||||
|
||||
_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
|
||||
_file_dialog.connect("dir_selected", Callable(self, "_on_file_selected"))
|
||||
|
||||
_scroll_container.get_v_scroll_bar().connect("changed", Callable(self, "_scrollbar_changed"))
|
75
addons/inkgd/editor/panel/stories/ink_story_panel.tscn
Normal file
75
addons/inkgd/editor/panel/stories/ink_story_panel.tscn
Normal file
|
@ -0,0 +1,75 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://b16uj28y2mqmk"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/inkgd/editor/panel/stories/ink_story_panel.gd" id="1"]
|
||||
|
||||
[sub_resource type="Image" id="Image_3omov"]
|
||||
data = {
|
||||
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
"format": "LumAlpha8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="2"]
|
||||
image = SubResource("Image_3omov")
|
||||
|
||||
[node name="Story" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
|
||||
[node name="StoriesVBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="ScrollContainer/StoriesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
alignment = 2
|
||||
|
||||
[node name="Label" type="Label" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Managed Stories"
|
||||
|
||||
[node name="AddNewStoryButton" type="Button" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 8
|
||||
text = "New story"
|
||||
icon = SubResource("2")
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BuildAllButton" type="Button" parent="ScrollContainer/StoriesVBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme_override_constants/h_separation = 10
|
||||
text = "Compile All"
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="ScrollContainer/StoriesVBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="ScrollContainer/StoriesVBoxContainer/MarginContainer"]
|
||||
self_modulate = Color(1, 1, 1, 0.686275)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="ScrollContainer/StoriesVBoxContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 5
|
||||
theme_override_constants/margin_top = 5
|
||||
theme_override_constants/margin_right = 5
|
||||
theme_override_constants/margin_bottom = 5
|
||||
|
||||
[node name="StoryConfigurationVBoxContainer" type="VBoxContainer" parent="ScrollContainer/StoriesVBoxContainer/MarginContainer/MarginContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 15
|
Loading…
Add table
Add a link
Reference in a new issue