mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-01-18 17:19:50 +00:00
The Recorder panel now automatically records for the current project
Making its behavior more intuitive and consistent with the other panels. This also allows for multiple projects to be recorder at the same time, something that was not previous before. Changing projects now also changes the UI accordingly, depending on whether the current project is being recorded or not. This change also fixes a memory leak, where either the first ever project or the last recorded one, stayed forever referenced in memory by the `project` variable. Also fixed an issue where the recorder's settings size label was not showing the correct project size.
This commit is contained in:
parent
8beb79a33b
commit
ec17e970e0
|
@ -3,6 +3,7 @@ class_name Project
|
|||
extends RefCounted
|
||||
## A class for project properties.
|
||||
|
||||
signal removed
|
||||
signal serialized(dict: Dictionary)
|
||||
signal about_to_deserialize(dict: Dictionary)
|
||||
signal resized
|
||||
|
@ -137,6 +138,7 @@ func remove() -> void:
|
|||
# Prevents memory leak (due to the layers' project reference stopping ref counting from freeing)
|
||||
layers.clear()
|
||||
Global.projects.erase(self)
|
||||
removed.emit()
|
||||
|
||||
|
||||
func remove_backup_file() -> void:
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
class_name RecorderPanel
|
||||
extends PanelContainer
|
||||
|
||||
signal frame_saved
|
||||
|
||||
enum Mode { CANVAS, PIXELORAMA }
|
||||
|
||||
var mode := Mode.CANVAS
|
||||
var chosen_dir := ""
|
||||
var chosen_dir := "":
|
||||
set(value):
|
||||
chosen_dir = value
|
||||
if chosen_dir.ends_with("/"): # Remove end back-slashes if present
|
||||
chosen_dir[-1] = ""
|
||||
var recorded_projects := {} ## [Dictionary] of [Project] and [Recorder].
|
||||
var save_dir := ""
|
||||
var project: Project
|
||||
var cache: Array[Image] = [] ## Images stored during recording
|
||||
var frame_captured := 0 ## Used to visualize frames captured
|
||||
var skip_amount := 1 ## Number of "do" actions after which a frame can be captured
|
||||
var current_frame_no := 0 ## Used to compare with skip_amount to see if it can be captured
|
||||
var skip_amount := 1 ## Number of "do" actions after which a frame can be captured.
|
||||
var resize_percent := 100
|
||||
var _path_dialog: FileDialog:
|
||||
get:
|
||||
|
@ -28,7 +28,6 @@ var _path_dialog: FileDialog:
|
|||
return _path_dialog
|
||||
|
||||
@onready var captured_label := %CapturedLabel as Label
|
||||
@onready var project_list := $"%TargetProjectOption" as OptionButton
|
||||
@onready var start_button := $"%Start" as Button
|
||||
@onready var size_label := $"%Size" as Label
|
||||
@onready var path_field := $"%Path" as LineEdit
|
||||
|
@ -36,94 +35,86 @@ var _path_dialog: FileDialog:
|
|||
@onready var options_container := %OptionsContainer as VBoxContainer
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
refresh_projects_list()
|
||||
project = Global.current_project
|
||||
frame_saved.connect(_on_frame_saved)
|
||||
# Make a recordings folder if there isn't one
|
||||
chosen_dir = Global.home_data_directory.path_join("Recordings")
|
||||
DirAccess.make_dir_recursive_absolute(chosen_dir)
|
||||
path_field.text = chosen_dir
|
||||
size_label.text = str("(", project.size.x, "×", project.size.y, ")")
|
||||
|
||||
|
||||
func initialize_recording() -> void:
|
||||
connect_undo() # connect to detect changes in project
|
||||
cache.clear() # clear the cache array to store new images
|
||||
frame_captured = 0
|
||||
current_frame_no = skip_amount - 1
|
||||
|
||||
# disable some options that are not required during recording
|
||||
project_list.visible = false
|
||||
captured_label.visible = true
|
||||
for child in options_container.get_children():
|
||||
if !child.is_in_group("visible during recording"):
|
||||
child.visible = false
|
||||
|
||||
save_dir = chosen_dir
|
||||
# Remove end back-slashes if present
|
||||
if save_dir.ends_with("/"):
|
||||
save_dir[-1] = ""
|
||||
class Recorder:
|
||||
var project: Project
|
||||
var recorder_panel: RecorderPanel
|
||||
var actions_done := -1
|
||||
var frames_captured := 0
|
||||
var save_directory := ""
|
||||
|
||||
func _init(_project: Project, _recorder_panel: RecorderPanel) -> void:
|
||||
project = _project
|
||||
recorder_panel = _recorder_panel
|
||||
# Create a new directory based on time
|
||||
var time_dict := Time.get_time_dict_from_system()
|
||||
var folder := str(project.name, time_dict.hour, "_", time_dict.minute, "_", time_dict.second)
|
||||
var dir := DirAccess.open(save_dir)
|
||||
save_dir = save_dir.path_join(folder)
|
||||
dir.make_dir_recursive(save_dir)
|
||||
|
||||
capture_frame() # capture first frame
|
||||
$Timer.start()
|
||||
var folder := str(
|
||||
project.name, time_dict.hour, "_", time_dict.minute, "_", time_dict.second
|
||||
)
|
||||
var dir := DirAccess.open(recorder_panel.chosen_dir)
|
||||
save_directory = recorder_panel.chosen_dir.path_join(folder)
|
||||
dir.make_dir_recursive(save_directory)
|
||||
project.removed.connect(recorder_panel.finalize_recording.bind(project))
|
||||
project.undo_redo.version_changed.connect(capture_frame)
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
# Needed so that the project won't be forever remained in memory because of bind().
|
||||
project.removed.disconnect(recorder_panel.finalize_recording)
|
||||
|
||||
func capture_frame() -> void:
|
||||
current_frame_no += 1
|
||||
if current_frame_no != skip_amount:
|
||||
actions_done += 1
|
||||
if actions_done % recorder_panel.skip_amount != 0:
|
||||
return
|
||||
current_frame_no = 0
|
||||
var image: Image
|
||||
if mode == Mode.PIXELORAMA:
|
||||
image = get_tree().root.get_viewport().get_texture().get_image()
|
||||
if recorder_panel.mode == RecorderPanel.Mode.PIXELORAMA:
|
||||
image = recorder_panel.get_window().get_texture().get_image()
|
||||
else:
|
||||
var frame := project.frames[project.current_frame]
|
||||
image = Image.create(project.size.x, project.size.y, false, Image.FORMAT_RGBA8)
|
||||
DrawingAlgos.blend_layers(image, frame, Vector2i.ZERO, project)
|
||||
|
||||
if mode == Mode.CANVAS:
|
||||
if resize_percent != 100:
|
||||
var resize := resize_percent / 100
|
||||
image.resize(
|
||||
image.get_width() * resize, image.get_height() * resize, Image.INTERPOLATE_NEAREST
|
||||
)
|
||||
|
||||
cache.append(image)
|
||||
if recorder_panel.resize_percent != 100:
|
||||
var resize := recorder_panel.resize_percent / 100
|
||||
var new_width := image.get_width() * resize
|
||||
var new_height := image.get_height() * resize
|
||||
image.resize(new_width, new_height, Image.INTERPOLATE_NEAREST)
|
||||
var save_file := str(project.name, "_", frames_captured, ".png")
|
||||
image.save_png(save_directory.path_join(save_file))
|
||||
frames_captured += 1
|
||||
recorder_panel.captured_label.text = str("Saved: ", frames_captured)
|
||||
|
||||
|
||||
func _on_Timer_timeout() -> void:
|
||||
# Saves frames little by little during recording
|
||||
if cache.size() > 0:
|
||||
save_frame(cache[0])
|
||||
cache.remove_at(0)
|
||||
func _ready() -> void:
|
||||
Global.project_switched.connect(_on_project_switched)
|
||||
# Make a recordings folder if there isn't one
|
||||
chosen_dir = Global.home_data_directory.path_join("Recordings")
|
||||
DirAccess.make_dir_recursive_absolute(chosen_dir)
|
||||
path_field.text = chosen_dir
|
||||
|
||||
|
||||
func save_frame(img: Image) -> void:
|
||||
var save_file := str(project.name, "_", frame_captured, ".png")
|
||||
img.save_png(save_dir.path_join(save_file))
|
||||
frame_saved.emit()
|
||||
func _on_project_switched() -> void:
|
||||
if recorded_projects.has(Global.current_project):
|
||||
initialize_recording()
|
||||
start_button.set_pressed_no_signal(true)
|
||||
Global.change_button_texturerect(start_button.get_child(0), "stop.png")
|
||||
else:
|
||||
finalize_recording()
|
||||
start_button.set_pressed_no_signal(false)
|
||||
Global.change_button_texturerect(start_button.get_child(0), "start.png")
|
||||
|
||||
|
||||
func _on_frame_saved() -> void:
|
||||
frame_captured += 1
|
||||
captured_label.text = str("Saved: ", frame_captured)
|
||||
func initialize_recording() -> void:
|
||||
# disable some options that are not required during recording
|
||||
captured_label.visible = true
|
||||
for child in options_container.get_children():
|
||||
if !child.is_in_group("visible during recording"):
|
||||
child.visible = false
|
||||
|
||||
|
||||
func finalize_recording() -> void:
|
||||
$Timer.stop()
|
||||
for img in cache:
|
||||
save_frame(img)
|
||||
cache.clear()
|
||||
disconnect_undo()
|
||||
project_list.visible = true
|
||||
func finalize_recording(project := Global.current_project) -> void:
|
||||
if recorded_projects.has(project):
|
||||
recorded_projects.erase(project)
|
||||
if project == Global.current_project:
|
||||
captured_label.visible = false
|
||||
for child in options_container.get_children():
|
||||
child.visible = true
|
||||
|
@ -131,30 +122,9 @@ func finalize_recording() -> void:
|
|||
size_label.get_parent().visible = false
|
||||
|
||||
|
||||
func disconnect_undo() -> void:
|
||||
project.undo_redo.version_changed.disconnect(capture_frame)
|
||||
|
||||
|
||||
func connect_undo() -> void:
|
||||
project.undo_redo.version_changed.connect(capture_frame)
|
||||
|
||||
|
||||
func _on_TargetProjectOption_item_selected(index: int) -> void:
|
||||
project = Global.projects[index]
|
||||
|
||||
|
||||
func _on_TargetProjectOption_pressed() -> void:
|
||||
refresh_projects_list()
|
||||
|
||||
|
||||
func refresh_projects_list() -> void:
|
||||
project_list.clear()
|
||||
for proj in Global.projects:
|
||||
project_list.add_item(proj.name)
|
||||
|
||||
|
||||
func _on_Start_toggled(button_pressed: bool) -> void:
|
||||
if button_pressed:
|
||||
recorded_projects[Global.current_project] = Recorder.new(Global.current_project, self)
|
||||
initialize_recording()
|
||||
Global.change_button_texturerect(start_button.get_child(0), "stop.png")
|
||||
else:
|
||||
|
@ -163,7 +133,8 @@ func _on_Start_toggled(button_pressed: bool) -> void:
|
|||
|
||||
|
||||
func _on_Settings_pressed() -> void:
|
||||
options_dialog.popup_on_parent(Rect2(position, options_dialog.size))
|
||||
_on_SpinBox_value_changed(resize_percent)
|
||||
options_dialog.popup_on_parent(Rect2i(position, options_dialog.size))
|
||||
|
||||
|
||||
func _on_SkipAmount_value_changed(value: float) -> void:
|
||||
|
@ -181,7 +152,7 @@ func _on_Mode_toggled(button_pressed: bool) -> void:
|
|||
|
||||
func _on_SpinBox_value_changed(value: float) -> void:
|
||||
resize_percent = value
|
||||
var new_size: Vector2 = project.size * (resize_percent / 100.0)
|
||||
var new_size: Vector2 = Global.current_project.size * (resize_percent / 100.0)
|
||||
size_label.text = str("(", new_size.x, "×", new_size.y, ")")
|
||||
|
||||
|
||||
|
|
|
@ -31,13 +31,6 @@ unique_name_in_owner = true
|
|||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TargetProjectOption" type="OptionButton" parent="ScrollContainer/CenterContainer/GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
tooltip_text = "Choose project"
|
||||
clip_text = true
|
||||
|
||||
[node name="Start" type="Button" parent="ScrollContainer/CenterContainer/GridContainer" groups=["UIButtons"]]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
|
@ -143,6 +136,8 @@ text = "Capture frame every"
|
|||
[node name="SkipAmount" type="SpinBox" parent="OptionsDialog/PanelContainer/OptionsContainer/ActionGap"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
min_value = 1.0
|
||||
value = 1.0
|
||||
suffix = "actions"
|
||||
|
||||
[node name="ModeHeader" type="HBoxContainer" parent="OptionsDialog/PanelContainer/OptionsContainer" groups=["visible during recording"]]
|
||||
|
@ -223,10 +218,6 @@ editable = false
|
|||
layout_mode = 2
|
||||
text = "Choose"
|
||||
|
||||
[node name="Timer" type="Timer" parent="."]
|
||||
|
||||
[connection signal="item_selected" from="ScrollContainer/CenterContainer/GridContainer/TargetProjectOption" to="." method="_on_TargetProjectOption_item_selected"]
|
||||
[connection signal="pressed" from="ScrollContainer/CenterContainer/GridContainer/TargetProjectOption" to="." method="_on_TargetProjectOption_pressed"]
|
||||
[connection signal="toggled" from="ScrollContainer/CenterContainer/GridContainer/Start" to="." method="_on_Start_toggled"]
|
||||
[connection signal="pressed" from="ScrollContainer/CenterContainer/GridContainer/Settings" to="." method="_on_Settings_pressed"]
|
||||
[connection signal="pressed" from="ScrollContainer/CenterContainer/GridContainer/OpenFolder" to="." method="_on_open_folder_pressed"]
|
||||
|
@ -234,4 +225,3 @@ text = "Choose"
|
|||
[connection signal="toggled" from="OptionsDialog/PanelContainer/OptionsContainer/ModeType/Mode" to="." method="_on_Mode_toggled"]
|
||||
[connection signal="value_changed" from="OptionsDialog/PanelContainer/OptionsContainer/OutputScale/Resize" to="." method="_on_SpinBox_value_changed"]
|
||||
[connection signal="pressed" from="OptionsDialog/PanelContainer/OptionsContainer/PathContainer/Choose" to="." method="_on_Choose_pressed"]
|
||||
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]
|
||||
|
|
Loading…
Reference in a new issue