extends Node
## GATE — DRAG THE FOCUSED VALUE WITH THE MOUSE (#120 §17.7 Q2, §17.8 step 2; Blender's).
## D1 ModalControls.drag: horizontal pixels x step / DRAG_PX
## D2 Shift (finer): a tenth
## D3 Ctrl: snapped to whole steps
## D4 an integer value (its setter rounds) still accumulates small motions
## D5 a change from elsewhere ([ ]) restarts the drag from the new value
## D6 in the map: Rotate on a selected brush, mouse motion turns it (drags_value)
## D7 ... and Move, whose value follows the aim, is not dragged by it
##
## RUN AS A SCENE: "$HYPER_TPS_GODOT" --headless res://scenes/test/gate_modal_drag.tscn
var fails := 0
var checks := 0
var map: Node3D
func _ok(what: String, cond: bool, note: Variant = "") -> void:
checks += 1
if not cond:
fails += 1
print(" %-64s %s %s" % [what, "OK" if cond else "**FAIL**", str(note)])
func _ready() -> void:
var gs: Node = get_tree().root.get_node_or_null("/root/GameState")
gs.route("world:level:*", 1, "peer")
var keet: Node = get_tree().root.get_node_or_null("/root/KeetService")
if keet != null:
keet.current_member_id = "gate-author"
get_tree().root.get_node("/root/GameMode").set_mode("build")
map = (load("res://scenes/maps/map_dev_test.tscn") as PackedScene).instantiate()
add_child(map)
for i in 6:
await get_tree().process_frame
await _run()
get_tree().root.get_node("/root/GameMode").set_mode("play")
if checks < 8:
print(" **FAIL** only %d checks ran -- setup died before the assertions" % checks)
fails += 1
print("\nGATE modal_drag: %s (%d checks, %d failed)" % ["PASS" if fails == 0 else "FAIL", checks, fails])
get_tree().quit(0 if fails == 0 else 1)
func _motion(x: float) -> void:
var ev := InputEventMouseMotion.new()
ev.position = Vector2(x, 300.0)
map._input(ev)
func _run() -> void:
# ---- D1..D5: ModalControls alone ----
var box := {"v": 0.0}
var m := ModalControls.new()
m.bind("w", "width", func() -> float: return box["v"], func(x: float) -> void: box["v"] = x, 1.0)
_ok("D1 ModalControls can drag", m.has_method("drag"))
if not m.has_method("drag"):
return
var px: float = ModalControls.DRAG_PX
m.drag(px * 2.0, false, false)
_ok("D1 two steps' worth of pixels = +2", is_equal_approx(box["v"], 2.0), box["v"])
box["v"] = 0.0
m.drag(px * 2.0, true, false)
_ok("D2 Shift: a tenth", is_equal_approx(box["v"], 0.2), box["v"])
box["v"] = 0.0
m.drag(px * 1.48, false, true)
_ok("D3 Ctrl: snapped to a whole step", is_equal_approx(box["v"], 1.0), box["v"])
var ibox := {"n": 2}
var mi := ModalControls.new()
mi.bind("n", "count", func() -> float: return float(ibox["n"]), func(x: float) -> void: ibox["n"] = int(roundf(x)), 1.0)
for i in 10:
mi.drag(px * 0.2, false, false) # each alone rounds to nothing
_ok("D4 small motions on an integer accumulate (2 + 2)", ibox["n"] == 4, ibox["n"])
box["v"] = 5.0 # changed from elsewhere
m.drag(px, false, false)
_ok("D5 a change from elsewhere restarts the drag there", is_equal_approx(box["v"], 6.0), box["v"])
# ---- D6 / D7: through the map ----
var level: Node = map.get("level")
var b: int = (level.get_live_brush_ids() as PackedInt32Array)[0]
map.select_brushes(PackedInt32Array([b]))
var ops: Node = map.call("_ensure_ops")
var rt: Node = map.get("_rotate")
ops.activate(rt)
_motion(400.0)
_motion(400.0 + px * 2.0)
_ok("D6 Rotate: two steps of mouse turn it 2 x 15 deg", is_equal_approx(float(rt.get("_deg")), 30.0), rt.get("_deg"))
ops.cancel()
await get_tree().process_frame
var mv: Node = map.get("_move")
map.select_brushes(PackedInt32Array([b]))
ops.activate(mv)
var s0 := int(mv.get("_steps"))
_motion(400.0)
_motion(400.0 + px * 3.0)
_ok("D7 Move (follows the aim) is not dragged", int(mv.get("_steps")) == s0, [s0, mv.get("_steps")])
ops.cancel()