making_main_screen_plugins.rst 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. .. _doc_making_main_screen_plugins:
  2. Making main screen plugins
  3. ==========================
  4. What this tutorial covers
  5. -------------------------
  6. As seen in the :ref:`doc_making_plugins` page, making a basic plugin that
  7. extends the editor is fairly easy. This plugin mechanism also allows you to
  8. create new UIs in the central part of the editor, similarly to the basic 2D, 3D,
  9. Script and AssetLib views. Such editor plugins are referred as "Main screen
  10. plugins".
  11. This tutorial leads you through the creation of a basic main screen plugin. With
  12. this plugin example, we want to demonstrate:
  13. - Creating a main screen plugin
  14. - Linking the main screen to another plugin GUI element (such as a Tab panel,
  15. similar to the Inspector tab)
  16. For the sake of simplicity, the two GUI elements of our main screen plugin will
  17. both consist in a Label and a Button. Pressing one element's button will display
  18. some text on the other's label node.
  19. Initializing the plugin
  20. -----------------------
  21. The plugin itself is a Godot project. It is best to set its contents in an
  22. ``addons/my_plugin_name/`` structure. The only files that lie in the root folder
  23. are the project.godot file, and the project icon.
  24. In the ``addons/my_plugin_name/`` folder, we create the ``plugin.cfg`` file as
  25. described in the :ref:`doc_making_plugins` page.
  26. ::
  27. [plugin]
  28. name="Main screen plugin demo"
  29. description="A plugin that adds a main screen panel and a side-panel which communicate with each other."
  30. author="Your Name Here"
  31. version="1.0.0"
  32. script="main_screen_plugin.gd"
  33. We also initialize the file targeted by the ``script=`` property of the ``.cfg``
  34. file. In our example, ``main_screen_plugin.gd``.
  35. ::
  36. tool
  37. extends EditorPlugin
  38. func _enter_tree():
  39. pass
  40. func _exit_tree():
  41. pass
  42. func has_main_screen():
  43. return true
  44. func make_visible(visible):
  45. pass
  46. func get_plugin_name():
  47. return "Main Screen Plugin"
  48. func get_plugin_icon():
  49. return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons")
  50. The important part in this script is the ``has_main_screen()`` function, which is
  51. overloaded so it returns ``true``. This function is automatically called by the
  52. editor on plugin activation, to tell it that this plugin adds a new center view to
  53. the editor. For now, we'll leave this script as-is and we'll come back to it
  54. later.
  55. Scenes
  56. ------
  57. The ``main_screen_plugin.gd`` file will be responsible for each of our plugin's
  58. UI element instantiation, and it will also manage the communication between them.
  59. As a matter of fact, we wish to design each UI element in their own scene.
  60. Different scenes are not aware of each other unless they are both children of a
  61. parent scene, yet they will then require ``get_node("../sibling")`` accessors.
  62. Such practice is more likely to produce errors at runtime, especially if these
  63. scenes do not share the same parent node. This is why, they should only be
  64. allowed to access their children.
  65. So, in order to communicate information to another scene, the best design is to
  66. define signals. If a user action in a UI scene #1 has to trigger something in
  67. another UI scene #2, then this user action has to emit a signal from scene #1,
  68. and scene #2 will be connected to that signal. Since all of our UI scenes will
  69. be instanced by ``main_screen_plugin.gd`` script, this one script will also
  70. connect each of them to the required signals.
  71. .. note:: If the ``main_screen_plugin.gd`` instantiates the UI scenes, won't
  72. they be sibling nodes then?
  73. Not necessarily: this script may add all UI scenes as children of the same node
  74. of the editor's scene tree - but maybe it won't. And the ``main_screen_plugin.gd``
  75. script will *not* be the parent node of any instantiated scene because it is a
  76. script, not a node! This script will only hold references to instantiated
  77. scenes.
  78. Main screen scene
  79. -----------------
  80. Create a new scene with a ``Panel`` root node. Select this root node,
  81. and in the viewport, click the ``Layout`` menu and select ``Full Rect``.
  82. You also need to enable the ``Expand`` vertical size flag in the inspector.
  83. The panel now uses all the space available in the viewport.
  84. Now, let's add a new script on the root node. Name it ``main_panel.gd``.
  85. We then add 2 children to this Panel node: first a ``Button`` node. Place it
  86. anywhere on the Panel. Then add a ``Label`` node.
  87. Now we need to define a behaviour when this button is pressed. This is covered
  88. by the :ref:`Handling a signal <doc_scripting_handling_a_signal>` page, so this
  89. part will not be described in details in this tutorial.
  90. Select the Button node and click the ``Node`` side dock.
  91. Select the ``pressed()`` signal and click the ``Connect`` button (you can also
  92. double-click the ``pressed()`` signal instead). In the window that opened,
  93. select the Panel node (we will centralize all behaviors in its attached
  94. script). Keep the default function name, make sure that the ``Make function``
  95. toggle is ON and hit ``Connect``. This creates an ``_on_Button_pressed()``
  96. function in the ``main_panel.gd`` script, that will be called every time the
  97. button is pressed.
  98. As the button gets pressed, we want the side-panel's ``Label`` node to show a
  99. specific text. As explained above, we cannot directly access the target scene,
  100. so we'll emit a signal instead. The ``main_screen_plugin.gd`` script will then
  101. connect this signal to the target scene. Let's continue in the ``main_panel.gd``
  102. script:
  103. ::
  104. tool
  105. extends Panel
  106. signal main_button_pressed(value)
  107. func _on_Button_pressed():
  108. emit_signal("main_button_pressed", "Hello from main screen!")
  109. In the same way, this main scene's Label node has to show a value when it
  110. receives a specific signal. Let's create a new
  111. ``_on_side_button_pressed(text_to_show)`` function for this purpose:
  112. ::
  113. func _on_side_button_pressed(text_to_show):
  114. $Label.text = text_to_show
  115. We are done for the main screen panel. Save the scene as ``main_panel.tscn``.
  116. Tabbed panel scene
  117. ------------------
  118. The tabbed panel scene is almost identical to the main panel scene. You can
  119. either duplicate the ``main_panel.tscn`` file and name the new file
  120. ``side_panel.tscn``, or re-create it from a new scene by following the previous
  121. section again. However, you will have to create a new script and attach it to
  122. the Panel root node. Save it as ``side_panel.gd``. Its content is slightly
  123. different, as the signal emitted and the target function have different names.
  124. Here is the script's full content:
  125. ::
  126. tool
  127. extends Panel
  128. signal side_button_pressed(value)
  129. func _on_Button_pressed():
  130. emit_signal("side_button_pressed", "Hello from side panel!")
  131. func _on_main_button_pressed(text_to_show):
  132. $Label.text = text_to_show
  133. Connecting the two scenes in the plugin script
  134. ----------------------------------------------
  135. We now need to update the ``main_screen_plugin.gd`` script so the plugin
  136. instances our 2 GUI scenes and places them at the right places in the editor.
  137. Here is the full ``main.gd``:
  138. ::
  139. tool
  140. extends EditorPlugin
  141. const MainPanel = preload("res://addons/my_plugin_name/main_panel.tscn")
  142. const SidePanel = preload("res://addons/my_plugin_name/side_panel.tscn")
  143. var main_panel_instance
  144. var side_panel_instance
  145. func _enter_tree():
  146. main_panel_instance = MainPanel.instance()
  147. side_panel_instance = SidePanel.instance()
  148. # Add the main panel to the editor's main viewport.
  149. get_editor_interface().get_editor_viewport().add_child(main_panel_instance)
  150. # Add the side panel to the Upper Left (UL) dock slot of the left part of the editor.
  151. # The editor has 4 dock slots (UL, UR, BL, BR) on each side (left/right) of the main screen.
  152. add_control_to_dock(DOCK_SLOT_LEFT_UL, side_panel_instance)
  153. # Hide the main panel
  154. make_visible(false)
  155. func _exit_tree():
  156. main_panel_instance.queue_free()
  157. side_panel_instance.queue_free()
  158. func _ready():
  159. main_panel_instance.connect("main_button_pressed", side_panel_instance, "_on_main_button_pressed")
  160. side_panel_instance.connect("side_button_pressed", main_panel_instance, "_on_side_button_pressed")
  161. func has_main_screen():
  162. return true
  163. func make_visible(visible):
  164. if visible:
  165. main_panel_instance.show()
  166. else:
  167. main_panel_instance.hide()
  168. func get_plugin_name():
  169. return "Main Screen Plugin"
  170. func get_plugin_icon():
  171. # Must return some kind of Texture for the icon.
  172. return get_editor_interface().get_base_control().get_icon("Node", "EditorIcons")
  173. A couple of specific lines were added. First, we defined the constants that
  174. contain our 2 GUI packed scenes (``MainPanel`` and ``SidePanel``). We will use
  175. these resources to instance both scenes.
  176. The ``_enter_tree()`` function is called before ``_ready()``. This is where we
  177. actually instance the 2 GUI scenes, and add them as children of specific parts
  178. of the editor. The side panel case is similar to the example shown in
  179. :ref:`doc_making_plugins` page: we add the scene in an editor dock. We specified
  180. it will be placed in the left-side dock, upper-left tab.
  181. ``EditorPlugin`` class does not provide any function to add an element in the
  182. main viewport. We thus have to use the
  183. ``get_editor_interface().get_editor_viewport()`` to obtain this viewport and add
  184. our main panel instance as a child to it. We call the ``make_visible(false)``
  185. function to hide the main panel so it is not directly shown when first
  186. activating the plugin.
  187. The ``_exit_tree()`` is pretty straightforward. It is automatically called when
  188. the plugin is deactivated. It is then important to ``queue_free()`` the elements
  189. previously instanced to preserve memory. If you don't, the elements will
  190. effectively be invisible in the editor, but they will remain present in the
  191. memory. Multiple de-activations/re-activations will then increase memory usage
  192. without any way to free it, which is not good.
  193. Finally the ``make_visible()`` function is overridden to hide or show the main
  194. panel as needed. This function is automatically called by the editor when the
  195. user clicks on another main viewport button such as 2D, 3D or Script.
  196. Try the plugin
  197. --------------
  198. Activate the plugin in the Project Settings. You'll observe a new button next to
  199. 2D, 3D, Script above the main viewport. You'll also notice a new tab in the left
  200. dock. Try to click the buttons in both side and main panels: events are emitted
  201. and caught by the corresponding target scene to change the Label caption inside it.
  202. If you would like to see a more complete example of what main screen plugins
  203. are capable of, check out the 2.5D demo projects here:
  204. https://github.com/godotengine/godot-demo-projects/tree/master/misc/2.5d