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    crate::declare_hash_u32_method! {
121        /// Returns a 32-bit integer hash value representing the string.
122    }
123
124    #[deprecated = "renamed to hash_u32"]
125    pub fn hash(&self) -> u32 {
126        self.as_inner()
127            .hash()
128            .try_into()
129            .expect("Godot hashes are uint32_t")
130    }
131
132    /// Returns the range `begin..exclusive_end` as a new `NodePath`.
133    ///
134    /// The absolute value of `begin` and `exclusive_end` will be clamped to [`get_total_count()`][Self::get_total_count].
135    ///
136    /// # Usage
137    /// For negative indices, use [`wrapped()`](crate::meta::wrapped).
138    ///
139    /// ```no_run
140    /// # use godot::builtin::NodePath;
141    /// # use godot::meta::wrapped;
142    /// let path = NodePath::from("path/to/Node:with:props");
143    ///
144    /// // If upper bound is not defined, `exclusive_end` will span to the end of the `NodePath`.
145    /// let sub = path.subpath(2..);
146    /// assert_eq!(sub, ":props".into());
147    ///
148    /// // If either `begin` or `exclusive_end` are negative, they will be relative to the end of the `NodePath`.
149    /// let total_count = path.get_total_count();
150    /// let wrapped_sub = path.subpath(wrapped(0..-2));
151    /// let normal_sub = path.subpath(0..total_count - 2);
152    /// // Both are equal to "path/to/Node".
153    /// assert_eq!(wrapped_sub, normal_sub);
154    /// ```
155    ///
156    /// _Godot equivalent: `slice`_
157    ///
158    /// # Compatibility
159    /// The `slice()` behavior for Godot <= 4.3 is unintuitive, see [#100954](https://github.com/godotengine/godot/pull/100954). Godot-rust
160    /// automatically changes this to the fixed version for Godot 4.4+, even when used in older versions. So, the behavior is always the same.
161    // i32 used because it can be negative and many Godot APIs use this, see https://github.com/godot-rust/gdext/pull/982/files#r1893732978.
162    #[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
163    #[doc(alias = "slice")]
164    pub fn subpath(&self, range: impl SignedRange) -> NodePath {
165        let (from, exclusive_end) = range.signed();
166        // Polyfill for bug https://github.com/godotengine/godot/pull/100954, fixed in 4.4.
167        let begin = if GdextBuild::since_api("4.4") {
168            from
169        } else {
170            let name_count = self.get_name_count() as i64;
171            let subname_count = self.get_subname_count() as i64;
172            let total_count = name_count + subname_count;
173
174            let mut begin = from.clamp(-total_count, total_count);
175            if begin < 0 {
176                begin += total_count;
177            }
178            if begin > name_count {
179                begin += 1;
180            }
181            begin
182        };
183
184        self.as_inner()
185            .slice(begin, exclusive_end.unwrap_or(i32::MAX as i64))
186    }
187
188    crate::meta::declare_arg_method! {
189        /// Use as argument for an [`impl AsArg<GString|StringName>`][crate::meta::AsArg] parameter.
190        ///
191        /// This is a convenient way to convert arguments of similar string types.
192        ///
193        /// # Example
194        /// [`PackedStringArray`][crate::builtin::PackedStringArray] can insert elements using `AsArg<GString>`, so let's pass a `NodePath`:
195        /// ```no_run
196        /// # use godot::prelude::*;
197        /// let node_path = NodePath::from("Node2D/Label");
198        ///
199        /// let mut array = PackedStringArray::new();
200        /// array.push(node_path.arg());
201        /// ```
202    }
203
204    #[doc(hidden)]
205    pub fn as_inner(&self) -> inner::InnerNodePath<'_> {
206        inner::InnerNodePath::from_outer(self)
207    }
208}
209
210// SAFETY:
211// - `move_return_ptr`
212//   Nothing special needs to be done beyond a `std::mem::swap` when returning a NodePath.
213//   So we can just use `ffi_methods`.
214//
215// - `from_arg_ptr`
216//   NodePaths are properly initialized through a `from_sys` call, but the ref-count should be
217//   incremented as that is the callee's responsibility. Which we do by calling
218//   `std::mem::forget(node_path.clone())`.
219unsafe impl GodotFfi for NodePath {
220    const VARIANT_TYPE: ExtVariantType = ExtVariantType::Concrete(sys::VariantType::NODE_PATH);
221
222    ffi_methods! { type sys::GDExtensionTypePtr = *mut Opaque; .. }
223}
224
225crate::meta::impl_godot_as_self!(NodePath: ByRef);
226
227impl_builtin_traits! {
228    for NodePath {
229        Default => node_path_construct_default;
230        Clone => node_path_construct_copy;
231        Drop => node_path_destroy;
232        Eq => node_path_operator_equal;
233        // NodePath provides no < operator.
234        Hash;
235    }
236}
237
238impl fmt::Display for NodePath {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        let string = GString::from(self);
241        <GString as fmt::Display>::fmt(&string, f)
242    }
243}
244
245/// Uses literal syntax from GDScript: `^"node_path"`
246impl fmt::Debug for NodePath {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        let string = GString::from(self);
249        write!(f, "^\"{string}\"")
250    }
251}
252
253// ----------------------------------------------------------------------------------------------------------------------------------------------
254// Conversion from/into other string-types
255
256impl_rust_string_conv!(NodePath);
257
258impl From<&str> for NodePath {
259    // NodePath doesn't offer direct construction from bytes; go via GString.
260    fn from(s: &str) -> Self {
261        Self::from(&GString::from(s))
262    }
263}
264
265impl From<&String> for NodePath {
266    fn from(s: &String) -> Self {
267        // NodePath doesn't offer direct construction from bytes; go via GString.
268        Self::from(&GString::from(s))
269    }
270}
271
272impl From<&GString> for NodePath {
273    fn from(string: &GString) -> Self {
274        unsafe {
275            Self::new_with_uninit(|self_ptr| {
276                let ctor = sys::builtin_fn!(node_path_from_string);
277                let args = [string.sys()];
278                ctor(self_ptr, args.as_ptr());
279            })
280        }
281    }
282}
283
284impl From<&StringName> for NodePath {
285    fn from(s: &StringName) -> Self {
286        Self::from(&GString::from(s))
287    }
288}
289
290#[cfg(feature = "serde")] #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
291mod serialize {
292    use std::fmt::Formatter;
293
294    use serde::de::{Error, Visitor};
295    use serde::{Deserialize, Deserializer, Serialize, Serializer};
296
297    use super::*;
298
299    // For "Available on crate feature `serde`" in docs. Cannot be inherited from module. Also does not support #[derive] (e.g. in Vector2).
300    #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
301    impl Serialize for NodePath {
302        #[inline]
303        fn serialize<S>(
304            &self,
305            serializer: S,
306        ) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
307        where
308            S: Serializer,
309        {
310            serializer.serialize_newtype_struct("NodePath", &*self.to_string())
311        }
312    }
313
314    #[cfg_attr(published_docs, doc(cfg(feature = "serde")))]
315    impl<'de> Deserialize<'de> for NodePath {
316        #[inline]
317        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
318        where
319            D: Deserializer<'de>,
320        {
321            struct NodePathVisitor;
322
323            impl<'de> Visitor<'de> for NodePathVisitor {
324                type Value = NodePath;
325
326                fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
327                    formatter.write_str("a NodePath")
328                }
329
330                fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
331                where
332                    E: Error,
333                {
334                    Ok(NodePath::from(s))
335                }
336
337                fn visit_newtype_struct<D>(
338                    self,
339                    deserializer: D,
340                ) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
341                where
342                    D: Deserializer<'de>,
343                {
344                    deserializer.deserialize_str(self)
345                }
346            }
347
348            deserializer.deserialize_newtype_struct("NodePath", NodePathVisitor)
349        }
350    }
351}