1 /**
2 Pre-parsed scene tree path.
3 
4 Copyright:
5 Copyright (c) 2007 Juan Linietsky, Ariel Manzur.
6 Copyright (c) 2014 Godot Engine contributors (cf. AUTHORS.md)
7 Copyright (c) 2017 Godot-D contributors
8 Copyright (c) 2022 Godot-DLang contributors
9 
10 License: $(LINK2 https://opensource.org/licenses/MIT, MIT License)
11 
12 
13 */
14 module godot.nodepath;
15 
16 import core.lifetime : emplace;
17 
18 import godot.string;
19 import godot.stringname;
20 import godot.abi;
21 import godot.builtins;
22 
23 // HACK: string
24 // There's basically a lot of those hacks
25 
26 /**
27 A pre-parsed relative or absolute path in a scene tree, for use with $(D Node.getNode) and similar functions. It can reference a node, a resource within a node, or a property of a node or resource. For instance, `"Path2D/PathFollow2D/Sprite:texture:size"` would refer to the size property of the texture resource on the node named “Sprite” which is a child of the other named nodes in the path. Note that if you want to get a resource, you must end the path with a colon, otherwise the last element will be used as a property name.
28 
29 Exporting a `NodePath` variable will give you a node selection widget in the properties panel of the editor, which can often be useful.
30 
31 A `NodePath` is made up of a list of node names, a list of “subnode” (resource) names, and the name of a property in the final node or resource.
32 */
33 struct NodePath {
34     //@nogc nothrow:
35 
36     package(godot) union nodepath {
37         godot_node_path _node_path;
38         NodePath_Bind _bind;
39     }
40 
41     package(godot) nodepath _nodepath;
42     alias _nodepath this;
43 
44     this(this) {
45         // do a copy as godot might free it right after property set
46         if (_node_path._opaque.ptr) {
47             godot_node_path other = _node_path;
48             //emplace(&this, _bind.new1(other));
49             //_godot_api.variant_new_copy(&_node_path, &other);
50         }
51     }
52 
53     package(godot) this(godot_node_path n) {
54         _node_path = n;
55     }
56 
57     this(in String from) {
58         emplace(&this, _bind.new2(from));
59         // //_godot_api.node_path_new(&_node_path, &from._godot_string);
60     }
61 
62     this(in string name) {
63         String from = String(name);
64         // // _godot_api.node_path_new(&_node_path, &from._godot_string);
65         emplace(&this, _bind.new2(from));
66     }
67 
68     // quick hack for beta2+ changes
69     this(in NodePath other) {
70         _node_path = other._node_path;
71     }
72 
73     bool opEquals(in NodePath other) const {
74         if (_node_path == other._node_path)
75             return true;
76         //return _godot_api.node_path_operator_equal(&_node_path, &other._node_path);
77         return _bind == other._bind;
78     }
79 
80     StringName getName(in int idx) const {
81         return _bind.getName(idx);
82         //godot_string str = _godot_api.node_path_get_name(&_node_path, idx);
83         //return String(str);
84     }
85 
86     int getNameCount() const {
87         //return _godot_api.node_path_get_name_count(&_node_path);
88         return cast(int) _bind.getNameCount();
89     }
90 
91     StringName getSubname(in int idx) const {
92         return _bind.getSubname(idx);
93         //godot_string str = _godot_api.node_path_get_subname(&_node_path, idx);
94         //return String(str);
95     }
96 
97     int getSubnameCount() const {
98         return cast(int) _bind.getSubnameCount();
99         //return _godot_api.node_path_get_subname_count(&_node_path);
100     }
101 
102     bool isAbsolute() const {
103         return _bind.isAbsolute();
104         //return cast(bool)_godot_api.node_path_is_absolute(&_node_path);
105     }
106 
107     bool isEmpty() const {
108         return _bind.isEmpty();
109         //return cast(bool)_godot_api.node_path_is_empty(&_node_path);
110     }
111 
112     String str() const {
113         return String_Bind.new3(cast() this);
114         //godot_string str = _godot_api.node_path_as_string(&_node_path);
115         //return String(str);
116     }
117 
118     /// Splits a NodePath into a main node path and a property subpath (starting
119     /// with a ':'). The second element is left empty if there is no property.
120     NodePath[2] split() const {
121         static immutable wchar_t colon = ':';
122         NodePath[2] ret;
123         if (_node_path == godot_node_path.init)
124             return ret;
125         String path = str();
126         immutable(wchar_t)[] data = path.data();
127         if (data.length == 0)
128             return ret;
129         if (data[0] == colon) {
130             ret[1] = cast() this;
131             return ret;
132         }
133 
134         ptrdiff_t colonIndex = 0;
135         // Windows requires UTF-16 decoding to find the ':'
136         static if (is(wchar_t == wchar)) {
137             import utf_bc;
138 
139             enum TextFormat gdFormat = TextFormat.UTF_16;
140             auto decoded = data.decode!gdFormat;
141             do {
142                 dchar front = decoded.front;
143                 decoded.popFront();
144                 if (front == colon)
145                     break;
146                 colonIndex += codeLength!gdFormat(front);
147             }
148             while (!decoded.empty);
149             if (decoded.empty)
150                 colonIndex = -1;
151         } else {
152             import std.algorithm : countUntil;
153 
154             colonIndex = data[].countUntil(colon);
155         }
156 
157         // node only
158         if (colonIndex == -1) {
159             ret[0] = cast() this;
160             return ret;
161         }
162         ret[0] = NodePath(String(data[0 .. colonIndex]));
163         ret[1] = NodePath(String(data[colonIndex .. $]));
164         return ret;
165     }
166 
167     //this(ref return scope const NodePath other) // copy ctor
168     NodePath opAssign(ref const NodePath other) {
169         _node_path = other._node_path;
170         //godot_node_path_copy(&_node_path, &other._node_path);
171         if (other._node_path._opaque.ptr) {
172             import godot.api;
173             return _bind.new1(other);
174         }
175 
176         //print(this);
177 
178         return this;
179     }
180 
181     NodePath opAssign(in NodePath other) {
182         _node_path = other._node_path;
183         //godot_node_path_copy(&_node_path, &other._node_path);
184         if (other._node_path._opaque.ptr) {
185             import godot.api;
186 
187             print(other);
188             return _bind.new1(other);
189         }
190         //import godot.api;
191         //print(other);
192         //print(this);
193 
194         return this;
195     }
196 
197     ~this() {
198         //_godot_api.variant_destroy(&_node_path);
199         //if (_node_path._opaque)
200         //	_bind._destructor();
201         //_node_path._opaque = 0;
202     }
203 }