1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-08 19:39:50 +00:00
Pixelorama/src/UI/Timeline/CelButton.gd
Emmanouil Papadeas 99b014ef7f Fix segmentation fault when resizing canvas to a large size
For some reason, cel_texture.texture.get_data() seemed to cause a segmentation fault, and then a crash, when the image size was large. Also added some extra static typing to the drag and drop methods, but they are unrelated to the crash.
2023-03-21 02:06:57 +02:00

292 lines
11 KiB
GDScript

extends Button
enum MenuOptions { DELETE, LINK, UNLINK, PROPERTIES }
var frame := 0
var layer := 0
var cel: BaseCel
onready var popup_menu: PopupMenu = get_node_or_null("PopupMenu")
onready var linked_indicator: Polygon2D = get_node_or_null("LinkedIndicator")
onready var cel_texture: TextureRect = $CelTexture
onready var transparent_checker: ColorRect = $CelTexture/TransparentChecker
func _ready() -> void:
cel = Global.current_project.frames[frame].cels[layer]
button_setup()
_dim_checker()
cel.connect("texture_changed", self, "_dim_checker")
func button_setup() -> void:
rect_min_size.x = Global.animation_timeline.cel_size
rect_min_size.y = Global.animation_timeline.cel_size
var base_layer: BaseLayer = Global.current_project.layers[layer]
hint_tooltip = tr("Frame: %s, Layer: %s") % [frame + 1, base_layer.name]
cel_texture.texture = cel.image_texture
if is_instance_valid(linked_indicator):
linked_indicator.visible = cel.link_set != null
if cel.link_set != null:
linked_indicator.color.h = cel.link_set["hue"]
# Reset the checkers size because it assumes you want the same size as the canvas
transparent_checker.rect_size = transparent_checker.get_parent().rect_size
func _on_CelButton_resized() -> void:
cel_texture.rect_min_size.x = rect_min_size.x - 4
cel_texture.rect_min_size.y = rect_min_size.y - 4
if is_instance_valid(linked_indicator):
linked_indicator.polygon[1].x = rect_min_size.x
linked_indicator.polygon[2].x = rect_min_size.x
linked_indicator.polygon[2].y = rect_min_size.y
linked_indicator.polygon[3].y = rect_min_size.y
func _on_CelButton_pressed() -> void:
var project = Global.current_project
if Input.is_action_just_released("left_mouse"):
Global.canvas.selection.transform_content_confirm()
var change_cel := true
var prev_curr_frame: int = project.current_frame
var prev_curr_layer: int = project.current_layer
if Input.is_action_pressed("shift"):
var frame_diff_sign = sign(frame - prev_curr_frame)
if frame_diff_sign == 0:
frame_diff_sign = 1
var layer_diff_sign = sign(layer - prev_curr_layer)
if layer_diff_sign == 0:
layer_diff_sign = 1
for i in range(prev_curr_frame, frame + frame_diff_sign, frame_diff_sign):
for j in range(prev_curr_layer, layer + layer_diff_sign, layer_diff_sign):
var frame_layer := [i, j]
if !project.selected_cels.has(frame_layer):
project.selected_cels.append(frame_layer)
elif Input.is_action_pressed("ctrl"):
var frame_layer := [frame, layer]
if project.selected_cels.has(frame_layer):
if project.selected_cels.size() > 1:
project.selected_cels.erase(frame_layer)
change_cel = false
else:
project.selected_cels.append(frame_layer)
else: # If the button is pressed without Shift or Control
project.selected_cels.clear()
var frame_layer := [frame, layer]
if !project.selected_cels.has(frame_layer):
project.selected_cels.append(frame_layer)
if change_cel:
project.change_cel(frame, layer)
else:
project.change_cel(project.selected_cels[0][0], project.selected_cels[0][1])
release_focus()
elif Input.is_action_just_released("right_mouse"):
if is_instance_valid(popup_menu):
popup_menu.popup(Rect2(get_global_mouse_position(), Vector2.ONE))
pressed = !pressed
elif Input.is_action_just_released("middle_mouse"):
pressed = !pressed
_delete_cel_content()
else: # An example of this would be Space
pressed = !pressed
func _on_PopupMenu_id_pressed(id: int) -> void:
match id:
MenuOptions.DELETE:
_delete_cel_content()
MenuOptions.LINK, MenuOptions.UNLINK:
var project: Project = Global.current_project
if id == MenuOptions.UNLINK:
project.undo_redo.create_action("Unlink Cel")
var selected_cels = project.selected_cels.duplicate()
if not selected_cels.has([frame, layer]):
selected_cels.append([frame, layer]) # Include this cel with the selected ones
for cel_index in selected_cels:
if layer != cel_index[1]: # Skip selected cels not on the same layer
continue
var s_cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
if s_cel.link_set == null: # Skip cels that aren't linked
continue
project.undo_redo.add_do_method(project.layers[layer], "link_cel", s_cel, null)
project.undo_redo.add_undo_method(
project.layers[layer], "link_cel", s_cel, s_cel.link_set
)
if s_cel.link_set.size() > 1: # Skip copying content if not linked to another
project.undo_redo.add_do_method(
s_cel, "set_content", s_cel.copy_content(), ImageTexture.new()
)
project.undo_redo.add_undo_method(
s_cel, "set_content", s_cel.get_content(), s_cel.image_texture
)
elif id == MenuOptions.LINK:
project.undo_redo.create_action("Link Cel")
var link_set: Dictionary = {} if cel.link_set == null else cel.link_set
if cel.link_set == null:
project.undo_redo.add_do_method(
project.layers[layer], "link_cel", cel, link_set
)
project.undo_redo.add_undo_method(project.layers[layer], "link_cel", cel, null)
for cel_index in project.selected_cels:
if layer != cel_index[1]: # Skip selected cels not on the same layer
continue
var s_cel: BaseCel = project.frames[cel_index[0]].cels[cel_index[1]]
if cel == s_cel: # Don't need to link cel to itself
continue
if s_cel.link_set == link_set: # Skip cels that were already linked
continue
project.undo_redo.add_do_method(
project.layers[layer], "link_cel", s_cel, link_set
)
project.undo_redo.add_undo_method(
project.layers[layer], "link_cel", s_cel, s_cel.link_set
)
project.undo_redo.add_do_method(
s_cel, "set_content", cel.get_content(), cel.image_texture
)
project.undo_redo.add_undo_method(
s_cel, "set_content", s_cel.get_content(), s_cel.image_texture
)
# Remove and add a new cel button to update appearance (can't use button_setup
# because there is no guarantee that it will be the exact same cel button instance)
# May be able to use button_setup with a lambda to find correct cel button in Godot 4
for f in project.frames.size():
project.undo_redo.add_do_method(
Global.animation_timeline, "project_cel_removed", f, layer
)
project.undo_redo.add_undo_method(
Global.animation_timeline, "project_cel_removed", f, layer
)
project.undo_redo.add_do_method(
Global.animation_timeline, "project_cel_added", f, layer
)
project.undo_redo.add_undo_method(
Global.animation_timeline, "project_cel_added", f, layer
)
project.undo_redo.add_do_method(Global, "undo_or_redo", false)
project.undo_redo.add_undo_method(Global, "undo_or_redo", true)
project.undo_redo.commit_action()
func _delete_cel_content() -> void:
var project: Project = Global.current_project
var empty_content = cel.create_empty_content()
var old_content = cel.get_content()
project.undos += 1
project.undo_redo.create_action("Draw")
if cel.link_set == null:
project.undo_redo.add_do_method(cel, "set_content", empty_content)
project.undo_redo.add_undo_method(cel, "set_content", old_content)
else:
for linked_cel in cel.link_set["cels"]:
project.undo_redo.add_do_method(linked_cel, "set_content", empty_content)
project.undo_redo.add_undo_method(linked_cel, "set_content", old_content)
project.undo_redo.add_do_method(Global, "undo_or_redo", false, frame, layer, project)
project.undo_redo.add_undo_method(Global, "undo_or_redo", true, frame, layer, project)
project.undo_redo.commit_action()
func _dim_checker() -> void:
var image := cel.get_image()
if image == null:
return
if image.is_empty() or image.is_invisible():
transparent_checker.self_modulate.a = 0.5
else:
transparent_checker.self_modulate.a = 1.0
func get_drag_data(_position: Vector2) -> Array:
var button := Button.new()
button.rect_size = rect_size
button.theme = Global.control.theme
var texture_rect := TextureRect.new()
texture_rect.rect_size = cel_texture.rect_size
texture_rect.rect_position = cel_texture.rect_position
texture_rect.expand = true
texture_rect.texture = cel_texture.texture
button.add_child(texture_rect)
set_drag_preview(button)
return ["Cel", frame, layer]
func can_drop_data(_pos: Vector2, data) -> bool:
var project: Project = Global.current_project
if typeof(data) == TYPE_ARRAY and data[0] == "Cel":
var drag_frame = data[1]
var drag_layer = data[2]
if project.layers[drag_layer].get_script() == project.layers[layer].get_script():
if ( # If both cels are on the same layer, or both are not linked
drag_layer == layer
or (
project.frames[frame].cels[layer].link_set == null
and project.frames[drag_frame].cels[drag_layer].link_set == null
)
):
if not (drag_frame == frame and drag_layer == layer):
var region: Rect2
if Input.is_action_pressed("ctrl") or layer != drag_layer: # Swap cels
region = get_global_rect()
else: # Move cels
if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): # Left
region = _get_region_rect(-0.125, 0.125)
region.position.x -= 2 # Container spacing
else: # Right
region = _get_region_rect(0.875, 1.125)
region.position.x += 2 # Container spacing
Global.animation_timeline.drag_highlight.rect_global_position = region.position
Global.animation_timeline.drag_highlight.rect_size = region.size
Global.animation_timeline.drag_highlight.visible = true
return true
Global.animation_timeline.drag_highlight.visible = false
return false
func drop_data(_pos: Vector2, data) -> void:
var drop_frame = data[1]
var drop_layer = data[2]
var project = Global.current_project
project.undo_redo.create_action("Move Cels")
if Input.is_action_pressed("ctrl") or layer != drop_layer: # Swap cels
project.undo_redo.add_do_method(project, "swap_cel", frame, layer, drop_frame, drop_layer)
project.undo_redo.add_undo_method(project, "swap_cel", frame, layer, drop_frame, drop_layer)
else: # Move cels
var to_frame: int
if _get_region_rect(0, 0.5).has_point(get_global_mouse_position()): # Left
to_frame = frame
else: # Right
to_frame = frame + 1
if drop_frame < frame:
to_frame -= 1
project.undo_redo.add_do_method(project, "move_cel", drop_frame, to_frame, layer)
project.undo_redo.add_undo_method(project, "move_cel", to_frame, drop_frame, layer)
project.undo_redo.add_do_method(project, "change_cel", frame, layer)
project.undo_redo.add_undo_method(
project, "change_cel", project.current_frame, project.current_layer
)
project.undo_redo.add_undo_method(Global, "undo_or_redo", true)
project.undo_redo.add_do_method(Global, "undo_or_redo", false)
project.undo_redo.commit_action()
func _get_region_rect(x_begin: float, x_end: float) -> Rect2:
var rect := get_global_rect()
rect.position.x += rect.size.x * x_begin
rect.size.x *= x_end - x_begin
return rect