This commit is contained in:
Gerard Gascón 2025-04-24 17:23:34 +02:00
commit b99855351d
434 changed files with 50357 additions and 0 deletions

View file

@ -0,0 +1,57 @@
@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 Popup
# A custom dialog showing a progress bar.
# Hiding this type to prevent registration of "private" nodes.
# See https://github.com/godotengine/godot-proposals/issues/1047
# class_name InkProgressDialog
# ############################################################################ #
# Nodes
# ############################################################################ #
@onready var _margin_container = $MarginContainer
@onready var _vbox_container = $MarginContainer/VBoxContainer
@onready var _title_label = $MarginContainer/VBoxContainer/TitleLabel
@onready var _progress_bar = $MarginContainer/VBoxContainer/ProgressBar
@onready var _current_step_label = $MarginContainer/VBoxContainer/CurrentStepLabel
# ############################################################################ #
# Properties
# ############################################################################ #
## The title of the progress.
var progress_title: String: get = get_progress_title, set = set_progress_title
func set_progress_title(text: String):
_title_label.text = text
func get_progress_title() -> String:
return _title_label.text
## The name of the current step.
var current_step_name: String: get = get_current_step_name, set = set_current_step_name
func set_current_step_name(text: String):
_current_step_label.text = text
func get_current_step_name() -> String:
return _current_step_label.text
## The current progress.
var progress: float:
get:
return _progress_bar.value
set(value):
_progress_bar.value = value
func update_layout(scale: float) -> void:
_margin_container.add_theme_constant_override("offset_right", 10 * scale)
_margin_container.add_theme_constant_override("offset_top", 10 * scale)
_margin_container.add_theme_constant_override("offset_left", 10 * scale)
_margin_container.add_theme_constant_override("offset_bottom", 10 * scale)
_vbox_container.add_theme_constant_override("separation", 5 * scale)

View file

@ -0,0 +1,63 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/inkgd/editor/panel/common/ink_progress_dialog.gd" type="Script" id=1]
[node name="InkProgressDialog" type="Popup"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -42.5
offset_right = 250.0
offset_bottom = 42.5
custom_minimum_size = Vector2( 500, 85 )
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
theme_override_constants/margin_right = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_left = 10
theme_override_constants/margin_bottom = 10
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
offset_left = 10.0
offset_top = 10.0
offset_right = 490.0
offset_bottom = 75.0
theme_override_constants/separation = 5
alignment = 1
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TitleLabel" type="Label" parent="MarginContainer/VBoxContainer"]
offset_top = 1.0
offset_right = 480.0
offset_bottom = 15.0
text = "Compiling..."
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ProgressBar" type="ProgressBar" parent="MarginContainer/VBoxContainer"]
offset_top = 25.0
offset_right = 480.0
offset_bottom = 39.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="CurrentStepLabel" type="Label" parent="MarginContainer/VBoxContainer"]
offset_top = 49.0
offset_right = 480.0
offset_bottom = 63.0
text = "the_intercept.ink"

View file

@ -0,0 +1,103 @@
@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 Window
# A custom dialog showing a message and, optionally, a command output.
# Hiding this type to prevent registration of "private" nodes.
# See https://github.com/godotengine/godot-proposals/issues/1047
# class_name InkRichDialog
# ############################################################################ #
# Nodes
# ############################################################################ #
@onready var _margin_container = $MarginContainer
@onready var _vbox_container = $MarginContainer/VBoxContainer
@onready var _message_label = $MarginContainer/VBoxContainer/MessageLabel
@onready var _accept_button = $MarginContainer/VBoxContainer/AcceptButton
@onready var _output_panel = $MarginContainer/VBoxContainer/OutputPanel
@onready var _output_label = find_child("OutputLabel")
# ############################################################################ #
# Properties
# ############################################################################ #
## The message displayed in the dialog.
var message_text: String: get = get_message_text, set = set_message_text
func set_message_text(text: String):
_message_label.text = text
func get_message_text() -> String:
return _message_label.text
## An output, often the result of a command, than can optionally be displayed
## in the dialog.
##
## Setting this property to null hides the corresponding panel in the dialog.
var output_text: String: get = get_output_text, set = set_output_text
func set_output_text(text: String):
_output_label.text = text
_output_label.visible = !(text == null || text.length() == 0)
func get_output_text() -> String:
return _output_label.text
# ############################################################################ #
# Overriden Methods
# ############################################################################ #
func _ready():
_accept_button.connect("pressed", Callable(self, "_accept_button_pressed"))
var font = _get_source_font()
if font != null:
_output_panel.add_theme_font_override("font", font)
# ############################################################################ #
# Methods
# ############################################################################ #
func update_layout(scale: float) -> void:
_margin_container.add_theme_constant_override("offset_right", 10 * scale)
_margin_container.add_theme_constant_override("offset_top", 10 * scale)
_margin_container.add_theme_constant_override("offset_left", 10 * scale)
_margin_container.add_theme_constant_override("offset_bottom", 10 * scale)
_vbox_container.add_theme_constant_override("separation", 10 * scale)
# ############################################################################ #
# Signal Receivers
# ############################################################################ #
func _accept_button_pressed():
self.get_parent().remove_child(self)
self.queue_free()
# ############################################################################ #
# Private helpers
# ############################################################################ #
## Gets the monospaced font used by the editor.
func _get_source_font():
var base_theme = _retrieve_base_theme()
if base_theme:
return base_theme.get_font("output_source", "EditorFonts")
else:
return null
## Gets the theme currently used by the editor.
func _retrieve_base_theme():
var parent = self
while(parent != null && parent.theme == null):
var older_parent = parent.get_parent()
if older_parent is Control:
parent = older_parent
else:
break
return parent.theme

View file

@ -0,0 +1,89 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/inkgd/editor/panel/common/ink_rich_dialog.gd" type="Script" id=1]
[node name="Window" type="Window"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -250.0
offset_top = -150.0
offset_right = 250.0
offset_bottom = 150.0
custom_minimum_size = Vector2( 500, 300 )
window_title = "Error"
resizable = true
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_right = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_left = 10
theme_override_constants/margin_bottom = 10
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
offset_left = 10.0
offset_top = 10.0
offset_right = 490.0
offset_bottom = 290.0
theme_override_constants/separation = 10
[node name="MessageLabel" type="Label" parent="MarginContainer/VBoxContainer"]
offset_right = 480.0
offset_bottom = 31.0
text = "Something went wrong while testing inklecate's setup. Please see the output below."
autowrap = true
[node name="OutputPanel" type="Panel" parent="MarginContainer/VBoxContainer"]
offset_top = 41.0
offset_right = 480.0
offset_bottom = 250.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="OutputScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer/OutputPanel"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="OutputMarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/OutputPanel/OutputScrollContainer"]
offset_right = 480.0
offset_bottom = 136.0
size_flags_horizontal = 3
theme_override_constants/margin_right = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_left = 10
theme_override_constants/margin_bottom = 10
__meta__ = {
"_edit_use_anchors_": false
}
[node name="OutputLabel" type="Label" parent="MarginContainer/VBoxContainer/OutputPanel/OutputScrollContainer/OutputMarginContainer"]
offset_left = 10.0
offset_top = 10.0
offset_right = 470.0
offset_bottom = 126.0
size_flags_vertical = 0
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
autowrap = true
[node name="AcceptButton" type="Button" parent="MarginContainer/VBoxContainer"]
offset_left = 224.0
offset_top = 260.0
offset_right = 255.0
offset_bottom = 280.0
size_flags_horizontal = 4
text = "OK"

View file

@ -0,0 +1,306 @@
@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 InkConfigurationPanel
# ############################################################################ #
# Imports
# ############################################################################ #
var InkExecutionConfiguration = load("res://addons/inkgd/editor/common/executors/structures/ink_execution_configuration.gd")
var InkConfigurationTester = load("res://addons/inkgd/editor/common/executors/ink_configuration_tester.gd")
var InkCSharpValidator = preload("res://addons/inkgd/editor/common/ink_csharp_validator.gd")
var InkRichDialog = load("res://addons/inkgd/editor/panel/common/ink_rich_dialog.tscn")
# ############################################################################ #
# Enums
# ############################################################################ #
## Represents which configuration setting triggered the file dialog.
enum FileDialogSelection {
UNKNOWN,
MONO_EXECUTABLE,
INKLECATE_EXECUTABLE
}
# ############################################################################ #
# Constants
# ############################################################################ #
const BOM = "\ufeff"
# ############################################################################ #
# Properties
# ############################################################################ #
var editor_interface: InkEditorInterface = null
var configuration: InkConfiguration = null
# ############################################################################ #
# Private Properties
# ############################################################################ #
var _file_dialog = EditorFileDialog.new()
## Configuration item for which the FileDialog is currently shown.
##
## Unknown by default.
var _file_dialog_selection = FileDialogSelection.UNKNOWN
# ############################################################################ #
# Nodes
# ############################################################################ #
@onready var _test_button = find_child("TestButton")
@onready var _use_mono_label = find_child("UseMonoLabel")
@onready var _use_mono_checkbox = find_child("UseMonoCheckBox")
@onready var _mono_label = find_child("MonoLabel")
@onready var _mono_container = find_child("MonoH")
@onready var _mono_line_edit = find_child("MonoLineEdit")
@onready var _mono_dialog_button = find_child("MonoDialogButton")
@onready var _executable_line_edit = find_child("ExecutableLineEdit")
@onready var _executable_dialog_button = find_child("ExecutableDialogButton")
@onready var _recompilation_mode_button = find_child("RecompilationModeOptionButton")
@onready var _mono_support_container = find_child("MonoSupportV")
@onready var _mono_support_documentation_button = find_child("DocumentationButton")
@onready var _mono_support_presence_label = _mono_support_container.find_child("PresenceLabel")
@onready var _mono_support_refresh_button = _mono_support_container.find_child("RefreshButton")
# ############################################################################ #
# 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:
print("[inkgd] [INFO] Ink Configuration Tab: dependencies not met, ignoring.")
return
_set_button_icons()
_apply_configuration()
_connect_signals()
_check_runtime_presence()
_mono_support_container.visible = _can_run_mono()
add_child(_file_dialog)
# ############################################################################ #
# Signal Receivers
# ############################################################################ #
func _configuration_entered(_new_text: String):
_configuration_focus_exited()
func _configuration_focus_exited():
configuration.mono_path = _mono_line_edit.text
configuration.inklecate_path = _executable_line_edit.text
configuration.persist()
func _use_mono_toggled(_toggled: bool):
configuration.use_mono = !configuration.use_mono
configuration.persist()
_update_mono_availability(false)
func _mono_button_pressed():
_reset_file_dialog()
_file_dialog_selection = FileDialogSelection.MONO_EXECUTABLE
_file_dialog.current_path = configuration.mono_path
_file_dialog.current_dir = configuration.mono_path.get_base_dir()
_file_dialog.current_file = configuration.mono_path.get_file()
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
func _executable_button_pressed():
_reset_file_dialog()
_file_dialog_selection = FileDialogSelection.INKLECATE_EXECUTABLE
_file_dialog.current_file = configuration.inklecate_path
_file_dialog.current_dir = configuration.inklecate_path.get_base_dir()
_file_dialog.current_file = configuration.inklecate_path.get_file()
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_file_dialog.access = FileDialog.ACCESS_FILESYSTEM
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
func _recompilation_mode_button_selected(index):
configuration.compilation_mode = index
configuration.persist()
func _test_button_pressed():
var test_configuration = InkExecutionConfiguration.new(configuration, false, true)
var tester = InkConfigurationTester.new(test_configuration)
var result = tester.test_availability()
# NOTE: At the moment, inklecate doesn't support a subcommand that would just
# exit with 0 so `_contains_inklecate_output_prefix` will always be executed.
if result.success:
var dialog = AcceptDialog.new()
add_child(dialog)
dialog.title = "Success"
dialog.dialog_text = "The configuration seems to be valid!"
dialog.popup_centered()
else:
var dialog = InkRichDialog.instantiate()
add_child(dialog)
dialog.title = "Error"
dialog.message_text = "Something went wrong while testing inklecate's setup. Please see the output below."
dialog.output_text = result.output
dialog.update_layout(editor_interface.scale)
dialog.popup_centered(Vector2(700, 400) * editor_interface.scale)
func _on_file_selected(path: String):
match _file_dialog_selection:
FileDialogSelection.MONO_EXECUTABLE:
configuration.mono_path = ProjectSettings.globalize_path(path)
_update_save_and_cleanup(configuration.mono_path, _mono_line_edit)
FileDialogSelection.INKLECATE_EXECUTABLE:
configuration.inklecate_path = ProjectSettings.globalize_path(path)
_update_save_and_cleanup(configuration.inklecate_path, _executable_line_edit)
_:
printerr("[inkgd] [ERROR] Unknown FileDialogSelection, failed to save FileDialog file.")
_file_dialog_selection = FileDialogSelection.UNKNOWN
func _check_runtime_presence():
var ink_engine_runtime = InkCSharpValidator.new().get_runtime_path()
var is_present = !ink_engine_runtime.is_empty()
if is_present:
_mono_support_presence_label.add_theme_color_override("font_color", Color.GREEN)
_mono_support_presence_label.text = "PRESENT"
else:
_mono_support_presence_label.add_theme_color_override("font_color", Color.RED)
_mono_support_presence_label.text = "MISSING"
func _mono_support_documentation_pressed():
OS.shell_open("https://inkgd.readthedocs.io/en/latest/advanced/migrating_to_godot_mono.html")
# ############################################################################ #
# Private helpers
# ############################################################################ #
func _reset_file_dialog():
_file_dialog.current_file = ""
_file_dialog.clear_filters()
func _update_save_and_cleanup(value, line_edit):
line_edit.text = value
line_edit.queue_redraw()
configuration.persist()
func _apply_configuration():
var compilation_mode = configuration.compilation_mode
var item_count = _recompilation_mode_button.get_item_count()
if compilation_mode >= 0 && compilation_mode < item_count:
_recompilation_mode_button.select(configuration.compilation_mode)
else:
_recompilation_mode_button.select(0)
_mono_line_edit.text = configuration.mono_path
_executable_line_edit.text = configuration.inklecate_path
_update_mono_availability(true)
func _update_mono_availability(updates_checkbox: bool = false):
var is_running_on_windows: bool = editor_interface.is_running_on_windows
var is_control_visible: bool = !is_running_on_windows && configuration.use_mono
_use_mono_label.visible = !is_running_on_windows
_use_mono_checkbox.visible = !is_running_on_windows
_mono_label.visible = is_control_visible
_mono_container.visible = is_control_visible
if updates_checkbox:
_use_mono_checkbox.set_pressed(configuration.use_mono)
func _set_button_icons():
var folder_icon = get_theme_icon("Folder", "EditorIcons")
var reload_icon = get_theme_icon("Reload", "EditorIcons")
var instance_icon = get_theme_icon("Instance", "EditorIcons")
_mono_dialog_button.icon = folder_icon
_executable_dialog_button.icon = folder_icon
_mono_support_documentation_button.icon = instance_icon
_mono_support_refresh_button.icon = reload_icon
func _connect_signals():
editor_interface.editor_filesystem.connect("filesystem_changed", Callable(self, "_check_runtime_presence"))
_test_button.connect("pressed", Callable(self, "_test_button_pressed"))
_use_mono_checkbox.connect("toggled", Callable(self, "_use_mono_toggled"))
_mono_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
_executable_line_edit.connect("text_submitted", Callable(self, "_configuration_entered"))
_mono_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
_executable_line_edit.connect("focus_exited", Callable(self, "_configuration_focus_exited"))
_mono_dialog_button.connect("pressed", Callable(self, "_mono_button_pressed"))
_executable_dialog_button.connect("pressed", Callable(self, "_executable_button_pressed"))
_recompilation_mode_button.connect("item_selected", Callable(self, "_recompilation_mode_button_selected"))
_mono_support_documentation_button.connect("pressed", Callable(self, "_mono_support_documentation_pressed"))
_mono_support_refresh_button.connect("pressed", Callable(self, "_check_runtime_presence"))
_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
func _can_run_mono():
return type_exists("_GodotSharp")

View file

@ -0,0 +1,258 @@
[gd_scene load_steps=6 format=3 uid="uid://cbuep470x6krw"]
[ext_resource type="Script" path="res://addons/inkgd/editor/panel/configuration/ink_configuration_panel.gd" id="1"]
[sub_resource type="Image" id="Image_h71nq"]
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="7"]
image = SubResource("Image_h71nq")
[sub_resource type="Image" id="Image_e0kbj"]
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="5"]
image = SubResource("Image_e0kbj")
[node name="Configuration" 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
pivot_offset = Vector2(1346, -25)
[node name="V" type="VBoxContainer" parent="ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 25
[node name="InklecateV" type="VBoxContainer" parent="ScrollContainer/V"]
layout_mode = 2
size_flags_horizontal = 3
[node name="H" type="HBoxContainer" parent="ScrollContainer/V/InklecateV"]
layout_mode = 2
[node name="Label" type="Label" parent="ScrollContainer/V/InklecateV/H"]
layout_mode = 2
size_flags_horizontal = 3
text = "Inklecate"
[node name="TestButton" type="Button" parent="ScrollContainer/V/InklecateV/H"]
layout_mode = 2
mouse_filter = 1
text = "Test configuration"
[node name="M" type="MarginContainer" parent="ScrollContainer/V/InklecateV"]
layout_mode = 2
[node name="Panel" type="Panel" parent="ScrollContainer/V/InklecateV/M"]
self_modulate = Color(1, 1, 1, 0.686275)
layout_mode = 2
[node name="M" type="MarginContainer" parent="ScrollContainer/V/InklecateV/M"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
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="V" type="VBoxContainer" parent="ScrollContainer/V/InklecateV/M/M"]
layout_mode = 2
size_flags_horizontal = 3
[node name="G" type="GridContainer" parent="ScrollContainer/V/InklecateV/M/M/V"]
layout_mode = 2
size_flags_horizontal = 3
columns = 2
[node name="UseMonoLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
tooltip_text = "When toggled, run inklecate through Mono.
Only enable this setting is you want to use an older version of inklecate or you have a custom setup.
Modern versions of inklecate come bundled with a mono runtime on all platforms."
mouse_filter = 1
text = "Use Mono / .NET Core"
[node name="UseMonoCheckBox" type="CheckBox" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
text = "Yes"
[node name="MonoLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
tooltip_text = "The path to Mono."
mouse_filter = 1
text = "Mono / .NET Core Executable"
[node name="MonoH" type="HBoxContainer" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
[node name="MonoLineEdit" type="LineEdit" parent="ScrollContainer/V/InklecateV/M/M/V/G/MonoH"]
layout_mode = 2
size_flags_horizontal = 3
text = "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono"
[node name="MonoDialogButton" type="Button" parent="ScrollContainer/V/InklecateV/M/M/V/G/MonoH"]
layout_mode = 2
icon = SubResource("7")
[node name="ExecutableLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
tooltip_text = "The path to inklecate."
mouse_filter = 1
text = "Executable"
[node name="ExecutableH" type="HBoxContainer" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ExecutableLineEdit" type="LineEdit" parent="ScrollContainer/V/InklecateV/M/M/V/G/ExecutableH"]
layout_mode = 2
size_flags_horizontal = 3
text = "/opt/homebrew/bin/inklecate"
[node name="ExecutableDialogButton" type="Button" parent="ScrollContainer/V/InklecateV/M/M/V/G/ExecutableH"]
layout_mode = 2
icon = SubResource("7")
[node name="Recompilation Mode" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
tooltip_text = "Define if/when your stories should be recompiled.
Manual: no automatic compilation.
During Build: every time the project is built/ran, the stories will be recompiled.
On change: as soon as an Ink resource is reimported by Godot, trigger a recompilation. A folder to watch can be defined in the \"Story\" tab."
mouse_filter = 1
text = "Recompilation Mode"
[node name="RecompilationModeOptionButton" type="OptionButton" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
layout_mode = 2
item_count = 3
selected = 0
popup/item_0/text = "Manual"
popup/item_0/id = 0
popup/item_1/text = "During Build"
popup/item_1/id = 1
popup/item_2/text = "On change (experimental)"
popup/item_2/id = 2
[node name="InkDirectoryLabel" type="Label" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
tooltip_text = "The path to Mono."
mouse_filter = 1
text = "Mono"
[node name="InkDirectoryH" type="HBoxContainer" parent="ScrollContainer/V/InklecateV/M/M/V/G"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="InkDirectoryLineEdit" type="LineEdit" parent="ScrollContainer/V/InklecateV/M/M/V/G/InkDirectoryH"]
layout_mode = 2
size_flags_horizontal = 3
[node name="InkDIrectoryDialogButton" type="Button" parent="ScrollContainer/V/InklecateV/M/M/V/G/InkDirectoryH"]
layout_mode = 2
icon = SubResource("5")
[node name="MonoSupportV" type="VBoxContainer" parent="ScrollContainer/V"]
layout_mode = 2
size_flags_horizontal = 3
[node name="H" type="HBoxContainer" parent="ScrollContainer/V/MonoSupportV"]
layout_mode = 2
[node name="Label" type="Label" parent="ScrollContainer/V/MonoSupportV/H"]
layout_mode = 2
size_flags_horizontal = 3
text = "Mono Support"
[node name="DocumentationButton" type="Button" parent="ScrollContainer/V/MonoSupportV/H"]
layout_mode = 2
mouse_filter = 1
theme_override_constants/h_separation = 8
text = "Documentation"
[node name="M" type="MarginContainer" parent="ScrollContainer/V/MonoSupportV"]
layout_mode = 2
[node name="Panel" type="Panel" parent="ScrollContainer/V/MonoSupportV/M"]
self_modulate = Color(1, 1, 1, 0.686275)
layout_mode = 2
[node name="M" type="MarginContainer" parent="ScrollContainer/V/MonoSupportV/M"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
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="V" type="VBoxContainer" parent="ScrollContainer/V/MonoSupportV/M/M"]
layout_mode = 2
size_flags_horizontal = 3
[node name="G" type="GridContainer" parent="ScrollContainer/V/MonoSupportV/M/M/V"]
layout_mode = 2
size_flags_horizontal = 3
columns = 2
[node name="DLLLabel" type="Label" parent="ScrollContainer/V/MonoSupportV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
tooltip_text = "Whether the ink runtime DLL (ink-engine-runtime.dll) can be found in the project or not."
mouse_filter = 1
text = "ink DLL"
[node name="DLLH" type="HBoxContainer" parent="ScrollContainer/V/MonoSupportV/M/M/V/G"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PresenceLabel" type="Label" parent="ScrollContainer/V/MonoSupportV/M/M/V/G/DLLH"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
mouse_filter = 1
theme_override_colors/font_color = Color(1, 0, 0, 1)
text = "MISSING"
[node name="RefreshButton" type="Button" parent="ScrollContainer/V/MonoSupportV/M/M/V/G/DLLH"]
layout_mode = 2
theme_override_constants/h_separation = 8
text = "Scan"
icon = SubResource("7")

View file

@ -0,0 +1,106 @@
@tool
# warning-ignore-all:return_value_discarded
# ############################################################################ #
# 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 InkBottomPanel
# ############################################################################ #
# Imports
# ############################################################################ #
var InkStoryPanelScene = load("res://addons/inkgd/editor/panel/stories/ink_story_panel.tscn")
var InkPreviewPanelScene = load("res://addons/inkgd/editor/panel/preview/ink_preview_panel.tscn")
var InkConfigurationPanelScene = load("res://addons/inkgd/editor/panel/configuration/ink_configuration_panel.tscn")
# ############################################################################ #
# Properties
# ############################################################################ #
var editor_interface: InkEditorInterface = null
var configuration: InkConfiguration = null
# ############################################################################ #
# Private Properties
# ############################################################################ #
var _progress_texture: AnimatedTexture
# ############################################################################ #
# Hierarchy Nodes
# ############################################################################ #
@onready var _tab_container: TabContainer = $TabContainer
@onready var _beta_button: LinkButton = $MarginContainer/LinkButton
@onready var _story_panel = InkStoryPanelScene.instantiate()
@onready var _preview_panel = InkPreviewPanelScene.instantiate()
@onready var _configuration_panel = InkConfigurationPanelScene.instantiate()
# ############################################################################ #
# 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:
print("[inkgd] [INFO] Ink Bottom Panel: dependencies not met, ignoring.")
return
_progress_texture = _create_progress_texture()
_story_panel.editor_interface = editor_interface
_story_panel.configuration = configuration
_story_panel.progress_texture = _progress_texture
_preview_panel.editor_interface = editor_interface
_preview_panel.configuration = configuration
_preview_panel.progress_texture = _progress_texture
_configuration_panel.editor_interface = editor_interface
_configuration_panel.configuration = configuration
_tab_container.add_child(_story_panel)
_tab_container.add_child(_preview_panel)
_tab_container.add_child(_configuration_panel)
_beta_button.connect("pressed", Callable(self, "_open_github_issues"))
_set_minimum_panel_size()
# ############################################################################ #
# Signals Receivers
# ############################################################################ #
func _open_github_issues():
OS.shell_open("https://github.com/ephread/inkgd/issues/new?assignees=&labels=&template=bug_report.md")
# ############################################################################ #
# Private helpers
# ############################################################################ #
func _create_progress_texture() -> AnimatedTexture:
var animated_texture = AnimatedTexture.new()
animated_texture.frames = 8
for index in range(8):
var texture = get_theme_icon(str("Progress", (index + 1)), "EditorIcons")
animated_texture.set_frame_texture(index, texture)
return animated_texture
func _set_minimum_panel_size():
# Adapting the minimum size of the panel to the scale of the editor.
custom_minimum_size = Vector2(900, 245) * editor_interface.scale

View file

@ -0,0 +1,51 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/inkgd/editor/panel/ink_bottom_panel.gd" type="Script" id=1]
[node name="Control" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TabContainer" type="TabContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
custom_minimum_size = Vector2( 0, 428.75 )
size_flags_vertical = 3
tab_alignment = 0
drag_to_rearrange_enabled = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="."]
anchor_right = 1.0
offset_bottom = 24.0
clip_contents = true
mouse_filter = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_constants/margin_right = 5
theme_override_constants/margin_top = 5
theme_override_constants/margin_left = 5
theme_override_constants/margin_bottom = 5
__meta__ = {
"_edit_use_anchors_": false
}
[node name="LinkButton" type="LinkButton" parent="MarginContainer"]
self_modulate = Color( 1, 1, 1, 0.647059 )
offset_left = 1887.0
offset_top = 5.0
offset_right = 1915.0
offset_bottom = 19.0
tooltip_text = "Open a new issue on Github to report a problem!"
size_flags_horizontal = 8
text = "beta"
underline = 1
__meta__ = {
"_edit_use_anchors_": false
}

View file

@ -0,0 +1,366 @@
@tool
# ############################################################################ #
# Copyright © 2018-2022 Paul Joannon
# 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 InkPreviewPanel
# ############################################################################ #
# Imports
# ############################################################################ #
var InkPlayerFactory := preload("res://addons/inkgd/ink_player_factory.gd") as GDScript
# ############################################################################ #
# Enums
# ############################################################################ #
enum StoryOrigin {
CONFIGURATION,
FILE
}
# ############################################################################ #
# Constants
# ############################################################################ #
const NAME = "name"
const STORY_ORIGIN = "story_origin"
const FILE_PATH = "file_path"
# ############################################################################ #
# Public Properties
# ############################################################################ #
var editor_interface: InkEditorInterface
var configuration: InkConfiguration
var progress_texture: AnimatedTexture
# ############################################################################ #
# Private Properties
# ############################################################################ #
var _scrollbar_max_value = -1
var _current_story_index = -1
var _custom_stories: Array = []
var _available_stories: Array = []
var _file_dialog = EditorFileDialog.new()
var _ink_player = InkPlayerFactory.create()
# ############################################################################ #
# On Ready | Private Properties
# ############################################################################ #
@onready var _play_icon = get_theme_icon("Play", "EditorIcons")
# ############################################################################ #
# On Ready | Private Nodes
# ############################################################################
@onready var _command_strip = find_child("CommandStripHBoxContainer")
@onready var _pick_story_button = _command_strip.get_node("PickStoryOptionButton")
@onready var _load_story_button = _command_strip.get_node("LoadStoryButton")
@onready var _start_button = _command_strip.get_node("StartButton")
@onready var _stop_button = _command_strip.get_node("StopButton")
@onready var _clear_button = _command_strip.get_node("ClearButton")
@onready var _scroll_container = find_child("ScrollContainer")
@onready var _story_container = _scroll_container.get_node("MarginContainer/StoryVBoxContainer")
@onready var _choice_area_container = find_child("ChoicesAreaVBoxContainer")
@onready var _choices_container = _choice_area_container.get_node("ChoicesVBoxContainer")
# ############################################################################ #
# 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 Preview Tab: dependencies not met, ignoring.")
return
add_child(_ink_player)
_connect_signals()
_apply_configuration()
_update_story_picker()
var load_icon = get_theme_icon("Load", "EditorIcons")
var stop_icon = get_theme_icon("Stop", "EditorIcons")
var clear_icon = get_theme_icon("Clear", "EditorIcons")
_start_button.icon = _play_icon
_load_story_button.icon = load_icon
_stop_button.icon = stop_icon
_clear_button.icon = clear_icon
_stop_button.visible = false
_choice_area_container.custom_minimum_size = Vector2(200, 0) * editor_interface.scale
_choice_area_container.visible = false
_file_dialog.connect("file_selected", Callable(self, "_on_file_selected"))
add_child(_file_dialog)
# ############################################################################ #
# Signal Receivers
# ############################################################################ #
func _start_button_pressed():
var file_path = _get_current_story_file_path()
if file_path == null:
return
print("[inkgd] [INFO] Previewing %s" % file_path)
_clear_content()
_ink_player.destroy()
_ink_player.ink_file = load(file_path)
_start_button.icon = progress_texture
_disable_command_strip(true)
_ink_player.create_story()
func _stop_button_pressed():
_start_button.visible = true
_stop_button.visible = false
_choice_area_container.visible = false
_ink_player.destroy()
_clear_choices()
_clear_content()
func _story_loaded(successfully: bool):
_disable_command_strip(false)
_start_button.icon = _play_icon
if !successfully:
return
_start_button.visible = false
_stop_button.visible = true
_ink_player.allow_external_function_fallbacks = true
_continue_story()
func _pick_story_button_selected(index):
if _current_story_index != index:
_stop_button_pressed()
_current_story_index = index
func _load_story_button_pressed():
_file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
_file_dialog.access = FileDialog.ACCESS_RESOURCES
_file_dialog.add_filter("*.json;Compiled Ink story")
_file_dialog.popup_centered(Vector2(1280, 800) * editor_interface.scale)
func _choice_button_pressed(index):
_clear_choices()
_ink_player.choose_choice_index(index)
_continue_story()
func _on_file_selected(path: String):
if _custom_stories.has(path):
return
for story_configuration in self.configuration.stories:
var target_file_path = self.configuration.get_target_file_path(story_configuration)
if target_file_path == path:
return
_custom_stories.append(path)
_apply_configuration()
_update_story_picker(path)
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
func _configuration_changed():
# Cleaning everything on configuration change may end up being frustrating.
# But for now, it's going to make everybody's life easier.
_stop_button_pressed()
_apply_configuration()
_update_story_picker()
# ############################################################################ #
# Private Methods
# ############################################################################ #
func _apply_configuration():
_available_stories.clear()
_current_story_index = -1
_pick_story_button.selected = _current_story_index
var i = 0
for story_configuration in self.configuration.stories:
var target_file_path = self.configuration.get_target_file_path(story_configuration)
if target_file_path != null && !target_file_path.is_empty():
_available_stories.append({
NAME: "Story %d - %s" % [i + 1, target_file_path.get_file()],
FILE_PATH: target_file_path,
STORY_ORIGIN: StoryOrigin.CONFIGURATION
})
i += 1
var j = 0
for custom_story_path in _custom_stories:
if custom_story_path != null && !custom_story_path.is_empty():
_available_stories.append({
NAME: custom_story_path.get_file(),
FILE_PATH: ProjectSettings.localize_path(custom_story_path),
STORY_ORIGIN: StoryOrigin.FILE
})
j += 1
func _update_story_picker(selected_path = null):
_pick_story_button.clear()
var i = 0
for story in _available_stories:
_pick_story_button.add_item(story[NAME], i)
if selected_path != null && story[FILE_PATH] == selected_path:
_current_story_index = i
_pick_story_button.selected = _current_story_index
i += 1
if _available_stories.size() > 0:
if _current_story_index == -1:
_current_story_index = 0
_pick_story_button.selected = _current_story_index
_pick_story_button.visible = true
else:
_current_story_index = -1
_pick_story_button.selected = _current_story_index
_pick_story_button.visible = false
func _continue_story():
while _ink_player.can_continue:
var text = _ink_player.continue_story()
if text.right(text.length() - 1) == "\n":
text.erase(text.length() - 1, 1)
var text_label = Label.new()
text_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
text_label.text = text
_story_container.add_child(text_label)
var tags = _ink_player.current_tags
if !tags.is_empty():
var tag_label = Label.new()
tag_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
tag_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
tag_label.text = "# " + ", ".join(PackedStringArray(tags))
tag_label.add_theme_color_override("font_color", Color(1, 1, 1, 0.4))
_story_container.add_child(tag_label)
var separator = HSeparator.new()
_story_container.add_child(separator)
if _ink_player.current_choices.size() > 0:
var i = 0
for choice in _ink_player.current_choices:
var button = Button.new()
button.text = choice.text
button.connect("pressed", Callable(self, "_choice_button_pressed").bind(i))
_choices_container.add_child(button)
i += 1
_choice_area_container.visible = true
else:
var label = Label.new()
label.text = "End of the story."
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
_story_container.add_child(label)
_choice_area_container.visible = false
func _get_current_story_file_path():
if _current_story_index >= 0 && _current_story_index < _available_stories.size():
return _available_stories[_current_story_index][FILE_PATH]
else:
return null
func _clear_content():
for child in _story_container.get_children():
_story_container.remove_child(child)
child.queue_free()
func _clear_choices():
for child in _choices_container.get_children():
_choices_container.remove_child(child)
child.queue_free()
func _disable_command_strip(disabled: bool):
_pick_story_button.disabled = disabled
_load_story_button.disabled = disabled
_start_button.disabled = disabled
_stop_button.disabled = disabled
_clear_button.disabled = disabled
func _connect_signals():
if configuration != null:
var is_signal_connected := configuration.is_connected(
"story_configuration_changed",
Callable(self, "_configuration_changed")
)
if !is_signal_connected:
configuration.connect(
"story_configuration_changed",
Callable(self, "_configuration_changed")
)
_ink_player.connect("loaded", Callable(self, "_story_loaded"))
_pick_story_button.connect("item_selected", Callable(self, "_pick_story_button_selected"))
_load_story_button.connect("pressed", Callable(self, "_load_story_button_pressed"))
_start_button.connect("pressed", Callable(self, "_start_button_pressed"))
_stop_button.connect("pressed", Callable(self, "_stop_button_pressed"))
_clear_button.connect("pressed", Callable(self, "_clear_content"))
_scroll_container.get_v_scroll_bar().connect("changed", Callable(self, "_scrollbar_changed"))

View file

@ -0,0 +1,119 @@
[gd_scene load_steps=4 format=3 uid="uid://0do8xgmkscjn"]
[ext_resource type="Script" path="res://addons/inkgd/editor/panel/preview/ink_preview_panel.gd" id="2"]
[sub_resource type="Image" id="Image_nl482"]
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_nl482")
[node name="Preview" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("2")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="StoryVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 5
[node name="CommandStripHBoxContainer" type="HBoxContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 10
[node name="LoadStoryButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
layout_mode = 2
theme_override_constants/h_separation = 8
text = "Load new…"
icon = SubResource("2")
[node name="PickStoryOptionButton" type="OptionButton" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
layout_mode = 2
size_flags_horizontal = 0
[node name="Control" type="Control" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="StartButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
layout_mode = 2
theme_override_constants/h_separation = 8
text = "Start"
icon = SubResource("2")
[node name="StopButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
visible = false
layout_mode = 2
theme_override_constants/h_separation = 10
text = "Stop"
icon = SubResource("2")
[node name="ClearButton" type="Button" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/CommandStripHBoxContainer"]
layout_mode = 2
theme_override_constants/h_separation = 8
text = "Clear"
icon = SubResource("2")
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Panel" type="Panel" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="StoryVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/StoryVBoxContainer/MarginContainer/ScrollContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ChoicesAreaVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
theme_override_constants/separation = 5
[node name="Button" type="Button" parent="MarginContainer/HSplitContainer/ChoicesAreaVBoxContainer"]
layout_mode = 2
mouse_filter = 2
text = "Choices"
flat = true
[node name="ChoicesVBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ChoicesAreaVBoxContainer"]
layout_mode = 2

View 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."

View 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"))

View 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 )

View 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"))

View 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