extends Sprite2D signal value_change(index: int, position: Vector2, rotation: float, scale: Vector2, skew: float) @onready var target: Sprite2D @export var zoom = 1.0 var target_index = -1 var is_dragging = false var cursor_sprite_frames: SpriteFrames var local_direction = 0 var cursor_direction = 0 var action = "" var transform_start_local_direction = 0 var transform_start_cursor_direction = 0 var transform_start_global_position: Vector2 var transform_start_position: Vector2 var transform_start_rotation: float var transform_start_scale: Vector2 var _corners = [ Vector2(1.0, 0.5), Vector2(1.0, 0.0), Vector2(0.5, 0.0), Vector2(0.0, 0.0), Vector2(0.0, 0.5), Vector2(0.0, 1.0), Vector2(0.5, 1.0), Vector2(1.0, 1.0) ] var editable = true func _ready() -> void: set_target(null, -1) cursor_sprite_frames = load("res://Resources/cursor_sprite_frames.tres") func set_target(_sprite: Sprite2D, _index: int, _editable: bool = false): target = _sprite target_index = _index editable = _editable if target == null: visible = false return visible = true var image = Image.create(target.texture.get_width() + 512, target.texture.get_height() + 512, false, Image.FORMAT_RGBA8) texture = ImageTexture.create_from_image(image) position = target.position rotation = target.rotation scale = target.scale skew = target.skew func grow_rect(rect: Rect2, amount: float, _scale: Vector2) -> Rect2: var amount_x = clampf(amount / abs(_scale.x), 1.0, 9999.9) if _scale.x != 0.0 else 1.0 var amount_y = clampf(amount / abs(_scale.y), 1.0, 9999.9) if _scale.y != 0.0 else 1.0 return rect.grow_individual(amount_x, amount_y, amount_x, amount_y) func _process(_delta: float) -> void: if target == null: return update_shader_params() func _input(event): if target == null or editable == false: return if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: if event.pressed and action != "": transform_start_global_position = get_global_mouse_position() transform_start_local_direction = local_direction transform_start_cursor_direction = cursor_direction transform_start_position = target.position transform_start_rotation = target.rotation transform_start_scale = target.scale is_dragging = true get_viewport().set_input_as_handled() else: is_dragging = false elif event is InputEventKey: if event.pressed and not event.echo: if Input.is_key_pressed(KEY_ENTER) or Input.is_key_pressed(KEY_KP_ENTER): is_dragging = false set_target(null, -1) Input.set_custom_mouse_cursor(null) return var _offset = Vector2.ZERO; if Input.is_key_pressed(KEY_LEFT): _offset.x = -1 if Input.is_key_pressed(KEY_RIGHT): _offset.x = 1 if Input.is_key_pressed(KEY_UP): _offset.y = -1 if Input.is_key_pressed(KEY_DOWN): _offset.y = 1 if _offset == Vector2.ZERO: return if event.ctrl_pressed: _offset *= 10; target.position += _offset; position = target.position value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew) get_viewport().set_input_as_handled() elif event is InputEventMouseMotion and is_dragging: if action == "Move": var _offset = get_global_mouse_position() - transform_start_global_position; if event.ctrl_pressed: if abs(_offset.x) < abs(_offset.y): _offset.x = 0 else: _offset.y = 0 target.position = transform_start_position + _offset; position = target.position value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew) get_viewport().set_input_as_handled() elif action == "Rotation": var vector_OA = (transform_start_global_position - transform_start_position).normalized() var vector_OB = (get_global_mouse_position() - transform_start_position).normalized() var angle_rad = -vector_OA.angle_to(vector_OB) if angle_rad < 0: angle_rad += 2 * PI if event.ctrl_pressed: angle_rad = deg_to_rad(round(rad_to_deg(angle_rad) / 15.0) * 15.0) target.rotation = transform_start_rotation - angle_rad rotation = target.rotation value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew) get_viewport().set_input_as_handled() elif action == "Scale": # 将起始点和当前点转换到Sprite的局部坐标系 var start_local = target.to_local(transform_start_global_position) var current_local = target.to_local(get_global_mouse_position()) # 在局部坐标系中计算缩放 var scale_delta = Vector2.ONE var start_vec = start_local - target.offset # 从中心点到起始点的向量 var current_vec = current_local - target.offset # 从中心点到当前点的向量 # 避免除零错误 if start_vec.x != 0: scale_delta.x = current_vec.x / start_vec.x if start_vec.y != 0: scale_delta.y = current_vec.y / start_vec.y # 根据拖拽方向锁定轴 if transform_start_local_direction == 0 or transform_start_local_direction == 4: # 左右 scale_delta.y = 1.0 elif transform_start_local_direction == 2 or transform_start_local_direction == 6: # 上下 scale_delta.x = 1.0 # 等比缩放 if event.ctrl_pressed: var uniform_scale = (scale_delta.x + scale_delta.y) / 2.0 scale_delta = Vector2(uniform_scale, uniform_scale) # 应用缩放 target.scale = transform_start_scale * scale_delta scale = target.scale value_change.emit(target_index, target.position, target.rotation, target.scale, target.skew) get_viewport().set_input_as_handled() #func _draw() -> void: #var _scale = target.scale * zoom #var r0 = target.get_rect() #var r1 = grow_rect(r0, -15, _scale) # move #var r2 = grow_rect(r0, 30, _scale) # scale #var r3 = grow_rect(r0, 80, _scale) # rotation #draw_rect(r0, Color.WHITE, false) #draw_rect(r1, Color.WHITE, false) #draw_rect(r2, Color.WHITE, false) #draw_rect(r3, Color.WHITE, false) # #var texture_size = target.texture.get_size() #var s = Vector2(1.0, texture_size.y / texture_size.x) if texture_size.x < texture_size.y else Vector2(texture_size.x / texture_size.y, 1.0) #var t = Transform2D(0.0, s, 0.0, Vector2.ZERO) #var scale_local_mouse_pos = t.affine_inverse() * get_local_mouse_position() # #var PI16 = PI / 16; #for i in range(32): #var p = (Vector2.RIGHT * 3000.0).rotated(i * PI16) #var point = t * p #draw_line(Vector2.ZERO, point, Color.WHITE, 2.0) func update_free_transform(update_cursor: bool=true): if update_cursor == false or editable == false or target == null: action = "" local_direction = -1 Input.set_custom_mouse_cursor(null) return var local_mouse_position = get_local_mouse_position() var PI8 = PI / 8; var texture_size = target.texture.get_size() var scale_factor = Vector2(1.0, texture_size.y / texture_size.x) if texture_size.x < texture_size.y else Vector2(texture_size.x / texture_size.y, 1.0) var scale_local_mouse_pos = local_mouse_position / scale_factor var angle = fposmod(PI / 16 - scale_local_mouse_pos.angle(), TAU) local_direction = -1 for i in range(9): if (i * 2) * PI8 <= angle and angle < (1 + i * 2) * PI8: local_direction = i % 8 break #print(rad_to_deg(angle), " ", local_direction, " ") var target_rect = target.get_rect() if local_direction != -1: var rect = Rect2(-target_rect.size / 2, target_rect.size) var trans = target.transform trans.origin = Vector2.ZERO cursor_direction = find_point_mapping(rect, trans, local_direction) if is_dragging == false: action = "" var _scale = target.scale * zoom var local_mouse_pos = local_mouse_position if grow_rect(target_rect, -15, _scale).has_point(local_mouse_pos): action = "Move" elif local_direction != -1: if grow_rect(target_rect, 30, _scale).has_point(local_mouse_pos): action = "Scale" elif grow_rect(target_rect, 80, _scale).has_point(local_mouse_pos): action = "Rotation" if action == "": Input.set_custom_mouse_cursor(null) elif action == "Move": var tex = cursor_sprite_frames.get_frame_texture(action, 0) Input.set_custom_mouse_cursor(tex, Input.CursorShape.CURSOR_ARROW, Vector2(tex.get_width() / 2.0, tex.get_height() / 2.0)) elif action == "Rotation": var curson_name = "%s_%d" % [action, cursor_direction] var tex = cursor_sprite_frames.get_frame_texture(curson_name, 0) Input.set_custom_mouse_cursor(tex, Input.CursorShape.CURSOR_ARROW, Vector2(tex.get_width() / 2.0, tex.get_height() / 2.0)) elif action == "Scale": if is_dragging and transform_start_local_direction in [0, 2, 4, 6]: return var curson_name = "%s_%d" % [action, cursor_direction] var tex = cursor_sprite_frames.get_frame_texture(curson_name, 0) Input.set_custom_mouse_cursor(tex, Input.CursorShape.CURSOR_ARROW, Vector2(tex.get_width() / 2.0, tex.get_height() / 2.0)) else: Input.set_custom_mouse_cursor(null) action = "" update_shader_params() func update_shader_params(): var rect = target.get_rect() var points_array = [ rect.position, # 左上角 rect.position + Vector2(rect.size.x, 0), # 右上角 rect.position + rect.size, # 右下角 rect.position + Vector2(0, rect.size.y) # 左下角 ] for i in range(4): points_array[i] = target.transform * points_array[i] material.set_shader_parameter("points", points_array) material.set_shader_parameter("editable", editable) func find_point_mapping(rect: Rect2, trans: Transform2D, direction: int) -> int: var points = [] for corner in _corners: points.append(rect.position + rect.size * corner) var min_index = -1 var min_angle = TAU for i in points.size(): var angle = abs((trans * points[i]).angle()) if angle < min_angle: min_angle = angle min_index = i if(trans.get_scale().y < 0): return (min_index - direction + 8) % 8 else: return (direction - min_index + 8) % 8