inspector_plugins.rst 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. .. _doc_inspector_plugins:
  2. Inspector plugins
  3. =================
  4. The inspector dock allows you to create custom widgets to edit properties
  5. through plugins. This can be beneficial when working with custom datatypes and
  6. resources, although you can use the feature to change the inspector widgets for
  7. built-in types. You can design custom controls for specific properties, entire
  8. objects, and even separate controls associated with particular datatypes.
  9. This guide explains how to use the :ref:`class_EditorInspectorPlugin` and
  10. :ref:`class_EditorProperty` classes to create a custom interface for integers,
  11. replacing the default behavior with a button that generates random values
  12. between 0 and 99.
  13. .. figure:: img/inspector_plugin_example.png
  14. :align: center
  15. The default behavior on the left and the end result on the right.
  16. Setting up your plugin
  17. ----------------------
  18. Create a new empty plugin to get started.
  19. .. seealso:: See :ref:`doc_making_plugins` guide to set up your new plugin.
  20. Let's assume you've called your plugin folder ``my_inspector_plugin``. If so,
  21. you should end up with a new ``addons/my_inspector_plugin`` folder that contains
  22. two files: ``plugin.cfg`` and ``plugin.gd``.
  23. As before, ``plugin.gd`` is a script extending :ref:`class_EditorPlugin` and you
  24. need to introduce new code for its ``_enter_tree`` and ``_exit_tree`` methods.
  25. To set up your inspector plugin, you must load its script, then create and add
  26. the instance by calling ``add_inspector_plugin()``. If the plugin is disabled,
  27. you should remove the instance you have added by calling
  28. ``remove_inspector_plugin()``.
  29. .. note:: Here, you are loading a script and not a packed scene. Therefore you
  30. should use ``new()`` instead of ``instantiate()``.
  31. .. tabs::
  32. .. code-tab:: gdscript GDScript
  33. # plugin.gd
  34. @tool
  35. extends EditorPlugin
  36. var plugin
  37. func _enter_tree():
  38. plugin = preload("res://addons/my_inspector_plugin/my_inspector_plugin.gd").new()
  39. add_inspector_plugin(plugin)
  40. func _exit_tree():
  41. remove_inspector_plugin(plugin)
  42. .. code-tab:: csharp
  43. // Plugin.cs
  44. #if TOOLS
  45. using Godot;
  46. [Tool]
  47. public partial class Plugin : EditorPlugin
  48. {
  49. private MyInspectorPlugin _plugin;
  50. public override void _EnterTree()
  51. {
  52. _plugin = new MyInspectorPlugin();
  53. AddInspectorPlugin(_plugin);
  54. }
  55. public override void _ExitTree()
  56. {
  57. RemoveInspectorPlugin(_plugin);
  58. }
  59. }
  60. #endif
  61. Interacting with the inspector
  62. ------------------------------
  63. To interact with the inspector dock, your ``my_inspector_plugin.gd`` script must
  64. extend the :ref:`class_EditorInspectorPlugin` class. This class provides several
  65. virtual methods that affect how the inspector handles properties.
  66. To have any effect at all, the script must implement the ``_can_handle()``
  67. method. This function is called for each edited :ref:`class_Object` and must
  68. return ``true`` if this plugin should handle the object or its properties.
  69. .. note:: This includes any :ref:`class_Resource` attached to the object.
  70. You can implement four other methods to add controls to the inspector at
  71. specific positions. The ``_parse_begin()`` and ``_parse_end()`` methods are called
  72. only once at the beginning and the end of parsing for each object, respectively.
  73. They can add controls at the top or bottom of the inspector layout by calling
  74. ``add_custom_control()``.
  75. As the editor parses the object, it calls the ``_parse_category()`` and
  76. ``_parse_property()`` methods. There, in addition to ``add_custom_control()``,
  77. you can call both ``add_property_editor()`` and
  78. ``add_property_editor_for_multiple_properties()``. Use these last two methods to
  79. specifically add :ref:`class_EditorProperty`-based controls.
  80. .. tabs::
  81. .. code-tab:: gdscript GDScript
  82. # my_inspector_plugin.gd
  83. extends EditorInspectorPlugin
  84. var RandomIntEditor = preload("res://addons/my_inspector_plugin/random_int_editor.gd")
  85. func _can_handle(object):
  86. # We support all objects in this example.
  87. return true
  88. func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide):
  89. # We handle properties of type integer.
  90. if type == TYPE_INT:
  91. # Create an instance of the custom property editor and register
  92. # it to a specific property path.
  93. add_property_editor(name, RandomIntEditor.new())
  94. # Inform the editor to remove the default property editor for
  95. # this property type.
  96. return true
  97. else:
  98. return false
  99. .. code-tab:: csharp
  100. // MyInspectorPlugin.cs
  101. #if TOOLS
  102. using Godot;
  103. public partial class MyInspectorPlugin : EditorInspectorPlugin
  104. {
  105. public override bool _CanHandle(GodotObject @object)
  106. {
  107. // We support all objects in this example.
  108. return true;
  109. }
  110. public override bool _ParseProperty(GodotObject @object, Variant.Type type,
  111. string name, PropertyHint hintType, string hintString,
  112. PropertyUsageFlags usageFlags, bool wide)
  113. {
  114. // We handle properties of type integer.
  115. if (type == Variant.Type.Int)
  116. {
  117. // Create an instance of the custom property editor and register
  118. // it to a specific property path.
  119. AddPropertyEditor(name, new RandomIntEditor());
  120. // Inform the editor to remove the default property editor for
  121. // this property type.
  122. return true;
  123. }
  124. return false;
  125. }
  126. }
  127. #endif
  128. Adding an interface to edit properties
  129. --------------------------------------
  130. The :ref:`class_EditorProperty` class is a special type of :ref:`class_Control`
  131. that can interact with the inspector dock's edited objects. It doesn't display
  132. anything but can house any other control nodes, including complex scenes.
  133. There are three essential parts to the script extending
  134. :ref:`class_EditorProperty`:
  135. 1. You must define the ``_init()`` method to set up the control nodes'
  136. structure.
  137. 2. You should implement the ``_update_property()`` to handle changes to the data
  138. from the outside.
  139. 3. A signal must be emitted at some point to inform the inspector that the
  140. control has changed the property using ``emit_changed``.
  141. You can display your custom widget in two ways. Use just the default ``add_child()``
  142. method to display it to the right of the property name, and use ``add_child()``
  143. followed by ``set_bottom_editor()`` to position it below the name.
  144. .. FIXME: The second tab has the C# lexer for highlighting disabled for now, as the provided code causes errors.
  145. .. tabs::
  146. .. code-tab:: gdscript GDScript
  147. # random_int_editor.gd
  148. extends EditorProperty
  149. # The main control for editing the property.
  150. var property_control = Button.new()
  151. # An internal value of the property.
  152. var current_value = 0
  153. # A guard against internal changes when the property is updated.
  154. var updating = false
  155. func _init():
  156. # Add the control as a direct child of EditorProperty node.
  157. add_child(property_control)
  158. # Make sure the control is able to retain the focus.
  159. add_focusable(property_control)
  160. # Setup the initial state and connect to the signal to track changes.
  161. refresh_control_text()
  162. property_control.pressed.connect(_on_button_pressed)
  163. func _on_button_pressed():
  164. # Ignore the signal if the property is currently being updated.
  165. if (updating):
  166. return
  167. # Generate a new random integer between 0 and 99.
  168. current_value = randi() % 100
  169. refresh_control_text()
  170. emit_changed(get_edited_property(), current_value)
  171. func _update_property():
  172. # Read the current value from the property.
  173. var new_value = get_edited_object()[get_edited_property()]
  174. if (new_value == current_value):
  175. return
  176. # Update the control with the new value.
  177. updating = true
  178. current_value = new_value
  179. refresh_control_text()
  180. updating = false
  181. func refresh_control_text():
  182. property_control.text = "Value: " + str(current_value)
  183. .. code-tab:: csharp
  184. // RandomIntEditor.cs
  185. #if TOOLS
  186. using Godot;
  187. public partial class RandomIntEditor : EditorProperty
  188. {
  189. // The main control for editing the property.
  190. private Button _propertyControl = new Button();
  191. // An internal value of the property.
  192. private int _currentValue = 0;
  193. // A guard against internal changes when the property is updated.
  194. private bool _updating = false;
  195. public RandomIntEditor()
  196. {
  197. // Add the control as a direct child of EditorProperty node.
  198. AddChild(_propertyControl);
  199. // Make sure the control is able to retain the focus.
  200. AddFocusable(_propertyControl);
  201. // Setup the initial state and connect to the signal to track changes.
  202. RefreshControlText();
  203. _propertyControl.Pressed += OnButtonPressed;
  204. }
  205. private void OnButtonPressed()
  206. {
  207. // Ignore the signal if the property is currently being updated.
  208. if (_updating)
  209. {
  210. return;
  211. }
  212. // Generate a new random integer between 0 and 99.
  213. _currentValue = (int)GD.Randi() % 100;
  214. RefreshControlText();
  215. EmitChanged(GetEditedProperty(), _currentValue);
  216. }
  217. public override void _UpdateProperty()
  218. {
  219. // Read the current value from the property.
  220. var newValue = (int)GetEditedObject().Get(GetEditedProperty());
  221. if (newValue == _currentValue)
  222. {
  223. return;
  224. }
  225. // Update the control with the new value.
  226. _updating = true;
  227. _currentValue = newValue;
  228. RefreshControlText();
  229. _updating = false;
  230. }
  231. private void RefreshControlText()
  232. {
  233. _propertyControl.Text = $"Value: {_currentValue}";
  234. }
  235. }
  236. #endif
  237. Using the example code above you should be able to make a custom widget that
  238. replaces the default :ref:`class_SpinBox` control for integers with a
  239. :ref:`class_Button` that generates random values.