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.
@@ -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] = [changed]
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 to: String = str(holder.get_path_to(target, unique))
412
- if unique:
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
- # Unbound and then bound again as Node.duplicate does: a callable made
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, aimed)
156
- and copy.connect(signal_name, aimed, flags) != OK
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: