godot_core/builtin/string/node_path.rs
1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::fmt;
9
10use godot_ffi as sys;
11use godot_ffi::{ffi_methods, ExtVariantType, GdextBuild, GodotFfi};
12
13use super::{GString, StringName};
14use crate::builtin::inner;
15use crate::meta::signed_range::SignedRange;
16
17/// A pre-parsed scene tree path.
18///
19/// # Null bytes
20///
21/// Note that Godot ignores any bytes after a null-byte. This means that for instance `"hello, world!"` and `"hello, world!\0 ignored by Godot"`
22/// will be treated as the same string if converted to a `NodePath`.
23///
24/// # All string types
25///
26/// | Intended use case | String type |
27/// |-------------------|--------------------------------------------|
28/// | General purpose | [`GString`][crate::builtin::GString] |
29/// | Interned names | [`StringName`][crate::builtin::StringName] |
30/// | Scene-node paths | **`NodePath`** |
31///
32/// # Godot docs
33///
34/// [`NodePath` (stable)](https://docs.godotengine.org/en/stable/classes/class_nodepath.html)
35pub struct NodePath {
36 opaque: sys::types::OpaqueNodePath,
37}
38
39impl NodePath {
40 fn from_opaque(opaque: sys::types::OpaqueNodePath) -> Self {
41 Self { opaque }
42 }
43
44 /// Returns the node name at position `index`.
45 ///
46 /// If you want to get a property name instead, check out [`get_subname()`][Self::get_subname].
47 ///
48 /// # Example
49 /// ```no_run
50 /// # use godot::prelude::*;
51 /// let path = NodePath::from("../RigidBody2D/Sprite2D");
52 /// godot_print!("{}", path.get_name(0)); // ".."
53 /// godot_print!("{}", path.get_name(1)); // "RigidBody2D"
54 /// godot_print!("{}", path.get_name(2)); // "Sprite"
55 /// ```
56 ///
57 /// # Panics
58 /// In Debug mode, if `index` is out of bounds. In Release, a Godot error is generated and the result is unspecified (but safe).
59 pub fn get_name(&self, index: usize) -> StringName {
60 let inner = self.as_inner();
61 let index = index as i64;
62
63 debug_assert!(
64 index < inner.get_name_count(),
65 "NodePath '{self}': name at index {index} is out of bounds"
66 );
67
68 inner.get_name(index)
69 }
70
71 /// Returns the node subname (property) at position `index`.
72 ///
73 /// If you want to get a node name instead, check out [`get_name()`][Self::get_name].
74 ///
75 /// # Example
76 /// ```no_run
77 /// # use godot::prelude::*;
78 /// let path = NodePath::from("Sprite2D:texture:resource_name");
79 /// godot_print!("{}", path.get_subname(0)); // "texture"
80 /// godot_print!("{}", path.get_subname(1)); // "resource_name"
81 /// ```
82 ///
83 /// # Panics
84 /// In Debug mode, if `index` is out of bounds. In Release, a Godot error is generated and the result is unspecified (but safe).
85 pub fn get_subname(&self, index: usize) -> StringName {
86 let inner = self.as_inner();
87 let index = index as i64;
88
89 debug_assert!(
90 index < inner.get_subname_count(),
91 "NodePath '{self}': subname at index {index} is out of bounds"
92 );
93
94 inner.get_subname(index)
95 }
96
97 /// Returns the number of node names in the path. Property subnames are not included.
98 pub fn get_name_count(&self) -> usize {
99 self.as_inner()
100 .get_name_count()
101 .try_into()
102 .expect("Godot name counts are non-negative ints")
103 }
104
105 /// Returns the number of property names ("subnames") in the path. Each subname in the node path is listed after a colon character (`:`).
106 pub fn get_subname_count(&self) -> usize {
107 self.as_inner()
108 .get_subname_count()
109 .try_into()
110 .expect("Godot subname counts are non-negative ints")
111 }
112
113 /// Returns the total number of names + subnames.
114 ///
115 /// This method does not exist in Godot and is provided in Rust for convenience.
116 pub fn get_total_count(&self) -> usize {
117 self.get_name_count() + self.get_subname_count()
118 }
119
120 /// Returns a 32-bit integer hash value representing the string.
121 pub fn hash(&self) -> u32 {
122 self.as_inner()
123 .hash()
124 .try_into()
125 .expect("Godot hashes are uint32_t")
126 }
127
128 /// Returns the range `begin..exclusive_end` as a new `NodePath`.
129 ///
130 /// The absolute value of `begin` and `exclusive_end` will be clamped to [`get_total_count()`][Self::get_total_count].
131 ///
132 /// # Usage
133 /// For negative indices, use [`wrapped()`](crate::meta::wrapped).
134 ///
135 /// ```no_run
136 /// # use godot::builtin::NodePath;
137 /// # use godot::meta::wrapped;
138 /// let path = NodePath::from("path/to/Node:with:props");
139 ///
140 /// // If upper bound is not defined, `exclusive_end` will span to the end of the `NodePath`.
141 /// let sub = path.subpath(2..);
142 /// assert_eq!(sub, ":props".into());
143 ///
144 /// // If either `begin` or `exclusive_end` are negative, they will be relative to the end of the `NodePath`.
145 /// let total_count = path.get_total_count();
146 /// let wrapped_sub = path.subpath(wrapped(0..-2));
147 /// let normal_sub = path.subpath(0..total_count - 2);
148 /// // Both are equal to "path/to/Node".
149 /// assert_eq!(wrapped_sub, normal_sub);
150 /// ```
151 ///
152 /// _Godot equivalent: `slice`_
153 ///
154 /// # Compatibility
155 /// The `slice()` behavior for Godot <= 4.3 is unintuitive, see [#100954](https://github.com/godotengine/godot/pull/100954). Godot-rust
156 /// automatically changes this to the fixed version for Godot 4.4+, even when used in older versions. So, the behavior is always the same.
157 // i32 used because it can be negative and many Godot APIs use this, see https://github.com/godot-rust/gdext/pull/982/files#r1893732978.
158 #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
159 #[doc(alias = "slice")]
160 pub fn subpath(&self, range: impl SignedRange) -> NodePath {
161 let (from, exclusive_end) = range.signed();
162 // Polyfill for bug https://github.com/godotengine/godot/pull/100954, fixed in 4.4.
163 let begin = if GdextBuild::since_api("4.4") {
164 from
165 } else {
166 let name_count = self.get_name_count() as i64;
167 let subname_count = self.get_subname_count() as i64;
168 let total_count = name_count + subname_count;
169
170 let mut begin = from.clamp(-total_count, total_count);
171 if begin < 0 {
172 begin += total_count;
173 }
174 if begin > name_count {
175 begin += 1;
176 }
177 begin
178 };
179
180 self.as_inner()
181 .slice(begin, exclusive_end.unwrap_or(i32::MAX as i64))
182 }
183
184 crate::meta::declare_arg_method! {
185 /// Use as argument for an [`impl AsArg<GString|StringName>`][crate::meta::AsArg] parameter.
186 ///
187 /// This is a convenient way to convert arguments of similar string types.
188 ///
189 /// # Example
190 /// [`PackedStringArray`][crate::builtin::PackedStringArray] can insert elements using `AsArg<GString>`, so let's pass a `NodePath`:
191 /// ```no_run
192 /// # use godot::prelude::*;
193 /// let node_path = NodePath::from("Node2D/Label");
194 ///
195 /// let mut array = PackedStringArray::new();
196 /// array.push(node_path.arg());
197 /// ```
198 }
199
200 #[doc(hidden)]
201 pub fn as_inner(&self) -> inner::InnerNodePath<'_> {
202 inner::InnerNodePath::from_outer(self)
203 }
204}
205
206// SAFETY:
207// - `move_return_ptr`
208// Nothing special needs to be done beyond a `std::mem::swap` when returning a NodePath.
209// So we can just use `ffi_methods`.
210//
211// - `from_arg_ptr`
212// NodePaths are properly initialized through a `from_sys` call, but the ref-count should be
213// incremented as that is the callee's responsibility. Which we do by calling
214// `std::mem::forget(node_path.clone())`.
215unsafe impl GodotFfi for NodePath {
216 const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::NODE_PATH);
217
218 ffi_methods! { type sys::GDExtensionTypePtr = *mut Opaque; .. }
219}
220
221crate::meta::impl_godot_as_self!(NodePath: ByRef);
222
223impl_builtin_traits! {
224 for NodePath {
225 Default => node_path_construct_default;
226 Clone => node_path_construct_copy;
227 Drop => node_path_destroy;
228 Eq => node_path_operator_equal;
229 // NodePath provides no < operator.
230 Hash;
231 }
232}
233
234impl fmt::Display for NodePath {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 let string = GString::from(self);
237 <GString as fmt::Display>::fmt(&string, f)
238 }
239}
240
241/// Uses literal syntax from GDScript: `^"node_path"`
242impl fmt::Debug for NodePath {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 let string = GString::from(self);
245 write!(f, "^\"{string}\"")
246 }
247}
248
249// ----------------------------------------------------------------------------------------------------------------------------------------------
250// Conversion from/into other string-types
251
252impl_rust_string_conv!(NodePath);
253
254impl From<&str> for NodePath {
255 // NodePath doesn't offer direct construction from bytes; go via GString.
256 fn from(s: &str) -> Self {
257 Self::from(&GString::from(s))
258 }
259}
260
261impl From<&String> for NodePath {
262 fn from(s: &String) -> Self {
263 // NodePath doesn't offer direct construction from bytes; go via GString.
264 Self::from(&GString::from(s))
265 }
266}
267
268impl From<&GString> for NodePath {
269 fn from(string: &GString) -> Self {
270 unsafe {
271 Self::new_with_uninit(|self_ptr| {
272 let ctor = sys::builtin_fn!(node_path_from_string);
273 let args = [string.sys()];
274 ctor(self_ptr, args.as_ptr());
275 })
276 }
277 }
278}
279
280impl From<&StringName> for NodePath {
281 fn from(s: &StringName) -> Self {
282 Self::from(&GString::from(s))
283 }
284}
285
286#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
287mod serialize {
288 use std::fmt::Formatter;
289
290 use serde::de::{Error, Visitor};
291 use serde::{Deserialize, Deserializer, Serialize, Serializer};
292
293 use super::*;
294
295 // For "Available on crate feature `serde`" in docs. Cannot be inherited from module. Also does not support #[derive] (e.g. in Vector2).
296 #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
297 impl Serialize for NodePath {
298 #[inline]
299 fn serialize<S>(
300 &self,
301 serializer: S,
302 ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
303 where
304 S: Serializer,
305 {
306 serializer.serialize_newtype_struct("NodePath", &*self.to_string())
307 }
308 }
309
310 #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
311 impl<'de> Deserialize<'de> for NodePath {
312 #[inline]
313 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
314 where
315 D: Deserializer<'de>,
316 {
317 struct NodePathVisitor;
318
319 impl<'de> Visitor<'de> for NodePathVisitor {
320 type Value = NodePath;
321
322 fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
323 formatter.write_str("a NodePath")
324 }
325
326 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
327 where
328 E: Error,
329 {
330 Ok(NodePath::from(s))
331 }
332
333 fn visit_newtype_struct<D>(
334 self,
335 deserializer: D,
336 ) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
337 where
338 D: Deserializer<'de>,
339 {
340 deserializer.deserialize_str(self)
341 }
342 }
343
344 deserializer.deserialize_newtype_struct("NodePath", NodePathVisitor)
345 }
346 }
347}