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) 2007-2020 Juan Linietsky, Ariel Manzur. */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
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
31 #ifdef TOOLS_ENABLED
32 #include "gdnative_library_editor_plugin.h"
33 #include "gdnative.h"
34
35 #include "editor/editor_scale.h"
36
edit(Ref<GDNativeLibrary> p_library)37 void GDNativeLibraryEditor::edit(Ref<GDNativeLibrary> p_library) {
38 library = p_library;
39 Ref<ConfigFile> config = p_library->get_config_file();
40
41 for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) {
42 for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) {
43
44 String target = E->key() + "." + it->get();
45 TargetConfig ecfg;
46 ecfg.library = config->get_value("entry", target, "");
47 ecfg.dependencies = config->get_value("dependencies", target, Array());
48 entry_configs[target] = ecfg;
49 }
50 }
51
52 _update_tree();
53 }
54
_bind_methods()55 void GDNativeLibraryEditor::_bind_methods() {
56
57 ClassDB::bind_method("_on_item_button", &GDNativeLibraryEditor::_on_item_button);
58 ClassDB::bind_method("_on_library_selected", &GDNativeLibraryEditor::_on_library_selected);
59 ClassDB::bind_method("_on_dependencies_selected", &GDNativeLibraryEditor::_on_dependencies_selected);
60 ClassDB::bind_method("_on_filter_selected", &GDNativeLibraryEditor::_on_filter_selected);
61 ClassDB::bind_method("_on_item_collapsed", &GDNativeLibraryEditor::_on_item_collapsed);
62 ClassDB::bind_method("_on_item_activated", &GDNativeLibraryEditor::_on_item_activated);
63 ClassDB::bind_method("_on_create_new_entry", &GDNativeLibraryEditor::_on_create_new_entry);
64 }
65
_update_tree()66 void GDNativeLibraryEditor::_update_tree() {
67
68 tree->clear();
69 TreeItem *root = tree->create_item();
70
71 PopupMenu *filter_list = filter->get_popup();
72 String text = "";
73 for (int i = 0; i < filter_list->get_item_count(); i++) {
74
75 if (!filter_list->is_item_checked(i)) {
76 continue;
77 }
78 Map<String, NativePlatformConfig>::Element *E = platforms.find(filter_list->get_item_metadata(i));
79 if (!text.empty()) {
80 text += ", ";
81 }
82 text += E->get().name;
83
84 TreeItem *platform = tree->create_item(root);
85 platform->set_text(0, E->get().name);
86 platform->set_metadata(0, E->get().library_extension);
87
88 platform->set_custom_bg_color(0, get_color("prop_category", "Editor"));
89 platform->set_custom_bg_color(1, get_color("prop_category", "Editor"));
90 platform->set_custom_bg_color(2, get_color("prop_category", "Editor"));
91 platform->set_selectable(0, false);
92 platform->set_expand_right(0, true);
93
94 for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) {
95
96 String target = E->key() + "." + it->get();
97 TreeItem *bit = tree->create_item(platform);
98
99 bit->set_text(0, it->get());
100 bit->set_metadata(0, target);
101 bit->set_selectable(0, false);
102 bit->set_custom_bg_color(0, get_color("prop_subsection", "Editor"));
103
104 bit->add_button(1, get_icon("Folder", "EditorIcons"), BUTTON_SELECT_LIBRARY, false, TTR("Select the dynamic library for this entry"));
105 String file = entry_configs[target].library;
106 if (!file.empty()) {
107 bit->add_button(1, get_icon("Clear", "EditorIcons"), BUTTON_CLEAR_LIBRARY, false, TTR("Clear"));
108 }
109 bit->set_text(1, file);
110
111 bit->add_button(2, get_icon("Folder", "EditorIcons"), BUTTON_SELECT_DEPENDENCES, false, TTR("Select dependencies of the library for this entry"));
112 Array files = entry_configs[target].dependencies;
113 if (files.size()) {
114 bit->add_button(2, get_icon("Clear", "EditorIcons"), BUTTON_CLEAR_DEPENDENCES, false, TTR("Clear"));
115 }
116 bit->set_text(2, Variant(files));
117
118 bit->add_button(3, get_icon("MoveUp", "EditorIcons"), BUTTON_MOVE_UP, false, TTR("Move Up"));
119 bit->add_button(3, get_icon("MoveDown", "EditorIcons"), BUTTON_MOVE_DOWN, false, TTR("Move Down"));
120 bit->add_button(3, get_icon("Remove", "EditorIcons"), BUTTON_ERASE_ENTRY, false, TTR("Remove current entry"));
121 }
122
123 TreeItem *new_arch = tree->create_item(platform);
124 new_arch->set_text(0, TTR("Double click to create a new entry"));
125 new_arch->set_text_align(0, TreeItem::ALIGN_CENTER);
126 new_arch->set_custom_color(0, get_color("accent_color", "Editor"));
127 new_arch->set_expand_right(0, true);
128 new_arch->set_metadata(1, E->key());
129
130 platform->set_collapsed(collapsed_items.find(E->get().name) != NULL);
131 }
132 filter->set_text(text);
133 }
134
_on_item_button(Object * item,int column,int id)135 void GDNativeLibraryEditor::_on_item_button(Object *item, int column, int id) {
136
137 String target = Object::cast_to<TreeItem>(item)->get_metadata(0);
138 String platform = target.substr(0, target.find("."));
139 String entry = target.substr(platform.length() + 1, target.length());
140 String section = (id == BUTTON_SELECT_DEPENDENCES || id == BUTTON_CLEAR_DEPENDENCES) ? "dependencies" : "entry";
141
142 if (id == BUTTON_SELECT_LIBRARY || id == BUTTON_SELECT_DEPENDENCES) {
143
144 TreeItem *treeItem = Object::cast_to<TreeItem>(item)->get_parent();
145 EditorFileDialog::Mode mode = EditorFileDialog::MODE_OPEN_FILE;
146
147 if (id == BUTTON_SELECT_DEPENDENCES) {
148 mode = EditorFileDialog::MODE_OPEN_FILES;
149 } else if (treeItem->get_text(0) == "iOS") {
150 mode = EditorFileDialog::MODE_OPEN_ANY;
151 }
152
153 file_dialog->set_meta("target", target);
154 file_dialog->set_meta("section", section);
155 file_dialog->clear_filters();
156
157 String filter_string = treeItem->get_metadata(0);
158 Vector<String> filters = filter_string.split(",", false, 0);
159 for (int i = 0; i < filters.size(); i++) {
160 file_dialog->add_filter(filters[i]);
161 }
162
163 file_dialog->set_mode(mode);
164 file_dialog->popup_centered_ratio();
165
166 } else if (id == BUTTON_CLEAR_LIBRARY) {
167 _set_target_value(section, target, "");
168 } else if (id == BUTTON_CLEAR_DEPENDENCES) {
169 _set_target_value(section, target, Array());
170 } else if (id == BUTTON_ERASE_ENTRY) {
171 _erase_entry(platform, entry);
172 } else if (id == BUTTON_MOVE_UP || id == BUTTON_MOVE_DOWN) {
173 _move_entry(platform, entry, id);
174 }
175 }
176
_on_library_selected(const String & file)177 void GDNativeLibraryEditor::_on_library_selected(const String &file) {
178
179 _set_target_value(file_dialog->get_meta("section"), file_dialog->get_meta("target"), file);
180 }
181
_on_dependencies_selected(const PoolStringArray & files)182 void GDNativeLibraryEditor::_on_dependencies_selected(const PoolStringArray &files) {
183
184 _set_target_value(file_dialog->get_meta("section"), file_dialog->get_meta("target"), files);
185 }
186
_on_filter_selected(int index)187 void GDNativeLibraryEditor::_on_filter_selected(int index) {
188
189 PopupMenu *filter_list = filter->get_popup();
190 filter_list->set_item_checked(index, !filter_list->is_item_checked(index));
191 _update_tree();
192 }
193
_on_item_collapsed(Object * p_item)194 void GDNativeLibraryEditor::_on_item_collapsed(Object *p_item) {
195
196 TreeItem *item = Object::cast_to<TreeItem>(p_item);
197 String name = item->get_text(0);
198
199 if (item->is_collapsed()) {
200 collapsed_items.insert(name);
201 } else if (Set<String>::Element *e = collapsed_items.find(name)) {
202 collapsed_items.erase(e);
203 }
204 }
205
_on_item_activated()206 void GDNativeLibraryEditor::_on_item_activated() {
207
208 TreeItem *item = tree->get_selected();
209 if (item && tree->get_selected_column() == 0 && item->get_metadata(0).get_type() == Variant::NIL) {
210 new_architecture_dialog->set_meta("platform", item->get_metadata(1));
211 new_architecture_dialog->popup_centered();
212 }
213 }
214
_on_create_new_entry()215 void GDNativeLibraryEditor::_on_create_new_entry() {
216
217 String platform = new_architecture_dialog->get_meta("platform");
218 String entry = new_architecture_input->get_text().strip_edges();
219 if (!entry.empty()) {
220 platforms[platform].entries.push_back(entry);
221 _update_tree();
222 }
223 }
224
_set_target_value(const String & section,const String & target,Variant file)225 void GDNativeLibraryEditor::_set_target_value(const String §ion, const String &target, Variant file) {
226 if (section == "entry")
227 entry_configs[target].library = file;
228 else if (section == "dependencies")
229 entry_configs[target].dependencies = file;
230 _translate_to_config_file();
231 _update_tree();
232 }
233
_erase_entry(const String & platform,const String & entry)234 void GDNativeLibraryEditor::_erase_entry(const String &platform, const String &entry) {
235
236 if (platforms.has(platform)) {
237 if (List<String>::Element *E = platforms[platform].entries.find(entry)) {
238
239 String target = platform + "." + entry;
240
241 platforms[platform].entries.erase(E);
242 _set_target_value("entry", target, "");
243 _set_target_value("dependencies", target, Array());
244 _translate_to_config_file();
245 _update_tree();
246 }
247 }
248 }
249
_move_entry(const String & platform,const String & entry,int dir)250 void GDNativeLibraryEditor::_move_entry(const String &platform, const String &entry, int dir) {
251 if (List<String>::Element *E = platforms[platform].entries.find(entry)) {
252 if (E->prev() && dir == BUTTON_MOVE_UP) {
253 platforms[platform].entries.insert_before(E->prev(), E->get());
254 platforms[platform].entries.erase(E);
255 } else if (E->next() && dir == BUTTON_MOVE_DOWN) {
256 platforms[platform].entries.insert_after(E->next(), E->get());
257 platforms[platform].entries.erase(E);
258 }
259 _translate_to_config_file();
260 _update_tree();
261 }
262 }
263
_translate_to_config_file()264 void GDNativeLibraryEditor::_translate_to_config_file() {
265
266 if (!library.is_null()) {
267
268 Ref<ConfigFile> config = library->get_config_file();
269 config->erase_section("entry");
270 config->erase_section("dependencies");
271
272 for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) {
273 for (List<String>::Element *it = E->value().entries.front(); it; it = it->next()) {
274
275 String target = E->key() + "." + it->get();
276 if (entry_configs[target].library.empty() && entry_configs[target].dependencies.empty())
277 continue;
278
279 config->set_value("entry", target, entry_configs[target].library);
280 config->set_value("dependencies", target, entry_configs[target].dependencies);
281 }
282 }
283
284 library->_change_notify();
285 }
286 }
287
GDNativeLibraryEditor()288 GDNativeLibraryEditor::GDNativeLibraryEditor() {
289
290 { // Define platforms
291 NativePlatformConfig platform_windows;
292 platform_windows.name = "Windows";
293 platform_windows.entries.push_back("64");
294 platform_windows.entries.push_back("32");
295 platform_windows.library_extension = "*.dll";
296 platforms["Windows"] = platform_windows;
297
298 NativePlatformConfig platform_linux;
299 platform_linux.name = "Linux/X11";
300 platform_linux.entries.push_back("64");
301 platform_linux.entries.push_back("32");
302 platform_linux.library_extension = "*.so";
303 platforms["X11"] = platform_linux;
304
305 NativePlatformConfig platform_osx;
306 platform_osx.name = "Mac OSX";
307 platform_osx.entries.push_back("64");
308 platform_osx.entries.push_back("32");
309 platform_osx.library_extension = "*.dylib";
310 platforms["OSX"] = platform_osx;
311
312 NativePlatformConfig platform_haiku;
313 platform_haiku.name = "Haiku";
314 platform_haiku.entries.push_back("64");
315 platform_haiku.entries.push_back("32");
316 platform_haiku.library_extension = "*.so";
317 platforms["Haiku"] = platform_haiku;
318
319 NativePlatformConfig platform_uwp;
320 platform_uwp.name = "UWP";
321 platform_uwp.entries.push_back("arm");
322 platform_uwp.entries.push_back("32");
323 platform_uwp.entries.push_back("64");
324 platform_uwp.library_extension = "*.dll";
325 platforms["UWP"] = platform_uwp;
326
327 NativePlatformConfig platform_android;
328 platform_android.name = "Android";
329 platform_android.entries.push_back("armeabi-v7a");
330 platform_android.entries.push_back("arm64-v8a");
331 platform_android.entries.push_back("x86");
332 platform_android.entries.push_back("x86_64");
333 platform_android.library_extension = "*.so";
334 platforms["Android"] = platform_android;
335
336 // TODO: Javascript platform is not supported yet
337 // NativePlatformConfig platform_html5;
338 // platform_html5.name = "HTML5";
339 // platform_html5.library_extension = "*.wasm";
340 // platforms["Javascript"] = platform_html5;
341
342 NativePlatformConfig platform_ios;
343 platform_ios.name = "iOS";
344 platform_ios.entries.push_back("armv7");
345 platform_ios.entries.push_back("arm64");
346 // iOS can use both Static and Dynamic libraries.
347 // Frameworks is actually a folder with files.
348 platform_ios.library_extension = "*.framework; Framework, *.xcframework; Binary Framework, *.a; Static Library, *.dylib; Dynamic Library";
349 platforms["iOS"] = platform_ios;
350 }
351
352 VBoxContainer *container = memnew(VBoxContainer);
353 add_child(container);
354 container->set_anchors_and_margins_preset(PRESET_WIDE);
355
356 HBoxContainer *hbox = memnew(HBoxContainer);
357 container->add_child(hbox);
358 Label *label = memnew(Label);
359 label->set_text(TTR("Platform:"));
360 hbox->add_child(label);
361 filter = memnew(MenuButton);
362 filter->set_h_size_flags(SIZE_EXPAND_FILL);
363 filter->set_text_align(filter->ALIGN_LEFT);
364 hbox->add_child(filter);
365 PopupMenu *filter_list = filter->get_popup();
366 filter_list->set_hide_on_checkable_item_selection(false);
367
368 int idx = 0;
369 for (Map<String, NativePlatformConfig>::Element *E = platforms.front(); E; E = E->next()) {
370 filter_list->add_check_item(E->get().name, idx);
371 filter_list->set_item_metadata(idx, E->key());
372 filter_list->set_item_checked(idx, true);
373 idx += 1;
374 }
375 filter_list->connect("index_pressed", this, "_on_filter_selected");
376
377 tree = memnew(Tree);
378 container->add_child(tree);
379 tree->set_v_size_flags(SIZE_EXPAND_FILL);
380 tree->set_hide_root(true);
381 tree->set_column_titles_visible(true);
382 tree->set_columns(4);
383 tree->set_column_expand(0, false);
384 tree->set_column_min_width(0, int(200 * EDSCALE));
385 tree->set_column_title(0, TTR("Platform"));
386 tree->set_column_title(1, TTR("Dynamic Library"));
387 tree->set_column_title(2, TTR("Dependencies"));
388 tree->set_column_expand(3, false);
389 tree->set_column_min_width(3, int(110 * EDSCALE));
390 tree->connect("button_pressed", this, "_on_item_button");
391 tree->connect("item_collapsed", this, "_on_item_collapsed");
392 tree->connect("item_activated", this, "_on_item_activated");
393
394 file_dialog = memnew(EditorFileDialog);
395 file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES);
396 file_dialog->set_resizable(true);
397 add_child(file_dialog);
398 file_dialog->connect("file_selected", this, "_on_library_selected");
399 file_dialog->connect("dir_selected", this, "_on_library_selected");
400 file_dialog->connect("files_selected", this, "_on_dependencies_selected");
401
402 new_architecture_dialog = memnew(ConfirmationDialog);
403 add_child(new_architecture_dialog);
404 new_architecture_dialog->set_title(TTR("Add an architecture entry"));
405 new_architecture_input = memnew(LineEdit);
406 new_architecture_dialog->add_child(new_architecture_input);
407 new_architecture_dialog->set_custom_minimum_size(Vector2(300, 80) * EDSCALE);
408 new_architecture_input->set_anchors_and_margins_preset(PRESET_HCENTER_WIDE, PRESET_MODE_MINSIZE, 5 * EDSCALE);
409 new_architecture_dialog->get_ok()->connect("pressed", this, "_on_create_new_entry");
410 }
411
edit(Object * p_node)412 void GDNativeLibraryEditorPlugin::edit(Object *p_node) {
413
414 Ref<GDNativeLibrary> new_library = Object::cast_to<GDNativeLibrary>(p_node);
415 if (new_library.is_valid())
416 library_editor->edit(new_library);
417 }
418
handles(Object * p_node) const419 bool GDNativeLibraryEditorPlugin::handles(Object *p_node) const {
420
421 return p_node->is_class("GDNativeLibrary");
422 }
423
make_visible(bool p_visible)424 void GDNativeLibraryEditorPlugin::make_visible(bool p_visible) {
425
426 if (p_visible) {
427 button->show();
428 EditorNode::get_singleton()->make_bottom_panel_item_visible(library_editor);
429
430 } else {
431 if (library_editor->is_visible_in_tree())
432 EditorNode::get_singleton()->hide_bottom_panel();
433 button->hide();
434 }
435 }
436
GDNativeLibraryEditorPlugin(EditorNode * p_node)437 GDNativeLibraryEditorPlugin::GDNativeLibraryEditorPlugin(EditorNode *p_node) {
438
439 library_editor = memnew(GDNativeLibraryEditor);
440 library_editor->set_custom_minimum_size(Size2(0, 250 * EDSCALE));
441 button = p_node->add_bottom_panel_item(TTR("GDNativeLibrary"), library_editor);
442 button->hide();
443 }
444
445 #endif
446