OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / scenes / game / shape_panel.gd

141 lines · gd ref:
extends VBoxContainer
## THE SHAPE PANEL (docs/parametric-shapes-design.md §4): the selected parametric
## shape's parameters, editable forever — a change writes ONE shape.set (one gesture,
## one Ctrl+Z) and the fold regenerates the pieces. Plus Make Real (§6) and Delete.
##
## Drawn by the same renderer as the tool row (ToolSettingsBar), pointed at a small
## operator-shaped TARGET whose rows are the type's descriptors (ParamShapes.rows), so
## the create tool and this panel show a parameter the same way.

const ParamShapes := preload("res://scenes/maps/shapes/param/param_shapes.gd")

var map: Node = null
var _title: Label = null
var _hint: Label = null
var _bar: Node = null
var _target: ShapeTarget = null
var _dot := ""


class ShapeTarget extends LevelOperator:
	var dot := ""

	func _type() -> Object:
		var st: Dictionary = map._persist.shape_state(dot) if map != null and dot != "" else {}
		return ParamShapes.get_type(String(st.get("type", ""))) if not st.is_empty() else null

	func op_name() -> String:
		var t := _type()
		return String(t.label()) if t != null else "shape"

	func tool_settings() -> Array:
		var t := _type()
		if t == null:
			return []
		var st: Dictionary = map._persist.shape_state(dot)
		var out: Array = ParamShapes.rows(t, t.resolve(st.get("params", {})))
		if String(st.get("type", "")) == "cable":
			# PROFILE CURVE (§14.2): a dropdown of the room's CLOSED curves, as follow-curve
			# picks its curve in the Stack panel. Index 0 = none.
			var dots := profile_curves()
			var names: Array = ["—"]
			var curves: Dictionary = map._persist.curve_states()
			for d: String in dots:
				names.append(String(curves[d].get("name", d)))
			out.append({"id": "profile_curve", "label": "Profile curve", "type": "enum", "choices": names,
				"value": dots.find(String((st["params"] as Dictionary).get("profile_curve", ""))) + 1,
				"tip": "A closed curve whose outline is the cable's cross-section (concave outlines use their hull)"})
		out.append({"id": "make_real", "label": "Make Real", "type": "action",
			"tip": "Turn the pieces into plain brushes you can edit one by one (Ctrl+Z undoes)."})
		out.append({"id": "delete", "label": "Delete", "type": "action", "tip": "Delete the shape (Ctrl+Z undoes)."})
		return out

	## The room's closed curves, by dot (sorted: the same order on every peer and every frame).
	func profile_curves() -> Array:
		var curves: Dictionary = map._persist.curve_states()
		var out: Array = []
		for d: String in curves:
			if bool(curves[d].get("closed", false)):
				out.append(d)
		out.sort()
		return out

	func adjust_ids() -> Array:
		var ids: Array = []
		for d: Dictionary in tool_settings():
			ids.append(String(d["id"]))
		return ids


func setup(map_node: Node) -> void:
	map = map_node
	_title = Label.new()
	add_child(_title)
	_hint = Label.new()
	_hint.text = "Select a shape (◇) to edit it."
	_hint.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
	add_child(_hint)
	_target = ShapeTarget.new()
	_target.map = map
	_target.level = map.get("level")
	_bar = HFlowContainer.new()
	_bar.set_script(load("res://scenes/game/tool_settings_bar.gd"))
	_bar.target = _target
	_bar.apply_override = Callable(self, "apply")
	_bar.setup(map)
	add_child(_bar)
	_follow()


## The shape this panel is showing ("" = none).
func shown_dot() -> String:
	_follow()
	return _dot


func setting_ids() -> Array:
	_follow()
	return _target.adjust_ids() if _dot != "" else []


func _process(_d: float) -> void:
	_follow()


func _follow() -> void:
	var dot: String = map.selected_shape() if map != null and map.has_method("selected_shape") else ""
	if dot == _dot:
		return
	_dot = dot
	_target.dot = dot
	_bar.visible = dot != ""
	_hint.visible = dot == ""
	_title.text = "%s ◇  %s" % [_target.op_name(), dot] if dot != "" else ""


## A change from the bar (or a gate): a param -> one shape.set; or an action.
func apply(sid: String, v: Variant) -> void:
	_follow()
	if _dot == "":
		return
	match sid:
		"make_real":
			map.make_real(_dot)
		"delete":
			map.delete_brushes(Array(map.level.get_group_brush_ids(_dot)))
		"profile_curve":
			# One shape.set: the curve AND profile custom (none = back to round).
			var dots: Array = _target.profile_curves()
			var i := int(v) - 1
			if i >= 0 and i < dots.size():
				map.set_shape_params(_dot, {"profile": "custom", "profile_curve": dots[i]}, "Profile curve")
			else:
				map.set_shape_params(_dot, {"profile": "round", "profile_curve": ""}, "Profile curve")
		_:
			if not sid.begins_with("p."):
				return
			var t := _target._type()
			var pv: Variant = ParamShapes.value_of(t, sid.substr(2), v) if t != null else null
			if pv != null:
				map.set_shape_params(_dot, {sid.substr(2): pv})