1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-03-15 15:55:18 +00:00
Pixelorama/src/UI/Canvas/Canvas.gd

238 lines
8.9 KiB
GDScript

class_name Canvas
extends Node2D
const CURSOR_SPEED_RATE := 6.0
var current_pixel := Vector2.ZERO
var sprite_changed_this_frame := false ## For optimization purposes
var update_all_layers := false
var project_changed := false
var move_preview_location := Vector2i.ZERO
var layer_texture_array := Texture2DArray.new()
var layer_metadata_image := Image.new()
var layer_metadata_texture := ImageTexture.new()
@onready var currently_visible_frame := $CurrentlyVisibleFrame as SubViewport
@onready var current_frame_drawer := $CurrentlyVisibleFrame/CurrentFrameDrawer as Node2D
@onready var tile_mode := $TileMode as Node2D
@onready var pixel_grid := $PixelGrid as Node2D
@onready var grid := $Grid as Node2D
@onready var selection := $Selection as Node2D
@onready var onion_past := $OnionPast as Node2D
@onready var onion_future := $OnionFuture as Node2D
@onready var crop_rect := $CropRect as CropRect
@onready var indicators := $Indicators as Node2D
@onready var previews := $Previews as Node2D
@onready var previews_sprite := $PreviewsSprite as Sprite2D
@onready var mouse_guide_container := $MouseGuideContainer as Node2D
@onready var gizmos_3d := $Gizmos3D as Node2D
@onready var measurements := $Measurements as Node2D
@onready var reference_image_container := $ReferenceImages as Node2D
func _ready() -> void:
material.set_shader_parameter("layers", layer_texture_array)
material.set_shader_parameter("metadata", layer_metadata_texture)
Global.project_switched.connect(
func():
project_changed = true
queue_redraw()
)
onion_past.type = onion_past.PAST
onion_past.blue_red_color = Global.onion_skinning_past_color
onion_future.type = onion_future.FUTURE
onion_future.blue_red_color = Global.onion_skinning_future_color
await get_tree().process_frame
camera_zoom()
func _draw() -> void:
var position_tmp := position
var scale_tmp := scale
if Global.mirror_view:
position_tmp.x = position_tmp.x + Global.current_project.size.x
scale_tmp.x = -1
# If we just use the first cel and it happens to be a GroupCel
# nothing will get drawn
var cel_to_draw := Global.current_project.find_first_drawable_cel()
draw_set_transform(position_tmp, rotation, scale_tmp)
# Placeholder so we can have a material here
if is_instance_valid(cel_to_draw):
draw_texture(cel_to_draw.image_texture, Vector2.ZERO)
draw_layers(project_changed)
project_changed = false
if Global.onion_skinning:
refresh_onion()
currently_visible_frame.size = Global.current_project.size
current_frame_drawer.queue_redraw()
tile_mode.queue_redraw()
draw_set_transform(position, rotation, scale)
func _input(event: InputEvent) -> void:
# Move the cursor with the keyboard (numpad keys by default)
var mouse_movement := Input.get_vector(
&"move_mouse_left", &"move_mouse_right", &"move_mouse_up", &"move_mouse_down"
)
# Don't process anything below if the input isn't a mouse event, a tool activation shortcut,
# or the numpad keys that move the cursor.
# This decreases CPU/GPU usage slightly.
if not event is InputEventMouseMotion:
if (
mouse_movement == Vector2.ZERO
and not (
event.is_action(&"activate_left_tool") or event.is_action(&"activate_right_tool")
)
):
return
# Get the viewport's mouse position instead of the local mouse position to use warp_mouse
var tmp_position := get_viewport().get_mouse_position()
if mouse_movement != Vector2.ZERO:
tmp_position += mouse_movement * CURSOR_SPEED_RATE
get_viewport().warp_mouse(tmp_position)
var tmp_transform := get_canvas_transform().affine_inverse()
current_pixel = tmp_transform.basis_xform(tmp_position) + tmp_transform.origin
sprite_changed_this_frame = false
Tools.handle_draw(Vector2i(current_pixel.floor()), event)
if sprite_changed_this_frame:
queue_redraw()
update_selected_cels_textures()
func camera_zoom() -> void:
for camera: CanvasCamera in get_tree().get_nodes_in_group("CanvasCameras"):
camera.fit_to_frame(Global.current_project.size)
Global.transparent_checker.update_rect()
func update_texture(layer_i: int, frame_i := -1, project := Global.current_project) -> void:
if frame_i == -1:
frame_i = project.current_frame
if frame_i < project.frames.size() and layer_i < project.layers.size():
var current_cel := project.frames[frame_i].cels[layer_i]
current_cel.update_texture()
# Needed so that changes happening to the non-selected layer(s) are also visible
# e.g. when undoing/redoing, when applying image effects to the entire frame, etc
if frame_i != project.current_frame:
# Don't update if the cel is on a different frame (can happen with undo/redo)
return
var layer := project.layers[layer_i].get_blender_ancestor()
var cel_image: Image
if layer is GroupLayer:
cel_image = layer.blend_children(
project.frames[project.current_frame], Vector2i.ZERO, Global.display_layer_effects
)
else:
if Global.display_layer_effects:
cel_image = layer.display_effects(current_cel)
else:
cel_image = current_cel.get_image()
if (
cel_image.get_size()
== Vector2i(layer_texture_array.get_width(), layer_texture_array.get_height())
):
layer_texture_array.update_layer(cel_image, project.ordered_layers[layer.index])
func update_selected_cels_textures(project := Global.current_project) -> void:
for cel_index in project.selected_cels:
var frame_index: int = cel_index[0]
var layer_index: int = cel_index[1]
if frame_index < project.frames.size() and layer_index < project.layers.size():
var current_cel := project.frames[frame_index].cels[layer_index]
current_cel.update_texture()
func draw_layers(force_recreate := false) -> void:
var project := Global.current_project
var recreate_texture_array := (
layer_texture_array.get_layers() != project.layers.size()
or layer_texture_array.get_width() != project.size.x
or layer_texture_array.get_height() != project.size.y
or force_recreate
)
if recreate_texture_array:
var textures: Array[Image] = []
textures.resize(project.layers.size())
# Nx4 texture, where N is the number of layers and the first row are the blend modes,
# the second are the opacities, the third are the origins and the fourth are the
# clipping mask booleans.
layer_metadata_image = Image.create(project.layers.size(), 4, false, Image.FORMAT_RGF)
# Draw current frame layers
for i in project.layers.size():
var layer := project.layers[i]
var ordered_index := project.ordered_layers[layer.index]
var cel_image := Image.new()
_update_texture_array_layer(project, layer, cel_image, false)
textures[ordered_index] = cel_image
# Store the origin
if [project.current_frame, i] in project.selected_cels:
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(
ordered_index, 2, Color(origin.x, origin.y, 0.0, 0.0)
)
else:
layer_metadata_image.set_pixel(ordered_index, 2, Color())
layer_texture_array.create_from_images(textures)
layer_metadata_texture.set_image(layer_metadata_image)
else: # Update the TextureArray
if layer_texture_array.get_layers() > 0:
for i in project.layers.size():
if not update_all_layers:
var test_array := [project.current_frame, i]
if not test_array in project.selected_cels:
continue
var layer := project.layers[i]
var ordered_index := project.ordered_layers[layer.index]
var cel_image := Image.new()
_update_texture_array_layer(project, layer, cel_image, true)
var parent_layer := layer.get_blender_ancestor()
if layer != parent_layer:
# True when the layer has parents. In that case, update its top-most parent.
_update_texture_array_layer(project, parent_layer, Image.new(), true)
# Update the origin
var origin := Vector2(move_preview_location).abs() / Vector2(cel_image.get_size())
layer_metadata_image.set_pixel(
ordered_index, 2, Color(origin.x, origin.y, 0.0, 0.0)
)
layer_metadata_texture.update(layer_metadata_image)
material.set_shader_parameter("origin_x_positive", move_preview_location.x > 0)
material.set_shader_parameter("origin_y_positive", move_preview_location.y > 0)
update_all_layers = false
func _update_texture_array_layer(
project: Project, layer: BaseLayer, cel_image: Image, update_layer: bool
) -> void:
var ordered_index := project.ordered_layers[layer.index]
var cel := project.frames[project.current_frame].cels[layer.index]
var include := true
if layer is GroupLayer and layer.blend_mode != BaseLayer.BlendModes.PASS_THROUGH:
cel_image.copy_from(
layer.blend_children(
project.frames[project.current_frame],
move_preview_location,
Global.display_layer_effects
)
)
else:
if Global.display_layer_effects:
cel_image.copy_from(layer.display_effects(cel))
else:
cel_image.copy_from(cel.get_image())
if layer.is_blended_by_ancestor():
include = false
if update_layer:
layer_texture_array.update_layer(cel_image, ordered_index)
DrawingAlgos.set_layer_metadata_image(layer, cel, layer_metadata_image, ordered_index, include)
func refresh_onion() -> void:
onion_past.queue_redraw()
onion_future.queue_redraw()