gdharness 1.3.67 → 1.3.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +33533 -32349
- package/build/godot/addons/gdharness_editor/bridge_client.gd +5 -3
- package/build/godot/addons/gdharness_editor/file_reads.gd +3 -0
- package/build/godot/addons/gdharness_editor/headless_check.gd +52 -0
- package/build/godot/addons/gdharness_editor/plugin.gd +80 -2
- package/build/godot/addons/gdharness_editor/scene_file.gd +192 -12
- package/build/godot/addons/gdharness_editor/tool_executor.gd +1 -0
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +3 -1
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +9 -13
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +87 -13
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +20 -4
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +43 -11
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +520 -9
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +2 -0
- package/build/godot/operations/editor_tools.gd +1 -0
- package/build/godot/operations/file_reads.gd +3 -0
- package/build/keeper.js +116 -114
- package/package.json +1 -1
|
@@ -180,19 +180,7 @@ static func follow(
|
|
|
180
180
|
) -> Dictionary:
|
|
181
181
|
var texts: Dictionary = {}
|
|
182
182
|
_read_scenes("res://", texts)
|
|
183
|
-
var kin: Array[String] =
|
|
184
|
-
var grew: bool = true
|
|
185
|
-
while grew:
|
|
186
|
-
grew = false
|
|
187
|
-
for scene_path: String in texts:
|
|
188
|
-
if kin.has(scene_path):
|
|
189
|
-
continue
|
|
190
|
-
var text: String = texts[scene_path]
|
|
191
|
-
for one: String in kin:
|
|
192
|
-
if text.contains(' path="%s"' % one):
|
|
193
|
-
kin.append(scene_path)
|
|
194
|
-
grew = true
|
|
195
|
-
break
|
|
183
|
+
var kin: Array[String] = _kin_of(changed, texts)
|
|
196
184
|
var followed: Array[String] = []
|
|
197
185
|
var not_followed: Array[Dictionary] = []
|
|
198
186
|
for scene_path: String in kin:
|
|
@@ -247,6 +235,92 @@ static func follow(
|
|
|
247
235
|
return answer
|
|
248
236
|
|
|
249
237
|
|
|
238
|
+
## Marks the instance a branch at [param at] in [param changed] became editable in every other
|
|
239
|
+
## .tscn that instances or inherits [param changed] at any depth, wherever that scene keeps values
|
|
240
|
+
## on a node inside it or adds one below it. Those still load, and the next save of that scene
|
|
241
|
+
## dropped them, since the packer skips every node under an instance whose children are not
|
|
242
|
+
## editable. Answers followed, the scenes marked, and notFollowed, each left with why, each only
|
|
243
|
+
## when it names any.
|
|
244
|
+
static func enclose(changed: String, at: String) -> Dictionary:
|
|
245
|
+
var texts: Dictionary = {}
|
|
246
|
+
_read_scenes("res://", texts)
|
|
247
|
+
var followed: Array[String] = []
|
|
248
|
+
var not_followed: Array[Dictionary] = []
|
|
249
|
+
for scene_path: String in _kin_of(changed, texts):
|
|
250
|
+
if scene_path == changed:
|
|
251
|
+
continue
|
|
252
|
+
var moves: Dictionary = {}
|
|
253
|
+
if _inherits(scene_path, changed, texts):
|
|
254
|
+
moves[at] = at
|
|
255
|
+
var read: Dictionary = GdhFileReads.scene_tree(scene_path, -1, false, false)
|
|
256
|
+
if not read.get("ok", false):
|
|
257
|
+
not_followed.append({ "scenePath": scene_path, "reason": str(read.get("error", "")) })
|
|
258
|
+
continue
|
|
259
|
+
var tree: Dictionary = read["tree"]
|
|
260
|
+
_instances_of(tree, changed, at, at, moves)
|
|
261
|
+
var text: String = texts[scene_path]
|
|
262
|
+
var marks: PackedStringArray = []
|
|
263
|
+
for path: String in moves:
|
|
264
|
+
var mark: String = '[editable path="%s"]' % path
|
|
265
|
+
if not text.contains(mark) and _holds_below(text, path):
|
|
266
|
+
var _marked: bool = marks.append(mark)
|
|
267
|
+
if marks.is_empty():
|
|
268
|
+
continue
|
|
269
|
+
var unsaved: String = GdhSceneFile.unsaved_in_editor(scene_path)
|
|
270
|
+
if not unsaved.is_empty():
|
|
271
|
+
not_followed.append({ "scenePath": scene_path, "reason": unsaved })
|
|
272
|
+
continue
|
|
273
|
+
var file: FileAccess = FileAccess.open(scene_path, FileAccess.WRITE)
|
|
274
|
+
if (file == null
|
|
275
|
+
or not file.store_string(text.rstrip("\n") + "\n\n" + "\n".join(marks) + "\n")):
|
|
276
|
+
not_followed.append(
|
|
277
|
+
{ "scenePath": scene_path, "reason": error_string(FileAccess.get_open_error()) }
|
|
278
|
+
)
|
|
279
|
+
continue
|
|
280
|
+
file.close()
|
|
281
|
+
GdhSceneFile.refresh(scene_path)
|
|
282
|
+
followed.append(scene_path)
|
|
283
|
+
var answer: Dictionary = {}
|
|
284
|
+
if not followed.is_empty():
|
|
285
|
+
answer["followed"] = followed
|
|
286
|
+
if not not_followed.is_empty():
|
|
287
|
+
answer["notFollowed"] = not_followed
|
|
288
|
+
return answer
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
## Whether [param text], a scene's file, has a node entry below [param path], one setting values on
|
|
292
|
+
## a node there or adding one.
|
|
293
|
+
static func _holds_below(text: String, path: String) -> bool:
|
|
294
|
+
for line: String in text.split("\n"):
|
|
295
|
+
if not line.begins_with("[node "):
|
|
296
|
+
continue
|
|
297
|
+
var parent: String = _attribute(line, "parent")
|
|
298
|
+
if parent.is_empty():
|
|
299
|
+
continue
|
|
300
|
+
var full: String = _under(parent, _attribute(line, "name"))
|
|
301
|
+
if full.begins_with(path + "/"):
|
|
302
|
+
return true
|
|
303
|
+
return false
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
## [param changed] and every scene in [param texts] that instances or inherits it at any depth.
|
|
307
|
+
static func _kin_of(changed: String, texts: Dictionary) -> Array[String]:
|
|
308
|
+
var kin: Array[String] = [changed]
|
|
309
|
+
var grew: bool = true
|
|
310
|
+
while grew:
|
|
311
|
+
grew = false
|
|
312
|
+
for scene_path: String in texts:
|
|
313
|
+
if kin.has(scene_path):
|
|
314
|
+
continue
|
|
315
|
+
var text: String = texts[scene_path]
|
|
316
|
+
for one: String in kin:
|
|
317
|
+
if text.contains(' path="%s"' % one):
|
|
318
|
+
kin.append(scene_path)
|
|
319
|
+
grew = true
|
|
320
|
+
break
|
|
321
|
+
return kin
|
|
322
|
+
|
|
323
|
+
|
|
250
324
|
## The text of every .tscn under [param directory] into [param texts], keyed by res:// path, leaving
|
|
251
325
|
## out the engine's own folder and hidden ones.
|
|
252
326
|
static func _read_scenes(directory: String, texts: Dictionary) -> void:
|
|
@@ -44,6 +44,9 @@ var _homes: Dictionary = {}
|
|
|
44
44
|
## rewrite that is not saved is put back.
|
|
45
45
|
var _outside: Array[Array] = []
|
|
46
46
|
var _scene: GdhSceneFile = null
|
|
47
|
+
## Each unique name a node of this scene had that is unique only inside an instance now, to the
|
|
48
|
+
## node's path from the root, for a %unique path through it to be written as the plain path.
|
|
49
|
+
var _enclosed: Dictionary = {}
|
|
47
50
|
var _moves: Dictionary = {}
|
|
48
51
|
var _back: Dictionary = {}
|
|
49
52
|
var _seen: Dictionary = {}
|
|
@@ -64,7 +67,7 @@ static func moved(given: String, moves: Dictionary) -> String:
|
|
|
64
67
|
## Carries [param moves], from the root of [param scene], into the paths it holds. Called after the
|
|
65
68
|
## move and before the write. Answers how many paths it rewrote.
|
|
66
69
|
func carry(scene: GdhSceneFile, moves: Dictionary) -> int:
|
|
67
|
-
if moves.is_empty():
|
|
70
|
+
if moves.is_empty() and _enclosed.is_empty():
|
|
68
71
|
return 0
|
|
69
72
|
_scene = scene
|
|
70
73
|
_moves = moves
|
|
@@ -110,6 +113,14 @@ func carry(scene: GdhSceneFile, moves: Dictionary) -> int:
|
|
|
110
113
|
return retargeted.size() - before
|
|
111
114
|
|
|
112
115
|
|
|
116
|
+
## Carries into [param scene] a branch saved as a scene of its own and instanced in its place, as
|
|
117
|
+
## [method carry] does: each %unique path through one of [param enclosed], a unique name to its
|
|
118
|
+
## node's path from the root, is written as the plain path. Answers how many paths it rewrote.
|
|
119
|
+
func carry_enclosed(scene: GdhSceneFile, enclosed: Dictionary) -> int:
|
|
120
|
+
_enclosed = enclosed
|
|
121
|
+
return carry(scene, {})
|
|
122
|
+
|
|
123
|
+
|
|
113
124
|
## Opens [param scene_path], carries [param moves] into it, and writes it when that rewrote a path.
|
|
114
125
|
## Answers "" or why it was left as it was.
|
|
115
126
|
func carry_into(scene_path: String, moves: Dictionary) -> String:
|
|
@@ -389,8 +400,12 @@ func _after(path: NodePath, was: String, now: String) -> Array:
|
|
|
389
400
|
var _named: bool = names.append(str(path.get_name(index)))
|
|
390
401
|
var start: String = was
|
|
391
402
|
var unique: bool = names[0].begins_with("%")
|
|
403
|
+
var enclosed: bool = false
|
|
392
404
|
if unique:
|
|
393
405
|
start = _unique_was(names[0].substr(1), now)
|
|
406
|
+
if start.is_empty() and _enclosed.has(names[0].substr(1)):
|
|
407
|
+
start = _enclosed[names[0].substr(1)]
|
|
408
|
+
enclosed = true
|
|
394
409
|
if start.is_empty():
|
|
395
410
|
return [UNCHANGED, ""]
|
|
396
411
|
names = names.slice(1)
|
|
@@ -398,7 +413,7 @@ func _after(path: NodePath, was: String, now: String) -> Array:
|
|
|
398
413
|
if target_was.is_empty():
|
|
399
414
|
return [UNCHANGED, ""]
|
|
400
415
|
var target_now: String = moved(target_was, _moves)
|
|
401
|
-
if target_now == target_was and now == was:
|
|
416
|
+
if target_now == target_was and now == was and not enclosed:
|
|
402
417
|
return [UNCHANGED, ""]
|
|
403
418
|
if target_now.is_empty():
|
|
404
419
|
return [DELETED, ""]
|
|
@@ -408,8 +423,9 @@ func _after(path: NodePath, was: String, now: String) -> Array:
|
|
|
408
423
|
return [UNCHANGED, ""]
|
|
409
424
|
if holder.get_node_or_null(NodePath(path.get_concatenated_names())) == target:
|
|
410
425
|
return [UNCHANGED, ""]
|
|
411
|
-
var
|
|
412
|
-
|
|
426
|
+
var through_unique: bool = unique and not enclosed
|
|
427
|
+
var to: String = str(holder.get_path_to(target, through_unique))
|
|
428
|
+
if through_unique:
|
|
413
429
|
to = _unique_to(start, target, to)
|
|
414
430
|
if path.get_subname_count() > 0:
|
|
415
431
|
to += ":" + path.get_concatenated_subnames()
|
|
@@ -6,6 +6,9 @@ extends RefCounted
|
|
|
6
6
|
const GdhFileWalk = preload("../file_walk.gd")
|
|
7
7
|
const GdhRead = preload("../reading.gd")
|
|
8
8
|
const GdhSame = preload("../same_values.gd")
|
|
9
|
+
## Control's layout modes, which the engine does not expose to scripts.
|
|
10
|
+
const POSITION_MODE: int = 0
|
|
11
|
+
const ANCHORS_MODE: int = 1
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
## The scene to instance for an add, or null for a node of a class, beside why neither can be made
|
|
@@ -142,18 +145,10 @@ static func reconnect(copies: Dictionary, top: Node) -> void:
|
|
|
142
145
|
and (target == top or top.is_ancestor_of(target as Node))
|
|
143
146
|
):
|
|
144
147
|
continue
|
|
145
|
-
|
|
146
|
-
# from the method alone would save the copy without the arguments the
|
|
147
|
-
# original passes.
|
|
148
|
-
var aimed: Callable = Callable(to, callable.get_method())
|
|
149
|
-
var unbound: int = callable.get_unbound_arguments_count()
|
|
150
|
-
if unbound > 0:
|
|
151
|
-
aimed = aimed.unbind(unbound)
|
|
152
|
-
if callable.get_bound_arguments_count() > 0:
|
|
153
|
-
aimed = aimed.bindv(callable.get_bound_arguments())
|
|
148
|
+
var joined: Callable = aimed(callable, to)
|
|
154
149
|
if (
|
|
155
|
-
not copy.is_connected(signal_name,
|
|
156
|
-
and copy.connect(signal_name,
|
|
150
|
+
not copy.is_connected(signal_name, joined)
|
|
151
|
+
and copy.connect(signal_name, joined, flags) != OK
|
|
157
152
|
):
|
|
158
153
|
push_error(
|
|
159
154
|
(
|
|
@@ -163,6 +158,19 @@ static func reconnect(copies: Dictionary, top: Node) -> void:
|
|
|
163
158
|
)
|
|
164
159
|
|
|
165
160
|
|
|
161
|
+
## [param callable] aimed at [param to], unbound and then bound again as Node.duplicate does: a
|
|
162
|
+
## callable made from the method alone would save the connection without the arguments the original
|
|
163
|
+
## passes.
|
|
164
|
+
static func aimed(callable: Callable, to: Object) -> Callable:
|
|
165
|
+
var aimed_at: Callable = Callable(to, callable.get_method())
|
|
166
|
+
var unbound: int = callable.get_unbound_arguments_count()
|
|
167
|
+
if unbound > 0:
|
|
168
|
+
aimed_at = aimed_at.unbind(unbound)
|
|
169
|
+
if callable.get_bound_arguments_count() > 0:
|
|
170
|
+
aimed_at = aimed_at.bindv(callable.get_bound_arguments())
|
|
171
|
+
return aimed_at
|
|
172
|
+
|
|
173
|
+
|
|
166
174
|
## Owns [param node] to [param root], and under it the nodes the root owns already and those of
|
|
167
175
|
## [param placed], the copies a duplicate placed. A node nothing owns is not the scene's: a script's
|
|
168
176
|
## _init made it, which a headless engine runs and the editor does not, and owning it saved it into
|
|
@@ -197,6 +205,30 @@ static func unplaceable(parent: Node, node: Node, asked: Variant) -> String:
|
|
|
197
205
|
return ""
|
|
198
206
|
|
|
199
207
|
|
|
208
|
+
## Whether [param node], an instance of [param packed] just added, would load at its parent's
|
|
209
|
+
## top-left corner rather than where its own scene places it. A Control its scene stores as
|
|
210
|
+
## uncontrolled takes position mode under a Control that is not a Container, so the file stores
|
|
211
|
+
## layout_mode 0 and leaves out the offsets, which are the scene's own. Setting position mode moves
|
|
212
|
+
## a Control to the corner, so the node loads there. Anchors mode with the same anchors places it
|
|
213
|
+
## the same and leaves its offsets as they load.
|
|
214
|
+
static func cornered(node: Node, packed: PackedScene) -> bool:
|
|
215
|
+
var control: Control = node as Control
|
|
216
|
+
if control == null or packed == null:
|
|
217
|
+
return false
|
|
218
|
+
if GdhRead.as_int(control.get("layout_mode")) != POSITION_MODE:
|
|
219
|
+
return false
|
|
220
|
+
if control.offset_left == 0.0 and control.offset_top == 0.0:
|
|
221
|
+
return false
|
|
222
|
+
var state: SceneState = packed.get_state()
|
|
223
|
+
while state != null:
|
|
224
|
+
for property: int in state.get_node_property_count(0):
|
|
225
|
+
if str(state.get_node_property_name(0, property)) == "layout_mode":
|
|
226
|
+
var stored: int = GdhRead.as_int(state.get_node_property_value(0, property))
|
|
227
|
+
return stored != POSITION_MODE and stored != ANCHORS_MODE
|
|
228
|
+
state = state.get_base_scene_state()
|
|
229
|
+
return false
|
|
230
|
+
|
|
231
|
+
|
|
200
232
|
## Moves [param node] to [param asked] among its siblings, when a place was asked for.
|
|
201
233
|
static func place(node: Node, asked: Variant) -> void:
|
|
202
234
|
if asked != null:
|