274 lines
6.7 KiB
GDScript
274 lines
6.7 KiB
GDScript
@tool
|
|
class_name Gizmo2D
|
|
extends RefCounted
|
|
|
|
# Gizmo types
|
|
enum {
|
|
GIZMO_POS, # square marker that represents (x,y) coordinates
|
|
GIZMO_HPOS, # vertical line that represents a horizontal coordinate
|
|
GIZMO_VPOS # horizontal line that represents a vertical coordinate
|
|
}
|
|
|
|
# Public vars
|
|
# Convenience accessors
|
|
var target_node: Node2D:
|
|
set = set_target_node,
|
|
get = get_target_node
|
|
var target_property: String:
|
|
set = set_target_property,
|
|
get = get_target_property
|
|
var position:
|
|
get = get_position
|
|
# Behavior flags
|
|
var show_connector: bool = true # Show gizmo-to-node connectors
|
|
var show_outlines: bool = true
|
|
var show_target_name: bool = true # Show target node name
|
|
var visible: bool = true # Gizmo visibility
|
|
|
|
# Private vars
|
|
# Context
|
|
var _type: int
|
|
var _target_node: Node2D
|
|
var _target_property: String
|
|
# Appearance
|
|
var _size: Vector2 # Gizmo width and height
|
|
var _color: Color # Gizmo color
|
|
var _label: String # A label to be painted near the Gizmo
|
|
var _font: Font # Label font
|
|
var _font_size: int # Label font size
|
|
# State
|
|
var _handle: Rect2 # Gizmo handle
|
|
var _current_position: Vector2 # The position the gizmo is representing in every moment
|
|
var _current_color: Color
|
|
var _is_grabbed: bool = false # Gizmo is moving
|
|
var _grab_center_pos: Vector2 # Starting center position when grabbing
|
|
var _grab_mouse_pos: Vector2 # Starting mouse position when grabbing
|
|
|
|
|
|
#region Virtual ####################################################################################
|
|
|
|
func _init(
|
|
node: Node,
|
|
property: String,
|
|
label: String,
|
|
type: int,
|
|
):
|
|
_target_node = node
|
|
_target_property = property
|
|
_type = type
|
|
_label = label
|
|
|
|
set_theme(
|
|
Color.AQUA,
|
|
24,
|
|
EditorInterface.get_editor_theme().default_font,
|
|
EditorInterface.get_editor_theme().default_font_size
|
|
)
|
|
|
|
_current_color = _color
|
|
|
|
|
|
#endregion
|
|
|
|
#region SetGet #####################################################################################
|
|
|
|
func set_theme(
|
|
color: Color,
|
|
size: int,
|
|
font: Font,
|
|
font_size: int
|
|
):
|
|
_color = color
|
|
_size = Vector2(size, size)
|
|
_font = font
|
|
_font_size = font_size
|
|
|
|
func set_target_node(node: Node2D):
|
|
_target_node = node
|
|
|
|
func get_target_node() -> Node2D:
|
|
return _target_node
|
|
|
|
func set_target_property(property: String):
|
|
_target_property = property
|
|
|
|
func get_target_property() -> String:
|
|
if _target_property:
|
|
return _target_property
|
|
return ""
|
|
|
|
|
|
#endregion
|
|
|
|
#region Private ####################################################################################
|
|
|
|
func _draw_outlines(viewport: Control):
|
|
viewport.draw_rect(
|
|
_handle,
|
|
Color.BLACK, false, 4
|
|
)
|
|
|
|
viewport.draw_string_outline(
|
|
_font,
|
|
_handle.position + Vector2(0, _size.y + 2 + _font.get_ascent(_font_size)),
|
|
_label, HORIZONTAL_ALIGNMENT_CENTER,
|
|
- 1,
|
|
_font_size,
|
|
6,
|
|
Color.BLACK
|
|
)
|
|
|
|
if show_target_name:
|
|
viewport.draw_string_outline(
|
|
_font,
|
|
_handle.position + Vector2(0, -_font.get_descent(_font_size)),
|
|
_target_node.name,
|
|
HORIZONTAL_ALIGNMENT_CENTER,
|
|
- 1,
|
|
_font_size,
|
|
6,
|
|
Color.BLACK
|
|
)
|
|
|
|
func _draw_gizmo(viewport: Control):
|
|
|
|
# Draw the handle (on top of the line, if it's present)
|
|
viewport.draw_rect(
|
|
_handle,
|
|
_current_color
|
|
)
|
|
|
|
# Draw gizmo-to-node connector, if active
|
|
if show_connector:
|
|
viewport.draw_dashed_line(
|
|
(_target_node.get_viewport_transform() * _target_node.get_global_transform()).origin,
|
|
_handle.get_center(),
|
|
_current_color.darkened(0.2),
|
|
2,
|
|
4
|
|
)
|
|
viewport.draw_circle(
|
|
_handle.get_center(),
|
|
3,
|
|
_current_color.darkened(0.2)
|
|
)
|
|
|
|
# Draw the label, if it's set and non empty
|
|
if _label:
|
|
viewport.draw_string(
|
|
_font,
|
|
_handle.position + Vector2(0, _size.y + 2 + _font.get_ascent(_font_size)),
|
|
_label, HORIZONTAL_ALIGNMENT_CENTER,
|
|
- 1,
|
|
_font_size,
|
|
_current_color
|
|
)
|
|
|
|
if show_target_name:
|
|
viewport.draw_string(
|
|
_font,
|
|
_handle.position + Vector2(0, -_font.get_descent(_font_size)),
|
|
_target_node.name,
|
|
HORIZONTAL_ALIGNMENT_CENTER,
|
|
- 1,
|
|
_font_size,
|
|
_current_color
|
|
)
|
|
|
|
func _can_draw():
|
|
return (visible and _target_node != null and _target_node.is_visible_in_tree())
|
|
|
|
|
|
#endregion
|
|
|
|
#region Public #####################################################################################
|
|
|
|
func draw(viewport: Control, coord: Variant) -> void:
|
|
# Handmade coordinates type overloading
|
|
if not (coord is Vector2 or coord is int):
|
|
return
|
|
# Check if the gizmo can be drawn
|
|
if not _can_draw():
|
|
return
|
|
|
|
# Coordinates normalization (to vector) for horizontal or vertical gizmos
|
|
# Both axis are set to the same value, then ignore one or the other
|
|
# depending on the gizmo type
|
|
if coord is int:
|
|
coord = Vector2(coord, coord)
|
|
|
|
# Calculate the GLOBAL coordinates of the center of the square handle
|
|
# This only takes into account the node offset discarding its transform basis
|
|
# (representing rotation, skew and scale) then it applies the viewport transform
|
|
# to take into account the zoom level
|
|
var center = _target_node.get_viewport_transform() * (_target_node.get_global_transform().origin + Vector2(coord))
|
|
|
|
# Set handle color
|
|
_current_color = _color
|
|
# Highlight handle if held by the mouse click
|
|
if _is_grabbed:
|
|
_current_color = _color.lightened(0.5)
|
|
|
|
# Draw an horizontal or vertical line if the gizmo is one-dimensional
|
|
match _type:
|
|
GIZMO_VPOS:
|
|
var viewport_width = EditorInterface.get_editor_viewport_2d().size.x
|
|
center.x = viewport_width / 2
|
|
viewport.draw_line(
|
|
Vector2(0, center.y),
|
|
Vector2(viewport_width, center.y),
|
|
_current_color,
|
|
2
|
|
)
|
|
GIZMO_HPOS:
|
|
var viewport_height = EditorInterface.get_editor_viewport_2d().size.y
|
|
center.y = viewport_height / 2
|
|
viewport.draw_line(
|
|
Vector2(center.x, 0),
|
|
Vector2(center.x, viewport_height),
|
|
_current_color,
|
|
2
|
|
)
|
|
|
|
# Initialize the handle in the right position
|
|
_handle = Rect2(center - _size / 2, _size)
|
|
|
|
if show_outlines:
|
|
_draw_outlines(viewport)
|
|
|
|
_draw_gizmo(viewport)
|
|
|
|
func drag_to(pos: Vector2):
|
|
# Distance between the mouse position and the gizmo center
|
|
var d = _grab_center_pos - _grab_mouse_pos
|
|
# Gizmo center position in global coordinates
|
|
var current_gizmo_pos = pos + d
|
|
# Distance between gizmo center position in 2D world node coordinates and
|
|
# node position ignoring its transform basis (representing rotation, skew and scale)
|
|
_current_position = _target_node.get_viewport_transform().affine_inverse() * current_gizmo_pos - (target_node.get_global_transform().origin)
|
|
|
|
func release():
|
|
_is_grabbed = false
|
|
|
|
func grab(pos: Vector2):
|
|
_is_grabbed = true
|
|
_grab_mouse_pos = pos
|
|
_grab_center_pos = _handle.get_center()
|
|
|
|
func cancel():
|
|
_is_grabbed = false
|
|
|
|
func has_point(pos: Vector2):
|
|
return visible and _handle.abs().has_point(pos)
|
|
|
|
func get_position():
|
|
match _type:
|
|
GIZMO_POS:
|
|
return _current_position
|
|
GIZMO_HPOS:
|
|
return _current_position.x
|
|
GIZMO_VPOS:
|
|
return _current_position.y
|
|
|
|
|
|
#endregion
|