extends EditorInspectorPlugin func _can_handle(object: Object) -> bool: return true func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool: if type == TYPE_VECTOR2 and hint_string == "direction": add_property_editor(name, DirectionInspector.new(), false) return true return false class DirectionInspector extends EditorProperty: var angle_selector: AngleSelector var snap_toggle: TextureButton var snap_spinbox: SpinBox var current_value: Vector2 var updating := false var snap := false var snap_amount: int = 0 func _init() -> void: var hbox = HBoxContainer.new() hbox.alignment = BoxContainer.ALIGNMENT_BEGIN add_child(hbox) var vbox = VBoxContainer.new() vbox.alignment = BoxContainer.ALIGNMENT_BEGIN vbox.add_theme_constant_override(&"separation", 4) vbox.add_spacer(true) hbox.add_child(vbox) snap_toggle = TextureButton.new() snap_toggle.toggle_mode = true snap_toggle.toggled.connect(_on_snap_toggled) snap_toggle.texture_normal = EditorInterface.get_editor_theme().get_icon(&"Snap", &"EditorIcons") snap_toggle.texture_pressed = snap_toggle.texture_normal snap_toggle.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN snap_toggle.stretch_mode = TextureButton.STRETCH_KEEP_ASPECT vbox.add_child(snap_toggle) snap_spinbox = SpinBox.new() snap_spinbox.editable = false snap_spinbox.rounded = true snap_spinbox.value_changed.connect(_on_snap_amount_changed) snap_spinbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL snap_spinbox.custom_minimum_size = Vector2.ZERO vbox.add_child(snap_spinbox) angle_selector = AngleSelector.new() angle_selector.direction_clicked.connect(_on_direction_clicked) hbox.add_child(angle_selector) add_focusable(angle_selector) custom_minimum_size = get_combined_minimum_size() size_flags_horizontal = Control.SIZE_EXPAND_FILL _refresh() func _update_property() -> void: var new_value: Vector2 = get_edited_object()[get_edited_property()] if new_value == current_value: return updating = true current_value = new_value _refresh() updating = false func _refresh() -> void: angle_selector.direction = current_value func _on_direction_clicked(direction: Vector2) -> void: if updating: return current_value = direction if snap and snap_amount > 0: var angle = direction.angle() var steps = PI * 0.5 / float(snap_amount) angle = roundf(angle / steps) * steps current_value = Vector2.from_angle(angle) _refresh() emit_changed(get_edited_property(), current_value) func _on_snap_toggled(toggled_on: bool) -> void: snap = toggled_on snap_spinbox.editable = snap if toggled_on: snap_toggle.modulate = EditorInterface.get_editor_theme().get_color(&"accent_color", &"Editor") else: snap_toggle.modulate = Color.WHITE func _on_snap_amount_changed(value: float) -> void: snap_amount = maxi(0, int(value)) class AngleSelector extends Control: signal direction_clicked(direction: Vector2) var direction: Vector2: set(value): direction = value.normalized() queue_redraw() func _init() -> void: custom_minimum_size = Vector2(48.0, 48.0) focus_mode = Control.FOCUS_CLICK func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton or event is InputEventMouseMotion: if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): direction_clicked.emit((size * 0.5).direction_to(get_local_mouse_position())) func _draw() -> void: var center = size * 0.5 var radius = minf(center.x, center.y) draw_circle(center, radius, Color.WHITE, false) draw_line(center, center + direction * radius, Color.RED, 2.0)