1 | /**************************************************************************/ |
2 | /* asset_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 | |
31 | #include "asset_library_editor_plugin.h" |
32 | |
33 | #include "core/input/input.h" |
34 | #include "core/io/json.h" |
35 | #include "core/io/stream_peer_tls.h" |
36 | #include "core/os/keyboard.h" |
37 | #include "core/version.h" |
38 | #include "editor/editor_node.h" |
39 | #include "editor/editor_paths.h" |
40 | #include "editor/editor_scale.h" |
41 | #include "editor/editor_settings.h" |
42 | #include "editor/editor_string_names.h" |
43 | #include "editor/gui/editor_file_dialog.h" |
44 | #include "editor/project_settings_editor.h" |
45 | #include "scene/gui/menu_button.h" |
46 | #include "scene/resources/image_texture.h" |
47 | |
48 | static inline void setup_http_request(HTTPRequest *request) { |
49 | request->set_use_threads(EDITOR_DEF("asset_library/use_threads" , true)); |
50 | |
51 | const String proxy_host = EDITOR_GET("network/http_proxy/host" ); |
52 | const int proxy_port = EDITOR_GET("network/http_proxy/port" ); |
53 | request->set_http_proxy(proxy_host, proxy_port); |
54 | request->set_https_proxy(proxy_host, proxy_port); |
55 | } |
56 | |
57 | void EditorAssetLibraryItem::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost) { |
58 | title->set_text(p_title); |
59 | asset_id = p_asset_id; |
60 | category->set_text(p_category); |
61 | category_id = p_category_id; |
62 | author->set_text(p_author); |
63 | author_id = p_author_id; |
64 | price->set_text(p_cost); |
65 | } |
66 | |
67 | void EditorAssetLibraryItem::set_image(int p_type, int p_index, const Ref<Texture2D> &p_image) { |
68 | ERR_FAIL_COND(p_type != EditorAssetLibrary::IMAGE_QUEUE_ICON); |
69 | ERR_FAIL_COND(p_index != 0); |
70 | |
71 | icon->set_texture_normal(p_image); |
72 | } |
73 | |
74 | void EditorAssetLibraryItem::_notification(int p_what) { |
75 | switch (p_what) { |
76 | case NOTIFICATION_ENTER_TREE: { |
77 | icon->set_texture_normal(get_editor_theme_icon(SNAME("ProjectIconLoading" ))); |
78 | category->add_theme_color_override("font_color" , Color(0.5, 0.5, 0.5)); |
79 | author->add_theme_color_override("font_color" , Color(0.5, 0.5, 0.5)); |
80 | price->add_theme_color_override("font_color" , Color(0.5, 0.5, 0.5)); |
81 | } break; |
82 | } |
83 | } |
84 | |
85 | void EditorAssetLibraryItem::_asset_clicked() { |
86 | emit_signal(SNAME("asset_selected" ), asset_id); |
87 | } |
88 | |
89 | void EditorAssetLibraryItem::_category_clicked() { |
90 | emit_signal(SNAME("category_selected" ), category_id); |
91 | } |
92 | |
93 | void EditorAssetLibraryItem::_author_clicked() { |
94 | emit_signal(SNAME("author_selected" ), author_id); |
95 | } |
96 | |
97 | void EditorAssetLibraryItem::_bind_methods() { |
98 | ClassDB::bind_method("set_image" , &EditorAssetLibraryItem::set_image); |
99 | ADD_SIGNAL(MethodInfo("asset_selected" )); |
100 | ADD_SIGNAL(MethodInfo("category_selected" )); |
101 | ADD_SIGNAL(MethodInfo("author_selected" )); |
102 | } |
103 | |
104 | EditorAssetLibraryItem::EditorAssetLibraryItem() { |
105 | Ref<StyleBoxEmpty> border; |
106 | border.instantiate(); |
107 | border->set_content_margin_all(5 * EDSCALE); |
108 | add_theme_style_override("panel" , border); |
109 | |
110 | HBoxContainer *hb = memnew(HBoxContainer); |
111 | // Add some spacing to visually separate the icon from the asset details. |
112 | hb->add_theme_constant_override("separation" , 15 * EDSCALE); |
113 | add_child(hb); |
114 | |
115 | icon = memnew(TextureButton); |
116 | icon->set_custom_minimum_size(Size2(64, 64) * EDSCALE); |
117 | icon->set_default_cursor_shape(CURSOR_POINTING_HAND); |
118 | icon->connect("pressed" , callable_mp(this, &EditorAssetLibraryItem::_asset_clicked)); |
119 | |
120 | hb->add_child(icon); |
121 | |
122 | VBoxContainer *vb = memnew(VBoxContainer); |
123 | |
124 | hb->add_child(vb); |
125 | vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
126 | |
127 | title = memnew(LinkButton); |
128 | title->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); |
129 | title->connect("pressed" , callable_mp(this, &EditorAssetLibraryItem::_asset_clicked)); |
130 | vb->add_child(title); |
131 | |
132 | category = memnew(LinkButton); |
133 | category->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); |
134 | category->connect("pressed" , callable_mp(this, &EditorAssetLibraryItem::_category_clicked)); |
135 | vb->add_child(category); |
136 | |
137 | author = memnew(LinkButton); |
138 | author->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); |
139 | author->connect("pressed" , callable_mp(this, &EditorAssetLibraryItem::_author_clicked)); |
140 | vb->add_child(author); |
141 | |
142 | price = memnew(Label); |
143 | vb->add_child(price); |
144 | |
145 | set_custom_minimum_size(Size2(250, 100) * EDSCALE); |
146 | set_h_size_flags(Control::SIZE_EXPAND_FILL); |
147 | } |
148 | |
149 | ////////////////////////////////////////////////////////////////////////////// |
150 | |
151 | void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const Ref<Texture2D> &p_image) { |
152 | switch (p_type) { |
153 | case EditorAssetLibrary::IMAGE_QUEUE_ICON: { |
154 | item->call("set_image" , p_type, p_index, p_image); |
155 | icon = p_image; |
156 | } break; |
157 | case EditorAssetLibrary::IMAGE_QUEUE_THUMBNAIL: { |
158 | for (int i = 0; i < preview_images.size(); i++) { |
159 | if (preview_images[i].id == p_index) { |
160 | if (preview_images[i].is_video) { |
161 | Ref<Image> overlay = previews->get_editor_theme_icon(SNAME("PlayOverlay" ))->get_image(); |
162 | Ref<Image> thumbnail = p_image->get_image(); |
163 | thumbnail = thumbnail->duplicate(); |
164 | Point2i overlay_pos = Point2i((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); |
165 | |
166 | // Overlay and thumbnail need the same format for `blend_rect` to work. |
167 | thumbnail->convert(Image::FORMAT_RGBA8); |
168 | thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); |
169 | preview_images[i].button->set_icon(ImageTexture::create_from_image(thumbnail)); |
170 | |
171 | // Make it clearer that clicking it will open an external link |
172 | preview_images[i].button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND); |
173 | } else { |
174 | preview_images[i].button->set_icon(p_image); |
175 | } |
176 | break; |
177 | } |
178 | } |
179 | } break; |
180 | case EditorAssetLibrary::IMAGE_QUEUE_SCREENSHOT: { |
181 | for (int i = 0; i < preview_images.size(); i++) { |
182 | if (preview_images[i].id == p_index) { |
183 | preview_images.write[i].image = p_image; |
184 | if (preview_images[i].button->is_pressed()) { |
185 | _preview_click(p_index); |
186 | } |
187 | break; |
188 | } |
189 | } |
190 | } break; |
191 | } |
192 | } |
193 | |
194 | void EditorAssetLibraryItemDescription::_notification(int p_what) { |
195 | switch (p_what) { |
196 | case NOTIFICATION_ENTER_TREE: |
197 | case NOTIFICATION_THEME_CHANGED: { |
198 | previews_bg->add_theme_style_override("panel" , previews->get_theme_stylebox(SNAME("normal" ), SNAME("TextEdit" ))); |
199 | } break; |
200 | } |
201 | } |
202 | |
203 | void EditorAssetLibraryItemDescription::_bind_methods() { |
204 | ClassDB::bind_method(D_METHOD("set_image" ), &EditorAssetLibraryItemDescription::set_image); |
205 | } |
206 | |
207 | void EditorAssetLibraryItemDescription::_link_click(const String &p_url) { |
208 | ERR_FAIL_COND(!p_url.begins_with("http" )); |
209 | OS::get_singleton()->shell_open(p_url); |
210 | } |
211 | |
212 | void EditorAssetLibraryItemDescription::_preview_click(int p_id) { |
213 | for (int i = 0; i < preview_images.size(); i++) { |
214 | if (preview_images[i].id == p_id) { |
215 | preview_images[i].button->set_pressed(true); |
216 | if (!preview_images[i].is_video) { |
217 | if (preview_images[i].image.is_valid()) { |
218 | preview->set_texture(preview_images[i].image); |
219 | child_controls_changed(); |
220 | } |
221 | } else { |
222 | _link_click(preview_images[i].video_link); |
223 | } |
224 | } else { |
225 | preview_images[i].button->set_pressed(false); |
226 | } |
227 | } |
228 | } |
229 | |
230 | void EditorAssetLibraryItemDescription::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost, int p_version, const String &p_version_string, const String &p_description, const String &p_download_url, const String &p_browse_url, const String &p_sha256_hash) { |
231 | asset_id = p_asset_id; |
232 | title = p_title; |
233 | download_url = p_download_url; |
234 | sha256 = p_sha256_hash; |
235 | item->configure(p_title, p_asset_id, p_category, p_category_id, p_author, p_author_id, p_cost); |
236 | description->clear(); |
237 | description->add_text(TTR("Version:" ) + " " + p_version_string + "\n" ); |
238 | description->add_text(TTR("Contents:" ) + " " ); |
239 | description->push_meta(p_browse_url); |
240 | description->add_text(TTR("View Files" )); |
241 | description->pop(); |
242 | description->add_text("\n" + TTR("Description:" ) + "\n\n" ); |
243 | description->append_text(p_description); |
244 | description->set_selection_enabled(true); |
245 | description->set_context_menu_enabled(true); |
246 | set_title(p_title); |
247 | } |
248 | |
249 | void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, const String &p_url) { |
250 | Preview new_preview; |
251 | new_preview.id = p_id; |
252 | new_preview.video_link = p_url; |
253 | new_preview.is_video = p_video; |
254 | new_preview.button = memnew(Button); |
255 | new_preview.button->set_icon(previews->get_editor_theme_icon(SNAME("ThumbnailWait" ))); |
256 | new_preview.button->set_toggle_mode(true); |
257 | new_preview.button->connect("pressed" , callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click).bind(p_id)); |
258 | preview_hb->add_child(new_preview.button); |
259 | if (!p_video) { |
260 | new_preview.image = previews->get_editor_theme_icon(SNAME("ThumbnailWait" )); |
261 | } |
262 | preview_images.push_back(new_preview); |
263 | if (preview_images.size() == 1 && !p_video) { |
264 | _preview_click(p_id); |
265 | } |
266 | } |
267 | |
268 | EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() { |
269 | HBoxContainer *hbox = memnew(HBoxContainer); |
270 | add_child(hbox); |
271 | VBoxContainer *desc_vbox = memnew(VBoxContainer); |
272 | hbox->add_child(desc_vbox); |
273 | hbox->add_theme_constant_override("separation" , 15 * EDSCALE); |
274 | |
275 | item = memnew(EditorAssetLibraryItem); |
276 | |
277 | desc_vbox->add_child(item); |
278 | desc_vbox->set_custom_minimum_size(Size2(440 * EDSCALE, 0)); |
279 | |
280 | description = memnew(RichTextLabel); |
281 | desc_vbox->add_child(description); |
282 | description->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
283 | description->connect("meta_clicked" , callable_mp(this, &EditorAssetLibraryItemDescription::_link_click)); |
284 | description->add_theme_constant_override("line_separation" , Math::round(5 * EDSCALE)); |
285 | |
286 | VBoxContainer *previews_vbox = memnew(VBoxContainer); |
287 | hbox->add_child(previews_vbox); |
288 | previews_vbox->add_theme_constant_override("separation" , 15 * EDSCALE); |
289 | previews_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
290 | previews_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
291 | |
292 | preview = memnew(TextureRect); |
293 | previews_vbox->add_child(preview); |
294 | preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); |
295 | preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); |
296 | preview->set_custom_minimum_size(Size2(640 * EDSCALE, 345 * EDSCALE)); |
297 | preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
298 | preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
299 | |
300 | previews_bg = memnew(PanelContainer); |
301 | previews_vbox->add_child(previews_bg); |
302 | previews_bg->set_custom_minimum_size(Size2(640 * EDSCALE, 101 * EDSCALE)); |
303 | |
304 | previews = memnew(ScrollContainer); |
305 | previews_bg->add_child(previews); |
306 | previews->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); |
307 | preview_hb = memnew(HBoxContainer); |
308 | preview_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
309 | |
310 | previews->add_child(preview_hb); |
311 | set_ok_button_text(TTR("Download" )); |
312 | set_cancel_button_text(TTR("Close" )); |
313 | } |
314 | |
315 | /////////////////////////////////////////////////////////////////////////////////// |
316 | |
317 | void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int p_code, const PackedStringArray &, const PackedByteArray &p_data) { |
318 | String error_text; |
319 | |
320 | switch (p_status) { |
321 | case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH: |
322 | case HTTPRequest::RESULT_CONNECTION_ERROR: |
323 | case HTTPRequest::RESULT_BODY_SIZE_LIMIT_EXCEEDED: { |
324 | error_text = TTR("Connection error, please try again." ); |
325 | status->set_text(TTR("Can't connect." )); |
326 | } break; |
327 | case HTTPRequest::RESULT_CANT_CONNECT: |
328 | case HTTPRequest::RESULT_TLS_HANDSHAKE_ERROR: { |
329 | error_text = TTR("Can't connect to host:" ) + " " + host; |
330 | status->set_text(TTR("Can't connect." )); |
331 | } break; |
332 | case HTTPRequest::RESULT_NO_RESPONSE: { |
333 | error_text = TTR("No response from host:" ) + " " + host; |
334 | status->set_text(TTR("No response." )); |
335 | } break; |
336 | case HTTPRequest::RESULT_CANT_RESOLVE: { |
337 | error_text = TTR("Can't resolve hostname:" ) + " " + host; |
338 | status->set_text(TTR("Can't resolve." )); |
339 | } break; |
340 | case HTTPRequest::RESULT_REQUEST_FAILED: { |
341 | error_text = TTR("Request failed, return code:" ) + " " + itos(p_code); |
342 | status->set_text(TTR("Request failed." )); |
343 | } break; |
344 | case HTTPRequest::RESULT_DOWNLOAD_FILE_CANT_OPEN: |
345 | case HTTPRequest::RESULT_DOWNLOAD_FILE_WRITE_ERROR: { |
346 | error_text = TTR("Cannot save response to:" ) + " " + download->get_download_file(); |
347 | status->set_text(TTR("Write error." )); |
348 | } break; |
349 | case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: { |
350 | error_text = TTR("Request failed, too many redirects" ); |
351 | status->set_text(TTR("Redirect loop." )); |
352 | } break; |
353 | case HTTPRequest::RESULT_TIMEOUT: { |
354 | error_text = TTR("Request failed, timeout" ); |
355 | status->set_text(TTR("Timeout." )); |
356 | } break; |
357 | default: { |
358 | if (p_code != 200) { |
359 | error_text = TTR("Request failed, return code:" ) + " " + itos(p_code); |
360 | status->set_text(TTR("Failed:" ) + " " + itos(p_code)); |
361 | } else if (!sha256.is_empty()) { |
362 | String download_sha256 = FileAccess::get_sha256(download->get_download_file()); |
363 | if (sha256 != download_sha256) { |
364 | error_text = TTR("Bad download hash, assuming file has been tampered with." ) + "\n" ; |
365 | error_text += TTR("Expected:" ) + " " + sha256 + "\n" + TTR("Got:" ) + " " + download_sha256; |
366 | status->set_text(TTR("Failed SHA-256 hash check" )); |
367 | } |
368 | } |
369 | } break; |
370 | } |
371 | |
372 | if (!error_text.is_empty()) { |
373 | download_error->set_text(TTR("Asset Download Error:" ) + "\n" + error_text); |
374 | download_error->popup_centered(); |
375 | // Let the user retry the download. |
376 | retry_button->show(); |
377 | return; |
378 | } |
379 | |
380 | install_button->set_disabled(false); |
381 | status->set_text(TTR("Ready to install!" )); |
382 | // Make the progress bar invisible but don't reflow other Controls around it. |
383 | progress->set_modulate(Color(0, 0, 0, 0)); |
384 | |
385 | set_process(false); |
386 | |
387 | // Automatically prompt for installation once the download is completed. |
388 | install(); |
389 | } |
390 | |
391 | void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asset_id, const Ref<Texture2D> &p_preview, const String &p_download_url, const String &p_sha256_hash) { |
392 | title->set_text(p_title); |
393 | icon->set_texture(p_preview); |
394 | asset_id = p_asset_id; |
395 | if (!p_preview.is_valid()) { |
396 | icon->set_texture(get_editor_theme_icon(SNAME("FileBrokenBigThumb" ))); |
397 | } |
398 | host = p_download_url; |
399 | sha256 = p_sha256_hash; |
400 | _make_request(); |
401 | } |
402 | |
403 | void EditorAssetLibraryItemDownload::_notification(int p_what) { |
404 | switch (p_what) { |
405 | case NOTIFICATION_ENTER_TREE: |
406 | case NOTIFICATION_THEME_CHANGED: { |
407 | panel->add_theme_style_override("panel" , get_theme_stylebox(SNAME("panel" ), SNAME("AssetLib" ))); |
408 | status->add_theme_color_override("font_color" , get_theme_color(SNAME("status_color" ), SNAME("AssetLib" ))); |
409 | dismiss_button->set_texture_normal(get_theme_icon(SNAME("dismiss" ), SNAME("AssetLib" ))); |
410 | } break; |
411 | |
412 | case NOTIFICATION_PROCESS: { |
413 | // Make the progress bar visible again when retrying the download. |
414 | progress->set_modulate(Color(1, 1, 1, 1)); |
415 | |
416 | if (download->get_downloaded_bytes() > 0) { |
417 | progress->set_max(download->get_body_size()); |
418 | progress->set_value(download->get_downloaded_bytes()); |
419 | } |
420 | |
421 | int cstatus = download->get_http_client_status(); |
422 | |
423 | if (cstatus == HTTPClient::STATUS_BODY) { |
424 | if (download->get_body_size() > 0) { |
425 | status->set_text(vformat( |
426 | TTR("Downloading (%s / %s)..." ), |
427 | String::humanize_size(download->get_downloaded_bytes()), |
428 | String::humanize_size(download->get_body_size()))); |
429 | } else { |
430 | // Total file size is unknown, so it cannot be displayed. |
431 | progress->set_modulate(Color(0, 0, 0, 0)); |
432 | status->set_text(vformat( |
433 | TTR("Downloading..." ) + " (%s)" , |
434 | String::humanize_size(download->get_downloaded_bytes()))); |
435 | } |
436 | } |
437 | |
438 | if (cstatus != prev_status) { |
439 | switch (cstatus) { |
440 | case HTTPClient::STATUS_RESOLVING: { |
441 | status->set_text(TTR("Resolving..." )); |
442 | progress->set_max(1); |
443 | progress->set_value(0); |
444 | } break; |
445 | case HTTPClient::STATUS_CONNECTING: { |
446 | status->set_text(TTR("Connecting..." )); |
447 | progress->set_max(1); |
448 | progress->set_value(0); |
449 | } break; |
450 | case HTTPClient::STATUS_REQUESTING: { |
451 | status->set_text(TTR("Requesting..." )); |
452 | progress->set_max(1); |
453 | progress->set_value(0); |
454 | } break; |
455 | default: { |
456 | } |
457 | } |
458 | prev_status = cstatus; |
459 | } |
460 | } break; |
461 | } |
462 | } |
463 | |
464 | void EditorAssetLibraryItemDownload::_close() { |
465 | // Clean up downloaded file. |
466 | DirAccess::remove_file_or_error(download->get_download_file()); |
467 | queue_free(); |
468 | } |
469 | |
470 | bool EditorAssetLibraryItemDownload::can_install() const { |
471 | return !install_button->is_disabled(); |
472 | } |
473 | |
474 | void EditorAssetLibraryItemDownload::install() { |
475 | String file = download->get_download_file(); |
476 | |
477 | if (external_install) { |
478 | emit_signal(SNAME("install_asset" ), file, title->get_text()); |
479 | return; |
480 | } |
481 | |
482 | asset_installer->set_asset_name(title->get_text()); |
483 | asset_installer->open_asset(file, true); |
484 | } |
485 | |
486 | void EditorAssetLibraryItemDownload::_make_request() { |
487 | // Hide the Retry button if we've just pressed it. |
488 | retry_button->hide(); |
489 | |
490 | download->cancel_request(); |
491 | download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_asset_" + itos(asset_id)) + ".zip" ); |
492 | |
493 | Error err = download->request(host); |
494 | if (err != OK) { |
495 | status->set_text(TTR("Error making request" )); |
496 | } else { |
497 | set_process(true); |
498 | } |
499 | } |
500 | |
501 | void EditorAssetLibraryItemDownload::_bind_methods() { |
502 | ADD_SIGNAL(MethodInfo("install_asset" , PropertyInfo(Variant::STRING, "zip_path" ), PropertyInfo(Variant::STRING, "name" ))); |
503 | } |
504 | |
505 | EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { |
506 | panel = memnew(PanelContainer); |
507 | add_child(panel); |
508 | |
509 | HBoxContainer *hb = memnew(HBoxContainer); |
510 | panel->add_child(hb); |
511 | icon = memnew(TextureRect); |
512 | icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); |
513 | icon->set_v_size_flags(0); |
514 | hb->add_child(icon); |
515 | |
516 | VBoxContainer *vb = memnew(VBoxContainer); |
517 | hb->add_child(vb); |
518 | vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
519 | |
520 | HBoxContainer *title_hb = memnew(HBoxContainer); |
521 | vb->add_child(title_hb); |
522 | title = memnew(Label); |
523 | title_hb->add_child(title); |
524 | title->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
525 | |
526 | dismiss_button = memnew(TextureButton); |
527 | dismiss_button->connect("pressed" , callable_mp(this, &EditorAssetLibraryItemDownload::_close)); |
528 | title_hb->add_child(dismiss_button); |
529 | |
530 | title->set_clip_text(true); |
531 | |
532 | vb->add_spacer(); |
533 | |
534 | status = memnew(Label(TTR("Idle" ))); |
535 | vb->add_child(status); |
536 | progress = memnew(ProgressBar); |
537 | vb->add_child(progress); |
538 | |
539 | HBoxContainer *hb2 = memnew(HBoxContainer); |
540 | vb->add_child(hb2); |
541 | hb2->add_spacer(); |
542 | |
543 | install_button = memnew(Button); |
544 | install_button->set_text(TTR("Install..." )); |
545 | install_button->set_disabled(true); |
546 | install_button->connect("pressed" , callable_mp(this, &EditorAssetLibraryItemDownload::install)); |
547 | |
548 | retry_button = memnew(Button); |
549 | retry_button->set_text(TTR("Retry" )); |
550 | retry_button->connect("pressed" , callable_mp(this, &EditorAssetLibraryItemDownload::_make_request)); |
551 | // Only show the Retry button in case of a failure. |
552 | retry_button->hide(); |
553 | |
554 | hb2->add_child(retry_button); |
555 | hb2->add_child(install_button); |
556 | set_custom_minimum_size(Size2(310, 0) * EDSCALE); |
557 | |
558 | download = memnew(HTTPRequest); |
559 | panel->add_child(download); |
560 | download->connect("request_completed" , callable_mp(this, &EditorAssetLibraryItemDownload::_http_download_completed)); |
561 | setup_http_request(download); |
562 | |
563 | download_error = memnew(AcceptDialog); |
564 | panel->add_child(download_error); |
565 | download_error->set_title(TTR("Download Error" )); |
566 | |
567 | asset_installer = memnew(EditorAssetInstaller); |
568 | panel->add_child(asset_installer); |
569 | asset_installer->connect("confirmed" , callable_mp(this, &EditorAssetLibraryItemDownload::_close)); |
570 | |
571 | prev_status = -1; |
572 | |
573 | external_install = false; |
574 | } |
575 | |
576 | //////////////////////////////////////////////////////////////////////////////// |
577 | void EditorAssetLibrary::_notification(int p_what) { |
578 | switch (p_what) { |
579 | case NOTIFICATION_READY: { |
580 | add_theme_style_override("panel" , get_theme_stylebox(SNAME("bg" ), SNAME("AssetLib" ))); |
581 | error_label->move_to_front(); |
582 | } break; |
583 | |
584 | case NOTIFICATION_ENTER_TREE: |
585 | case NOTIFICATION_THEME_CHANGED: { |
586 | error_tr->set_texture(get_editor_theme_icon(SNAME("Error" ))); |
587 | filter->set_right_icon(get_editor_theme_icon(SNAME("Search" ))); |
588 | library_scroll_bg->add_theme_style_override("panel" , get_theme_stylebox(SNAME("panel" ), SNAME("Tree" ))); |
589 | downloads_scroll->add_theme_style_override("panel" , get_theme_stylebox(SNAME("panel" ), SNAME("Tree" ))); |
590 | error_label->add_theme_color_override("color" , get_theme_color(SNAME("error_color" ), EditorStringName(Editor))); |
591 | } break; |
592 | |
593 | case NOTIFICATION_VISIBILITY_CHANGED: { |
594 | if (is_visible()) { |
595 | #ifndef ANDROID_ENABLED |
596 | // Focus the search box automatically when switching to the Templates tab (in the Project Manager) |
597 | // or switching to the AssetLib tab (in the editor). |
598 | // The Project Manager's project filter box is automatically focused in the project manager code. |
599 | filter->grab_focus(); |
600 | #endif |
601 | |
602 | if (initial_loading) { |
603 | _repository_changed(0); // Update when shown for the first time. |
604 | } |
605 | } |
606 | } break; |
607 | |
608 | case NOTIFICATION_PROCESS: { |
609 | HTTPClient::Status s = request->get_http_client_status(); |
610 | const bool loading = s != HTTPClient::STATUS_DISCONNECTED; |
611 | |
612 | if (loading) { |
613 | library_scroll->set_modulate(Color(1, 1, 1, 0.5)); |
614 | } else { |
615 | library_scroll->set_modulate(Color(1, 1, 1, 1)); |
616 | } |
617 | |
618 | const bool no_downloads = downloads_hb->get_child_count() == 0; |
619 | if (no_downloads == downloads_scroll->is_visible()) { |
620 | downloads_scroll->set_visible(!no_downloads); |
621 | } |
622 | |
623 | } break; |
624 | |
625 | case NOTIFICATION_RESIZED: { |
626 | _update_asset_items_columns(); |
627 | } break; |
628 | |
629 | case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { |
630 | _update_repository_options(); |
631 | setup_http_request(request); |
632 | } break; |
633 | } |
634 | } |
635 | |
636 | void EditorAssetLibrary::_update_repository_options() { |
637 | Dictionary default_urls; |
638 | default_urls["godotengine.org (Official)" ] = "https://godotengine.org/asset-library/api" ; |
639 | Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls" , default_urls, true); |
640 | repository->clear(); |
641 | Array keys = available_urls.keys(); |
642 | for (int i = 0; i < keys.size(); i++) { |
643 | String key = keys[i]; |
644 | repository->add_item(key); |
645 | repository->set_item_metadata(i, available_urls[key]); |
646 | } |
647 | } |
648 | |
649 | void EditorAssetLibrary::shortcut_input(const Ref<InputEvent> &p_event) { |
650 | ERR_FAIL_COND(p_event.is_null()); |
651 | |
652 | const Ref<InputEventKey> key = p_event; |
653 | |
654 | if (key.is_valid() && key->is_pressed()) { |
655 | if (key->is_match(InputEventKey::create_reference(KeyModifierMask::CMD_OR_CTRL | Key::F)) && is_visible_in_tree()) { |
656 | filter->grab_focus(); |
657 | filter->select_all(); |
658 | accept_event(); |
659 | } |
660 | } |
661 | } |
662 | |
663 | void EditorAssetLibrary::_install_asset() { |
664 | ERR_FAIL_NULL(description); |
665 | |
666 | EditorAssetLibraryItemDownload *d = _get_asset_in_progress(description->get_asset_id()); |
667 | if (d) { |
668 | d->install(); |
669 | return; |
670 | } |
671 | |
672 | EditorAssetLibraryItemDownload *download = memnew(EditorAssetLibraryItemDownload); |
673 | downloads_hb->add_child(download); |
674 | download->configure(description->get_title(), description->get_asset_id(), description->get_preview_icon(), description->get_download_url(), description->get_sha256()); |
675 | |
676 | if (templates_only) { |
677 | download->set_external_install(true); |
678 | download->connect("install_asset" , callable_mp(this, &EditorAssetLibrary::_install_external_asset)); |
679 | } |
680 | } |
681 | |
682 | const char *EditorAssetLibrary::sort_key[SORT_MAX] = { |
683 | "updated" , |
684 | "updated" , |
685 | "name" , |
686 | "name" , |
687 | "cost" , |
688 | "cost" , |
689 | }; |
690 | |
691 | const char *EditorAssetLibrary::sort_text[SORT_MAX] = { |
692 | TTRC("Recently Updated" ), |
693 | TTRC("Least Recently Updated" ), |
694 | TTRC("Name (A-Z)" ), |
695 | TTRC("Name (Z-A)" ), |
696 | TTRC("License (A-Z)" ), // "cost" stores the SPDX license name in the Godot Asset Library. |
697 | TTRC("License (Z-A)" ), // "cost" stores the SPDX license name in the Godot Asset Library. |
698 | }; |
699 | |
700 | const char *EditorAssetLibrary::support_key[SUPPORT_MAX] = { |
701 | "official" , |
702 | "community" , |
703 | "testing" , |
704 | }; |
705 | |
706 | const char *EditorAssetLibrary::support_text[SUPPORT_MAX] = { |
707 | TTRC("Official" ), |
708 | TTRC("Community" ), |
709 | TTRC("Testing" ), |
710 | }; |
711 | |
712 | void EditorAssetLibrary::_select_author(int p_id) { |
713 | // Open author window. |
714 | } |
715 | |
716 | void EditorAssetLibrary::_select_category(int p_id) { |
717 | for (int i = 0; i < categories->get_item_count(); i++) { |
718 | if (i == 0) { |
719 | continue; |
720 | } |
721 | int id = categories->get_item_metadata(i); |
722 | if (id == p_id) { |
723 | categories->select(i); |
724 | _search(); |
725 | break; |
726 | } |
727 | } |
728 | } |
729 | |
730 | void EditorAssetLibrary::_select_asset(int p_id) { |
731 | _api_request("asset/" + itos(p_id), REQUESTING_ASSET); |
732 | } |
733 | |
734 | void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedByteArray &p_data, int p_queue_id) { |
735 | Object *obj = ObjectDB::get_instance(image_queue[p_queue_id].target); |
736 | |
737 | if (obj) { |
738 | bool image_set = false; |
739 | PackedByteArray image_data = p_data; |
740 | |
741 | if (use_cache) { |
742 | String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); |
743 | |
744 | Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".data" , FileAccess::READ); |
745 | if (file.is_valid()) { |
746 | PackedByteArray cached_data; |
747 | int len = file->get_32(); |
748 | cached_data.resize(len); |
749 | |
750 | uint8_t *w = cached_data.ptrw(); |
751 | file->get_buffer(w, len); |
752 | |
753 | image_data = cached_data; |
754 | } |
755 | } |
756 | |
757 | int len = image_data.size(); |
758 | const uint8_t *r = image_data.ptr(); |
759 | Ref<Image> image = Ref<Image>(memnew(Image)); |
760 | |
761 | uint8_t png_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 }; |
762 | uint8_t jpg_signature[3] = { 255, 216, 255 }; |
763 | uint8_t webp_signature[4] = { 82, 73, 70, 70 }; |
764 | uint8_t bmp_signature[2] = { 66, 77 }; |
765 | |
766 | if (r) { |
767 | if ((memcmp(&r[0], &png_signature[0], 8) == 0) && Image::_png_mem_loader_func) { |
768 | image->copy_internals_from(Image::_png_mem_loader_func(r, len)); |
769 | } else if ((memcmp(&r[0], &jpg_signature[0], 3) == 0) && Image::_jpg_mem_loader_func) { |
770 | image->copy_internals_from(Image::_jpg_mem_loader_func(r, len)); |
771 | } else if ((memcmp(&r[0], &webp_signature[0], 4) == 0) && Image::_webp_mem_loader_func) { |
772 | image->copy_internals_from(Image::_webp_mem_loader_func(r, len)); |
773 | } else if ((memcmp(&r[0], &bmp_signature[0], 2) == 0) && Image::_bmp_mem_loader_func) { |
774 | image->copy_internals_from(Image::_bmp_mem_loader_func(r, len)); |
775 | } else if (Image::_svg_scalable_mem_loader_func) { |
776 | image->copy_internals_from(Image::_svg_scalable_mem_loader_func(r, len, 1.0)); |
777 | } |
778 | } |
779 | |
780 | if (!image->is_empty()) { |
781 | switch (image_queue[p_queue_id].image_type) { |
782 | case IMAGE_QUEUE_ICON: |
783 | |
784 | image->resize(64 * EDSCALE, 64 * EDSCALE, Image::INTERPOLATE_LANCZOS); |
785 | |
786 | break; |
787 | case IMAGE_QUEUE_THUMBNAIL: { |
788 | float max_height = 85 * EDSCALE; |
789 | |
790 | float scale_ratio = max_height / (image->get_height() * EDSCALE); |
791 | if (scale_ratio < 1) { |
792 | image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_LANCZOS); |
793 | } |
794 | } break; |
795 | case IMAGE_QUEUE_SCREENSHOT: { |
796 | float max_height = 397 * EDSCALE; |
797 | |
798 | float scale_ratio = max_height / (image->get_height() * EDSCALE); |
799 | if (scale_ratio < 1) { |
800 | image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_LANCZOS); |
801 | } |
802 | } break; |
803 | } |
804 | |
805 | Ref<ImageTexture> tex = ImageTexture::create_from_image(image); |
806 | |
807 | obj->call("set_image" , image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, tex); |
808 | image_set = true; |
809 | } |
810 | |
811 | if (!image_set && final) { |
812 | obj->call("set_image" , image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_editor_theme_icon(SNAME("FileBrokenBigThumb" ))); |
813 | } |
814 | } |
815 | } |
816 | |
817 | void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, const PackedStringArray &, const PackedByteArray &p_data, int p_queue_id) { |
818 | ERR_FAIL_COND(!image_queue.has(p_queue_id)); |
819 | |
820 | if (p_status == HTTPRequest::RESULT_SUCCESS && p_code < HTTPClient::RESPONSE_BAD_REQUEST) { |
821 | if (p_code != HTTPClient::RESPONSE_NOT_MODIFIED) { |
822 | for (int i = 0; i < headers.size(); i++) { |
823 | if (headers[i].findn("ETag:" ) == 0) { // Save etag |
824 | String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); |
825 | String new_etag = headers[i].substr(headers[i].find(":" ) + 1, headers[i].length()).strip_edges(); |
826 | Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag" , FileAccess::WRITE); |
827 | if (file.is_valid()) { |
828 | file->store_line(new_etag); |
829 | } |
830 | |
831 | int len = p_data.size(); |
832 | const uint8_t *r = p_data.ptr(); |
833 | file = FileAccess::open(cache_filename_base + ".data" , FileAccess::WRITE); |
834 | if (file.is_valid()) { |
835 | file->store_32(len); |
836 | file->store_buffer(r, len); |
837 | } |
838 | |
839 | break; |
840 | } |
841 | } |
842 | } |
843 | _image_update(p_code == HTTPClient::RESPONSE_NOT_MODIFIED, true, p_data, p_queue_id); |
844 | |
845 | } else { |
846 | WARN_PRINT("Error getting image file from URL: " + image_queue[p_queue_id].image_url); |
847 | Object *obj = ObjectDB::get_instance(image_queue[p_queue_id].target); |
848 | if (obj) { |
849 | obj->call("set_image" , image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_editor_theme_icon(SNAME("FileBrokenBigThumb" ))); |
850 | } |
851 | } |
852 | |
853 | image_queue[p_queue_id].request->queue_free(); |
854 | image_queue.erase(p_queue_id); |
855 | |
856 | _update_image_queue(); |
857 | } |
858 | |
859 | void EditorAssetLibrary::_update_image_queue() { |
860 | const int max_images = 6; |
861 | int current_images = 0; |
862 | |
863 | List<int> to_delete; |
864 | for (KeyValue<int, ImageQueue> &E : image_queue) { |
865 | if (!E.value.active && current_images < max_images) { |
866 | String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + E.value.image_url.md5_text()); |
867 | Vector<String> ; |
868 | |
869 | if (FileAccess::exists(cache_filename_base + ".etag" ) && FileAccess::exists(cache_filename_base + ".data" )) { |
870 | Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag" , FileAccess::READ); |
871 | if (file.is_valid()) { |
872 | headers.push_back("If-None-Match: " + file->get_line()); |
873 | } |
874 | } |
875 | |
876 | Error err = E.value.request->request(E.value.image_url, headers); |
877 | if (err != OK) { |
878 | to_delete.push_back(E.key); |
879 | } else { |
880 | E.value.active = true; |
881 | } |
882 | current_images++; |
883 | } else if (E.value.active) { |
884 | current_images++; |
885 | } |
886 | } |
887 | |
888 | while (to_delete.size()) { |
889 | image_queue[to_delete.front()->get()].request->queue_free(); |
890 | image_queue.erase(to_delete.front()->get()); |
891 | to_delete.pop_front(); |
892 | } |
893 | } |
894 | |
895 | void EditorAssetLibrary::_request_image(ObjectID p_for, String p_image_url, ImageType p_type, int p_image_index) { |
896 | ImageQueue iq; |
897 | iq.image_url = p_image_url; |
898 | iq.image_index = p_image_index; |
899 | iq.image_type = p_type; |
900 | iq.request = memnew(HTTPRequest); |
901 | setup_http_request(iq.request); |
902 | |
903 | iq.target = p_for; |
904 | iq.queue_id = ++last_queue_id; |
905 | iq.active = false; |
906 | |
907 | iq.request->connect("request_completed" , callable_mp(this, &EditorAssetLibrary::_image_request_completed).bind(iq.queue_id)); |
908 | |
909 | image_queue[iq.queue_id] = iq; |
910 | |
911 | add_child(iq.request); |
912 | |
913 | _image_update(true, false, PackedByteArray(), iq.queue_id); |
914 | _update_image_queue(); |
915 | } |
916 | |
917 | void EditorAssetLibrary::_repository_changed(int p_repository_id) { |
918 | library_error->hide(); |
919 | library_info->set_text(TTR("Loading..." )); |
920 | library_info->show(); |
921 | |
922 | asset_top_page->hide(); |
923 | asset_bottom_page->hide(); |
924 | asset_items->hide(); |
925 | |
926 | filter->set_editable(false); |
927 | sort->set_disabled(true); |
928 | categories->set_disabled(true); |
929 | support->set_disabled(true); |
930 | |
931 | host = repository->get_item_metadata(p_repository_id); |
932 | if (templates_only) { |
933 | _api_request("configure" , REQUESTING_CONFIG, "?type=project" ); |
934 | } else { |
935 | _api_request("configure" , REQUESTING_CONFIG); |
936 | } |
937 | } |
938 | |
939 | void EditorAssetLibrary::_support_toggled(int p_support) { |
940 | support->get_popup()->set_item_checked(p_support, !support->get_popup()->is_item_checked(p_support)); |
941 | _search(); |
942 | } |
943 | |
944 | void EditorAssetLibrary::_rerun_search(int p_ignore) { |
945 | _search(); |
946 | } |
947 | |
948 | void EditorAssetLibrary::_search(int p_page) { |
949 | String args; |
950 | |
951 | if (templates_only) { |
952 | args += "?type=project&" ; |
953 | } else { |
954 | args += "?" ; |
955 | } |
956 | args += String() + "sort=" + sort_key[sort->get_selected()]; |
957 | |
958 | // We use the "branch" version, i.e. major.minor, as patch releases should be compatible |
959 | args += "&godot_version=" + String(VERSION_BRANCH); |
960 | |
961 | String support_list; |
962 | for (int i = 0; i < SUPPORT_MAX; i++) { |
963 | if (support->get_popup()->is_item_checked(i)) { |
964 | support_list += String(support_key[i]) + "+" ; |
965 | } |
966 | } |
967 | if (!support_list.is_empty()) { |
968 | args += "&support=" + support_list.substr(0, support_list.length() - 1); |
969 | } |
970 | |
971 | if (categories->get_selected() > 0) { |
972 | args += "&category=" + itos(categories->get_item_metadata(categories->get_selected())); |
973 | } |
974 | |
975 | // Sorting options with an odd index are always the reverse of the previous one |
976 | if (sort->get_selected() % 2 == 1) { |
977 | args += "&reverse=true" ; |
978 | } |
979 | |
980 | if (!filter->get_text().is_empty()) { |
981 | args += "&filter=" + filter->get_text().uri_encode(); |
982 | } |
983 | |
984 | if (p_page > 0) { |
985 | args += "&page=" + itos(p_page); |
986 | } |
987 | |
988 | _api_request("asset" , REQUESTING_SEARCH, args); |
989 | } |
990 | |
991 | void EditorAssetLibrary::_search_text_changed(const String &p_text) { |
992 | filter_debounce_timer->start(); |
993 | } |
994 | |
995 | void EditorAssetLibrary::_filter_debounce_timer_timeout() { |
996 | _search(); |
997 | } |
998 | |
999 | void EditorAssetLibrary::_request_current_config() { |
1000 | _repository_changed(repository->get_selected()); |
1001 | } |
1002 | |
1003 | HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int p_page_len, int p_total_items, int p_current_items) { |
1004 | HBoxContainer *hbc = memnew(HBoxContainer); |
1005 | |
1006 | if (p_page_count < 2) { |
1007 | return hbc; |
1008 | } |
1009 | |
1010 | //do the mario |
1011 | int from = p_page - 5; |
1012 | if (from < 0) { |
1013 | from = 0; |
1014 | } |
1015 | int to = from + 10; |
1016 | if (to > p_page_count) { |
1017 | to = p_page_count; |
1018 | } |
1019 | |
1020 | hbc->add_spacer(); |
1021 | hbc->add_theme_constant_override("separation" , 5 * EDSCALE); |
1022 | |
1023 | Button *first = memnew(Button); |
1024 | first->set_text(TTR("First" , "Pagination" )); |
1025 | if (p_page != 0) { |
1026 | first->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_search).bind(0)); |
1027 | } else { |
1028 | first->set_disabled(true); |
1029 | first->set_focus_mode(Control::FOCUS_NONE); |
1030 | } |
1031 | hbc->add_child(first); |
1032 | |
1033 | Button *prev = memnew(Button); |
1034 | prev->set_text(TTR("Previous" , "Pagination" )); |
1035 | if (p_page > 0) { |
1036 | prev->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_search).bind(p_page - 1)); |
1037 | } else { |
1038 | prev->set_disabled(true); |
1039 | prev->set_focus_mode(Control::FOCUS_NONE); |
1040 | } |
1041 | hbc->add_child(prev); |
1042 | hbc->add_child(memnew(VSeparator)); |
1043 | |
1044 | for (int i = from; i < to; i++) { |
1045 | if (i == p_page) { |
1046 | Button *current = memnew(Button); |
1047 | // Keep the extended padding for the currently active page (see below). |
1048 | current->set_text(vformat(" %d " , i + 1)); |
1049 | current->set_disabled(true); |
1050 | current->set_focus_mode(Control::FOCUS_NONE); |
1051 | |
1052 | hbc->add_child(current); |
1053 | } else { |
1054 | Button *current = memnew(Button); |
1055 | // Add padding to make page number buttons easier to click. |
1056 | current->set_text(vformat(" %d " , i + 1)); |
1057 | current->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_search).bind(i)); |
1058 | |
1059 | hbc->add_child(current); |
1060 | } |
1061 | } |
1062 | |
1063 | Button *next = memnew(Button); |
1064 | next->set_text(TTR("Next" , "Pagination" )); |
1065 | if (p_page < p_page_count - 1) { |
1066 | next->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_search).bind(p_page + 1)); |
1067 | } else { |
1068 | next->set_disabled(true); |
1069 | next->set_focus_mode(Control::FOCUS_NONE); |
1070 | } |
1071 | hbc->add_child(memnew(VSeparator)); |
1072 | hbc->add_child(next); |
1073 | |
1074 | Button *last = memnew(Button); |
1075 | last->set_text(TTR("Last" , "Pagination" )); |
1076 | if (p_page != p_page_count - 1) { |
1077 | last->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_search).bind(p_page_count - 1)); |
1078 | } else { |
1079 | last->set_disabled(true); |
1080 | last->set_focus_mode(Control::FOCUS_NONE); |
1081 | } |
1082 | hbc->add_child(last); |
1083 | |
1084 | hbc->add_spacer(); |
1085 | |
1086 | return hbc; |
1087 | } |
1088 | |
1089 | void EditorAssetLibrary::_api_request(const String &p_request, RequestType p_request_type, const String &p_arguments) { |
1090 | if (requesting != REQUESTING_NONE) { |
1091 | request->cancel_request(); |
1092 | } |
1093 | |
1094 | requesting = p_request_type; |
1095 | |
1096 | error_hb->hide(); |
1097 | request->request(host + "/" + p_request + p_arguments); |
1098 | } |
1099 | |
1100 | void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const PackedStringArray &, const PackedByteArray &p_data) { |
1101 | String str; |
1102 | |
1103 | { |
1104 | int datalen = p_data.size(); |
1105 | const uint8_t *r = p_data.ptr(); |
1106 | str.parse_utf8((const char *)r, datalen); |
1107 | } |
1108 | |
1109 | bool error_abort = true; |
1110 | |
1111 | switch (p_status) { |
1112 | case HTTPRequest::RESULT_CANT_RESOLVE: { |
1113 | error_label->set_text(TTR("Can't resolve hostname:" ) + " " + host); |
1114 | } break; |
1115 | case HTTPRequest::RESULT_BODY_SIZE_LIMIT_EXCEEDED: |
1116 | case HTTPRequest::RESULT_CONNECTION_ERROR: |
1117 | case HTTPRequest::RESULT_CHUNKED_BODY_SIZE_MISMATCH: { |
1118 | error_label->set_text(TTR("Connection error, please try again." )); |
1119 | } break; |
1120 | case HTTPRequest::RESULT_TLS_HANDSHAKE_ERROR: |
1121 | case HTTPRequest::RESULT_CANT_CONNECT: { |
1122 | error_label->set_text(TTR("Can't connect to host:" ) + " " + host); |
1123 | } break; |
1124 | case HTTPRequest::RESULT_NO_RESPONSE: { |
1125 | error_label->set_text(TTR("No response from host:" ) + " " + host); |
1126 | } break; |
1127 | case HTTPRequest::RESULT_REQUEST_FAILED: { |
1128 | error_label->set_text(TTR("Request failed, return code:" ) + " " + itos(p_code)); |
1129 | } break; |
1130 | case HTTPRequest::RESULT_REDIRECT_LIMIT_REACHED: { |
1131 | error_label->set_text(TTR("Request failed, too many redirects" )); |
1132 | |
1133 | } break; |
1134 | default: { |
1135 | if (p_code != 200) { |
1136 | error_label->set_text(TTR("Request failed, return code:" ) + " " + itos(p_code)); |
1137 | } else { |
1138 | error_abort = false; |
1139 | } |
1140 | } break; |
1141 | } |
1142 | |
1143 | if (error_abort) { |
1144 | if (requesting == REQUESTING_CONFIG) { |
1145 | library_info->hide(); |
1146 | library_error->show(); |
1147 | } |
1148 | error_hb->show(); |
1149 | return; |
1150 | } |
1151 | |
1152 | Dictionary d; |
1153 | { |
1154 | JSON json; |
1155 | json.parse(str); |
1156 | d = json.get_data(); |
1157 | } |
1158 | |
1159 | RequestType requested = requesting; |
1160 | requesting = REQUESTING_NONE; |
1161 | |
1162 | switch (requested) { |
1163 | case REQUESTING_CONFIG: { |
1164 | categories->clear(); |
1165 | categories->add_item(TTR("All" )); |
1166 | categories->set_item_metadata(0, 0); |
1167 | if (d.has("categories" )) { |
1168 | Array clist = d["categories" ]; |
1169 | for (int i = 0; i < clist.size(); i++) { |
1170 | Dictionary cat = clist[i]; |
1171 | if (!cat.has("name" ) || !cat.has("id" )) { |
1172 | continue; |
1173 | } |
1174 | String name = cat["name" ]; |
1175 | int id = cat["id" ]; |
1176 | categories->add_item(name); |
1177 | categories->set_item_metadata(-1, id); |
1178 | category_map[cat["id" ]] = name; |
1179 | } |
1180 | } |
1181 | |
1182 | filter->set_editable(true); |
1183 | sort->set_disabled(false); |
1184 | categories->set_disabled(false); |
1185 | support->set_disabled(false); |
1186 | |
1187 | _search(); |
1188 | } break; |
1189 | case REQUESTING_SEARCH: { |
1190 | initial_loading = false; |
1191 | |
1192 | if (asset_items) { |
1193 | memdelete(asset_items); |
1194 | } |
1195 | |
1196 | if (asset_top_page) { |
1197 | memdelete(asset_top_page); |
1198 | } |
1199 | |
1200 | if (asset_bottom_page) { |
1201 | memdelete(asset_bottom_page); |
1202 | } |
1203 | |
1204 | int page = 0; |
1205 | int pages = 1; |
1206 | int page_len = 10; |
1207 | int total_items = 1; |
1208 | Array result; |
1209 | |
1210 | if (d.has("page" )) { |
1211 | page = d["page" ]; |
1212 | } |
1213 | if (d.has("pages" )) { |
1214 | pages = d["pages" ]; |
1215 | } |
1216 | if (d.has("page_length" )) { |
1217 | page_len = d["page_length" ]; |
1218 | } |
1219 | if (d.has("total" )) { |
1220 | total_items = d["total" ]; |
1221 | } |
1222 | if (d.has("result" )) { |
1223 | result = d["result" ]; |
1224 | } |
1225 | |
1226 | asset_top_page = _make_pages(page, pages, page_len, total_items, result.size()); |
1227 | library_vb->add_child(asset_top_page); |
1228 | |
1229 | asset_items = memnew(GridContainer); |
1230 | _update_asset_items_columns(); |
1231 | asset_items->add_theme_constant_override("h_separation" , 10 * EDSCALE); |
1232 | asset_items->add_theme_constant_override("v_separation" , 10 * EDSCALE); |
1233 | |
1234 | library_vb->add_child(asset_items); |
1235 | |
1236 | asset_bottom_page = _make_pages(page, pages, page_len, total_items, result.size()); |
1237 | library_vb->add_child(asset_bottom_page); |
1238 | |
1239 | if (result.is_empty()) { |
1240 | String support_list; |
1241 | for (int i = 0; i < SUPPORT_MAX; i++) { |
1242 | if (support->get_popup()->is_item_checked(i)) { |
1243 | if (!support_list.is_empty()) { |
1244 | support_list += ", " ; |
1245 | } |
1246 | support_list += TTRGET(support_text[i]); |
1247 | } |
1248 | } |
1249 | if (support_list.is_empty()) { |
1250 | support_list = "-" ; |
1251 | } |
1252 | |
1253 | if (!filter->get_text().is_empty()) { |
1254 | library_info->set_text( |
1255 | vformat(TTR("No results for \"%s\" for support level(s): %s." ), filter->get_text(), support_list)); |
1256 | } else { |
1257 | // No results, even though the user didn't search for anything specific. |
1258 | // This is typically because the version number changed recently |
1259 | // and no assets compatible with the new version have been published yet. |
1260 | library_info->set_text( |
1261 | vformat(TTR("No results compatible with %s %s for support level(s): %s.\nCheck the enabled support levels using the 'Support' button in the top-right corner." ), String(VERSION_SHORT_NAME).capitalize(), String(VERSION_BRANCH), support_list)); |
1262 | } |
1263 | library_info->show(); |
1264 | } else { |
1265 | library_info->hide(); |
1266 | } |
1267 | |
1268 | for (int i = 0; i < result.size(); i++) { |
1269 | Dictionary r = result[i]; |
1270 | |
1271 | ERR_CONTINUE(!r.has("title" )); |
1272 | ERR_CONTINUE(!r.has("asset_id" )); |
1273 | ERR_CONTINUE(!r.has("author" )); |
1274 | ERR_CONTINUE(!r.has("author_id" )); |
1275 | ERR_CONTINUE(!r.has("category_id" )); |
1276 | ERR_FAIL_COND(!category_map.has(r["category_id" ])); |
1277 | ERR_CONTINUE(!r.has("cost" )); |
1278 | |
1279 | EditorAssetLibraryItem *item = memnew(EditorAssetLibraryItem); |
1280 | asset_items->add_child(item); |
1281 | item->configure(r["title" ], r["asset_id" ], category_map[r["category_id" ]], r["category_id" ], r["author" ], r["author_id" ], r["cost" ]); |
1282 | item->connect("asset_selected" , callable_mp(this, &EditorAssetLibrary::_select_asset)); |
1283 | item->connect("author_selected" , callable_mp(this, &EditorAssetLibrary::_select_author)); |
1284 | item->connect("category_selected" , callable_mp(this, &EditorAssetLibrary::_select_category)); |
1285 | |
1286 | if (r.has("icon_url" ) && !r["icon_url" ].operator String().is_empty()) { |
1287 | _request_image(item->get_instance_id(), r["icon_url" ], IMAGE_QUEUE_ICON, 0); |
1288 | } |
1289 | } |
1290 | |
1291 | if (!result.is_empty()) { |
1292 | library_scroll->set_v_scroll(0); |
1293 | } |
1294 | } break; |
1295 | case REQUESTING_ASSET: { |
1296 | Dictionary r = d; |
1297 | |
1298 | ERR_FAIL_COND(!r.has("title" )); |
1299 | ERR_FAIL_COND(!r.has("asset_id" )); |
1300 | ERR_FAIL_COND(!r.has("author" )); |
1301 | ERR_FAIL_COND(!r.has("author_id" )); |
1302 | ERR_FAIL_COND(!r.has("version" )); |
1303 | ERR_FAIL_COND(!r.has("version_string" )); |
1304 | ERR_FAIL_COND(!r.has("category_id" )); |
1305 | ERR_FAIL_COND(!category_map.has(r["category_id" ])); |
1306 | ERR_FAIL_COND(!r.has("cost" )); |
1307 | ERR_FAIL_COND(!r.has("description" )); |
1308 | ERR_FAIL_COND(!r.has("download_url" )); |
1309 | ERR_FAIL_COND(!r.has("download_hash" )); |
1310 | ERR_FAIL_COND(!r.has("browse_url" )); |
1311 | |
1312 | if (description) { |
1313 | memdelete(description); |
1314 | } |
1315 | |
1316 | description = memnew(EditorAssetLibraryItemDescription); |
1317 | add_child(description); |
1318 | description->popup_centered(); |
1319 | description->connect("confirmed" , callable_mp(this, &EditorAssetLibrary::_install_asset)); |
1320 | |
1321 | description->configure(r["title" ], r["asset_id" ], category_map[r["category_id" ]], r["category_id" ], r["author" ], r["author_id" ], r["cost" ], r["version" ], r["version_string" ], r["description" ], r["download_url" ], r["browse_url" ], r["download_hash" ]); |
1322 | |
1323 | EditorAssetLibraryItemDownload *download_item = _get_asset_in_progress(description->get_asset_id()); |
1324 | if (download_item) { |
1325 | if (download_item->can_install()) { |
1326 | description->set_ok_button_text(TTR("Install" )); |
1327 | description->get_ok_button()->set_disabled(false); |
1328 | } else { |
1329 | description->set_ok_button_text(TTR("Downloading..." )); |
1330 | description->get_ok_button()->set_disabled(true); |
1331 | } |
1332 | } else { |
1333 | description->set_ok_button_text(TTR("Download" )); |
1334 | description->get_ok_button()->set_disabled(false); |
1335 | } |
1336 | |
1337 | if (r.has("icon_url" ) && !r["icon_url" ].operator String().is_empty()) { |
1338 | _request_image(description->get_instance_id(), r["icon_url" ], IMAGE_QUEUE_ICON, 0); |
1339 | } |
1340 | |
1341 | if (d.has("previews" )) { |
1342 | Array previews = d["previews" ]; |
1343 | |
1344 | for (int i = 0; i < previews.size(); i++) { |
1345 | Dictionary p = previews[i]; |
1346 | |
1347 | ERR_CONTINUE(!p.has("type" )); |
1348 | ERR_CONTINUE(!p.has("link" )); |
1349 | |
1350 | bool is_video = p.has("type" ) && String(p["type" ]) == "video" ; |
1351 | String video_url; |
1352 | if (is_video && p.has("link" )) { |
1353 | video_url = p["link" ]; |
1354 | } |
1355 | |
1356 | description->add_preview(i, is_video, video_url); |
1357 | |
1358 | if (p.has("thumbnail" )) { |
1359 | _request_image(description->get_instance_id(), p["thumbnail" ], IMAGE_QUEUE_THUMBNAIL, i); |
1360 | } |
1361 | |
1362 | if (!is_video) { |
1363 | _request_image(description->get_instance_id(), p["link" ], IMAGE_QUEUE_SCREENSHOT, i); |
1364 | } |
1365 | } |
1366 | } |
1367 | } break; |
1368 | default: |
1369 | break; |
1370 | } |
1371 | } |
1372 | |
1373 | void EditorAssetLibrary::_asset_file_selected(const String &p_file) { |
1374 | if (asset_installer) { |
1375 | memdelete(asset_installer); |
1376 | asset_installer = nullptr; |
1377 | } |
1378 | |
1379 | asset_installer = memnew(EditorAssetInstaller); |
1380 | asset_installer->set_asset_name(p_file); |
1381 | add_child(asset_installer); |
1382 | asset_installer->open_asset(p_file); |
1383 | } |
1384 | |
1385 | void EditorAssetLibrary::_asset_open() { |
1386 | asset_open->popup_file_dialog(); |
1387 | } |
1388 | |
1389 | void EditorAssetLibrary::_manage_plugins() { |
1390 | ProjectSettingsEditor::get_singleton()->popup_project_settings(true); |
1391 | ProjectSettingsEditor::get_singleton()->set_plugins_page(); |
1392 | } |
1393 | |
1394 | EditorAssetLibraryItemDownload *EditorAssetLibrary::_get_asset_in_progress(int p_asset_id) const { |
1395 | for (int i = 0; i < downloads_hb->get_child_count(); i++) { |
1396 | EditorAssetLibraryItemDownload *d = Object::cast_to<EditorAssetLibraryItemDownload>(downloads_hb->get_child(i)); |
1397 | if (d && d->get_asset_id() == p_asset_id) { |
1398 | return d; |
1399 | } |
1400 | } |
1401 | |
1402 | return nullptr; |
1403 | } |
1404 | |
1405 | void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_title) { |
1406 | emit_signal(SNAME("install_asset" ), p_zip_path, p_title); |
1407 | } |
1408 | |
1409 | void EditorAssetLibrary::_update_asset_items_columns() { |
1410 | int new_columns = get_size().x / (450.0 * EDSCALE); |
1411 | new_columns = MAX(1, new_columns); |
1412 | |
1413 | if (new_columns != asset_items->get_columns()) { |
1414 | asset_items->set_columns(new_columns); |
1415 | } |
1416 | } |
1417 | |
1418 | void EditorAssetLibrary::() { |
1419 | support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, false); |
1420 | } |
1421 | |
1422 | void EditorAssetLibrary::_bind_methods() { |
1423 | ADD_SIGNAL(MethodInfo("install_asset" , PropertyInfo(Variant::STRING, "zip_path" ), PropertyInfo(Variant::STRING, "name" ))); |
1424 | } |
1425 | |
1426 | EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { |
1427 | requesting = REQUESTING_NONE; |
1428 | templates_only = p_templates_only; |
1429 | initial_loading = true; |
1430 | |
1431 | VBoxContainer *library_main = memnew(VBoxContainer); |
1432 | add_child(library_main); |
1433 | |
1434 | HBoxContainer *search_hb = memnew(HBoxContainer); |
1435 | |
1436 | library_main->add_child(search_hb); |
1437 | library_main->add_theme_constant_override("separation" , 10 * EDSCALE); |
1438 | |
1439 | filter = memnew(LineEdit); |
1440 | if (templates_only) { |
1441 | filter->set_placeholder(TTR("Search Templates, Projects, and Demos" )); |
1442 | } else { |
1443 | filter->set_placeholder(TTR("Search Assets (Excluding Templates, Projects, and Demos)" )); |
1444 | } |
1445 | filter->set_clear_button_enabled(true); |
1446 | search_hb->add_child(filter); |
1447 | filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1448 | filter->connect("text_changed" , callable_mp(this, &EditorAssetLibrary::_search_text_changed)); |
1449 | |
1450 | // Perform a search automatically if the user hasn't entered any text for a certain duration. |
1451 | // This way, the user doesn't need to press Enter to initiate their search. |
1452 | filter_debounce_timer = memnew(Timer); |
1453 | filter_debounce_timer->set_one_shot(true); |
1454 | filter_debounce_timer->set_wait_time(0.25); |
1455 | filter_debounce_timer->connect("timeout" , callable_mp(this, &EditorAssetLibrary::_filter_debounce_timer_timeout)); |
1456 | search_hb->add_child(filter_debounce_timer); |
1457 | |
1458 | if (!p_templates_only) { |
1459 | search_hb->add_child(memnew(VSeparator)); |
1460 | } |
1461 | |
1462 | Button *open_asset = memnew(Button); |
1463 | open_asset->set_text(TTR("Import..." )); |
1464 | search_hb->add_child(open_asset); |
1465 | open_asset->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_asset_open)); |
1466 | |
1467 | Button *plugins = memnew(Button); |
1468 | plugins->set_text(TTR("Plugins..." )); |
1469 | search_hb->add_child(plugins); |
1470 | plugins->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_manage_plugins)); |
1471 | |
1472 | if (p_templates_only) { |
1473 | open_asset->hide(); |
1474 | plugins->hide(); |
1475 | } |
1476 | |
1477 | HBoxContainer *search_hb2 = memnew(HBoxContainer); |
1478 | library_main->add_child(search_hb2); |
1479 | |
1480 | search_hb2->add_child(memnew(Label(TTR("Sort:" ) + " " ))); |
1481 | sort = memnew(OptionButton); |
1482 | for (int i = 0; i < SORT_MAX; i++) { |
1483 | sort->add_item(TTRGET(sort_text[i])); |
1484 | } |
1485 | |
1486 | search_hb2->add_child(sort); |
1487 | |
1488 | sort->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1489 | sort->set_clip_text(true); |
1490 | sort->connect("item_selected" , callable_mp(this, &EditorAssetLibrary::_rerun_search)); |
1491 | |
1492 | search_hb2->add_child(memnew(VSeparator)); |
1493 | |
1494 | search_hb2->add_child(memnew(Label(TTR("Category:" ) + " " ))); |
1495 | categories = memnew(OptionButton); |
1496 | categories->add_item(TTR("All" )); |
1497 | search_hb2->add_child(categories); |
1498 | categories->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1499 | categories->set_clip_text(true); |
1500 | categories->connect("item_selected" , callable_mp(this, &EditorAssetLibrary::_rerun_search)); |
1501 | |
1502 | search_hb2->add_child(memnew(VSeparator)); |
1503 | |
1504 | search_hb2->add_child(memnew(Label(TTR("Site:" ) + " " ))); |
1505 | repository = memnew(OptionButton); |
1506 | |
1507 | _update_repository_options(); |
1508 | |
1509 | repository->connect("item_selected" , callable_mp(this, &EditorAssetLibrary::_repository_changed)); |
1510 | |
1511 | search_hb2->add_child(repository); |
1512 | repository->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1513 | repository->set_clip_text(true); |
1514 | |
1515 | search_hb2->add_child(memnew(VSeparator)); |
1516 | |
1517 | support = memnew(MenuButton); |
1518 | search_hb2->add_child(support); |
1519 | support->set_text(TTR("Support" )); |
1520 | support->get_popup()->set_hide_on_checkable_item_selection(false); |
1521 | support->get_popup()->add_check_item(TTRGET(support_text[SUPPORT_OFFICIAL]), SUPPORT_OFFICIAL); |
1522 | support->get_popup()->add_check_item(TTRGET(support_text[SUPPORT_COMMUNITY]), SUPPORT_COMMUNITY); |
1523 | support->get_popup()->add_check_item(TTRGET(support_text[SUPPORT_TESTING]), SUPPORT_TESTING); |
1524 | support->get_popup()->set_item_checked(SUPPORT_OFFICIAL, true); |
1525 | support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, true); |
1526 | support->get_popup()->connect("id_pressed" , callable_mp(this, &EditorAssetLibrary::_support_toggled)); |
1527 | |
1528 | ///////// |
1529 | |
1530 | library_scroll_bg = memnew(PanelContainer); |
1531 | library_main->add_child(library_scroll_bg); |
1532 | library_scroll_bg->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1533 | |
1534 | library_scroll = memnew(ScrollContainer); |
1535 | library_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); |
1536 | |
1537 | library_scroll_bg->add_child(library_scroll); |
1538 | |
1539 | Ref<StyleBoxEmpty> border2; |
1540 | border2.instantiate(); |
1541 | border2->set_content_margin_individual(15 * EDSCALE, 15 * EDSCALE, 35 * EDSCALE, 15 * EDSCALE); |
1542 | |
1543 | PanelContainer *library_vb_border = memnew(PanelContainer); |
1544 | library_scroll->add_child(library_vb_border); |
1545 | library_vb_border->add_theme_style_override("panel" , border2); |
1546 | library_vb_border->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1547 | |
1548 | library_vb = memnew(VBoxContainer); |
1549 | library_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); |
1550 | |
1551 | library_vb_border->add_child(library_vb); |
1552 | |
1553 | library_info = memnew(Label); |
1554 | library_info->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
1555 | library_vb->add_child(library_info); |
1556 | |
1557 | library_error = memnew(VBoxContainer); |
1558 | library_error->hide(); |
1559 | library_vb->add_child(library_error); |
1560 | |
1561 | library_error_label = memnew(Label(TTR("Failed to get repository configuration." ))); |
1562 | library_error_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); |
1563 | library_error->add_child(library_error_label); |
1564 | |
1565 | library_error_retry = memnew(Button(TTR("Retry" ))); |
1566 | library_error_retry->set_h_size_flags(SIZE_SHRINK_CENTER); |
1567 | library_error_retry->connect("pressed" , callable_mp(this, &EditorAssetLibrary::_request_current_config)); |
1568 | library_error->add_child(library_error_retry); |
1569 | |
1570 | asset_top_page = memnew(HBoxContainer); |
1571 | library_vb->add_child(asset_top_page); |
1572 | |
1573 | asset_items = memnew(GridContainer); |
1574 | _update_asset_items_columns(); |
1575 | asset_items->add_theme_constant_override("h_separation" , 10 * EDSCALE); |
1576 | asset_items->add_theme_constant_override("v_separation" , 10 * EDSCALE); |
1577 | |
1578 | library_vb->add_child(asset_items); |
1579 | |
1580 | asset_bottom_page = memnew(HBoxContainer); |
1581 | library_vb->add_child(asset_bottom_page); |
1582 | |
1583 | request = memnew(HTTPRequest); |
1584 | add_child(request); |
1585 | setup_http_request(request); |
1586 | request->connect("request_completed" , callable_mp(this, &EditorAssetLibrary::_http_request_completed)); |
1587 | |
1588 | last_queue_id = 0; |
1589 | |
1590 | library_vb->add_theme_constant_override("separation" , 20 * EDSCALE); |
1591 | |
1592 | error_hb = memnew(HBoxContainer); |
1593 | library_main->add_child(error_hb); |
1594 | error_label = memnew(Label); |
1595 | error_hb->add_child(error_label); |
1596 | error_tr = memnew(TextureRect); |
1597 | error_tr->set_v_size_flags(Control::SIZE_SHRINK_CENTER); |
1598 | error_hb->add_child(error_tr); |
1599 | |
1600 | description = nullptr; |
1601 | |
1602 | set_process(true); |
1603 | set_process_shortcut_input(true); // Global shortcuts since there is no main element to be focused. |
1604 | |
1605 | downloads_scroll = memnew(ScrollContainer); |
1606 | downloads_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); |
1607 | library_main->add_child(downloads_scroll); |
1608 | downloads_hb = memnew(HBoxContainer); |
1609 | downloads_scroll->add_child(downloads_hb); |
1610 | |
1611 | asset_open = memnew(EditorFileDialog); |
1612 | |
1613 | asset_open->set_access(EditorFileDialog::ACCESS_FILESYSTEM); |
1614 | asset_open->add_filter("*.zip" , TTR("Assets ZIP File" )); |
1615 | asset_open->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); |
1616 | add_child(asset_open); |
1617 | asset_open->connect("file_selected" , callable_mp(this, &EditorAssetLibrary::_asset_file_selected)); |
1618 | |
1619 | asset_installer = nullptr; |
1620 | } |
1621 | |
1622 | /////// |
1623 | |
1624 | bool AssetLibraryEditorPlugin::is_available() { |
1625 | #ifdef WEB_ENABLED |
1626 | // Asset Library can't work on Web editor for now as most assets are sourced |
1627 | // directly from GitHub which does not set CORS. |
1628 | return false; |
1629 | #else |
1630 | return StreamPeerTLS::is_available(); |
1631 | #endif |
1632 | } |
1633 | |
1634 | void AssetLibraryEditorPlugin::make_visible(bool p_visible) { |
1635 | if (p_visible) { |
1636 | addon_library->show(); |
1637 | } else { |
1638 | addon_library->hide(); |
1639 | } |
1640 | } |
1641 | |
1642 | AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() { |
1643 | addon_library = memnew(EditorAssetLibrary); |
1644 | addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL); |
1645 | EditorNode::get_singleton()->get_main_screen_control()->add_child(addon_library); |
1646 | addon_library->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); |
1647 | addon_library->hide(); |
1648 | } |
1649 | |
1650 | AssetLibraryEditorPlugin::~AssetLibraryEditorPlugin() { |
1651 | } |
1652 | |