@@ -41,8 +41,80 @@ impl NodePath {
41
41
Self { opaque }
42
42
}
43
43
44
- pub fn is_empty ( & self ) -> bool {
45
- self . as_inner ( ) . is_empty ( )
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 ( )
46
118
}
47
119
48
120
/// Returns a 32-bit integer hash value representing the string.
@@ -53,6 +125,40 @@ impl NodePath {
53
125
. expect ( "Godot hashes are uint32_t" )
54
126
}
55
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
+ /// So, to express "until the end", you can simply pass a large value for `exclusive_end`, such as `i32::MAX`.
132
+ ///
133
+ /// If either `begin` or `exclusive_end` are negative, they will be relative to the end of the `NodePath`. \
134
+ /// For example, `path.subpath(0, -2)` is a shorthand for `path.subpath(0, path.get_total_count() - 2)`.
135
+ ///
136
+ /// _Godot equivalent: `slice`_
137
+ ///
138
+ /// # Compatibility
139
+ /// The `slice()` behavior for Godot <= 4.3 is unintuitive, see [#100954](https://github.com/godotengine/godot/pull/100954). godot-rust
140
+ /// automatically changes this to the fixed version for Godot 4.4+, even when used in older versions. So, the behavior is always the same.
141
+ // i32 used because it can be negative and many Godot APIs use this, see https://github.com/godot-rust/gdext/pull/982/files#r1893732978.
142
+ #[ cfg( since_api = "4.3" ) ]
143
+ #[ doc( alias = "slice" ) ]
144
+ pub fn subpath ( & self , begin : i32 , exclusive_end : i32 ) -> NodePath {
145
+ // Polyfill for bug https://github.com/godotengine/godot/pull/100954.
146
+ // TODO(v0.3) make polyfill (everything but last line) conditional if PR is merged in 4.4.
147
+ let name_count = self . get_name_count ( ) as i32 ;
148
+ let subname_count = self . get_subname_count ( ) as i32 ;
149
+ let total_count = name_count + subname_count;
150
+
151
+ let mut begin = begin. clamp ( -total_count, total_count) ;
152
+ if begin < 0 {
153
+ begin += total_count;
154
+ }
155
+ if begin > name_count {
156
+ begin += 1 ;
157
+ }
158
+
159
+ self . as_inner ( ) . slice ( begin as i64 , exclusive_end as i64 )
160
+ }
161
+
56
162
crate :: meta:: declare_arg_method! {
57
163
/// Use as argument for an [`impl AsArg<GString|StringName>`][crate::meta::AsArg] parameter.
58
164
///
0 commit comments