2067 lines
94 KiB
GDScript
2067 lines
94 KiB
GDScript
@tool
|
|
extends EditorPlugin
|
|
|
|
class_name CurvedLines2D
|
|
|
|
const SETTING_NAME_EDITING_ENABLED := "addons/curved_lines_2d/editing_enabled"
|
|
const SETTING_NAME_HINTS_ENABLED := "addons/curved_lines_2d/hints_enabled"
|
|
const SETTING_NAME_SHOW_POINT_NUMBERS := "addons/curved_lines_2d/show_point_numbers"
|
|
const SETTING_NAME_STROKE_WIDTH := "addons/curved_lines_2d/stroke_width"
|
|
const SETTING_NAME_STROKE_COLOR := "addons/curved_lines_2d/stroke_color"
|
|
const SETTING_NAME_USE_LINE_2D_FOR_STROKE = "addons/curved_lines_2d/use_line_2d_for_stroke"
|
|
const SETTING_NAME_FILL_COLOR := "addons/curved_lines_2d/fill_color"
|
|
const SETTING_NAME_ADD_STROKE_ENABLED := "addons/curved_lines_2d/add_stroke_enabled"
|
|
const SETTING_NAME_ADD_FILL_ENABLED := "addons/curved_lines_2d/add_fill_enabled"
|
|
const SETTING_NAME_ADD_COLLISION_TYPE = "addons/curved_lines_2d/add_collision_type"
|
|
|
|
const SETTING_NAME_PAINT_ORDER := "addons/curved_lines_2d/paint_order"
|
|
const SETTING_NAME_DEFAULT_LINE_BEGIN_CAP := "addons/curved_lines_2d/line_begin_cap"
|
|
const SETTING_NAME_DEFAULT_LINE_END_CAP := "addons/curved_lines_2d/line_end_cap"
|
|
const SETTING_NAME_DEFAULT_LINE_JOINT_MODE := "addons/curved_lines_2d/line_joint_mode"
|
|
const SETTING_NAME_SNAP_TO_PIXEL := "addons/curved_lines_2d/snap_to_pixel"
|
|
const SETTING_NAME_SNAP_RESOLUTION := "addons/curved_lines_2d/snap_resolution"
|
|
|
|
const SETTING_NAME_CURVE_UPDATE_CURVE_AT_RUNTIME := "addons/curved_lines_2d/update_curve_at_runtime"
|
|
const SETTING_NAME_CURVE_RESOURCE_LOCAL_TO_SCENE := "addons/curved_lines_2d/make_resources_local_to_scene"
|
|
const SETTING_NAME_CURVE_TOLERANCE_DEGREES := "addons/curved_lines_2d/default_tolerance_degrees"
|
|
const SETTING_NAME_CURVE_MAX_STAGES := "addons/curved_lines_2d/default_max_stages"
|
|
|
|
const SETTING_NAME_ANTIALIASED_LINE_2D := "addons/curved_lines_2d/antialiased_line_2d"
|
|
|
|
const META_NAME_HOVER_POINT_IDX := "_hover_point_idx_"
|
|
const META_NAME_HOVER_CP_IN_IDX := "_hover_cp_in_idx_"
|
|
const META_NAME_HOVER_CP_OUT_IDX := "_hover_cp_out_idx_"
|
|
const META_NAME_HOVER_CLOSEST_POINT := "_hover_closest_point_on_curve_"
|
|
const META_NAME_HOVER_GRADIENT_FROM := "_hover_gradient_from_"
|
|
const META_NAME_HOVER_GRADIENT_TO := "_hover_gradient_to_"
|
|
const META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX := "_hover_gradient_color_stop_idx_"
|
|
const META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE := "_hover_closest_point_on_gradient_"
|
|
|
|
const META_NAME_SELECT_HINT := "_select_hint_"
|
|
|
|
const VIEWPORT_ORANGE := Color(0.737, 0.463, 0.337)
|
|
|
|
enum PaintOrder {
|
|
FILL_STROKE_MARKERS,
|
|
STROKE_FILL_MARKERS,
|
|
FILL_MARKERS_STROKE,
|
|
MARKERS_FILL_STROKE,
|
|
STROKE_MARKERS_FILL,
|
|
MARKERS_STROKE_FILL
|
|
}
|
|
enum UndoRedoEntry { UNDOS, DOS, NAME, DO_PROPS, UNDO_PROPS }
|
|
|
|
const PAINT_ORDER_MAP := {
|
|
PaintOrder.FILL_STROKE_MARKERS: ['_add_fill_to_created_shape', '_add_stroke_to_created_shape', '_add_collision_to_created_shape'],
|
|
PaintOrder.STROKE_FILL_MARKERS: ['_add_stroke_to_created_shape', '_add_fill_to_created_shape', '_add_collision_to_created_shape'],
|
|
PaintOrder.FILL_MARKERS_STROKE: ['_add_fill_to_created_shape', '_add_collision_to_created_shape', '_add_stroke_to_created_shape'],
|
|
PaintOrder.MARKERS_FILL_STROKE: ['_add_collision_to_created_shape', '_add_fill_to_created_shape', '_add_stroke_to_created_shape'],
|
|
PaintOrder.STROKE_MARKERS_FILL: ['_add_stroke_to_created_shape', '_add_collision_to_created_shape', '_add_fill_to_created_shape'],
|
|
PaintOrder.MARKERS_STROKE_FILL: ['_add_collision_to_created_shape', '_add_stroke_to_created_shape', '_add_fill_to_created_shape']
|
|
}
|
|
const SHAPE_NAME_MAP := {
|
|
ScalableVectorShape2D.ShapeType.RECT: "a rectangle",
|
|
ScalableVectorShape2D.ShapeType.ELLIPSE: "a circle",
|
|
ScalableVectorShape2D.ShapeType.PATH: "an empty shape"
|
|
}
|
|
const OPERATION_NAME_MAP := {
|
|
Geometry2D.OPERATION_DIFFERENCE: { "verb": "cut out", "noun": "cutout" },
|
|
Geometry2D.OPERATION_INTERSECTION: { "verb": "clip off", "noun": "clip" },
|
|
Geometry2D.OPERATION_UNION: { "verb": "merge with", "noun": "merged shape" }
|
|
}
|
|
|
|
enum UniformTransformMode { NONE, TRANSLATE, ROTATE, SCALE }
|
|
|
|
var plugin : Line2DGeneratorInspectorPlugin
|
|
var scalable_vector_shapes_2d_dock
|
|
var select_mode_button : Button
|
|
var undo_redo : EditorUndoRedoManager
|
|
var in_undo_redo_transaction := false
|
|
var shape_preview : Curve2D = null
|
|
var selection_candidate : Node = null
|
|
var current_cutout_shape := ScalableVectorShape2D.ShapeType.RECT
|
|
var current_clip_operation := Geometry2D.OPERATION_DIFFERENCE
|
|
|
|
var undo_redo_transaction : Dictionary = {
|
|
UndoRedoEntry.NAME: "",
|
|
UndoRedoEntry.DOS: [],
|
|
UndoRedoEntry.UNDOS: [],
|
|
UndoRedoEntry.DO_PROPS: [],
|
|
UndoRedoEntry.UNDO_PROPS: []
|
|
}
|
|
|
|
var set_global_position_popup_panel : PopupPanel
|
|
var arc_settings_popup_panel : PopupPanel
|
|
|
|
var _vp_horizontal_scrollbar_locked_value := 0.0
|
|
var _locking_vp_horizontal_scrollbar := false
|
|
|
|
var uniform_transform_edit_buttons : Control
|
|
var _uniform_transform_mode := UniformTransformMode.NONE
|
|
var _drag_start := Vector2.ZERO
|
|
var _prev_uniform_rotate_angle := 0.0
|
|
var _stored_natural_center := Vector2.ZERO
|
|
var _lmb_is_down_inside_viewport := false
|
|
|
|
var merge_node_toggle_button : Button
|
|
var _merge_box_rect := Rect2(Vector2.ZERO, Vector2.ZERO)
|
|
|
|
func _enter_tree():
|
|
scalable_vector_shapes_2d_dock = preload("res://addons/curved_lines_2d/scalable_vector_shapes_2d_dock.tscn").instantiate()
|
|
plugin = preload("res://addons/curved_lines_2d/line_2d_generator_inspector_plugin.gd").new()
|
|
add_inspector_plugin(plugin)
|
|
add_custom_type(
|
|
"DrawablePath2D",
|
|
"Path2D",
|
|
preload("res://addons/curved_lines_2d/drawable_path_2d.gd"),
|
|
preload("res://addons/curved_lines_2d/DrawablePath2D.svg")
|
|
)
|
|
add_custom_type(
|
|
"ScalableVectorShape2D",
|
|
"Node2D",
|
|
preload("res://addons/curved_lines_2d/scalable_vector_shape_2d.gd"),
|
|
preload("res://addons/curved_lines_2d/DrawablePath2D.svg")
|
|
)
|
|
add_custom_type(
|
|
"AdaptableVectorShape3D",
|
|
"Node3D",
|
|
preload("res://addons/curved_lines_2d/adaptable_vector_shape_3d.gd"),
|
|
preload("res://addons/curved_lines_2d/AdaptableVectorShape3D.svg")
|
|
)
|
|
undo_redo = get_undo_redo()
|
|
add_control_to_bottom_panel(scalable_vector_shapes_2d_dock as Control, "Scalable Vector Shapes 2D")
|
|
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
|
|
undo_redo.version_changed.connect(update_overlays)
|
|
make_bottom_panel_item_visible(scalable_vector_shapes_2d_dock)
|
|
|
|
set_global_position_popup_panel = preload("res://addons/curved_lines_2d/set_global_position_popup_panel.tscn").instantiate()
|
|
arc_settings_popup_panel = preload("res://addons/curved_lines_2d/arc_settings_popup_panel.tscn").instantiate()
|
|
EditorInterface.get_base_control().add_child(set_global_position_popup_panel)
|
|
EditorInterface.get_base_control().add_child(arc_settings_popup_panel)
|
|
if not set_global_position_popup_panel.value_changed.is_connected(_on_global_position_for_handle_changed):
|
|
set_global_position_popup_panel.value_changed.connect(_on_global_position_for_handle_changed)
|
|
if not set_global_position_popup_panel.visibility_changed.is_connected(_commit_undo_redo_transaction):
|
|
set_global_position_popup_panel.visibility_changed.connect(_commit_undo_redo_transaction)
|
|
|
|
if not scalable_vector_shapes_2d_dock.shape_created.is_connected(_on_shape_created):
|
|
scalable_vector_shapes_2d_dock.shape_created.connect(_on_shape_created)
|
|
if not scalable_vector_shapes_2d_dock.set_shape_preview.is_connected(_on_shape_preview):
|
|
scalable_vector_shapes_2d_dock.set_shape_preview.connect(_on_shape_preview)
|
|
if not scalable_vector_shapes_2d_dock.edit_tab.rect_created.is_connected(_on_rect_created):
|
|
scalable_vector_shapes_2d_dock.edit_tab.rect_created.connect(_on_rect_created)
|
|
if not scalable_vector_shapes_2d_dock.edit_tab.ellipse_created.is_connected(_on_ellipse_created):
|
|
scalable_vector_shapes_2d_dock.edit_tab.ellipse_created.connect(_on_ellipse_created)
|
|
scene_changed.connect(_on_scene_changed)
|
|
|
|
uniform_transform_edit_buttons = load("res://addons/curved_lines_2d/uniform_transform_edit_buttons.tscn").instantiate()
|
|
var canvas_editor_buttons_container = EditorInterface.get_editor_viewport_2d().find_parent("*CanvasItemEditor*").find_child("*HFlowContainer*", true, false)
|
|
canvas_editor_buttons_container.add_child(uniform_transform_edit_buttons)
|
|
merge_node_toggle_button = Button.new()
|
|
merge_node_toggle_button.tooltip_text = "Merge vertices (M)"
|
|
merge_node_toggle_button.icon = load("res://addons/curved_lines_2d/MergeChain.svg")
|
|
merge_node_toggle_button.toggle_mode = true
|
|
merge_node_toggle_button.flat = true
|
|
merge_node_toggle_button.toggled.connect(_on_merge_node_toggle_button_toggled)
|
|
canvas_editor_buttons_container.add_child(merge_node_toggle_button)
|
|
|
|
if not _get_select_mode_button().toggled.is_connected(_on_select_mode_toggled):
|
|
_get_select_mode_button().toggled.connect(_on_select_mode_toggled)
|
|
_on_select_mode_toggled(_get_select_mode_button().button_pressed)
|
|
uniform_transform_edit_buttons.mode_changed.connect(_on_uniform_transform_mode_changed)
|
|
uniform_transform_edit_buttons.flip_horizontal.connect(_flip_svs_horizontal)
|
|
uniform_transform_edit_buttons.flip_vertical.connect(_flip_svs_vertical)
|
|
|
|
|
|
func select_node_reversibly(target_node : Node) -> void:
|
|
if is_instance_valid(target_node):
|
|
EditorInterface.edit_node(target_node)
|
|
|
|
|
|
func _on_merge_node_toggle_button_toggled(toggled_on : bool) -> void:
|
|
_merge_box_rect.size = Vector2.ZERO
|
|
if not toggled_on:
|
|
return
|
|
var scene_root := EditorInterface.get_edited_scene_root()
|
|
if is_instance_valid(scene_root):
|
|
EditorInterface.edit_node(scene_root)
|
|
update_overlays()
|
|
|
|
|
|
func _on_select_mode_toggled(toggled_on : bool) -> void:
|
|
var current_selection := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
if toggled_on and _is_svs_valid(current_selection):
|
|
uniform_transform_edit_buttons.enable()
|
|
merge_node_toggle_button.show()
|
|
elif toggled_on:
|
|
uniform_transform_edit_buttons.hide()
|
|
merge_node_toggle_button.show()
|
|
else:
|
|
uniform_transform_edit_buttons.hide()
|
|
merge_node_toggle_button.hide()
|
|
merge_node_toggle_button.button_pressed = false
|
|
|
|
|
|
func _on_uniform_transform_mode_changed(new_mode : UniformTransformMode) -> void:
|
|
_uniform_transform_mode = new_mode
|
|
update_overlays()
|
|
|
|
|
|
func _is_ctrl_or_cmd_pressed() -> bool:
|
|
return Input.is_key_pressed(KEY_CTRL) or Input.is_key_pressed(KEY_META)
|
|
|
|
|
|
func _on_shape_preview(curve : Curve2D):
|
|
shape_preview = curve
|
|
update_overlays()
|
|
|
|
|
|
func _on_rect_created(width : float, height : float, rx : float, ry : float, scene_root : Node) -> void:
|
|
var new_rect := ScalableVectorShape2D.new()
|
|
new_rect.shape_type = ScalableVectorShape2D.ShapeType.RECT
|
|
new_rect.size = Vector2(width, height)
|
|
new_rect.rx = rx
|
|
new_rect.ry = ry
|
|
_create_shape(new_rect, scene_root, "Rectangle")
|
|
|
|
|
|
func _on_ellipse_created(rx : float, ry : float, scene_root : Node) -> void:
|
|
var new_ellipse := ScalableVectorShape2D.new()
|
|
new_ellipse.shape_type = ScalableVectorShape2D.ShapeType.ELLIPSE
|
|
new_ellipse.size = Vector2(rx * 2, ry * 2)
|
|
_create_shape(new_ellipse, scene_root, "Ellipse")
|
|
|
|
|
|
func _on_shape_created(curve : Curve2D, scene_root : Node, node_name : String) -> void:
|
|
var new_shape := ScalableVectorShape2D.new()
|
|
new_shape.curve = curve
|
|
_create_shape(new_shape, scene_root, node_name)
|
|
|
|
|
|
func _create_shape(new_shape : ScalableVectorShape2D, scene_root : Node, node_name : String, is_cutout_for : ScalableVectorShape2D = null) -> void:
|
|
var current_selection := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
var parent = current_selection if current_selection is Node else scene_root
|
|
new_shape.update_curve_at_runtime = _is_setting_update_curve_at_runtime()
|
|
new_shape.curve.resource_local_to_scene = _is_making_curve_resources_local_to_scene()
|
|
new_shape.arc_list.resource_local_to_scene = _is_making_curve_resources_local_to_scene()
|
|
new_shape.tolerance_degrees = _get_default_tolerance_degrees()
|
|
new_shape.max_stages = _get_default_max_stages()
|
|
new_shape.name = node_name
|
|
if not is_instance_valid(is_cutout_for):
|
|
new_shape.position = Vector2.ZERO
|
|
undo_redo.create_action("Add a %s to the scene " % node_name)
|
|
undo_redo.add_do_method(parent, 'add_child', new_shape, true)
|
|
undo_redo.add_do_method(new_shape, 'set_owner', scene_root)
|
|
undo_redo.add_do_reference(new_shape)
|
|
undo_redo.add_undo_method(parent, 'remove_child', new_shape)
|
|
if is_instance_valid(is_cutout_for):
|
|
var new_clip_paths := is_cutout_for.clip_paths.duplicate()
|
|
match current_clip_operation:
|
|
Geometry2D.OPERATION_UNION:
|
|
new_shape.use_union_in_stead_of_clipping = true
|
|
Geometry2D.OPERATION_INTERSECTION:
|
|
new_shape.use_interect_when_clipping = true
|
|
Geometry2D.OPERATION_DIFFERENCE:
|
|
new_shape.use_interect_when_clipping = false
|
|
new_shape.use_union_in_stead_of_clipping = false
|
|
new_clip_paths.append(new_shape)
|
|
undo_redo.add_do_property(is_cutout_for, 'clip_paths', new_clip_paths)
|
|
undo_redo.add_undo_property(is_cutout_for, 'clip_paths', is_cutout_for.clip_paths)
|
|
else:
|
|
for draw_fn in PAINT_ORDER_MAP[_get_default_paint_order()]:
|
|
call(draw_fn, new_shape, scene_root)
|
|
undo_redo.add_do_method(self, 'select_node_reversibly', new_shape)
|
|
undo_redo.add_undo_method(self, 'select_node_reversibly', parent)
|
|
undo_redo.commit_action()
|
|
new_shape.stroke_color = _get_default_stroke_color()
|
|
new_shape.stroke_width = _get_default_stroke_width()
|
|
new_shape.begin_cap_mode = _get_default_begin_cap()
|
|
new_shape.end_cap_mode = _get_default_end_cap()
|
|
new_shape.line_joint_mode = _get_default_joint_mode()
|
|
if not is_instance_valid(is_cutout_for):
|
|
_set_viewport_pos_to_selection()
|
|
|
|
|
|
func _create_svs_vertex_merge_2d() -> void:
|
|
var vertex_map := _find_merge_vertices()
|
|
if vertex_map.size() < 2:
|
|
return
|
|
var new_vertex_merge := SVSVertexMerge2D.new()
|
|
new_vertex_merge.name = "SVSVertexMerge2D"
|
|
var scene_root := EditorInterface.get_edited_scene_root()
|
|
var current_selection := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
var parent = current_selection if current_selection is Node else scene_root
|
|
undo_redo.create_action("Add SVSVertexMerge2D")
|
|
undo_redo.add_do_method(parent, 'add_child', new_vertex_merge, true)
|
|
undo_redo.add_do_method(new_vertex_merge, 'set_owner', scene_root)
|
|
undo_redo.add_do_property(new_vertex_merge, "vertex_map", vertex_map)
|
|
undo_redo.add_undo_property(new_vertex_merge, "vertex_map", {})
|
|
undo_redo.add_undo_method(new_vertex_merge, 'set_owner', null)
|
|
undo_redo.add_undo_method(parent, 'remove_child', new_vertex_merge)
|
|
for svs in vertex_map.keys():
|
|
undo_redo.add_undo_property(svs, "curve", svs.curve.duplicate())
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _add_fill_to_created_shape(new_shape : ScalableVectorShape2D, scene_root : Node) -> void:
|
|
if _is_add_fill_enabled():
|
|
var polygon := Polygon2D.new()
|
|
polygon.name = "Fill"
|
|
polygon.color = _get_default_fill_color()
|
|
undo_redo.add_do_property(new_shape, 'polygon', polygon)
|
|
undo_redo.add_do_method(new_shape, 'add_child', polygon, true)
|
|
undo_redo.add_do_method(polygon, 'set_owner', scene_root)
|
|
undo_redo.add_do_reference(polygon)
|
|
undo_redo.add_undo_method(new_shape, 'remove_child', polygon)
|
|
|
|
|
|
func _add_stroke_to_created_shape(new_shape : ScalableVectorShape2D, scene_root : Node) -> void:
|
|
if _is_add_stroke_enabled():
|
|
if _using_line_2d_for_stroke():
|
|
var line := Line2D.new()
|
|
line.name = "Stroke"
|
|
line.sharp_limit = 90.0
|
|
if CurvedLines2D._use_antialiased_line_2d():
|
|
line.texture = load("res://addons/curved_lines_2d/LumAlpha8.tex")
|
|
line.texture_mode = Line2D.LINE_TEXTURE_TILE
|
|
line.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
|
|
|
|
undo_redo.add_do_property(new_shape, 'line', line)
|
|
undo_redo.add_do_method(new_shape, 'add_child', line, true)
|
|
undo_redo.add_do_method(line, 'set_owner', scene_root)
|
|
undo_redo.add_do_reference(line)
|
|
undo_redo.add_undo_method(new_shape, 'remove_child', line)
|
|
else:
|
|
var poly_stroke := Polygon2D.new()
|
|
poly_stroke.name = "Stroke"
|
|
undo_redo.add_do_property(new_shape, 'poly_stroke', poly_stroke)
|
|
undo_redo.add_do_method(new_shape, 'add_child', poly_stroke, true)
|
|
undo_redo.add_do_method(poly_stroke, 'set_owner', scene_root)
|
|
undo_redo.add_do_reference(poly_stroke)
|
|
undo_redo.add_undo_method(new_shape, 'remove_child', poly_stroke)
|
|
|
|
|
|
func _add_collision_to_created_shape(new_shape : ScalableVectorShape2D, scene_root : Node) -> void:
|
|
if _add_collision_object_type() != ScalableVectorShape2D.CollisionObjectType.NONE:
|
|
var collision : CollisionObject2D = null
|
|
match _add_collision_object_type():
|
|
ScalableVectorShape2D.CollisionObjectType.STATIC_BODY_2D:
|
|
collision = StaticBody2D.new()
|
|
ScalableVectorShape2D.CollisionObjectType.AREA_2D:
|
|
collision = Area2D.new()
|
|
ScalableVectorShape2D.CollisionObjectType.ANIMATABLE_BODY_2D:
|
|
collision = AnimatableBody2D.new()
|
|
ScalableVectorShape2D.CollisionObjectType.RIGID_BODY_2D:
|
|
collision = RigidBody2D.new()
|
|
ScalableVectorShape2D.CollisionObjectType.CHARACTER_BODY_2D:
|
|
collision = CharacterBody2D.new()
|
|
ScalableVectorShape2D.CollisionObjectType.PHYSICAL_BONE_2D:
|
|
collision = PhysicalBone2D.new()
|
|
undo_redo.add_do_method(new_shape, 'add_child', collision, true)
|
|
undo_redo.add_do_reference(collision)
|
|
undo_redo.add_do_method(collision, 'set_owner', scene_root)
|
|
undo_redo.add_do_property(new_shape, 'collision_object', collision)
|
|
undo_redo.add_undo_method(new_shape, 'remove_child', collision)
|
|
|
|
func _scene_can_export_animations() -> bool:
|
|
return (EditorInterface.get_edited_scene_root() is CanvasItem and
|
|
not EditorInterface.get_edited_scene_root().find_children("*", "AnimationPlayer").filter(
|
|
func(an): return an.owner == EditorInterface.get_edited_scene_root()
|
|
).is_empty() and
|
|
not EditorInterface.get_edited_scene_root()
|
|
.find_children("*", "ScalableVectorShape2D").is_empty()
|
|
)
|
|
|
|
func _on_selection_changed():
|
|
var scene_root := EditorInterface.get_edited_scene_root()
|
|
var current_selection := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
if _is_editing_enabled() and is_instance_valid(scene_root):
|
|
# inelegant fix to always keep an instance of Node selected, so
|
|
# _forward_canvas_gui_input will still be called upon losing focus
|
|
if (not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
|
|
and EditorInterface.get_selection().get_selected_nodes().is_empty()):
|
|
EditorInterface.edit_node(scene_root)
|
|
if current_selection is AnimationPlayer and _scene_can_export_animations():
|
|
scalable_vector_shapes_2d_dock.set_selected_animation_player(current_selection)
|
|
_on_select_mode_toggled(_get_select_mode_button().button_pressed)
|
|
update_overlays()
|
|
|
|
|
|
func _on_scene_changed(scn : Node):
|
|
if _scene_can_export_animations():
|
|
var anim_pl = scn.find_children("*", "AnimationPlayer").filter(
|
|
func(an): return an.owner == EditorInterface.get_edited_scene_root()
|
|
).pop_back()
|
|
scalable_vector_shapes_2d_dock.set_selected_animation_player(anim_pl)
|
|
else:
|
|
scalable_vector_shapes_2d_dock.set_selected_animation_player(null)
|
|
|
|
|
|
func _handles(object: Object) -> bool:
|
|
return object is Node
|
|
|
|
|
|
func _find_scalable_vector_shape_2d_nodes() -> Array[Node]:
|
|
var scene_root := EditorInterface.get_edited_scene_root()
|
|
if is_instance_valid(scene_root):
|
|
var result := scene_root.find_children("*", "ScalableVectorShape2D")
|
|
if scene_root is ScalableVectorShape2D:
|
|
result.push_front(scene_root)
|
|
return result
|
|
return []
|
|
|
|
|
|
func _find_scalable_vector_shape_2d_nodes_at(pos : Vector2) -> Array[Node]:
|
|
if is_instance_valid(EditorInterface.get_edited_scene_root()):
|
|
return (_find_scalable_vector_shape_2d_nodes()
|
|
.filter(func(x : ScalableVectorShape2D): return not x.has_meta("_edit_lock_"))
|
|
.filter(func(x : ScalableVectorShape2D): return x.is_visible_in_tree())
|
|
.filter(func(x : ScalableVectorShape2D): return x.owner == EditorInterface.get_edited_scene_root())
|
|
.filter(func(x : ScalableVectorShape2D): return x.has_point(pos))
|
|
)
|
|
return []
|
|
|
|
|
|
func _is_change_pivot_button_active() -> bool:
|
|
var results = (
|
|
EditorInterface.get_editor_viewport_2d().find_parent("*CanvasItemEditor*")
|
|
.find_children("*Button*", "", true, false)
|
|
)
|
|
if results.size() >= 6:
|
|
return results[5].button_pressed
|
|
return false
|
|
|
|
|
|
func _get_select_mode_button() -> Button:
|
|
if is_instance_valid(select_mode_button):
|
|
return select_mode_button
|
|
else:
|
|
select_mode_button = (
|
|
EditorInterface.get_editor_viewport_2d().find_parent("*CanvasItemEditor*")
|
|
.find_child("*Button*", true, false)
|
|
)
|
|
return select_mode_button
|
|
|
|
|
|
func _get_viewport_center() -> Vector2:
|
|
var tr := EditorInterface.get_editor_viewport_2d().global_canvas_transform
|
|
var og := tr.get_origin()
|
|
var sz := Vector2(EditorInterface.get_editor_viewport_2d().size)
|
|
return (sz / 2) / tr.get_scale() - og / tr.get_scale()
|
|
|
|
|
|
func _set_viewport_pos_to_selection() -> void:
|
|
EditorInterface.get_editor_viewport_2d().get_parent().grab_focus()
|
|
var key_ev := InputEventKey.new()
|
|
key_ev.keycode = KEY_F
|
|
key_ev.pressed = true
|
|
Input.parse_input_event(key_ev)
|
|
|
|
|
|
func _vp_transform(p : Vector2) -> Vector2:
|
|
var s := EditorInterface.get_editor_viewport_2d().get_final_transform().get_scale()
|
|
var o := EditorInterface.get_editor_viewport_2d().get_final_transform().get_origin()
|
|
return (p * s) + o
|
|
|
|
|
|
func _is_svs_valid(svs : Object) -> bool:
|
|
return is_instance_valid(svs) and svs is ScalableVectorShape2D and svs.curve
|
|
|
|
|
|
func _get_hovered_handle_metadata(svs : ScalableVectorShape2D) -> Dictionary:
|
|
|
|
if svs.has_meta(META_NAME_HOVER_POINT_IDX):
|
|
return {
|
|
'global_pos': svs.to_global(svs.curve.get_point_position(
|
|
svs.get_meta(META_NAME_HOVER_POINT_IDX)
|
|
)),
|
|
'meta_name': META_NAME_HOVER_POINT_IDX,
|
|
'point_idx': svs.get_meta(META_NAME_HOVER_POINT_IDX)
|
|
}
|
|
elif svs.has_meta(META_NAME_HOVER_CP_IN_IDX):
|
|
return {
|
|
'global_pos': svs.to_global(svs.curve.get_point_position(
|
|
svs.get_meta(META_NAME_HOVER_CP_IN_IDX)
|
|
) + svs.curve.get_point_in(
|
|
svs.get_meta(META_NAME_HOVER_CP_IN_IDX)
|
|
)),
|
|
'meta_name': META_NAME_HOVER_CP_IN_IDX,
|
|
'point_idx': svs.get_meta(META_NAME_HOVER_CP_IN_IDX)
|
|
}
|
|
elif svs.has_meta(META_NAME_HOVER_CP_OUT_IDX):
|
|
return {
|
|
'global_pos': svs.to_global(svs.curve.get_point_position(
|
|
svs.get_meta(META_NAME_HOVER_CP_OUT_IDX)
|
|
) + svs.curve.get_point_out(
|
|
svs.get_meta(META_NAME_HOVER_CP_OUT_IDX)
|
|
)),
|
|
'meta_name': META_NAME_HOVER_CP_OUT_IDX,
|
|
'point_idx': svs.get_meta(META_NAME_HOVER_CP_OUT_IDX)
|
|
}
|
|
return {}
|
|
|
|
|
|
func _curve_control_has_hover(svs : ScalableVectorShape2D) -> bool:
|
|
return (
|
|
svs.has_meta(META_NAME_HOVER_POINT_IDX) or
|
|
svs.has_meta(META_NAME_HOVER_CP_IN_IDX) or
|
|
svs.has_meta(META_NAME_HOVER_CP_OUT_IDX)
|
|
)
|
|
|
|
|
|
func _handle_has_hover(svs : ScalableVectorShape2D) -> bool:
|
|
return (
|
|
svs.has_meta(META_NAME_HOVER_POINT_IDX) or
|
|
svs.has_meta(META_NAME_HOVER_CP_IN_IDX) or
|
|
svs.has_meta(META_NAME_HOVER_CP_OUT_IDX) or
|
|
svs.has_meta(META_NAME_HOVER_GRADIENT_FROM) or
|
|
svs.has_meta(META_NAME_HOVER_GRADIENT_TO) or
|
|
svs.has_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX)
|
|
)
|
|
|
|
|
|
func _draw_control_point_handle(viewport_control : Control, svs : ScalableVectorShape2D,
|
|
handle : Dictionary, prefix : String, is_hovered : bool, self_is_hovered : bool) -> String:
|
|
if handle[prefix].length():
|
|
var color := VIEWPORT_ORANGE if is_hovered else Color.WHITE
|
|
var width := 2 if is_hovered else 1
|
|
viewport_control.draw_line(_vp_transform(handle['point_position']),
|
|
_vp_transform(handle[prefix + '_position']), Color.WEB_GRAY, 1, true)
|
|
viewport_control.draw_circle(_vp_transform(handle[prefix + '_position']), 5, Color.DIM_GRAY)
|
|
viewport_control.draw_circle(_vp_transform(handle[prefix + '_position']), 5, color, false, width)
|
|
if self_is_hovered:
|
|
var hint_txt := "Control point " + prefix
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
hint_txt += "\n - Drag to move\n - Right click to delete"
|
|
hint_txt += "\n - Hold Shift + Drag to move mirrored"
|
|
if Input.is_key_pressed(KEY_ALT):
|
|
hint_txt += "\n - Click to set exact global position (Alt held)"
|
|
else:
|
|
hint_txt += "\n - Alt + Click to set exact global position"
|
|
return hint_txt
|
|
return ""
|
|
|
|
|
|
func _draw_rect_control_point_handle(viewport_control : Control, svs : ScalableVectorShape2D,
|
|
handle : Dictionary, prefix : String, is_hovered : bool) -> String:
|
|
var prop_name := "rx" if prefix == "in" else "ry"
|
|
var color := VIEWPORT_ORANGE if is_hovered else Color.WHITE
|
|
var width := 2 if is_hovered else 1
|
|
viewport_control.draw_line(
|
|
_vp_transform(handle['top_left_pos']),
|
|
_vp_transform(handle[prefix + '_position']), Color.WEB_GRAY, 1, true)
|
|
viewport_control.draw_circle(_vp_transform(handle[prefix + '_position']), 5, Color.DIM_GRAY)
|
|
viewport_control.draw_circle(_vp_transform(handle[prefix + '_position']), 5, color, false, width)
|
|
if is_hovered:
|
|
var hint_txt := "Control point rounded corners "
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
var dir := "right / left" if prefix == 'in' else "up / down"
|
|
hint_txt += "\n - Drag %s to move \n - Right click to remove rounded corners" % dir
|
|
hint_txt += "\n - Hold Shift + Drag to drag only this handle"
|
|
return hint_txt
|
|
return ""
|
|
|
|
|
|
func _draw_hint(viewport_control : Control, txt : String, force_draw := false) -> void:
|
|
if set_global_position_popup_panel.visible:
|
|
return
|
|
if not _get_select_mode_button().button_pressed:
|
|
return
|
|
if not _are_hints_enabled() and not force_draw:
|
|
return
|
|
var txt_pos := (_vp_transform(EditorInterface.get_editor_viewport_2d().get_mouse_position())
|
|
+ Vector2(15, 8))
|
|
var lines := txt.split("\n")
|
|
for i in range(lines.size()):
|
|
var text := lines[i]
|
|
var pos := txt_pos + Vector2.DOWN * (i * (ThemeDB.fallback_font_size + ThemeDB.fallback_font_size * .2))
|
|
viewport_control.draw_string_outline(ThemeDB.fallback_font, pos, text,
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, ThemeDB.fallback_font_size, 3, Color.BLACK)
|
|
viewport_control.draw_string(ThemeDB.fallback_font, pos, text,
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, ThemeDB.fallback_font_size, Color.WHITE_SMOKE)
|
|
|
|
|
|
func _draw_point_number(viewport_control: Control, p : Vector2, text : String) -> void:
|
|
if not _am_showing_point_numbers():
|
|
return
|
|
var pos := _vp_transform(p)
|
|
var width := 8 * (text.length() + 1)
|
|
viewport_control.draw_string_outline(ThemeDB.fallback_font, pos + + Vector2(-width, 6), text,
|
|
HORIZONTAL_ALIGNMENT_LEFT, width, ThemeDB.fallback_font_size, 3, Color.BLACK)
|
|
viewport_control.draw_string(ThemeDB.fallback_font, pos + Vector2(-width, 6), text,
|
|
HORIZONTAL_ALIGNMENT_LEFT, width, ThemeDB.fallback_font_size, Color.WHITE_SMOKE)
|
|
|
|
|
|
func _draw_handles(viewport_control : Control, svs : ScalableVectorShape2D) -> void:
|
|
if not _get_select_mode_button().button_pressed:
|
|
return
|
|
var hint_txt := ""
|
|
var point_txt := ""
|
|
var point_pos_txt := ""
|
|
var point_hint_pos := Vector2.ZERO
|
|
var handles = svs.get_curve_handles()
|
|
for i in range(handles.size()):
|
|
var handle = handles[i]
|
|
|
|
var is_hovered : bool = svs.get_meta(META_NAME_HOVER_POINT_IDX, -1) == i
|
|
var cp_in_is_hovered : bool = svs.get_meta(META_NAME_HOVER_CP_IN_IDX, -1) == i
|
|
var cp_out_is_hovered : bool = svs.get_meta(META_NAME_HOVER_CP_OUT_IDX, -1) == i
|
|
var color := VIEWPORT_ORANGE if is_hovered else Color.WHITE
|
|
var width := 2 if is_hovered else 1
|
|
if is_hovered:
|
|
point_pos_txt = "Global point position: (%.3f, %.3f)" % [handle["point_position"].x, handle["point_position"].y]
|
|
elif cp_in_is_hovered:
|
|
point_pos_txt = "Global curve handle position: (%.3f, %.3f)" % [handle["in_position"].x,handle["in_position"].y]
|
|
elif cp_out_is_hovered:
|
|
point_pos_txt = "Global curve handle position: (%.3f, %.3f)" % [handle["out_position"].x, handle["out_position"].y]
|
|
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.RECT:
|
|
hint_txt += _draw_rect_control_point_handle(viewport_control, svs, handle, 'in',
|
|
cp_in_is_hovered)
|
|
if handle['out'].length():
|
|
hint_txt += _draw_rect_control_point_handle(viewport_control, svs, handle, 'out',
|
|
cp_out_is_hovered)
|
|
elif svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
if not svs.is_arc_start(i - 1):
|
|
hint_txt += _draw_control_point_handle(viewport_control, svs, handle, 'in',
|
|
is_hovered or cp_in_is_hovered, cp_in_is_hovered)
|
|
if not svs.is_arc_start(i):
|
|
hint_txt +=_draw_control_point_handle(viewport_control, svs, handle, 'out',
|
|
is_hovered or cp_out_is_hovered, cp_out_is_hovered)
|
|
|
|
if handle['mirrored']:
|
|
# mirrored handles
|
|
var rect := Rect2(_vp_transform(handle['point_position']) - Vector2(5, 5), Vector2(10, 10))
|
|
viewport_control.draw_rect(rect, Color.DIM_GRAY, .5)
|
|
viewport_control.draw_rect(rect, color, false, width)
|
|
else:
|
|
# unmirrored handles / zero length handles
|
|
var p1 := _vp_transform(handle['point_position'])
|
|
var pts := PackedVector2Array([
|
|
Vector2(p1.x - 8, p1.y), Vector2(p1.x, p1.y - 8),
|
|
Vector2(p1.x + 8, p1.y), Vector2(p1.x, p1.y + 8)
|
|
])
|
|
viewport_control.draw_polygon(pts, [Color.DIM_GRAY])
|
|
pts.append(Vector2(p1.x - 8, p1.y))
|
|
viewport_control.draw_polyline(pts, color, width)
|
|
|
|
if is_hovered:
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
point_txt = str(i) + handle['is_closed']
|
|
point_hint_pos = handle['point_position']
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
if Input.is_key_pressed(KEY_SHIFT) and svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
hint_txt += " - Release mouse to set curve handles"
|
|
else:
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.RECT:
|
|
hint_txt += " - Drag to resize rectange"
|
|
elif svs.shape_type == ScalableVectorShape2D.ShapeType.ELLIPSE:
|
|
hint_txt += " - Drag to resize ellipse"
|
|
else:
|
|
hint_txt += " - Drag to move"
|
|
if handle['is_closed'].length() > 0:
|
|
hint_txt += "\n - Double click to break loop"
|
|
elif svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
hint_txt += "\n - Right click to delete"
|
|
if not svs.is_curve_closed() and (
|
|
(i == 0 and handles.size() > 2) or
|
|
(i == handles.size() - 1 and i > 1)
|
|
):
|
|
hint_txt += "\n - Double click to close loop"
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
hint_txt += "\n - Hold Shift + Drag to create curve handles"
|
|
if Input.is_key_pressed(KEY_ALT):
|
|
hint_txt += "\n - Click to set exact global position (Alt held)"
|
|
else:
|
|
hint_txt += "\n - Alt + Click to set exact global position"
|
|
|
|
|
|
var gradient_handles := svs.get_gradient_handles()
|
|
if not gradient_handles.is_empty():
|
|
var p1 := _vp_transform(gradient_handles['fill_from_pos'])
|
|
var p2 := _vp_transform(gradient_handles['fill_to_pos'])
|
|
var hint_color := svs.shape_hint_color if svs.shape_hint_color else Color.LIME_GREEN
|
|
|
|
if svs.has_meta(META_NAME_HOVER_GRADIENT_FROM):
|
|
hint_txt = "- Drag to move gradient start position"
|
|
viewport_control.draw_circle(p1, 16, hint_color)
|
|
viewport_control.draw_circle(p1, 12, Color.WHITE, false, 0.5, true)
|
|
if svs.has_meta(META_NAME_HOVER_GRADIENT_TO):
|
|
hint_txt = "- Drag to move gradient end position"
|
|
viewport_control.draw_circle(p2, 16, hint_color)
|
|
viewport_control.draw_circle(p2, 12, Color.WHITE, false, 0.5, true)
|
|
|
|
for p : Vector2 in gradient_handles['stop_positions']:
|
|
viewport_control.draw_circle(_vp_transform(p) + Vector2(1,1), 5, Color(0.0,0.0,0.0, 0.4), true, -1, true)
|
|
|
|
viewport_control.draw_line(p1, p2, hint_color, .5, true)
|
|
|
|
for idx in range(gradient_handles['stop_positions'].size()):
|
|
var p := _vp_transform(gradient_handles['stop_positions'][idx])
|
|
var color := (Color.WHITE
|
|
if svs.get_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX, -1) == idx
|
|
else Color.WEB_GRAY)
|
|
viewport_control.draw_circle(p, 5, gradient_handles["stop_colors"][idx])
|
|
viewport_control.draw_circle(p, 5, color, false, 0.5, true)
|
|
|
|
var p1_color := Color.WHITE if svs.has_meta(META_NAME_HOVER_GRADIENT_FROM) else hint_color
|
|
var p2_color := Color.WHITE if svs.has_meta(META_NAME_HOVER_GRADIENT_TO) else hint_color
|
|
_draw_crosshair(viewport_control, p1 , 8, 8, p1_color, 1)
|
|
_draw_crosshair(viewport_control, p2 , 8, 8, p2_color, 1)
|
|
if svs.has_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX):
|
|
hint_txt = "- Drag to move color stop\n- Right click to remove color stop"
|
|
if (svs.has_meta(META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE)
|
|
and not _is_ctrl_or_cmd_pressed()
|
|
and not Input.is_key_pressed(KEY_SHIFT)):
|
|
_draw_crosshair(viewport_control, svs.get_meta(META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE))
|
|
hint_txt = "- Double click to add color stop here"
|
|
if not point_txt.is_empty():
|
|
_draw_point_number(viewport_control, point_hint_pos, point_txt)
|
|
|
|
if not _are_hints_enabled() and _am_showing_point_numbers():
|
|
_draw_hint(viewport_control, point_pos_txt, true)
|
|
elif _are_hints_enabled() and _am_showing_point_numbers() and point_pos_txt.length():
|
|
hint_txt += "\n\n - " + point_pos_txt
|
|
|
|
if not hint_txt.is_empty():
|
|
_draw_hint(viewport_control, hint_txt)
|
|
|
|
|
|
|
|
func _set_handle_hover(g_mouse_pos : Vector2, svs : ScalableVectorShape2D) -> void:
|
|
var mouse_pos := _vp_transform(g_mouse_pos)
|
|
var handles = svs.get_curve_handles()
|
|
var gradient_handles = svs.get_gradient_handles()
|
|
svs.remove_meta(META_NAME_HOVER_POINT_IDX)
|
|
svs.remove_meta(META_NAME_HOVER_CP_IN_IDX)
|
|
svs.remove_meta(META_NAME_HOVER_CP_OUT_IDX)
|
|
svs.remove_meta(META_NAME_HOVER_GRADIENT_FROM)
|
|
svs.remove_meta(META_NAME_HOVER_GRADIENT_TO)
|
|
svs.remove_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX)
|
|
svs.remove_meta(META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE)
|
|
for i in range(handles.size()):
|
|
var handle = handles[i]
|
|
if mouse_pos.distance_to(_vp_transform(handle['point_position'])) < 10:
|
|
svs.set_meta(META_NAME_HOVER_POINT_IDX, i)
|
|
elif mouse_pos.distance_to(_vp_transform(handle['in_position'])) < 10:
|
|
svs.set_meta(META_NAME_HOVER_CP_IN_IDX, i)
|
|
elif mouse_pos.distance_to(_vp_transform(handle['out_position'])) < 10:
|
|
svs.set_meta(META_NAME_HOVER_CP_OUT_IDX, i)
|
|
if not gradient_handles.is_empty() and not _handle_has_hover(svs):
|
|
var stop_idx = gradient_handles['stop_positions'].find_custom(func(p):
|
|
return mouse_pos.distance_to(_vp_transform(p)) < 6)
|
|
if stop_idx > -1:
|
|
svs.set_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX, stop_idx)
|
|
elif mouse_pos.distance_to(_vp_transform(gradient_handles['fill_from_pos'])) < 20:
|
|
svs.set_meta(META_NAME_HOVER_GRADIENT_FROM, true)
|
|
elif mouse_pos.distance_to(_vp_transform(gradient_handles['fill_to_pos'])) < 20:
|
|
svs.set_meta(META_NAME_HOVER_GRADIENT_TO, true)
|
|
else:
|
|
var p := Geometry2D.get_closest_point_to_segment(mouse_pos,
|
|
_vp_transform(gradient_handles['fill_from_pos']),
|
|
_vp_transform(gradient_handles['fill_to_pos']))
|
|
if mouse_pos.distance_to(p) < 10:
|
|
svs.set_meta(META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE, p)
|
|
|
|
var closest_point_on_curve := svs.get_closest_point_on_curve(g_mouse_pos)
|
|
if mouse_pos.distance_to(_vp_transform(closest_point_on_curve.point_position)) < 15:
|
|
svs.set_meta(META_NAME_HOVER_CLOSEST_POINT, closest_point_on_curve)
|
|
|
|
|
|
func _draw_curve(viewport_control : Control, svs : ScalableVectorShape2D,
|
|
is_selected := true) -> void:
|
|
var color := svs.shape_hint_color if svs.shape_hint_color else Color.LIME_GREEN
|
|
if not is_selected:
|
|
color = Color(0.5, 0.5, 0.5, 0.2)
|
|
_draw_curve_def(viewport_control, svs, color, 1.0, is_selected)
|
|
|
|
|
|
func _draw_curve_def(viewport_control : Control, svs : ScalableVectorShape2D, color : Color,
|
|
width : float, antialiased : bool) -> void:
|
|
if not svs.is_visible_in_tree():
|
|
return
|
|
var points = svs.get_poly_points().map(_vp_transform)
|
|
var last_p := Vector2.INF
|
|
for p : Vector2 in points:
|
|
if last_p != Vector2.INF:
|
|
viewport_control.draw_line(last_p, p, color, width, antialiased)
|
|
last_p = p
|
|
if is_instance_valid(svs.line) and svs.line.closed and points.size() > 1:
|
|
viewport_control.draw_dashed_line(last_p, points[0], color, width, 5.0, true, antialiased)
|
|
|
|
|
|
|
|
func _draw_crosshair(viewport_control : Control, p : Vector2, orbit := 2.0, outer_orbit := 6.0,
|
|
color := Color.WHITE, width := 1) -> void:
|
|
if not _get_select_mode_button().button_pressed:
|
|
return
|
|
var line_len = outer_orbit + orbit
|
|
viewport_control.draw_line(p - line_len * Vector2.UP, p - orbit * Vector2.UP, Color.WEB_GRAY, width + 1)
|
|
viewport_control.draw_line(p - line_len * Vector2.RIGHT, p - orbit * Vector2.RIGHT, Color.WEB_GRAY, width + 1)
|
|
viewport_control.draw_line(p - line_len * Vector2.DOWN, p - orbit * Vector2.DOWN, Color.WEB_GRAY, width + 1)
|
|
viewport_control.draw_line(p - line_len * Vector2.LEFT, p - orbit * Vector2.LEFT, Color.WEB_GRAY, width + 1)
|
|
viewport_control.draw_line(p - line_len * Vector2.UP, p - orbit * Vector2.UP, color, width)
|
|
viewport_control.draw_line(p - line_len * Vector2.RIGHT, p - orbit * Vector2.RIGHT, color, width)
|
|
viewport_control.draw_line(p - line_len * Vector2.DOWN, p - orbit * Vector2.DOWN, color, width)
|
|
viewport_control.draw_line(p - line_len * Vector2.LEFT, p - orbit * Vector2.LEFT, color, width)
|
|
|
|
|
|
func _draw_add_point_hint(viewport_control : Control, svs : ScalableVectorShape2D, only_cutout_hints : bool) -> void:
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
var p := _vp_transform(mouse_pos)
|
|
if _is_ctrl_or_cmd_pressed() and Input.is_key_pressed(KEY_SHIFT):
|
|
if svs.has_fine_point(mouse_pos):
|
|
_draw_crosshair(viewport_control, p)
|
|
_draw_hint(viewport_control,
|
|
"- Click to %s %s here (Ctrl+Shift held)\n" %
|
|
[OPERATION_NAME_MAP[current_clip_operation]["verb"], SHAPE_NAME_MAP[current_cutout_shape]] +
|
|
"- Use mousewheel to to change the shape of your %s (Ctrl+Shift held)\n" %
|
|
OPERATION_NAME_MAP[current_clip_operation]["noun"] +
|
|
"- Use right click to change the clipping operation (Ctrl+Shift held)"
|
|
)
|
|
else:
|
|
_draw_hint(viewport_control,
|
|
"- Hover over the selected shape to %s %s (Ctrl+Shift held)\n" %
|
|
[
|
|
OPERATION_NAME_MAP[current_clip_operation]["verb"],
|
|
SHAPE_NAME_MAP[current_cutout_shape]
|
|
] +
|
|
"- Use mousewheel to to change the shape of your %s (Ctrl+Shift held)\n" %
|
|
OPERATION_NAME_MAP[current_clip_operation]["noun"] +
|
|
"- Use right click to change the clipping operation (Ctrl+Shift held)"
|
|
)
|
|
elif _is_ctrl_or_cmd_pressed() and not only_cutout_hints:
|
|
_draw_crosshair(viewport_control, p)
|
|
_draw_hint(viewport_control, "- Click to add point here (Ctrl held)")
|
|
elif Input.is_key_pressed(KEY_SHIFT):
|
|
_draw_hint(viewport_control, "- Use mousewheel to resize shape (Shift held)")
|
|
elif not svs.has_meta(META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE):
|
|
var hint := "- Hold Ctrl to add points to selected shape (or Cmd for mac)
|
|
- Hold Shift to resize shape with mousewheel"
|
|
if only_cutout_hints:
|
|
hint = "- Hold Shift to resize shape with mousewheel"
|
|
if svs.has_fine_point(mouse_pos):
|
|
hint += "\n- Double click to subdivide all curve segments
|
|
- Hold Ctrl+Shift to %s %s here (or Cmd+Shift for mac)\n" % [
|
|
OPERATION_NAME_MAP[current_clip_operation]["verb"],
|
|
SHAPE_NAME_MAP[current_cutout_shape]
|
|
]
|
|
_draw_hint(viewport_control, hint)
|
|
|
|
|
|
func _draw_closest_point_on_curve(viewport_control : Control, svs : ScalableVectorShape2D) -> void:
|
|
if _is_ctrl_or_cmd_pressed() or Input.is_key_pressed(KEY_SHIFT):
|
|
_draw_add_point_hint(viewport_control, svs, false)
|
|
return
|
|
|
|
if svs.has_meta(META_NAME_HOVER_CLOSEST_POINT):
|
|
var hint := ""
|
|
var md_p : ClosestPointOnCurveMeta = svs.get_meta(META_NAME_HOVER_CLOSEST_POINT)
|
|
if svs.is_arc_start(md_p.before_segment - 1):
|
|
var arc_start_idx := md_p.before_segment - 1
|
|
var arc := svs.arc_list.get_arc_for_point(arc_start_idx)
|
|
var arc_points := Array(svs.tessellate_arc_segment(
|
|
svs.curve.get_point_position(arc_start_idx),
|
|
arc.radius, arc.rotation_deg, arc.large_arc_flag, arc.sweep_flag,
|
|
svs.curve.get_point_position(arc_start_idx + 1),
|
|
)).map(func(p): return _vp_transform(svs.to_global(p)))
|
|
viewport_control.draw_polyline(arc_points, Color.RED, 3.0, true)
|
|
hint = "- Left click to open arc settings"
|
|
hint += "\n- Right click to remove arc (straighten this line segment)"
|
|
else:
|
|
if Input.is_key_pressed(KEY_ALT):
|
|
_draw_crosshair(viewport_control, _vp_transform(svs.to_global(svs.get_curve_segment_halfway_point(md_p.before_segment))), 3.0, 8, Color.ANTIQUE_WHITE, 2)
|
|
else:
|
|
_draw_crosshair(viewport_control, _vp_transform(md_p.point_position))
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
if svs.curve.point_count > 1:
|
|
if Input.is_key_pressed(KEY_ALT):
|
|
hint += "\n- Left Click to add point halfway the line (Alt held)"
|
|
else:
|
|
hint = "- Double click to add point on the line"
|
|
hint += "\n- Alt + Click to add point halfway the line"
|
|
if md_p.before_segment < svs.curve.point_count:
|
|
hint += "\n- Drag to change curve"
|
|
hint += "\n- Right click to convert line segment to arc"
|
|
else:
|
|
_draw_add_point_hint(viewport_control, svs, false)
|
|
if not hint.is_empty():
|
|
_draw_hint(viewport_control, hint)
|
|
|
|
|
|
func _draw_outline_for_uniform_transforms(viewport_control : Control, svs : ScalableVectorShape2D) -> void:
|
|
viewport_control.draw_polyline(svs.get_bounding_box().map(_vp_transform), VIEWPORT_ORANGE, 2.0)
|
|
_draw_curve_def(viewport_control, svs, svs.shape_hint_color, 0.5, true)
|
|
for idx in svs.curve.point_count:
|
|
var p := svs.to_global(svs.curve.get_point_position(idx))
|
|
_draw_crosshair(viewport_control, _vp_transform(p), 2.0, 4.0, Color.WHITE)
|
|
var natural_center := svs.to_global(svs.get_center())
|
|
if (_uniform_transform_mode == UniformTransformMode.ROTATE
|
|
and _lmb_is_down_inside_viewport
|
|
and Input.is_key_pressed(KEY_SHIFT)
|
|
):
|
|
natural_center = _stored_natural_center
|
|
|
|
_draw_crosshair(viewport_control, _vp_transform(natural_center), 2.0, 4.0, Color.WHITE)
|
|
|
|
|
|
func _draw_canvas_for_uniform_translate(viewport_control : Control, svs : ScalableVectorShape2D) -> void:
|
|
_draw_outline_for_uniform_transforms(viewport_control, svs)
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
_draw_hint(viewport_control, "- Drag to move all points (left mouse button held)")
|
|
else:
|
|
_draw_hint(viewport_control, "- Hold left mouse button to start moving all points" +
|
|
"\n- Press Q to return to normal editing")
|
|
|
|
|
|
func _draw_canvas_for_uniform_rotate(viewport_control : Control, svs : ScalableVectorShape2D) -> void:
|
|
_draw_outline_for_uniform_transforms(viewport_control, svs)
|
|
if _lmb_is_down_inside_viewport:
|
|
var hint_text := "- Drag to rotate all points (left mouse button held)"
|
|
if _is_ctrl_or_cmd_pressed():
|
|
hint_text += "\n- Rotating in steps of 5° (Ctrl held)"
|
|
else:
|
|
hint_text += "\n- Hold Ctrl to rotate in steps of 5°"
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
if Input.is_key_pressed(KEY_SHIFT):
|
|
hint_text += "\n- Rotating around the natural center in stead of the pivot (Shift held)"
|
|
else:
|
|
hint_text += "\n- Hold Shift to rotate around the natural center in stead of the pivot"
|
|
_draw_hint(viewport_control, hint_text)
|
|
else:
|
|
_draw_hint(viewport_control, "- Hold left mouse button to start rotating all points" +
|
|
"\n- Press Q to return to normal editing")
|
|
if _lmb_is_down_inside_viewport:
|
|
var rotation_origin = svs.global_position
|
|
if (Input.is_key_pressed(KEY_SHIFT) or
|
|
svs.shape_type != ScalableVectorShape2D.ShapeType.PATH
|
|
):
|
|
rotation_origin = _stored_natural_center
|
|
var rotation_target := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if _is_ctrl_or_cmd_pressed():
|
|
var ang := snapped(rotation_origin.angle_to_point(rotation_target), deg_to_rad(5.0))
|
|
var dst := rotation_origin.distance_to(rotation_target)
|
|
rotation_target = rotation_origin + Vector2.RIGHT.rotated(ang) * dst
|
|
viewport_control.draw_line(_vp_transform(rotation_origin),_vp_transform(rotation_target),
|
|
svs.shape_hint_color, 1, true)
|
|
|
|
|
|
func _draw_canvas_for_uniform_scale(viewport_control : Control, svs : ScalableVectorShape2D) -> void:
|
|
_draw_outline_for_uniform_transforms(viewport_control, svs)
|
|
if _lmb_is_down_inside_viewport:
|
|
var hint_text := "- Drag to scale all points (left mouse button held)"
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
if Input.is_key_pressed(KEY_SHIFT):
|
|
hint_text += "\n- Scaling out from natural center (Shift held)"
|
|
else:
|
|
hint_text += "\n- Hold Shift to scale out from natural center in stead of pivot"
|
|
_draw_hint(viewport_control, hint_text)
|
|
else:
|
|
_draw_hint(viewport_control, "- Hold left mouse button to start scaling all points" +
|
|
"\n- Press Q to return to normal editing")
|
|
|
|
if _lmb_is_down_inside_viewport:
|
|
var origin = svs.global_position
|
|
if (Input.is_key_pressed(KEY_SHIFT) or
|
|
svs.shape_type != ScalableVectorShape2D.ShapeType.PATH
|
|
):
|
|
origin = svs.to_global(svs.get_center())
|
|
var target := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
viewport_control.draw_line(_vp_transform(origin),_vp_transform(target),
|
|
svs.shape_hint_color, 1, true)
|
|
|
|
|
|
func _find_merge_vertices() -> Dictionary[ScalableVectorShape2D, int]:
|
|
var vertex_map : Dictionary[ScalableVectorShape2D, int] = {}
|
|
for svs : ScalableVectorShape2D in EditorInterface.get_edited_scene_root().find_children("*", "ScalableVectorShape2D"):
|
|
var point_was_found_inside_rect := false
|
|
for idx in svs.curve.point_count:
|
|
var p := svs.to_global(svs.curve.get_point_position(idx))
|
|
var p_inside_rect := _merge_box_rect.abs().has_point(_vp_transform(p))
|
|
if p_inside_rect and not point_was_found_inside_rect:
|
|
vertex_map[svs] = idx
|
|
point_was_found_inside_rect = true
|
|
|
|
return vertex_map
|
|
|
|
|
|
func _handle_draw_vertex_merge_box(viewport_control: Control) -> void:
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
_draw_hint(viewport_control, "Drag box around a point of 2 or more ScalableVectorShape2D to merge them")
|
|
|
|
viewport_control.draw_rect(_merge_box_rect, Color.LIME, false, 1)
|
|
for svs : ScalableVectorShape2D in EditorInterface.get_edited_scene_root().find_children("*", "ScalableVectorShape2D"):
|
|
for idx in svs.curve.point_count:
|
|
_draw_crosshair(
|
|
viewport_control,
|
|
_vp_transform(svs.to_global(svs.curve.get_point_position(idx))),
|
|
2.0, 4.0, VIEWPORT_ORANGE, 1
|
|
)
|
|
|
|
var vertex_map := _find_merge_vertices()
|
|
for svs : ScalableVectorShape2D in vertex_map.keys():
|
|
_draw_crosshair(viewport_control, _vp_transform(
|
|
svs.to_global(svs.curve.get_point_position(vertex_map[svs]))
|
|
), 2.0, 5.0, Color.WHITE, 2)
|
|
|
|
if vertex_map.size() > 1:
|
|
var entries = ""
|
|
for k in vertex_map.keys():
|
|
entries += "\n - " + k.name
|
|
_draw_hint(viewport_control, "\nMerge points of:%s" % entries)
|
|
|
|
|
|
func _forward_canvas_draw_over_viewport(viewport_control: Control) -> void:
|
|
if not _is_editing_enabled():
|
|
return
|
|
if not is_instance_valid(EditorInterface.get_edited_scene_root()):
|
|
return
|
|
if merge_node_toggle_button.button_pressed:
|
|
return _handle_draw_vertex_merge_box(viewport_control)
|
|
|
|
var current_selection := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
if _is_svs_valid(current_selection) and _get_select_mode_button().button_pressed:
|
|
if _uniform_transform_mode == UniformTransformMode.TRANSLATE:
|
|
return _draw_canvas_for_uniform_translate(viewport_control, current_selection)
|
|
elif _uniform_transform_mode == UniformTransformMode.SCALE:
|
|
return _draw_canvas_for_uniform_scale(viewport_control, current_selection)
|
|
elif _uniform_transform_mode == UniformTransformMode.ROTATE:
|
|
return _draw_canvas_for_uniform_rotate(viewport_control, current_selection)
|
|
|
|
var all_valid_svs_nodes := _find_scalable_vector_shape_2d_nodes().filter(_is_svs_valid)
|
|
for result : ScalableVectorShape2D in all_valid_svs_nodes:
|
|
if result == current_selection:
|
|
viewport_control.draw_polyline(result.get_bounding_box().map(_vp_transform),
|
|
VIEWPORT_ORANGE, 2.0)
|
|
_draw_curve(viewport_control, result)
|
|
_draw_handles(viewport_control, result)
|
|
if not _handle_has_hover(result):
|
|
if result.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
if result.has_meta(META_NAME_HOVER_CLOSEST_POINT):
|
|
_draw_closest_point_on_curve(viewport_control, result)
|
|
else:
|
|
_draw_add_point_hint(viewport_control, result, false)
|
|
else:
|
|
_draw_add_point_hint(viewport_control, result, true)
|
|
elif result.has_meta(META_NAME_SELECT_HINT):
|
|
viewport_control.draw_polyline(result.get_bounding_box().map(_vp_transform),
|
|
Color.WEB_GRAY, 1.0)
|
|
if not(result.line or result.collision_polygon or result.polygon):
|
|
_draw_curve(viewport_control, result, false)
|
|
|
|
if shape_preview:
|
|
var points := Array(shape_preview.tessellate())
|
|
var stroke_width = (_get_default_stroke_width() * EditorInterface.get_editor_viewport_2d()
|
|
.get_final_transform().get_scale().x)
|
|
if current_selection is Node2D:
|
|
points = points.map(current_selection.to_global)
|
|
stroke_width *= current_selection.global_scale.x
|
|
elif current_selection is Control:
|
|
points = points.map(func(p): return current_selection.get_global_transform() * p)
|
|
stroke_width *= current_selection.get_global_transform().get_scale().x
|
|
points = points.map(_vp_transform)
|
|
match _get_default_paint_order():
|
|
PaintOrder.MARKERS_STROKE_FILL, PaintOrder.STROKE_FILL_MARKERS, PaintOrder.STROKE_MARKERS_FILL:
|
|
if _is_add_stroke_enabled():
|
|
viewport_control.draw_polyline(points, _get_default_stroke_color(), stroke_width)
|
|
if _is_add_fill_enabled():
|
|
viewport_control.draw_polygon(points, [_get_default_fill_color()])
|
|
PaintOrder.MARKERS_FILL_STROKE, PaintOrder.FILL_STROKE_MARKERS, PaintOrder.FILL_MARKERS_STROKE, _:
|
|
if _is_add_fill_enabled():
|
|
viewport_control.draw_polygon(points, [_get_default_fill_color()])
|
|
if _is_add_stroke_enabled():
|
|
viewport_control.draw_polyline(points, _get_default_stroke_color(), stroke_width)
|
|
|
|
if not _is_add_fill_enabled() and not _is_add_stroke_enabled():
|
|
viewport_control.draw_polyline(points, Color.LIME, 1)
|
|
|
|
|
|
func _start_undo_redo_transaction(name := "") -> void:
|
|
in_undo_redo_transaction = true
|
|
undo_redo_transaction = {
|
|
UndoRedoEntry.NAME: name,
|
|
UndoRedoEntry.DOS: [],
|
|
UndoRedoEntry.UNDOS: [],
|
|
UndoRedoEntry.DO_PROPS: [],
|
|
UndoRedoEntry.UNDO_PROPS : []
|
|
}
|
|
|
|
func _commit_undo_redo_transaction() -> void:
|
|
if not in_undo_redo_transaction:
|
|
return
|
|
in_undo_redo_transaction = false
|
|
undo_redo.create_action(undo_redo_transaction[UndoRedoEntry.NAME])
|
|
for do_method in undo_redo_transaction[UndoRedoEntry.DOS]:
|
|
undo_redo.callv('add_do_method', do_method)
|
|
for do_prop in undo_redo_transaction[UndoRedoEntry.DO_PROPS]:
|
|
undo_redo.callv('add_do_property', do_prop)
|
|
for undo_method in undo_redo_transaction[UndoRedoEntry.UNDOS]:
|
|
undo_redo.callv('add_undo_method', undo_method)
|
|
for undo_prop in undo_redo_transaction[UndoRedoEntry.UNDO_PROPS]:
|
|
undo_redo.callv('add_undo_property', undo_prop)
|
|
undo_redo.commit_action(false)
|
|
undo_redo_transaction = {
|
|
UndoRedoEntry.NAME: name,
|
|
UndoRedoEntry.DOS: [],
|
|
UndoRedoEntry.UNDOS: [],
|
|
UndoRedoEntry.UNDO_PROPS: [],
|
|
UndoRedoEntry.DO_PROPS: []
|
|
}
|
|
|
|
|
|
func _on_global_position_for_handle_changed(global_pos : Vector2, meta_name: String, idx : int) -> void:
|
|
var cur := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
if _is_svs_valid(cur):
|
|
match(meta_name):
|
|
META_NAME_HOVER_CP_IN_IDX:
|
|
_update_curve_cp_in_position(cur, global_pos, idx)
|
|
META_NAME_HOVER_CP_OUT_IDX:
|
|
_update_curve_cp_out_position(cur, global_pos, idx)
|
|
META_NAME_HOVER_POINT_IDX:
|
|
_update_curve_point_position(cur, global_pos, idx)
|
|
update_overlays()
|
|
|
|
|
|
func _update_curve_point_position(current_selection : ScalableVectorShape2D, mouse_pos : Vector2, idx : int) -> void:
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Move point on " + str(current_selection))
|
|
if idx == 0 and current_selection.is_curve_closed():
|
|
var idx_1 = current_selection.curve.point_count - 1
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([
|
|
current_selection.curve, 'set_point_position', idx_1, current_selection.curve.get_point_position(idx_1)
|
|
])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([
|
|
current_selection.curve, 'set_point_position', idx, current_selection.curve.get_point_position(idx)
|
|
])
|
|
|
|
undo_redo_transaction[UndoRedoEntry.DOS] = []
|
|
if idx == 0 and current_selection.is_curve_closed():
|
|
var idx_1 = current_selection.curve.point_count - 1
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([
|
|
current_selection, 'set_global_curve_point_position', mouse_pos, idx_1,
|
|
_is_snapped_to_pixel(), _get_snap_resolution()
|
|
])
|
|
current_selection.set_global_curve_point_position(mouse_pos, idx_1,
|
|
_is_snapped_to_pixel(), _get_snap_resolution())
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([
|
|
current_selection, 'set_global_curve_point_position', mouse_pos, idx,
|
|
_is_snapped_to_pixel(), _get_snap_resolution()
|
|
])
|
|
current_selection.set_global_curve_point_position(mouse_pos, idx,
|
|
_is_snapped_to_pixel(), _get_snap_resolution())
|
|
|
|
|
|
func _update_rect_dimensions(svs : ScalableVectorShape2D, mouse_pos : Vector2) -> void:
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Change rect size on " + str(svs))
|
|
undo_redo_transaction[UndoRedoEntry.UNDO_PROPS] = [[svs, 'size', svs.size]]
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
var top_left := (-svs.size * 0.5).rotated(svs.spin) + svs.offset
|
|
svs.size = ((svs.to_local(mouse_pos)) - top_left).rotated(-svs.spin)
|
|
undo_redo_transaction[UndoRedoEntry.DO_PROPS] = [[svs, 'size', svs.size]]
|
|
|
|
|
|
func _update_rect_corner_radius(svs : ScalableVectorShape2D, mouse_pos : Vector2, prop_name : String, is_symmetrical : bool) -> void:
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Change rect " + prop_name + " on " + str(svs))
|
|
undo_redo_transaction[UndoRedoEntry.UNDO_PROPS] = [
|
|
[svs, 'rx', svs.rx], [svs, 'ry', svs.ry]
|
|
]
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
var top_left := (-svs.size * 0.5).rotated(svs.spin) + svs.offset
|
|
if prop_name == 'rx':
|
|
svs.rx = svs.to_local(mouse_pos).rotated(-svs.spin).x - top_left.rotated(-svs.spin).x
|
|
if is_symmetrical:
|
|
svs.ry = svs.rx
|
|
if prop_name == 'ry':
|
|
svs.ry = svs.to_local(mouse_pos).rotated(-svs.spin).y - top_left.rotated(-svs.spin).y
|
|
if is_symmetrical:
|
|
svs.rx = svs.ry
|
|
|
|
undo_redo_transaction[UndoRedoEntry.DO_PROPS] = [
|
|
[svs, 'rx', svs.rx],
|
|
[svs, 'ry', svs.ry]
|
|
]
|
|
|
|
|
|
func _update_curve_cp_in_position(current_selection : ScalableVectorShape2D, mouse_pos : Vector2, idx : int) -> void:
|
|
if idx == 0:
|
|
idx = current_selection.curve.point_count - 1
|
|
|
|
var cp_in_is_cp_out_of_loop_start := (Input.is_key_pressed(KEY_SHIFT) and
|
|
not(idx == current_selection.curve.point_count - 1
|
|
and not current_selection.is_curve_closed())
|
|
)
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Move control point in %d on %s" % [idx, current_selection])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([current_selection.curve, 'set_point_in', idx, current_selection.curve.get_point_in(idx)])
|
|
if cp_in_is_cp_out_of_loop_start:
|
|
var idx_1 = 0 if idx == current_selection.curve.point_count - 1 else idx
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([current_selection.curve, 'set_point_out', idx_1, current_selection.curve.get_point_out(idx_1)])
|
|
|
|
current_selection.set_global_curve_cp_in_position(mouse_pos, idx,
|
|
_is_snapped_to_pixel(), _get_snap_resolution())
|
|
undo_redo_transaction[UndoRedoEntry.DOS] = [[
|
|
current_selection, 'set_global_curve_cp_in_position', mouse_pos, idx,
|
|
_is_snapped_to_pixel(), _get_snap_resolution()
|
|
]]
|
|
if cp_in_is_cp_out_of_loop_start:
|
|
var idx_1 = 0 if idx == current_selection.curve.point_count - 1 else idx
|
|
current_selection.curve.set_point_out(idx_1, -current_selection.curve.get_point_in(idx))
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([current_selection.curve, 'set_point_out', idx_1, -current_selection.curve.get_point_in(idx)])
|
|
|
|
|
|
func _update_gradient_from_position(svs : ScalableVectorShape2D, mouse_pos : Vector2) -> void:
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Move gradient from position for %s" % str(svs))
|
|
undo_redo_transaction[UndoRedoEntry.UNDO_PROPS].append([svs.polygon.texture, 'fill_from',
|
|
svs.polygon.texture.fill_from])
|
|
var box := svs.get_bounding_rect()
|
|
svs.polygon.texture.fill_from = (svs.to_local(mouse_pos) - box.position) / box.size
|
|
undo_redo_transaction[UndoRedoEntry.DO_PROPS] = [[
|
|
svs.polygon.texture, 'fill_from', svs.polygon.texture.fill_from
|
|
]]
|
|
|
|
|
|
func _update_gradient_to_position(svs : ScalableVectorShape2D, mouse_pos : Vector2) -> void:
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Move gradient from position for %s" % str(svs))
|
|
undo_redo_transaction[UndoRedoEntry.UNDO_PROPS].append([svs.polygon.texture, 'fill_to',
|
|
svs.polygon.texture.fill_to])
|
|
var box := svs.get_bounding_rect()
|
|
svs.polygon.texture.fill_to = (svs.to_local(mouse_pos) - box.position) / box.size
|
|
undo_redo_transaction[UndoRedoEntry.DO_PROPS] = [[
|
|
svs.polygon.texture, 'fill_to', svs.polygon.texture.fill_to
|
|
]]
|
|
|
|
|
|
func _get_gradient_offset(svs : ScalableVectorShape2D, mouse_pos : Vector2) -> float:
|
|
var box := svs.get_bounding_rect()
|
|
var gradient_tex : GradientTexture2D = svs.polygon.texture
|
|
var p := ((svs.to_local(mouse_pos) - box.position) / box.size)
|
|
var p1 := Geometry2D.get_closest_point_to_segment(p, gradient_tex.fill_from, gradient_tex.fill_to)
|
|
return p1.distance_to(gradient_tex.fill_from) / gradient_tex.fill_from.distance_to(gradient_tex.fill_to)
|
|
|
|
|
|
func _update_gradient_stop_color_pos(svs : ScalableVectorShape2D, mouse_pos : Vector2, idx : int) -> void:
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
var new_offset := _get_gradient_offset(svs, mouse_pos)
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Move gradient offset %d on %s" % [idx, svs])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs.polygon.texture.gradient,
|
|
'set_offset', idx, svs.polygon.texture.gradient.offsets[idx]])
|
|
undo_redo_transaction[UndoRedoEntry.DOS] = [[
|
|
svs.polygon.texture.gradient, 'set_offset', idx, new_offset
|
|
]]
|
|
svs.polygon.texture.gradient.set_offset(idx, new_offset)
|
|
|
|
|
|
func _add_color_stop(svs : ScalableVectorShape2D, mouse_pos : Vector2) -> void:
|
|
var new_offset := _get_gradient_offset(svs, mouse_pos)
|
|
var colors = Array(svs.polygon.texture.gradient.colors)
|
|
var offsets = Array(svs.polygon.texture.gradient.offsets)
|
|
var stops = {}
|
|
for idx in range(colors.size()):
|
|
stops[offsets[idx]] = colors[idx]
|
|
stops[new_offset] = svs.polygon.texture.gradient.sample(new_offset)
|
|
var stop_keys := stops.keys()
|
|
stop_keys.sort()
|
|
var new_colors = []
|
|
var new_offsets = []
|
|
for offset in stop_keys:
|
|
new_colors.append(stops[offset])
|
|
new_offsets.append(offset)
|
|
|
|
undo_redo.create_action("Add color stop to %s " % str(svs))
|
|
undo_redo.add_do_property(svs.polygon.texture.gradient, 'colors', new_colors)
|
|
undo_redo.add_do_property(svs.polygon.texture.gradient, 'offsets', new_offsets)
|
|
undo_redo.add_do_method(svs, 'notify_assigned_node_change')
|
|
undo_redo.add_undo_property(svs.polygon.texture.gradient, 'colors', colors)
|
|
undo_redo.add_undo_property(svs.polygon.texture.gradient, 'offsets', offsets)
|
|
undo_redo.add_undo_method(svs, 'notify_assigned_node_change')
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _remove_color_stop(svs : ScalableVectorShape2D, remove_idx : int) -> void:
|
|
var colors = Array(svs.polygon.texture.gradient.colors)
|
|
var offsets = Array(svs.polygon.texture.gradient.offsets)
|
|
var stops = {}
|
|
for idx in range(colors.size()):
|
|
if idx != remove_idx:
|
|
stops[offsets[idx]] = colors[idx]
|
|
var stop_keys := stops.keys()
|
|
stop_keys.sort()
|
|
var new_colors = []
|
|
var new_offsets = []
|
|
for offset in stop_keys:
|
|
new_colors.append(stops[offset])
|
|
new_offsets.append(offset)
|
|
|
|
undo_redo.create_action("Remove color stop from %s " % str(svs))
|
|
undo_redo.add_do_property(svs.polygon.texture.gradient, 'colors', new_colors)
|
|
undo_redo.add_do_property(svs.polygon.texture.gradient, 'offsets', new_offsets)
|
|
undo_redo.add_do_method(svs, 'notify_assigned_node_change')
|
|
undo_redo.add_undo_property(svs.polygon.texture.gradient, 'colors', colors)
|
|
undo_redo.add_undo_property(svs.polygon.texture.gradient, 'offsets', offsets)
|
|
undo_redo.add_undo_method(svs, 'notify_assigned_node_change')
|
|
undo_redo.commit_action()
|
|
|
|
func _update_curve_cp_out_position(current_selection : ScalableVectorShape2D, mouse_pos : Vector2, idx : int) -> void:
|
|
if idx == current_selection.curve.point_count - 1:
|
|
idx = 0
|
|
|
|
var cp_out_is_cp_in_of_loop_end := (Input.is_key_pressed(KEY_SHIFT)
|
|
and not(idx == 0 and not current_selection.is_curve_closed()))
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Move control point out %d on %s" % [idx, current_selection])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([current_selection.curve, 'set_point_out', idx, current_selection.curve.get_point_out(idx)])
|
|
if cp_out_is_cp_in_of_loop_end:
|
|
var idx_1 = current_selection.curve.point_count - 1 if idx == 0 else idx
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([current_selection.curve, 'set_point_in', idx_1, current_selection.curve.get_point_in(idx_1)])
|
|
|
|
current_selection.set_global_curve_cp_out_position(mouse_pos, idx,
|
|
_is_snapped_to_pixel(), _get_snap_resolution())
|
|
undo_redo_transaction[UndoRedoEntry.DOS] = [[
|
|
current_selection, 'set_global_curve_cp_out_position', mouse_pos, idx,
|
|
_is_snapped_to_pixel(), _get_snap_resolution()
|
|
]]
|
|
if cp_out_is_cp_in_of_loop_end:
|
|
var idx_1 = current_selection.curve.point_count - 1 if idx == 0 else idx
|
|
current_selection.curve.set_point_in(idx_1, -current_selection.curve.get_point_out(idx))
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([current_selection.curve, 'set_point_in', idx_1, -current_selection.curve.get_point_out(idx)])
|
|
|
|
|
|
func _set_shape_origin(current_selection : ScalableVectorShape2D, mouse_pos : Vector2) -> void:
|
|
undo_redo.create_action("Set origin on %s" % current_selection)
|
|
undo_redo.add_do_method(current_selection, 'set_origin', mouse_pos)
|
|
undo_redo.add_undo_method(current_selection, 'set_origin', current_selection.global_position)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _get_curve_backup(curve_in : Curve2D) -> Curve2D:
|
|
return curve_in.duplicate()
|
|
|
|
|
|
func _resize_shape(svs : ScalableVectorShape2D, s : float) -> void:
|
|
if svs.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Resize shape %s" % str(svs))
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([
|
|
svs, 'replace_curve_points', _get_curve_backup(svs.curve)])
|
|
|
|
undo_redo_transaction[UndoRedoEntry.DOS] = []
|
|
for idx in range(svs.curve.point_count):
|
|
svs.curve.set_point_position(idx, svs.curve.get_point_position(idx) * s)
|
|
svs.curve.set_point_in(idx, svs.curve.get_point_in(idx) * s)
|
|
svs.curve.set_point_out(idx, svs.curve.get_point_out(idx) * s)
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs.curve,
|
|
'set_point_position', idx, svs.curve.get_point_position(idx) * s])
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs.curve,
|
|
'set_point_in', idx, svs.curve.get_point_in(idx) * s])
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs.curve,
|
|
'set_point_out', idx, svs.curve.get_point_out(idx) * s])
|
|
else:
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Resize shape %s" % str(svs))
|
|
undo_redo_transaction[UndoRedoEntry.UNDO_PROPS] = [[svs, 'size', svs.size]]
|
|
undo_redo_transaction[UndoRedoEntry.DO_PROPS] = [[svs, 'size', svs.size * s]]
|
|
svs.size *= s
|
|
|
|
|
|
func _remove_point_from_curve(current_selection : ScalableVectorShape2D, idx : int) -> void:
|
|
var orig_n := current_selection.curve.point_count
|
|
if current_selection.is_curve_closed() and idx == 0:
|
|
idx = orig_n - 1
|
|
|
|
var backup := _get_curve_backup(current_selection.curve)
|
|
undo_redo.create_action("Remove point %d from %s" % [idx, str(current_selection)])
|
|
undo_redo.add_do_method(current_selection.curve, 'set_point_in', 0, Vector2.ZERO)
|
|
if orig_n > 2:
|
|
undo_redo.add_do_method(current_selection.curve, 'set_point_out', orig_n - 2, Vector2.ZERO)
|
|
|
|
var redo_arcs : Array[ScalableArc] = []
|
|
for a : ScalableArc in current_selection.arc_list.arcs:
|
|
if a.start_point == idx or a.start_point == idx - 1:
|
|
undo_redo.add_do_method(current_selection.arc_list, 'remove_arc_for_point', a.start_point)
|
|
redo_arcs.append(a)
|
|
|
|
undo_redo.add_do_method(current_selection.curve, 'remove_point', idx)
|
|
undo_redo.add_do_method(current_selection.arc_list, 'handle_point_removed_at_index', idx)
|
|
undo_redo.add_undo_method(current_selection, 'replace_curve_points', backup)
|
|
undo_redo.add_undo_method(current_selection.arc_list, 'handle_point_added_at_index', idx)
|
|
for a in redo_arcs:
|
|
undo_redo.add_undo_reference(a)
|
|
undo_redo.add_undo_method(current_selection.arc_list, 'add_arc', a)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _remove_cp_in_from_curve(current_selection : ScalableVectorShape2D, idx : int) -> void:
|
|
if idx == 0:
|
|
idx = current_selection.curve.point_count - 1
|
|
undo_redo.create_action("Remove control point in %d from %s " % [idx, str(current_selection)])
|
|
undo_redo.add_do_method(current_selection.curve, 'set_point_in', idx, Vector2.ZERO)
|
|
undo_redo.add_undo_method(current_selection.curve, 'set_point_in', idx, current_selection.curve.get_point_in(idx))
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _remove_cp_out_from_curve(current_selection : ScalableVectorShape2D, idx : int) -> void:
|
|
if idx == current_selection.curve.point_count - 1:
|
|
idx = 0
|
|
undo_redo.create_action("Remove control point out %d from %s " % [idx, str(current_selection)])
|
|
undo_redo.add_do_method(current_selection.curve, 'set_point_out', idx, Vector2.ZERO)
|
|
undo_redo.add_undo_method(current_selection.curve, 'set_point_out', idx, current_selection.curve.get_point_out(idx))
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _remove_rounded_corners_from_rect(svs : ScalableVectorShape2D):
|
|
undo_redo.create_action("Remove rounded corners from %s " % str(svs))
|
|
undo_redo.add_do_property(svs, 'rx', 0.0)
|
|
undo_redo.add_do_property(svs, 'ry', 0.0)
|
|
undo_redo.add_undo_property(svs, 'rx', svs.rx)
|
|
undo_redo.add_undo_property(svs, 'ry', svs.ry)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _add_point_to_curve(svs : ScalableVectorShape2D, local_pos : Vector2,
|
|
cp_in := Vector2.ZERO, cp_out := Vector2.ZERO, idx := -1, do_commit := true) -> void:
|
|
undo_redo.create_action("Add point at %s to %s " % [str(local_pos), str(svs)])
|
|
|
|
undo_redo.add_do_method(svs.curve, 'add_point', local_pos, cp_in, cp_out, idx)
|
|
if idx < 0:
|
|
undo_redo.add_undo_method(svs.curve, 'remove_point', svs.curve.point_count)
|
|
else:
|
|
undo_redo.add_do_method(svs.arc_list, 'handle_point_added_at_index', idx)
|
|
undo_redo.add_undo_method(svs.curve, 'remove_point', idx)
|
|
undo_redo.add_undo_method(svs.arc_list, 'handle_point_removed_at_index', idx)
|
|
if not do_commit:
|
|
return
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _create_arc(svs : ScalableVectorShape2D, start_point_idx : int) -> void:
|
|
undo_redo.create_action("Remove arc for segment %d on %s " % [start_point_idx, str(svs)])
|
|
undo_redo.add_do_method(svs, 'add_arc', start_point_idx)
|
|
undo_redo.add_undo_method(svs.arc_list, 'remove_arc_for_point', start_point_idx)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _remove_arc(svs : ScalableVectorShape2D, start_point_idx : int) -> void:
|
|
var redo_arc := svs.arc_list.get_arc_for_point(start_point_idx)
|
|
if redo_arc == null:
|
|
return
|
|
undo_redo.create_action("Remove arc for segment %d on %s " % [start_point_idx, str(svs)])
|
|
undo_redo.add_do_method(svs.arc_list, 'remove_arc_for_point', start_point_idx)
|
|
undo_redo.add_undo_method(svs.arc_list, 'add_arc', redo_arc)
|
|
undo_redo.add_undo_reference(redo_arc)
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _add_point_on_position(svs : ScalableVectorShape2D, pos : Vector2) -> void:
|
|
if svs.shape_type != ScalableVectorShape2D.ShapeType.PATH:
|
|
return
|
|
_add_point_to_curve(svs, svs.to_local(pos))
|
|
|
|
|
|
func _start_cutout_shape(svs : ScalableVectorShape2D, pos : Vector2) -> void:
|
|
var new_shape = ScalableVectorShape2D.new()
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
new_shape.curve = Curve2D.new()
|
|
new_shape.position = svs.to_local(mouse_pos)
|
|
new_shape.shape_type = current_cutout_shape
|
|
new_shape.curve.add_point(Vector2.ZERO)
|
|
|
|
_create_shape(new_shape, EditorInterface.get_edited_scene_root(), "CutoutOf%s" % svs.name, svs)
|
|
|
|
|
|
func _add_point_on_curve_segment(svs : ScalableVectorShape2D, subdivide := false) -> void:
|
|
if svs.shape_type != ScalableVectorShape2D.ShapeType.PATH:
|
|
return
|
|
if not svs.has_meta(META_NAME_HOVER_CLOSEST_POINT):
|
|
return
|
|
var md_closest_point : ClosestPointOnCurveMeta = svs.get_meta(META_NAME_HOVER_CLOSEST_POINT)
|
|
if svs.is_arc_start(md_closest_point.before_segment - 1):
|
|
return
|
|
var placement_point := svs.get_curve_segment_halfway_point(md_closest_point.before_segment) if subdivide else md_closest_point.local_point_position
|
|
if md_closest_point.before_segment >= svs.curve.point_count:
|
|
_add_point_to_curve(svs, placement_point)
|
|
else:
|
|
if (
|
|
svs.curve.get_point_out(md_closest_point.before_segment - 1).length() > 0.0 or
|
|
svs.curve.get_point_in(md_closest_point.before_segment).length() > 0.0
|
|
):
|
|
# This is a curved segment, so when a point is added, control points are recalculated
|
|
var sliced_segment := svs.get_sliced_curve_segment(md_closest_point.before_segment, placement_point)
|
|
_add_point_to_curve(svs, placement_point,
|
|
Vector2.ZERO, Vector2.ZERO, md_closest_point.before_segment, false)
|
|
undo_redo.add_do_method(svs.curve, "set_point_out", md_closest_point.before_segment - 1, sliced_segment.get_point_out(0))
|
|
undo_redo.add_undo_method(svs.curve, "set_point_out", md_closest_point.before_segment -1, svs.curve.get_point_out(md_closest_point.before_segment - 1))
|
|
undo_redo.add_do_method(svs.curve, "set_point_in", md_closest_point.before_segment, sliced_segment.get_point_in(1))
|
|
undo_redo.add_undo_method(svs.curve, "set_point_in", md_closest_point.before_segment, svs.curve.get_point_in(md_closest_point.before_segment))
|
|
undo_redo.add_do_method(svs.curve, "set_point_out", md_closest_point.before_segment, sliced_segment.get_point_out(1))
|
|
undo_redo.add_undo_method(svs.curve, "set_point_out", md_closest_point.before_segment, svs.curve.get_point_out(md_closest_point.before_segment))
|
|
undo_redo.add_do_method(svs.curve, "set_point_in", md_closest_point.before_segment + 1, sliced_segment.get_point_in(2))
|
|
undo_redo.add_undo_reference(svs.curve)
|
|
undo_redo.commit_action()
|
|
else:
|
|
_add_point_to_curve(svs, placement_point,
|
|
Vector2.ZERO, Vector2.ZERO, md_closest_point.before_segment)
|
|
|
|
|
|
func _subdivide_curve(svs : ScalableVectorShape2D) -> void:
|
|
undo_redo.create_action("Subdivide shape %s" % str(svs))
|
|
undo_redo.add_do_property(svs, 'curve', svs.get_subdivided_curve())
|
|
undo_redo.add_undo_property(svs, 'curve', svs.curve.duplicate())
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _drag_curve_segment(svs : ScalableVectorShape2D, mouse_pos : Vector2) -> void:
|
|
if svs.shape_type != ScalableVectorShape2D.ShapeType.PATH:
|
|
return
|
|
if not svs.has_meta(META_NAME_HOVER_CLOSEST_POINT):
|
|
return
|
|
var md_closest_point : ClosestPointOnCurveMeta = svs.get_meta(META_NAME_HOVER_CLOSEST_POINT)
|
|
if svs.is_arc_start(md_closest_point.before_segment - 1) or md_closest_point.before_segment >= svs.curve.point_count or md_closest_point.before_segment < 1:
|
|
return
|
|
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
# Compute control points based on mouse position to align middle of segment curve to it
|
|
# using the quadratic Bézier control point
|
|
var idx : int = md_closest_point.before_segment
|
|
var segment_start_point := svs.curve.get_point_position(idx - 1)
|
|
var segment_end_point := svs.curve.get_point_position(idx)
|
|
var halfway_point := (segment_start_point + segment_end_point) / 2
|
|
var dir := halfway_point.direction_to(svs.to_local(mouse_pos))
|
|
var distance := halfway_point.distance_to(svs.to_local(mouse_pos))
|
|
var quadratic_bezier_control_point := halfway_point + distance * 2 * dir
|
|
var new_point_out := (quadratic_bezier_control_point - segment_start_point) * (2.0 / 3.0)
|
|
var new_point_in := (quadratic_bezier_control_point - segment_end_point) * (2.0 / 3.0)
|
|
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Change curve segment %d->%d for %s" % [idx - 1, idx, str(svs)])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs.curve, 'set_point_in', idx, svs.curve.get_point_in(idx)])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs.curve, 'set_point_out', idx - 1, svs.curve.get_point_out(idx - 1)])
|
|
undo_redo_transaction[UndoRedoEntry.DOS] = [[svs.curve, 'set_point_out', idx - 1, new_point_out]]
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs.curve, 'set_point_in', idx, new_point_in])
|
|
svs.curve.set_point_out(idx - 1, new_point_out)
|
|
svs.curve.set_point_in(idx, new_point_in)
|
|
md_closest_point["point_position"] = mouse_pos
|
|
svs.set_meta(META_NAME_HOVER_CLOSEST_POINT, md_closest_point)
|
|
update_overlays()
|
|
|
|
|
|
func _toggle_loop_if_applies(svs : ScalableVectorShape2D, idx : int) -> void:
|
|
if svs.shape_type != ScalableVectorShape2D.ShapeType.PATH:
|
|
return
|
|
if svs.curve.point_count < 3:
|
|
return
|
|
if idx == 0 or idx == svs.curve.point_count - 1:
|
|
if svs.is_curve_closed():
|
|
_remove_point_from_curve(svs, svs.curve.point_count - 1)
|
|
else:
|
|
_add_point_to_curve(svs, svs.curve.get_point_position(0))
|
|
|
|
|
|
func _get_vp_h_scroll_bar() -> HScrollBar:
|
|
var editor_vp := EditorInterface.get_editor_viewport_2d().find_parent("*CanvasItemEditor*")
|
|
if editor_vp == null:
|
|
return null
|
|
return editor_vp.find_child("*HScrollBar*", true, false)
|
|
|
|
|
|
func _handle_input_for_uniform_translate(event : InputEvent, svs : ScalableVectorShape2D) -> bool:
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
|
|
if (event as InputEventMouseButton).pressed:
|
|
_drag_start = mouse_pos
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Translate curve points for %s" % str(svs))
|
|
update_overlays()
|
|
return true
|
|
|
|
if event is InputEventMouseMotion:
|
|
update_overlays()
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
var drag_delta := mouse_pos - _drag_start
|
|
if _is_snapped_to_pixel():
|
|
drag_delta = drag_delta.snapped(Vector2.ONE * _get_snap_resolution())
|
|
if drag_delta.abs() > Vector2.ZERO:
|
|
_drag_start = mouse_pos
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs, 'translate_points_by', drag_delta])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs, 'translate_points_by', -drag_delta])
|
|
svs.translate_points_by(drag_delta)
|
|
return true
|
|
return false
|
|
|
|
|
|
func _flip_svs_horizontal():
|
|
var svs := EditorInterface.get_selection().get_selected_nodes().pop_front()
|
|
if svs is ScalableVectorShape2D:
|
|
undo_redo.create_action("Flip %s horizontally" % str(svs))
|
|
undo_redo.add_do_method(svs, 'flip_points')
|
|
undo_redo.add_undo_method(svs, 'flip_points')
|
|
undo_redo.commit_action()
|
|
|
|
|
|
func _flip_svs_vertical():
|
|
var svs := EditorInterface.get_selection().get_selected_nodes().pop_front()
|
|
if svs is ScalableVectorShape2D:
|
|
undo_redo.create_action("Flip %s vertically" % str(svs))
|
|
undo_redo.add_do_method(svs, 'flip_points', Vector2(1, -1))
|
|
undo_redo.add_undo_method(svs, 'flip_points', Vector2(1, -1))
|
|
undo_redo.commit_action()
|
|
|
|
|
|
|
|
func _handle_input_for_uniform_scale(event : InputEvent, svs : ScalableVectorShape2D) -> bool:
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
|
|
if (event as InputEventMouseButton).pressed:
|
|
_drag_start = mouse_pos
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Scale curve points for %s" % str(svs))
|
|
update_overlays()
|
|
return true
|
|
|
|
if event is InputEventMouseMotion:
|
|
update_overlays()
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
var drag_delta := mouse_pos - _drag_start
|
|
if _is_snapped_to_pixel():
|
|
drag_delta = drag_delta.snapped(Vector2.ONE * _get_snap_resolution())
|
|
if drag_delta.abs() > Vector2.ZERO:
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs, 'scale_points_by', _drag_start, mouse_pos, Input.is_key_pressed(KEY_SHIFT)])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs, 'scale_points_by', mouse_pos, _drag_start, Input.is_key_pressed(KEY_SHIFT)])
|
|
svs.scale_points_by(_drag_start, mouse_pos, Input.is_key_pressed(KEY_SHIFT))
|
|
_drag_start = mouse_pos
|
|
return true
|
|
return false
|
|
|
|
|
|
func _handle_input_for_uniform_rotate(event : InputEvent, svs : ScalableVectorShape2D) -> bool:
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
|
|
if (event as InputEventMouseButton).pressed:
|
|
_drag_start = mouse_pos
|
|
_prev_uniform_rotate_angle = 0.0
|
|
_stored_natural_center = svs.to_global(svs.get_center())
|
|
if not in_undo_redo_transaction:
|
|
_start_undo_redo_transaction("Rotate curve points for %s" % str(svs))
|
|
update_overlays()
|
|
return true
|
|
|
|
if event is InputEventMouseMotion:
|
|
update_overlays()
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
var use_cntr := Input.is_key_pressed(KEY_SHIFT) or svs.shape_type != ScalableVectorShape2D.ShapeType.PATH
|
|
var rotation_origin = (
|
|
_stored_natural_center
|
|
if use_cntr else
|
|
svs.global_position
|
|
)
|
|
var rotation_target := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
var ang := rotation_origin.angle_to_point(rotation_target) - rotation_origin.angle_to_point(_drag_start)
|
|
if _is_ctrl_or_cmd_pressed():
|
|
ang = snappedf(ang, deg_to_rad(5.0))
|
|
if ang != _prev_uniform_rotate_angle:
|
|
var drag_delta := ang - _prev_uniform_rotate_angle
|
|
if use_cntr:
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs, 'rotate_points_by', drag_delta, svs.to_local(rotation_origin)])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs, 'rotate_points_by', -drag_delta, svs.to_local(rotation_origin)])
|
|
svs.rotate_points_by(drag_delta, svs.to_local(rotation_origin))
|
|
else:
|
|
undo_redo_transaction[UndoRedoEntry.DOS].append([svs, 'rotate_points_by', drag_delta])
|
|
undo_redo_transaction[UndoRedoEntry.UNDOS].append([svs, 'rotate_points_by', -drag_delta])
|
|
svs.rotate_points_by(drag_delta)
|
|
|
|
_prev_uniform_rotate_angle = ang
|
|
return true
|
|
return false
|
|
|
|
|
|
func _handle_draw_merge_box_input(event) -> bool:
|
|
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
|
|
if event.is_pressed():
|
|
_merge_box_rect.position = _vp_transform(EditorInterface.get_editor_viewport_2d().get_mouse_position())
|
|
else:
|
|
_create_svs_vertex_merge_2d()
|
|
merge_node_toggle_button.button_pressed = false
|
|
update_overlays()
|
|
return true
|
|
if event is InputEventMouseMotion:
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
_merge_box_rect.size = _vp_transform(EditorInterface.get_editor_viewport_2d().get_mouse_position()) - _merge_box_rect.position
|
|
update_overlays()
|
|
return true
|
|
|
|
|
|
func _forward_canvas_gui_input(event: InputEvent) -> bool:
|
|
if merge_node_toggle_button.button_pressed:
|
|
return _handle_draw_merge_box_input(event)
|
|
|
|
if (
|
|
event is InputEventKey and
|
|
(event as InputEventKey).pressed and
|
|
(event as InputEventKey).keycode == KEY_M and
|
|
_get_select_mode_button().button_pressed
|
|
):
|
|
merge_node_toggle_button.button_pressed = true
|
|
|
|
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
|
|
_lmb_is_down_inside_viewport = (event as InputEventMouseButton).pressed
|
|
if (in_undo_redo_transaction and event is InputEventMouseButton
|
|
and event.button_index == MOUSE_BUTTON_LEFT
|
|
and not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)):
|
|
_commit_undo_redo_transaction()
|
|
if (in_undo_redo_transaction and event is InputEventKey
|
|
and event.keycode == KEY_SHIFT and
|
|
not Input.is_key_pressed(KEY_SHIFT)):
|
|
_commit_undo_redo_transaction()
|
|
if not _is_editing_enabled():
|
|
return false
|
|
if not _is_change_pivot_button_active() and not _get_select_mode_button().button_pressed:
|
|
return false
|
|
if not is_instance_valid(EditorInterface.get_edited_scene_root()):
|
|
return false
|
|
var current_selection := EditorInterface.get_selection().get_selected_nodes().pop_back()
|
|
if _is_svs_valid(current_selection) and _get_select_mode_button().button_pressed:
|
|
if _uniform_transform_mode == UniformTransformMode.TRANSLATE:
|
|
return _handle_input_for_uniform_translate(event, current_selection)
|
|
elif _uniform_transform_mode == UniformTransformMode.SCALE:
|
|
return _handle_input_for_uniform_scale(event, current_selection)
|
|
elif _uniform_transform_mode == UniformTransformMode.ROTATE:
|
|
return _handle_input_for_uniform_rotate(event, current_selection)
|
|
|
|
if _is_svs_valid(current_selection) and _is_ctrl_or_cmd_pressed() and Input.is_key_pressed(KEY_SHIFT):
|
|
var vp_horiz_scrollbar := _get_vp_h_scroll_bar()
|
|
if vp_horiz_scrollbar is HScrollBar:
|
|
if not _locking_vp_horizontal_scrollbar:
|
|
_vp_horizontal_scrollbar_locked_value = vp_horiz_scrollbar.value
|
|
_locking_vp_horizontal_scrollbar = true
|
|
vp_horiz_scrollbar.value = _vp_horizontal_scrollbar_locked_value
|
|
else:
|
|
_locking_vp_horizontal_scrollbar = false
|
|
|
|
|
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
if _is_change_pivot_button_active():
|
|
if _is_svs_valid(current_selection):
|
|
_set_shape_origin(current_selection, mouse_pos)
|
|
else:
|
|
if _is_svs_valid(current_selection) and _handle_has_hover(current_selection):
|
|
if event.double_click and current_selection.has_meta(META_NAME_HOVER_POINT_IDX):
|
|
_toggle_loop_if_applies(current_selection, current_selection.get_meta(META_NAME_HOVER_POINT_IDX))
|
|
elif (_is_svs_valid(current_selection) and Input.is_key_pressed(KEY_ALT)
|
|
and current_selection.shape_type == ScalableVectorShape2D.ShapeType.PATH
|
|
and _curve_control_has_hover(current_selection)):
|
|
set_global_position_popup_panel.popup_with_value(
|
|
_get_hovered_handle_metadata(current_selection),
|
|
_is_snapped_to_pixel(),
|
|
_get_snap_resolution()
|
|
)
|
|
return true
|
|
elif _is_svs_valid(current_selection) and _is_ctrl_or_cmd_pressed() and Input.is_key_pressed(KEY_SHIFT):
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
if (not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and
|
|
current_selection.has_fine_point(mouse_pos)):
|
|
_start_cutout_shape(current_selection, mouse_pos)
|
|
return true
|
|
elif _is_svs_valid(current_selection) and _is_ctrl_or_cmd_pressed():
|
|
if _is_snapped_to_pixel():
|
|
mouse_pos = mouse_pos.snapped(Vector2.ONE * _get_snap_resolution())
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
_add_point_on_position(current_selection, mouse_pos)
|
|
return true
|
|
elif _is_svs_valid(current_selection) and current_selection.has_meta(META_NAME_HOVER_CLOSEST_POINT):
|
|
var cp_md : ClosestPointOnCurveMeta = current_selection.get_meta(META_NAME_HOVER_CLOSEST_POINT)
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and current_selection.is_arc_start(cp_md.before_segment - 1):
|
|
arc_settings_popup_panel.popup_with_value(current_selection.arc_list.get_arc_for_point(cp_md.before_segment - 1))
|
|
elif Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and Input.is_key_pressed(KEY_ALT):
|
|
_add_point_on_curve_segment(current_selection, true)
|
|
elif event.double_click:
|
|
_add_point_on_curve_segment(current_selection)
|
|
return true
|
|
elif _is_svs_valid(current_selection) and current_selection.has_meta(META_NAME_HOVER_CLOSEST_POINT_ON_GRADIENT_LINE):
|
|
if event.double_click:
|
|
_add_color_stop(current_selection, mouse_pos)
|
|
return true
|
|
elif _is_svs_valid(current_selection) and current_selection.has_fine_point(mouse_pos) and event.double_click:
|
|
_subdivide_curve(current_selection)
|
|
return true
|
|
else:
|
|
var results := _find_scalable_vector_shape_2d_nodes_at(mouse_pos)
|
|
var refined_result := results.rfind_custom(func(x): return x.has_fine_point(mouse_pos))
|
|
if refined_result > -1 and results[refined_result]:
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
selection_candidate = results[refined_result]
|
|
return true
|
|
else:
|
|
if selection_candidate == results[refined_result]:
|
|
EditorInterface.edit_node(results[refined_result])
|
|
return true
|
|
var result = results.pop_back()
|
|
if is_instance_valid(result):
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
selection_candidate = result
|
|
return true
|
|
else:
|
|
if selection_candidate == result:
|
|
EditorInterface.edit_node(result)
|
|
return true
|
|
return false
|
|
|
|
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
|
|
if _is_svs_valid(current_selection) and _is_ctrl_or_cmd_pressed() and Input.is_key_pressed(KEY_SHIFT):
|
|
if not event.is_pressed():
|
|
current_clip_operation += 1
|
|
current_clip_operation = 2 if current_clip_operation < 0 else current_clip_operation
|
|
current_clip_operation = 0 if current_clip_operation > 2 else current_clip_operation
|
|
return true
|
|
if _is_svs_valid(current_selection) and _handle_has_hover(current_selection):
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
|
if current_selection.has_meta(META_NAME_HOVER_POINT_IDX) and current_selection.shape_type == ScalableVectorShape2D.ShapeType.PATH:
|
|
_remove_point_from_curve(current_selection, current_selection.get_meta(META_NAME_HOVER_POINT_IDX))
|
|
elif current_selection.has_meta(META_NAME_HOVER_CP_IN_IDX):
|
|
if current_selection.shape_type == ScalableVectorShape2D.ShapeType.RECT:
|
|
_remove_rounded_corners_from_rect(current_selection)
|
|
else:
|
|
_remove_cp_in_from_curve(current_selection, current_selection.get_meta(META_NAME_HOVER_CP_IN_IDX))
|
|
elif current_selection.has_meta(META_NAME_HOVER_CP_OUT_IDX):
|
|
if current_selection.shape_type == ScalableVectorShape2D.ShapeType.RECT:
|
|
_remove_rounded_corners_from_rect(current_selection)
|
|
else:
|
|
_remove_cp_out_from_curve(current_selection, current_selection.get_meta(META_NAME_HOVER_CP_OUT_IDX))
|
|
elif current_selection.has_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX):
|
|
_remove_color_stop(current_selection, current_selection.get_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX))
|
|
return true
|
|
if _is_svs_valid(current_selection) and current_selection.has_meta(META_NAME_HOVER_CLOSEST_POINT) and not _handle_has_hover(current_selection):
|
|
if not Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
|
|
var cp_md = current_selection.get_meta(META_NAME_HOVER_CLOSEST_POINT)
|
|
if current_selection.is_arc_start(cp_md.before_segment - 1):
|
|
_remove_arc(current_selection, cp_md.before_segment - 1)
|
|
else:
|
|
_create_arc(current_selection, cp_md.before_segment - 1)
|
|
return true
|
|
|
|
if (event is InputEventMouseButton and Input.is_key_pressed(KEY_SHIFT) and
|
|
event.button_index in [MOUSE_BUTTON_WHEEL_UP, MOUSE_BUTTON_WHEEL_DOWN]):
|
|
if _is_svs_valid(current_selection):
|
|
if _is_ctrl_or_cmd_pressed():
|
|
if not event.is_pressed():
|
|
current_cutout_shape += 1 if event.button_index == MOUSE_BUTTON_WHEEL_DOWN else -1
|
|
current_cutout_shape = 2 if current_cutout_shape < 0 else current_cutout_shape
|
|
current_cutout_shape = 0 if current_cutout_shape > 2 else current_cutout_shape
|
|
return true
|
|
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
|
|
_resize_shape(current_selection, 0.99)
|
|
else:
|
|
_resize_shape(current_selection, 1.01)
|
|
return true
|
|
|
|
|
|
if event is InputEventMouseMotion:
|
|
var mouse_pos := EditorInterface.get_editor_viewport_2d().get_mouse_position()
|
|
for result in _find_scalable_vector_shape_2d_nodes():
|
|
result.remove_meta(META_NAME_SELECT_HINT)
|
|
|
|
if _is_svs_valid(current_selection) and not _handle_has_hover(current_selection) and current_selection.has_meta(META_NAME_HOVER_CLOSEST_POINT):
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
|
|
_drag_curve_segment(current_selection, mouse_pos)
|
|
return true
|
|
|
|
if _is_svs_valid(current_selection):
|
|
current_selection.remove_meta(META_NAME_HOVER_CLOSEST_POINT)
|
|
|
|
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) and _is_svs_valid(current_selection):
|
|
if _handle_has_hover(current_selection):
|
|
if current_selection.has_meta(META_NAME_HOVER_POINT_IDX):
|
|
var pt_idx : int = current_selection.get_meta(META_NAME_HOVER_POINT_IDX)
|
|
if current_selection.shape_type != ScalableVectorShape2D.ShapeType.PATH:
|
|
_update_rect_dimensions(current_selection, mouse_pos)
|
|
elif Input.is_key_pressed(KEY_SHIFT):
|
|
if pt_idx == 0:
|
|
_update_curve_cp_out_position(current_selection, mouse_pos, pt_idx)
|
|
else:
|
|
_update_curve_cp_in_position(current_selection, mouse_pos, pt_idx)
|
|
else:
|
|
_update_curve_point_position(current_selection, mouse_pos, pt_idx)
|
|
elif current_selection.has_meta(META_NAME_HOVER_CP_IN_IDX):
|
|
if current_selection.shape_type == ScalableVectorShape2D.ShapeType.RECT:
|
|
_update_rect_corner_radius(current_selection, mouse_pos, "rx", !Input.is_key_pressed(KEY_SHIFT))
|
|
else:
|
|
_update_curve_cp_in_position(current_selection, mouse_pos, current_selection.get_meta(META_NAME_HOVER_CP_IN_IDX))
|
|
elif current_selection.has_meta(META_NAME_HOVER_CP_OUT_IDX):
|
|
if current_selection.shape_type == ScalableVectorShape2D.ShapeType.RECT:
|
|
_update_rect_corner_radius(current_selection, mouse_pos, "ry", !Input.is_key_pressed(KEY_SHIFT))
|
|
else:
|
|
_update_curve_cp_out_position(current_selection, mouse_pos, current_selection.get_meta(META_NAME_HOVER_CP_OUT_IDX))
|
|
elif current_selection.has_meta(META_NAME_HOVER_GRADIENT_FROM):
|
|
_update_gradient_from_position(current_selection, mouse_pos)
|
|
elif current_selection.has_meta(META_NAME_HOVER_GRADIENT_TO):
|
|
_update_gradient_to_position(current_selection, mouse_pos)
|
|
elif current_selection.has_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX):
|
|
_update_gradient_stop_color_pos(current_selection, mouse_pos,
|
|
current_selection.get_meta(META_NAME_HOVER_GRADIENT_COLOR_STOP_IDX))
|
|
update_overlays()
|
|
return true
|
|
else:
|
|
for result : ScalableVectorShape2D in _find_scalable_vector_shape_2d_nodes_at(mouse_pos):
|
|
result.set_meta(META_NAME_SELECT_HINT, true)
|
|
if _is_svs_valid(current_selection):
|
|
_set_handle_hover(mouse_pos, current_selection)
|
|
update_overlays()
|
|
return false
|
|
|
|
|
|
static func _is_editing_enabled() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_EDITING_ENABLED):
|
|
return ProjectSettings.get_setting(SETTING_NAME_EDITING_ENABLED)
|
|
return true
|
|
|
|
|
|
static func _are_hints_enabled() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_HINTS_ENABLED):
|
|
return ProjectSettings.get_setting(SETTING_NAME_HINTS_ENABLED)
|
|
return true
|
|
|
|
|
|
static func _am_showing_point_numbers() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_SHOW_POINT_NUMBERS):
|
|
return ProjectSettings.get_setting(SETTING_NAME_SHOW_POINT_NUMBERS)
|
|
return true
|
|
|
|
|
|
static func _get_default_stroke_width() -> float:
|
|
if ProjectSettings.has_setting(SETTING_NAME_STROKE_WIDTH):
|
|
return ProjectSettings.get_setting(SETTING_NAME_STROKE_WIDTH)
|
|
return 10.0
|
|
|
|
|
|
static func _get_default_stroke_color() -> Color:
|
|
if ProjectSettings.has_setting(SETTING_NAME_STROKE_COLOR):
|
|
return ProjectSettings.get_setting(SETTING_NAME_STROKE_COLOR)
|
|
return Color.WHITE
|
|
|
|
|
|
static func _get_default_begin_cap() -> Line2D.LineCapMode:
|
|
if ProjectSettings.has_setting(SETTING_NAME_DEFAULT_LINE_BEGIN_CAP):
|
|
return ProjectSettings.get_setting(SETTING_NAME_DEFAULT_LINE_BEGIN_CAP)
|
|
return Line2D.LineCapMode.LINE_CAP_NONE
|
|
|
|
|
|
static func _get_default_end_cap() -> Line2D.LineCapMode:
|
|
if ProjectSettings.has_setting(SETTING_NAME_DEFAULT_LINE_END_CAP):
|
|
return ProjectSettings.get_setting(SETTING_NAME_DEFAULT_LINE_END_CAP)
|
|
return Line2D.LineCapMode.LINE_CAP_NONE
|
|
|
|
|
|
static func _get_default_joint_mode() -> Line2D.LineJointMode:
|
|
if ProjectSettings.has_setting(SETTING_NAME_DEFAULT_LINE_JOINT_MODE):
|
|
return ProjectSettings.get_setting(SETTING_NAME_DEFAULT_LINE_JOINT_MODE)
|
|
return Line2D.LineJointMode.LINE_JOINT_SHARP
|
|
|
|
|
|
static func _get_default_fill_color() -> Color:
|
|
if ProjectSettings.has_setting(SETTING_NAME_FILL_COLOR):
|
|
return ProjectSettings.get_setting(SETTING_NAME_FILL_COLOR)
|
|
return Color.WHITE
|
|
|
|
|
|
static func _is_add_stroke_enabled() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_ADD_STROKE_ENABLED):
|
|
return ProjectSettings.get_setting(SETTING_NAME_ADD_STROKE_ENABLED)
|
|
return true
|
|
|
|
|
|
static func _using_line_2d_for_stroke() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_USE_LINE_2D_FOR_STROKE):
|
|
return ProjectSettings.get_setting(SETTING_NAME_USE_LINE_2D_FOR_STROKE)
|
|
return true
|
|
|
|
|
|
static func _is_add_fill_enabled() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_ADD_FILL_ENABLED):
|
|
return ProjectSettings.get_setting(SETTING_NAME_ADD_FILL_ENABLED)
|
|
return true
|
|
|
|
|
|
static func _add_collision_object_type() -> ScalableVectorShape2D.CollisionObjectType:
|
|
if ProjectSettings.has_setting(SETTING_NAME_ADD_COLLISION_TYPE):
|
|
return ProjectSettings.get_setting(SETTING_NAME_ADD_COLLISION_TYPE)
|
|
return ScalableVectorShape2D.CollisionObjectType.NONE
|
|
|
|
|
|
|
|
static func _get_default_paint_order() -> PaintOrder:
|
|
if ProjectSettings.has_setting(SETTING_NAME_PAINT_ORDER):
|
|
return ProjectSettings.get_setting(SETTING_NAME_PAINT_ORDER)
|
|
return PaintOrder.FILL_STROKE_MARKERS
|
|
|
|
|
|
static func _is_snapped_to_pixel() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_SNAP_TO_PIXEL):
|
|
return ProjectSettings.get_setting(SETTING_NAME_SNAP_TO_PIXEL)
|
|
return false
|
|
|
|
|
|
static func _get_snap_resolution() -> float:
|
|
if ProjectSettings.has_setting(SETTING_NAME_SNAP_RESOLUTION):
|
|
return ProjectSettings.get_setting(SETTING_NAME_SNAP_RESOLUTION)
|
|
return 1.0
|
|
|
|
|
|
static func _is_setting_update_curve_at_runtime() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_CURVE_UPDATE_CURVE_AT_RUNTIME):
|
|
return ProjectSettings.get_setting(SETTING_NAME_CURVE_UPDATE_CURVE_AT_RUNTIME)
|
|
return true
|
|
|
|
|
|
static func _is_making_curve_resources_local_to_scene() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_CURVE_RESOURCE_LOCAL_TO_SCENE):
|
|
return ProjectSettings.get_setting(SETTING_NAME_CURVE_RESOURCE_LOCAL_TO_SCENE)
|
|
return true
|
|
|
|
|
|
static func _use_antialiased_line_2d() -> bool:
|
|
if ProjectSettings.has_setting(SETTING_NAME_ANTIALIASED_LINE_2D):
|
|
return ProjectSettings.get_setting(SETTING_NAME_ANTIALIASED_LINE_2D)
|
|
return false
|
|
|
|
|
|
static func _get_default_tolerance_degrees() -> float:
|
|
if ProjectSettings.has_setting(SETTING_NAME_CURVE_TOLERANCE_DEGREES):
|
|
return ProjectSettings.get_setting(SETTING_NAME_CURVE_TOLERANCE_DEGREES)
|
|
return 4.0
|
|
|
|
|
|
static func _get_default_max_stages() -> int:
|
|
if ProjectSettings.has_setting(SETTING_NAME_CURVE_MAX_STAGES):
|
|
return ProjectSettings.get_setting(SETTING_NAME_CURVE_MAX_STAGES)
|
|
return 5
|
|
|
|
|
|
func _exit_tree():
|
|
if _get_select_mode_button().toggled.is_connected(_on_select_mode_toggled):
|
|
_get_select_mode_button().toggled.disconnect(_on_select_mode_toggled)
|
|
|
|
uniform_transform_edit_buttons.queue_free()
|
|
merge_node_toggle_button.queue_free()
|
|
remove_inspector_plugin(plugin)
|
|
remove_custom_type("DrawablePath2D")
|
|
remove_custom_type("ScalableVectorShape2D")
|
|
remove_control_from_bottom_panel(scalable_vector_shapes_2d_dock)
|
|
scalable_vector_shapes_2d_dock.free()
|
|
set_global_position_popup_panel.free()
|