gdnative_library_editor_plugin.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /**************************************************************************/
  2. /* gdnative_library_editor_plugin.cpp */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #ifdef TOOLS_ENABLED
  31. #include "gdnative_library_editor_plugin.h"
  32. #include "gdnative.h"
  33. #include "editor/editor_scale.h"
  34. void GDNativeLibraryEditor::edit(Ref<GDNativeLibrary> p_library) {
  35. library = p_library;
  36. Ref<ConfigFile> config = p_library->get_config_file();
  37. for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) {
  38. for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) {
  39. String target = E->key() + "." + it->get();
  40. TargetConfig ecfg;
  41. ecfg.library = config->get_value("entry", target, "");
  42. ecfg.dependencies = config->get_value("dependencies", target, Array());
  43. entry_configs[target] = ecfg;
  44. }
  45. }
  46. _update_tree();
  47. }
  48. void GDNativeLibraryEditor::_bind_methods() {
  49. ClassDB::bind_method("_on_item_button", &GDNativeLibraryEditor::_on_item_button);
  50. ClassDB::bind_method("_on_library_selected", &GDNativeLibraryEditor::_on_library_selected);
  51. ClassDB::bind_method("_on_dependencies_selected", &GDNativeLibraryEditor::_on_dependencies_selected);
  52. ClassDB::bind_method("_on_filter_selected", &GDNativeLibraryEditor::_on_filter_selected);
  53. ClassDB::bind_method("_on_item_collapsed", &GDNativeLibraryEditor::_on_item_collapsed);
  54. ClassDB::bind_method("_on_item_activated", &GDNativeLibraryEditor::_on_item_activated);
  55. ClassDB::bind_method("_on_create_new_entry", &GDNativeLibraryEditor::_on_create_new_entry);
  56. }
  57. void GDNativeLibraryEditor::_update_tree() {
  58. tree->clear();
  59. TreeItem *root = tree->create_item();
  60. PopupMenu *filter_list = filter->get_popup();
  61. String text = "";
  62. for (int i = 0; i < filter_list->get_item_count(); i++) {
  63. if (!filter_list->is_item_checked(i)) {
  64. continue;
  65. }
  66. Map<String, NativePlatformConfig>::Element *E = platforms.find(filter_list->get_item_metadata(i));
  67. if (!text.empty()) {
  68. text += ", ";
  69. }
  70. text += E->get().name;
  71. TreeItem *platform = tree->create_item(root);
  72. platform->set_text(0, E->get().name);
  73. platform->set_metadata(0, E->get().library_extension);
  74. platform->set_custom_bg_color(0, get_color("prop_category", "Editor"));
  75. platform->set_custom_bg_color(1, get_color("prop_category", "Editor"));
  76. platform->set_custom_bg_color(2, get_color("prop_category", "Editor"));
  77. platform->set_selectable(0, false);
  78. platform->set_expand_right(0, true);
  79. for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) {
  80. String target = E->key() + "." + it->get();
  81. TreeItem *bit = tree->create_item(platform);
  82. bit->set_text(0, it->get());
  83. bit->set_metadata(0, target);
  84. bit->set_selectable(0, false);
  85. bit->set_custom_bg_color(0, get_color("prop_subsection", "Editor"));
  86. bit->add_button(1, get_icon("Folder", "EditorIcons"), BUTTON_SELECT_LIBRARY, false, TTR("Select the dynamic library for this entry"));
  87. String file = entry_configs[target].library;
  88. if (!file.empty()) {
  89. bit->add_button(1, get_icon("Clear", "EditorIcons"), BUTTON_CLEAR_LIBRARY, false, TTR("Clear"));
  90. }
  91. bit->set_text(1, file);
  92. bit->add_button(2, get_icon("Folder", "EditorIcons"), BUTTON_SELECT_DEPENDENCES, false, TTR("Select dependencies of the library for this entry"));
  93. Array files = entry_configs[target].dependencies;
  94. if (files.size()) {
  95. bit->add_button(2, get_icon("Clear", "EditorIcons"), BUTTON_CLEAR_DEPENDENCES, false, TTR("Clear"));
  96. }
  97. bit->set_text(2, Variant(files));
  98. bit->add_button(3, get_icon("MoveUp", "EditorIcons"), BUTTON_MOVE_UP, false, TTR("Move Up"));
  99. bit->add_button(3, get_icon("MoveDown", "EditorIcons"), BUTTON_MOVE_DOWN, false, TTR("Move Down"));
  100. bit->add_button(3, get_icon("Remove", "EditorIcons"), BUTTON_ERASE_ENTRY, false, TTR("Remove current entry"));
  101. }
  102. TreeItem *new_arch = tree->create_item(platform);
  103. new_arch->set_text(0, TTR("Double click to create a new entry"));
  104. new_arch->set_text_align(0, TreeItem::ALIGN_CENTER);
  105. new_arch->set_custom_color(0, get_color("accent_color", "Editor"));
  106. new_arch->set_expand_right(0, true);
  107. new_arch->set_metadata(1, E->key());
  108. platform->set_collapsed(collapsed_items.find(E->get().name) != nullptr);
  109. }
  110. filter->set_text(text);
  111. }
  112. void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) {
  113. String target = Object::cast_to<TreeItem>(item)->get_metadata(0);
  114. String platform = target.substr(0, target.find("."));
  115. String entry = target.substr(platform.length() + 1, target.length());
  116. String section = (id == BUTTON_SELECT_DEPENDENCES || id == BUTTON_CLEAR_DEPENDENCES) ? "dependencies" : "entry";
  117. if (id == BUTTON_SELECT_LIBRARY || id == BUTTON_SELECT_DEPENDENCES) {
  118. TreeItem *treeItem = Object::cast_to<TreeItem>(item)->get_parent();
  119. EditorFileDialog::Mode mode = EditorFileDialog::MODE_OPEN_FILE;
  120. if (id == BUTTON_SELECT_DEPENDENCES) {
  121. mode = EditorFileDialog::MODE_OPEN_FILES;
  122. } else if (treeItem->get_text(0) == "iOS" || treeItem->get_text(0) == "macOS") {
  123. mode = EditorFileDialog::MODE_OPEN_ANY;
  124. }
  125. file_dialog->set_meta("target", target);
  126. file_dialog->set_meta("section", section);
  127. file_dialog->clear_filters();
  128. String filter_string = treeItem->get_metadata(0);
  129. Vector<String> filters = filter_string.split(",", false, 0);
  130. for (int i = 0; i < filters.size(); i++) {
  131. file_dialog->add_filter(filters[i]);
  132. }
  133. file_dialog->set_mode(mode);
  134. file_dialog->popup_centered_ratio();
  135. } else if (id == BUTTON_CLEAR_LIBRARY) {
  136. _set_target_value(section, target, "");
  137. } else if (id == BUTTON_CLEAR_DEPENDENCES) {
  138. _set_target_value(section, target, Array());
  139. } else if (id == BUTTON_ERASE_ENTRY) {
  140. _erase_entry(platform, entry);
  141. } else if (id == BUTTON_MOVE_UP || id == BUTTON_MOVE_DOWN) {
  142. _move_entry(platform, entry, id);
  143. }
  144. }
  145. void GDNativeLibraryEditor::_on_library_selected(const String &file) {
  146. _set_target_value(file_dialog->get_meta("section"), file_dialog->get_meta("target"), file);
  147. }
  148. void GDNativeLibraryEditor::_on_dependencies_selected(const PoolStringArray &files) {
  149. _set_target_value(file_dialog->get_meta("section"), file_dialog->get_meta("target"), files);
  150. }
  151. void GDNativeLibraryEditor::_on_filter_selected(int index) {
  152. PopupMenu *filter_list = filter->get_popup();
  153. filter_list->set_item_checked(index, !filter_list->is_item_checked(index));
  154. _update_tree();
  155. }
  156. void GDNativeLibraryEditor::_on_item_collapsed(Object *p_item) {
  157. TreeItem *item = Object::cast_to<TreeItem>(p_item);
  158. String name = item->get_text(0);
  159. if (item->is_collapsed()) {
  160. collapsed_items.insert(name);
  161. } else if (Set<String>::Element *e = collapsed_items.find(name)) {
  162. collapsed_items.erase(e);
  163. }
  164. }
  165. void GDNativeLibraryEditor::_on_item_activated() {
  166. TreeItem *item = tree->get_selected();
  167. if (item && tree->get_selected_column() == 0 && item->get_metadata(0).get_type() == Variant::NIL) {
  168. new_architecture_dialog->set_meta("platform", item->get_metadata(1));
  169. new_architecture_dialog->popup_centered();
  170. }
  171. }
  172. void GDNativeLibraryEditor::_on_create_new_entry() {
  173. String platform = new_architecture_dialog->get_meta("platform");
  174. String entry = new_architecture_input->get_text().strip_edges();
  175. if (!entry.empty()) {
  176. platforms[platform].entries.push_back(entry);
  177. _update_tree();
  178. }
  179. }
  180. void GDNativeLibraryEditor::_set_target_value(const String &section, const String &target, Variant file) {
  181. if (section == "entry") {
  182. entry_configs[target].library = file;
  183. } else if (section == "dependencies") {
  184. entry_configs[target].dependencies = file;
  185. }
  186. _translate_to_config_file();
  187. _update_tree();
  188. }
  189. void GDNativeLibraryEditor::_erase_entry(const String &platform, const String &entry) {
  190. if (platforms.has(platform)) {
  191. if (List<String>::Element *E = platforms[platform].entries.find(entry)) {
  192. String target = platform + "." + entry;
  193. platforms[platform].entries.erase(E);
  194. _set_target_value("entry", target, "");
  195. _set_target_value("dependencies", target, Array());
  196. _translate_to_config_file();
  197. _update_tree();
  198. }
  199. }
  200. }
  201. void GDNativeLibraryEditor::_move_entry(const String &platform, const String &entry, int dir) {
  202. if (List<String>::Element *E = platforms[platform].entries.find(entry)) {
  203. if (E->prev() && dir == BUTTON_MOVE_UP) {
  204. platforms[platform].entries.insert_before(E->prev(), E->get());
  205. platforms[platform].entries.erase(E);
  206. } else if (E->next() && dir == BUTTON_MOVE_DOWN) {
  207. platforms[platform].entries.insert_after(E->next(), E->get());
  208. platforms[platform].entries.erase(E);
  209. }
  210. _translate_to_config_file();
  211. _update_tree();
  212. }
  213. }
  214. void GDNativeLibraryEditor::_translate_to_config_file() {
  215. if (!library.is_null()) {
  216. Ref<ConfigFile> config = library->get_config_file();
  217. config->erase_section("entry");
  218. config->erase_section("dependencies");
  219. for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) {
  220. for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) {
  221. String target = E->key() + "." + it->get();
  222. if (entry_configs[target].library.empty() && entry_configs[target].dependencies.empty()) {
  223. continue;
  224. }
  225. config->set_value("entry", target, entry_configs[target].library);
  226. config->set_value("dependencies", target, entry_configs[target].dependencies);
  227. }
  228. }
  229. library->_change_notify();
  230. }
  231. }
  232. GDNativeLibraryEditor::GDNativeLibraryEditor() {
  233. { // Define platforms
  234. NativePlatformConfig platform_windows;
  235. platform_windows.name = "Windows";
  236. platform_windows.entries.push_back("64");
  237. platform_windows.entries.push_back("32");
  238. platform_windows.library_extension = "*.dll";
  239. platforms["Windows"] = platform_windows;
  240. NativePlatformConfig platform_linux;
  241. platform_linux.name = "Linux/X11";
  242. platform_linux.entries.push_back("64");
  243. platform_linux.entries.push_back("32");
  244. platform_linux.library_extension = "*.so";
  245. platforms["X11"] = platform_linux;
  246. NativePlatformConfig platform_osx;
  247. platform_osx.name = "macOS";
  248. platform_osx.entries.push_back("64");
  249. platform_osx.library_extension = "*.framework; Framework, *.dylib; Dynamic Library";
  250. platforms["OSX"] = platform_osx;
  251. NativePlatformConfig platform_haiku;
  252. platform_haiku.name = "Haiku";
  253. platform_haiku.entries.push_back("64");
  254. platform_haiku.entries.push_back("32");
  255. platform_haiku.library_extension = "*.so";
  256. platforms["Haiku"] = platform_haiku;
  257. NativePlatformConfig platform_uwp;
  258. platform_uwp.name = "UWP";
  259. platform_uwp.entries.push_back("arm");
  260. platform_uwp.entries.push_back("32");
  261. platform_uwp.entries.push_back("64");
  262. platform_uwp.library_extension = "*.dll";
  263. platforms["UWP"] = platform_uwp;
  264. NativePlatformConfig platform_android;
  265. platform_android.name = "Android";
  266. platform_android.entries.push_back("armeabi-v7a");
  267. platform_android.entries.push_back("arm64-v8a");
  268. platform_android.entries.push_back("x86");
  269. platform_android.entries.push_back("x86_64");
  270. platform_android.library_extension = "*.so";
  271. platforms["Android"] = platform_android;
  272. NativePlatformConfig platform_html5;
  273. platform_html5.name = "HTML5";
  274. platform_html5.entries.push_back("wasm32");
  275. platform_html5.library_extension = "*.wasm";
  276. platforms["HTML5"] = platform_html5;
  277. NativePlatformConfig platform_ios;
  278. platform_ios.name = "iOS";
  279. platform_ios.entries.push_back("armv7");
  280. platform_ios.entries.push_back("arm64");
  281. platform_ios.entries.push_back("x86_64");
  282. // iOS can use both Static and Dynamic libraries.
  283. // Frameworks is actually a folder with files.
  284. platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library";
  285. platforms["iOS"] = platform_ios;
  286. }
  287. VBoxContainer *container = memnew(VBoxContainer);
  288. add_child(container);
  289. container->set_anchors_and_margins_preset(PRESET_WIDE);
  290. HBoxContainer *hbox = memnew(HBoxContainer);
  291. container->add_child(hbox);
  292. Label *label = memnew(Label);
  293. label->set_text(TTR("Platform:"));
  294. hbox->add_child(label);
  295. filter = memnew(MenuButton);
  296. filter->set_h_size_flags(SIZE_EXPAND_FILL);
  297. filter->set_text_align(filter->ALIGN_LEFT);
  298. hbox->add_child(filter);
  299. PopupMenu *filter_list = filter->get_popup();
  300. filter_list->set_hide_on_checkable_item_selection(false);
  301. int idx = 0;
  302. for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) {
  303. filter_list->add_check_item(E->get().name, idx);
  304. filter_list->set_item_metadata(idx, E->key());
  305. filter_list->set_item_checked(idx, true);
  306. idx += 1;
  307. }
  308. filter_list->connect("index_pressed", this, "_on_filter_selected");
  309. tree = memnew(Tree);
  310. container->add_child(tree);
  311. tree->set_v_size_flags(SIZE_EXPAND_FILL);
  312. tree->set_hide_root(true);
  313. tree->set_column_titles_visible(true);
  314. tree->set_columns(4);
  315. tree->set_column_expand(0, false);
  316. tree->set_column_min_width(0, int(200 * EDSCALE));
  317. tree->set_column_title(0, TTR("Platform"));
  318. tree->set_column_title(1, TTR("Dynamic Library"));
  319. tree->set_column_title(2, TTR("Dependencies"));
  320. tree->set_column_expand(3, false);
  321. tree->set_column_min_width(3, int(110 * EDSCALE));
  322. tree->connect("button_pressed", this, "_on_item_button");
  323. tree->connect("item_collapsed", this, "_on_item_collapsed");
  324. tree->connect("item_activated", this, "_on_item_activated");
  325. file_dialog = memnew(EditorFileDialog);
  326. file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
  327. file_dialog->set_resizable(true);
  328. add_child(file_dialog);
  329. file_dialog->connect("file_selected", this, "_on_library_selected");
  330. file_dialog->connect("dir_selected", this, "_on_library_selected");
  331. file_dialog->connect("files_selected", this, "_on_dependencies_selected");
  332. new_architecture_dialog = memnew(ConfirmationDialog);
  333. add_child(new_architecture_dialog);
  334. new_architecture_dialog->set_title(TTR("Add an architecture entry"));
  335. new_architecture_input = memnew(LineEdit);
  336. new_architecture_dialog->add_child(new_architecture_input);
  337. new_architecture_dialog->set_custom_minimum_size(Vector2(300, 80) * EDSCALE);
  338. new_architecture_input->set_anchors_and_margins_preset(PRESET_HCENTER_WIDE, PRESET_MODE_MINSIZE, 5 * EDSCALE);
  339. new_architecture_dialog->get_ok()->connect("pressed", this, "_on_create_new_entry");
  340. }
  341. void GDNativeLibraryEditorPlugin::edit(Object *p_node) {
  342. Ref<GDNativeLibrary> new_library = Object::cast_to<GDNativeLibrary>(p_node);
  343. if (new_library.is_valid()) {
  344. library_editor->edit(new_library);
  345. }
  346. }
  347. bool GDNativeLibraryEditorPlugin::handles(Object *p_node) const {
  348. return p_node->is_class("GDNativeLibrary");
  349. }
  350. void GDNativeLibraryEditorPlugin::make_visible(bool p_visible) {
  351. if (p_visible) {
  352. button->show();
  353. EditorNode::get_singleton()->make_bottom_panel_item_visible(library_editor);
  354. } else {
  355. if (library_editor->is_visible_in_tree()) {
  356. EditorNode::get_singleton()->hide_bottom_panel();
  357. }
  358. button->hide();
  359. }
  360. }
  361. GDNativeLibraryEditorPlugin::GDNativeLibraryEditorPlugin(EditorNode *p_node) {
  362. library_editor = memnew(GDNativeLibraryEditor);
  363. library_editor->set_custom_minimum_size(Size2(0, 250 * EDSCALE));
  364. button = p_node->add_bottom_panel_item(TTR("GDNativeLibrary"), library_editor);
  365. button->hide();
  366. }
  367. #endif