Figments-of-the-Night/addons/inkgd/editor/panel/preview/ink_preview_panel.gd
Gerard Gascón b99855351d init
2025-04-24 17:23:34 +02:00

366 lines
11 KiB
GDScript

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