extends Control ## A draggable modifier piece. Follows the cursor directly — no ghost, no ## Godot drag-and-drop API (which fights with 3D raycasting). ## ## While dragging it asks the DropCatcher which slot is under the cursor, so ## the player can SEE what they're about to hit before committing. @export var paint_texture: Texture2D: set(value): paint_texture = value queue_redraw() @export_range(0.0, 1.0) var top_width_ratio := 0.55 # Injected by the tray on spawn. var drop_catcher: Control var tray: Node var modifier: Modifier var _dragging := false var _home_position := Vector2.ZERO var _drag_offset := Vector2.ZERO var _hovered_slot: StringName = &"" func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_STOP if custom_minimum_size == Vector2.ZERO: custom_minimum_size = Vector2(90, 110) size = custom_minimum_size func _draw() -> void: var w := size.x var h := size.y var inset := w * (1.0 - top_width_ratio) * 0.5 var points := PackedVector2Array([ Vector2(inset, 0), Vector2(w - inset, 0), Vector2(w, h), Vector2(0, h), ]) if paint_texture != null: var uvs := PackedVector2Array([ Vector2(inset / w, 0), Vector2((w - inset) / w, 0), Vector2(1, 1), Vector2(0, 1), ]) var white := PackedColorArray([Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE]) draw_polygon(points, white, uvs, paint_texture) else: draw_colored_polygon(points, Color(0.8, 0.8, 0.8)) # Outline turns green when hovering a valid slot — the player's only cue # that the drop will land. var outline_col := Color(0, 0, 0, 0.6) var outline_w := 2.0 if _dragging and _hovered_slot != &"": outline_col = Color(0.3, 1.0, 0.4, 0.9) outline_w = 4.0 var outline := points outline.append(points[0]) draw_polyline(outline, outline_col, outline_w) func _gui_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if event.pressed: _start_drag() else: _end_drag() func _start_drag() -> void: _dragging = true _home_position = global_position _drag_offset = global_position - get_global_mouse_position() # top_level lets us move by global_position without reparenting. # Reparenting mid-drag breaks the release event. top_level = true z_index = 100 modulate.a = 0.85 func _end_drag() -> void: if not _dragging: return _dragging = false _hovered_slot = &"" modulate.a = 1.0 z_index = 0 var applied := false if drop_catcher == null: push_error("SectorSwatch has no drop_catcher.") else: applied = drop_catcher.apply_modifier_at_screen_pos( get_global_mouse_position(), modifier ) # Always snap home. The tray decides whether to consume the piece. top_level = false global_position = _home_position queue_redraw() if applied and tray != null and tray.has_method(&"on_piece_used"): tray.on_piece_used(self) func _process(_delta: float) -> void: if not _dragging: return var mouse := get_global_mouse_position() global_position = mouse + _drag_offset # Live hover feedback — which slot would this land on right now? if drop_catcher != null and drop_catcher.has_method(&"hover"): var slot: StringName = drop_catcher.hover(mouse) if slot != _hovered_slot: _hovered_slot = slot queue_redraw()