using System;
using System.Diagnostics.CodeAnalysis;
using Godot.NativeInterop;
#nullable enable
namespace Godot
{
/// <summary>
/// A pre-parsed relative or absolute path in a scene tree,
/// for use with <see cref="Node.GetNode(NodePath)"/> and similar functions.
/// It can reference a node, a resource within a node, or a property
/// of a node or resource.
/// For instance, <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c>
/// would refer to the <c>size</c> property of the <c>texture</c>
/// resource on the node named <c>"Sprite2D"</c> which is a child of
/// the other named nodes in the path.
/// You will usually just pass a string to <see cref="Node.GetNode(NodePath)"/>
/// and it will be automatically converted, but you may occasionally
/// want to parse a path ahead of time with NodePath.
/// Exporting a NodePath variable will give you a node selection widget
/// in the properties panel of the editor, which can often be useful.
/// A NodePath is composed of a list of slash-separated node names
/// (like a filesystem path) and an optional colon-separated list of
/// "subnames" which can be resources or properties.
///
/// Note: In the editor, NodePath properties are automatically updated when moving,
/// renaming or deleting a node in the scene tree, but they are never updated at runtime.
/// </summary>
/// <example>
/// Some examples of NodePaths include the following:
/// <code>
/// // No leading slash means it is relative to the current node.
/// new NodePath("A"); // Immediate child A.
/// new NodePath("A/B"); // A's child B.
/// new NodePath("."); // The current node.
/// new NodePath(".."); // The parent node.
/// new NodePath("../C"); // A sibling node C.
/// // A leading slash means it is absolute from the SceneTree.
/// new NodePath("/root"); // Equivalent to GetTree().Root
/// new NodePath("/root/Main"); // If your main scene's root node were named "Main".
/// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene.
/// </code>
/// </example>
public sealed class NodePath : IDisposable, IEquatable<NodePath?>
{
internal godot_node_path.movable NativeValue;
private WeakReference<IDisposable>? _weakReferenceToSelf;
~NodePath()
{
Dispose(false);
}
/// <summary>
/// Disposes of this <see cref="NodePath"/>.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Dispose(bool disposing)
{
// Always dispose `NativeValue` even if disposing is true
NativeValue.DangerousSelfRef.Dispose();
if (_weakReferenceToSelf != null)
{
DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf);
}
}
private NodePath(godot_node_path nativeValueToOwn)
{
NativeValue = (godot_node_path.movable)nativeValueToOwn;
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
// Explicit name to make it very clear
internal static NodePath CreateTakingOwnershipOfDisposableValue(godot_node_path nativeValueToOwn)
=> new NodePath(nativeValueToOwn);
/// <summary>
/// Constructs an empty <see cref="NodePath"/>.
/// </summary>
public NodePath()
{
}
/// <summary>
/// Constructs a <see cref="NodePath"/> from a string <paramref name="path"/>,
/// e.g.: <c>"Path2D/PathFollow2D/Sprite2D:texture:size"</c>.
/// A path is absolute if it starts with a slash. Absolute paths
/// are only valid in the global scene tree, not within individual
/// scenes. In a relative path, <c>"."</c> and <c>".."</c> indicate
/// the current node and its parent.
/// The "subnames" optionally included after the path to the target
/// node can point to resources or properties, and can also be nested.
/// </summary>
/// <example>
/// Examples of valid NodePaths (assuming that those nodes exist and
/// have the referenced resources or properties):
/// <code>
/// // Points to the Sprite2D node.
/// "Path2D/PathFollow2D/Sprite2D"
/// // Points to the Sprite2D node and its "texture" resource.
/// // GetNode() would retrieve "Sprite2D", while GetNodeAndResource()
/// // would retrieve both the Sprite2D node and the "texture" resource.
/// "Path2D/PathFollow2D/Sprite2D:texture"
/// // Points to the Sprite2D node and its "position" property.
/// "Path2D/PathFollow2D/Sprite2D:position"
/// // Points to the Sprite2D node and the "x" component of its "position" property.
/// "Path2D/PathFollow2D/Sprite2D:position:x"
/// // Absolute path (from "root")
/// "/root/Level/Path2D"
/// </code>
/// </example>
/// <param name="path">A string that represents a path in a scene tree.</param>
public NodePath(string path)
{
if (!string.IsNullOrEmpty(path))
{
NativeValue = (godot_node_path.movable)NativeFuncs.godotsharp_node_path_new_from_string(path);
_weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this);
}
}
/// <summary>
/// Converts a string to a <see cref="NodePath"/>.
/// </summary>
/// <param name="from">The string to convert.</param>
public static implicit operator NodePath(string from) => new NodePath(from);
/// <summary>
/// Converts this <see cref="NodePath"/> to a string.
/// </summary>
/// <param name="from">The <see cref="NodePath"/> to convert.</param>
[return: NotNullIfNotNull("from")]
public static implicit operator string?(NodePath? from) => from?.ToString();
/// <summary>
/// Converts this <see cref="NodePath"/> to a string.
/// </summary>
/// <returns>A string representation of this <see cref="NodePath"/>.</returns>
public override string ToString()
{
if (IsEmpty)
return string.Empty;
var src = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_as_string(out godot_string dest, src);
using (dest)
return Marshaling.ConvertStringToManaged(dest);
}
/// <summary>
/// Returns a node path with a colon character (<c>:</c>) prepended,
/// transforming it to a pure property path with no node name (defaults
/// to resolving from the current node).
/// </summary>
/// <example>
/// <code>
/// // This will be parsed as a node path to the "x" property in the "position" node.
/// var nodePath = new NodePath("position:x");
/// // This will be parsed as a node path to the "x" component of the "position" property in the current node.
/// NodePath propertyPath = nodePath.GetAsPropertyPath();
/// GD.Print(propertyPath); // :position:x
/// </code>
/// </example>
/// <returns>The <see cref="NodePath"/> as a pure property path.</returns>
public NodePath GetAsPropertyPath()
{
godot_node_path propertyPath = default;
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_as_property_path(self, ref propertyPath);
return CreateTakingOwnershipOfDisposableValue(propertyPath);
}
/// <summary>
/// Returns all names concatenated with a slash character (<c>/</c>).
/// </summary>
/// <example>
/// <code>
/// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
/// GD.Print(nodepath.GetConcatenatedNames()); // Path2D/PathFollow2D/Sprite2D
/// </code>
/// </example>
/// <returns>The names concatenated with <c>/</c>.</returns>
public string GetConcatenatedNames()
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_concatenated_names(self, out godot_string names);
using (names)
return Marshaling.ConvertStringToManaged(names);
}
/// <summary>
/// Returns all subnames concatenated with a colon character (<c>:</c>)
/// as separator, i.e. the right side of the first colon in a node path.
/// </summary>
/// <example>
/// <code>
/// var nodepath = new NodePath("Path2D/PathFollow2D/Sprite2D:texture:load_path");
/// GD.Print(nodepath.GetConcatenatedSubnames()); // texture:load_path
/// </code>
/// </example>
/// <returns>The subnames concatenated with <c>:</c>.</returns>
public string GetConcatenatedSubNames()
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_concatenated_subnames(self, out godot_string subNames);
using (subNames)
return Marshaling.ConvertStringToManaged(subNames);
}
/// <summary>
/// Gets the node name indicated by <paramref name="idx"/> (0 to <see cref="GetNameCount"/>).
/// </summary>
/// <example>
/// <code>
/// var nodePath = new NodePath("Path2D/PathFollow2D/Sprite2D");
/// GD.Print(nodePath.GetName(0)); // Path2D
/// GD.Print(nodePath.GetName(1)); // PathFollow2D
/// GD.Print(nodePath.GetName(2)); // Sprite
/// </code>
/// </example>
/// <param name="idx">The name index.</param>
/// <returns>The name at the given index <paramref name="idx"/>.</returns>
public string GetName(int idx)
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_name(self, idx, out godot_string name);
using (name)
return Marshaling.ConvertStringToManaged(name);
}
/// <summary>
/// Gets the number of node names which make up the path.
/// Subnames (see <see cref="GetSubNameCount"/>) are not included.
/// For example, <c>"Path2D/PathFollow2D/Sprite2D"</c> has 3 names.
/// </summary>
/// <returns>The number of node names which make up the path.</returns>
public int GetNameCount()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_get_name_count(self);
}
/// <summary>
/// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubNameCount"/>).
/// </summary>
/// <param name="idx">The subname index.</param>
/// <returns>The subname at the given index <paramref name="idx"/>.</returns>
public string GetSubName(int idx)
{
var self = (godot_node_path)NativeValue;
NativeFuncs.godotsharp_node_path_get_subname(self, idx, out godot_string subName);
using (subName)
return Marshaling.ConvertStringToManaged(subName);
}
/// <summary>
/// Gets the number of resource or property names ("subnames") in the path.
/// Each subname is listed after a colon character (<c>:</c>) in the node path.
/// For example, <c>"Path2D/PathFollow2D/Sprite2D:texture:load_path"</c> has 2 subnames.
/// </summary>
/// <returns>The number of subnames in the path.</returns>
public int GetSubNameCount()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_get_subname_count(self);
}
/// <summary>
/// Returns <see langword="true"/> if the node path is absolute (as opposed to relative),
/// which means that it starts with a slash character (<c>/</c>). Absolute node paths can
/// be used to access the root node (<c>"/root"</c>) or autoloads (e.g. <c>"/global"</c>
/// if a "global" autoload was registered).
/// </summary>
/// <returns>If the <see cref="NodePath"/> is an absolute path.</returns>
public bool IsAbsolute()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_is_absolute(self).ToBool();
}
/// <summary>
/// Returns <see langword="true"/> if the node path is empty.
/// </summary>
/// <returns>If the <see cref="NodePath"/> is empty.</returns>
public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty;
public static bool operator ==(NodePath? left, NodePath? right)
{
if (left is null)
return right is null;
return left.Equals(right);
}
public static bool operator !=(NodePath? left, NodePath? right)
{
return !(left == right);
}
public bool Equals([NotNullWhen(true)] NodePath? other)
{
if (other is null)
return false;
var self = (godot_node_path)NativeValue;
var otherNative = (godot_node_path)other.NativeValue;
return NativeFuncs.godotsharp_node_path_equals(self, otherNative).ToBool();
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return ReferenceEquals(this, obj) || (obj is NodePath other && Equals(other));
}
public override int GetHashCode()
{
var self = (godot_node_path)NativeValue;
return NativeFuncs.godotsharp_node_path_hash(self);
}
}
}