diff --git a/doc/classes/EditorFeatureProfile.xml b/doc/classes/EditorFeatureProfile.xml index 4a807d34d2..4258b54e6c 100644 --- a/doc/classes/EditorFeatureProfile.xml +++ b/doc/classes/EditorFeatureProfile.xml @@ -104,7 +104,7 @@ The Script tab, which contains the script editor and class reference browser. If this feature is disabled, the Script tab won't display. - The AssetLib tab. If this feature is disabled, the AssetLib tab won't display. + The Asset Store tab. If this feature is disabled, the Asset Store tab won't display. Scene tree editing. If this feature is disabled, the Scene tree dock will still be visible but will be read-only. diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index 1211578c63..bca119ca75 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -500,7 +500,7 @@ - Sets the editor's current main screen to the one specified in [param name]. [param name] must match the title of the tab in question exactly (e.g. [code]2D[/code], [code]3D[/code], [code skip-lint]Script[/code], [code]Game[/code], or [code]AssetLib[/code] for default tabs). + Sets the editor's current main screen to the one specified in [param name]. [param name] must match the title of the tab in question exactly (e.g. [code]2D[/code], [code]3D[/code], [code skip-lint]Script[/code], [code]Game[/code], or [code]Asset Store[/code] for default tabs). diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index d215732a99..a7fc5dc905 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -234,7 +234,7 @@ Override this method in your plugin to return a [Texture2D] in order to give it an icon. - For main screen plugins, this appears at the top of the screen, to the right of the "2D", "3D", "Script", "Game", and "AssetLib" buttons. + For main screen plugins, this appears at the top of the screen, to the right of the "2D", "3D", "Script", "Game", and "Asset Store" buttons. Ideally, the plugin icon should be white with a transparent background and 16×16 pixels in size. [codeblocks] [gdscript] @@ -260,7 +260,7 @@ Override this method in your plugin to provide the name of the plugin when displayed in the Godot editor. - For main screen plugins, this appears at the top of the screen, to the right of the "2D", "3D", "Script", "Game", and "AssetLib" buttons. + For main screen plugins, this appears at the top of the screen, to the right of the "2D", "3D", "Script", "Game", and "Asset Store" buttons. @@ -329,7 +329,7 @@ - Returns [code]true[/code] if this is a main screen editor plugin (it goes in the workspace selector together with [b]2D[/b], [b]3D[/b], [b]Script[/b], [b]Game[/b], and [b]AssetLib[/b]). + Returns [code]true[/code] if this is a main screen editor plugin (it goes in the workspace selector together with [b]2D[/b], [b]3D[/b], [b]Script[/b], [b]Game[/b], and [b]Asset Store[/b]). When the plugin's workspace is selected, other main screen plugins will be hidden, but your plugin will not appear automatically. It needs to be added as a child of [method EditorInterface.get_editor_main_screen] and made visible inside [method _make_visible]. Use [method _get_plugin_name] and [method _get_plugin_icon] to customize the plugin button's appearance. [codeblock] @@ -814,7 +814,7 @@ - Emitted when user changes the workspace ([b]2D[/b], [b]3D[/b], [b]Script[/b], [b]Game[/b], [b]AssetLib[/b]). Also works with custom screens defined by plugins. + Emitted when user changes the workspace ([b]2D[/b], [b]3D[/b], [b]Script[/b], [b]Game[/b], [b]Asset Store[/b]). Also works with custom screens defined by plugins. diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index aaec10c2a6..99e13bd97f 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -245,7 +245,7 @@ - If [code]true[/code], the Asset Library uses multiple threads for its HTTP requests. This prevents the Asset Library from blocking the main thread for every loaded asset. + If [code]true[/code], the Asset Store uses multiple threads for its HTTP requests. This prevents the Asset Store from blocking the main thread for every loaded asset. If [code]true[/code], automatically switches to the [b]Remote[/b] scene tree when running the project from the editor. If [code]false[/code], stays on the [b]Local[/b] scene tree when running the project from the editor. @@ -1321,7 +1321,7 @@ All update modes will ignore builds with different major versions (e.g. Godot 4 -> Godot 5). - Determines whether online features, such as the Asset Library or update checks, are enabled in the editor. If this is a privacy concern, disabling these online features prevents the editor from making HTTP requests to the Godot website or third-party platforms hosting assets from the Asset Library. + Determines whether online features, such as the Asset Store or update checks, are enabled in the editor. If this is a privacy concern, disabling these online features prevents the editor from making HTTP requests to the Godot website or third-party platforms hosting assets from the Asset Store. Editor plugins and tool scripts are recommended to follow this setting. However, Godot can't prevent them from violating this rule. @@ -1331,11 +1331,11 @@ The port to listen to when starting the remote debugger. Godot will try to use port numbers above the configured number if the configured number is already taken by another application. - The host to use to contact the HTTP and HTTPS proxy in the editor (for the asset library and export template downloads). See also [member network/http_proxy/port]. + The host to use to contact the HTTP and HTTPS proxy in the editor (for the asset store and export template downloads). See also [member network/http_proxy/port]. [b]Note:[/b] Godot currently doesn't automatically use system proxy settings, so you have to enter them manually here if needed. - The port number to use to contact the HTTP and HTTPS proxy in the editor (for the asset library and export template downloads). See also [member network/http_proxy/host]. + The port number to use to contact the HTTP and HTTPS proxy in the editor (for the asset store and export template downloads). See also [member network/http_proxy/host]. [b]Note:[/b] Godot currently doesn't automatically use system proxy settings, so you have to enter them manually here if needed. @@ -1366,7 +1366,7 @@ If [code]true[/code] the language server will run in a separate thread, if [code]false[/code] it will run on the main thread. - The TLS certificate bundle to use for HTTP requests made within the editor (e.g. from the AssetLib tab). If left empty, the [url=https://github.com/godotengine/godot/blob/master/thirdparty/certs/ca-bundle.crt]included Mozilla certificate bundle[/url] will be used. + The TLS certificate bundle to use for HTTP requests made within the editor (e.g. from the Asset Store tab). If left empty, the [url=https://github.com/godotengine/godot/blob/master/thirdparty/certs/ca-bundle.crt]included Mozilla certificate bundle[/url] will be used. If [code]true[/code], enable TLSv1.3 negotiation. diff --git a/editor/asset_library/asset_library_editor_plugin.cpp b/editor/asset_library/asset_library_editor_plugin.cpp index be8a688645..bbc4c2fa2f 100644 --- a/editor/asset_library/asset_library_editor_plugin.cpp +++ b/editor/asset_library/asset_library_editor_plugin.cpp @@ -47,9 +47,12 @@ #include "editor/settings/editor_settings.h" #include "editor/settings/project_settings_editor.h" #include "editor/themes/editor_scale.h" +#include "scene/gui/color_rect.h" #include "scene/gui/menu_button.h" #include "scene/gui/separator.h" +#include "scene/main/scene_tree.h" #include "scene/resources/image_texture.h" +#include "scene/resources/style_box_flat.h" static inline void setup_http_request(HTTPRequest *request) { request->set_use_threads(EDITOR_GET("asset_library/use_threads")); @@ -60,40 +63,42 @@ static inline void setup_http_request(HTTPRequest *request) { request->set_https_proxy(proxy_host, proxy_port); } -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) { +void EditorAssetLibraryItem::configure(const String &p_title, const String &p_asset_id, const String &p_author, const String &p_author_id, const String &p_license_type, const String &p_license_url, int p_rating) { title_text = p_title; title->set_text(title_text); title->set_tooltip_text(title_text); asset_id = p_asset_id; - category->set_text(p_category); - category_id = p_category_id; author->set_text(p_author); author_id = p_author_id; - price->set_text(p_cost); + license->set_text(p_license_type); + license_url = p_license_url; + rating_count->set_text(itos(p_rating)); + + if (author_id.is_empty()) { + author->set_disabled(true); + author->set_mouse_filter(MOUSE_FILTER_IGNORE); + } _calculate_misc_links_size(); } void EditorAssetLibraryItem::set_image(int p_type, int p_index, const Ref &p_image) { - ERR_FAIL_COND(p_type != EditorAssetLibrary::IMAGE_QUEUE_ICON); + ERR_FAIL_COND(p_type != EditorAssetLibrary::IMAGE_QUEUE_THUMBNAIL); ERR_FAIL_COND(p_index != 0); - icon->set_texture_normal(p_image); + icon->set_texture(p_image); } void EditorAssetLibraryItem::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { - icon->set_texture_normal(get_editor_theme_icon(SNAME("ProjectIconLoading"))); - category->add_theme_color_override(SceneStringName(font_color), Color(0.5, 0.5, 0.5)); - author->add_theme_color_override(SceneStringName(font_color), Color(0.5, 0.5, 0.5)); - price->add_theme_color_override(SceneStringName(font_color), Color(0.5, 0.5, 0.5)); + case NOTIFICATION_READY: { + icon->set_texture(get_editor_theme_icon(SNAME("AssetThumbLoading"))); + } break; - if (author->get_default_cursor_shape() == CURSOR_ARROW) { - // Disable visible feedback if author link isn't clickable. - author->add_theme_color_override("font_pressed_color", Color(0.5, 0.5, 0.5)); - author->add_theme_color_override("font_hover_color", Color(0.5, 0.5, 0.5)); - } + case NOTIFICATION_THEME_CHANGED: { + author->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("faded_text"), SNAME("AssetLib"))); + license->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("faded_text"), SNAME("AssetLib"))); + rating_icon->set_texture(get_editor_theme_icon(SNAME("ThumbsUp"))); _calculate_misc_links_size(); } break; @@ -101,10 +106,6 @@ void EditorAssetLibraryItem::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { _calculate_misc_links_size(); } break; - - case NOTIFICATION_RESIZED: { - calculate_misc_links_ratio(); - } break; } } @@ -112,182 +113,210 @@ void EditorAssetLibraryItem::_calculate_misc_links_size() { Ref text_buf; text_buf.instantiate(); text_buf->add_string(author->get_text(), author->get_button_font(), author->get_button_font_size()); - author_width = text_buf->get_line_width(); + author->set_custom_maximum_size(Size2(text_buf->get_line_width(), -1)); text_buf->clear(); - const Ref font = get_theme_font(SceneStringName(font), SNAME("Label")); - const int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label")); - text_buf->add_string(price->get_text(), font, font_size); - price_width = text_buf->get_line_width(); - - calculate_misc_links_ratio(); -} - -void EditorAssetLibraryItem::calculate_misc_links_ratio() { - const int separators_width = 15 * EDSCALE; - const float total_width = author_price_hbox->get_size().width - (separator->get_size().width + separators_width); - if (total_width <= 0) { - return; - } - - float ratio_left = 1; - // Make the ratios a fraction bigger, to avoid unnecessary trimming. - const float extra_ratio = 4.0 / total_width; - - const float author_ratio = MIN(1, author_width / total_width); - author->set_stretch_ratio(author_ratio + extra_ratio); - ratio_left -= author_ratio; - - const float price_ratio = MIN(1, price_width / total_width); - price->set_stretch_ratio(price_ratio + extra_ratio); - ratio_left -= price_ratio; - - spacer->set_stretch_ratio(ratio_left); + text_buf->add_string(license->get_text(), license->get_button_font(), license->get_button_font_size()); + license->set_custom_maximum_size(Size2(text_buf->get_line_width(), -1)); } void EditorAssetLibraryItem::_asset_clicked() { - emit_signal(SNAME("asset_selected"), asset_id); -} - -void EditorAssetLibraryItem::_category_clicked() { - emit_signal(SNAME("category_selected"), category_id); + emit_signal(SNAME("asset_selected"), author_id + "/" + asset_id + "/"); } void EditorAssetLibraryItem::_author_clicked() { - emit_signal(SNAME("author_selected"), author->get_text()); + OS::get_singleton()->shell_open("https://store.godotengine.org/publisher/" + author_id.uri_encode() + "/"); +} + +void EditorAssetLibraryItem::_license_clicked() { + ERR_FAIL_COND(!license_url.begins_with("http")); + OS::get_singleton()->shell_open(license_url); } void EditorAssetLibraryItem::_bind_methods() { ClassDB::bind_method("set_image", &EditorAssetLibraryItem::set_image); ADD_SIGNAL(MethodInfo("asset_selected")); - ADD_SIGNAL(MethodInfo("category_selected")); ADD_SIGNAL(MethodInfo("author_selected")); } EditorAssetLibraryItem::EditorAssetLibraryItem(bool p_clickable) { - Ref border; - border.instantiate(); - border->set_content_margin_all(5 * EDSCALE); - add_theme_style_override(SceneStringName(panel), border); + is_clickable = p_clickable; + if (p_clickable) { + button = memnew(Button); + button->set_theme_type_variation(SceneStringName(FlatButton)); + add_child(button); + button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_asset_clicked)); + } + + margin = memnew(MarginContainer); + int margin_size = 5 * EDSCALE; + margin->add_theme_constant_override(SNAME("margin_left"), margin_size); + margin->add_theme_constant_override(SNAME("margin_right"), margin_size); + margin->add_theme_constant_override(SNAME("margin_top"), margin_size); + margin->add_theme_constant_override(SNAME("margin_bottom"), margin_size); + margin->set_mouse_filter(MOUSE_FILTER_IGNORE); + margin->set_clip_contents(true); + add_child(margin); HBoxContainer *hb = memnew(HBoxContainer); // Add some spacing to visually separate the icon from the asset details. hb->add_theme_constant_override("separation", 15 * EDSCALE); - add_child(hb); + hb->set_mouse_filter(MOUSE_FILTER_IGNORE); + margin->add_child(hb); - icon = memnew(TextureButton); - icon->set_accessibility_name(TTRC("Open asset details")); - icon->set_custom_minimum_size(Size2(64, 64) * EDSCALE); + icon = memnew(TextureRect); + icon->set_accessibility_name(TTRC("Thumbnail")); + icon->set_custom_minimum_size(EditorAssetLibrary::THUMBNAIL_SIZE * EDSCALE); + icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + icon->set_mouse_filter(MOUSE_FILTER_IGNORE); hb->add_child(icon); VBoxContainer *vb = memnew(VBoxContainer); - + vb->set_mouse_filter(MOUSE_FILTER_IGNORE); + vb->set_h_size_flags(SIZE_EXPAND_FILL); hb->add_child(vb); - vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - - title = memnew(LinkButton); - title->set_accessibility_name(TTRC("Title")); - title->set_auto_translate_mode(AutoTranslateMode::AUTO_TRANSLATE_MODE_DISABLED); - title->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); - title->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); - vb->add_child(title); - - category = memnew(LinkButton); - category->set_accessibility_name(TTRC("Category")); - category->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); - category->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); - vb->add_child(category); - - author_price_hbox = memnew(HBoxContainer); - author_price_hbox->add_theme_constant_override("separation", 5 * EDSCALE); - vb->add_child(author_price_hbox); - - author = memnew(LinkButton); - author->set_tooltip_text(TTRC("Author")); - author->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); - author->set_accessibility_name(TTRC("Author")); - author->set_h_size_flags(Control::SIZE_EXPAND_FILL); - author_price_hbox->add_child(author); - - separator = memnew(HSeparator); - author_price_hbox->add_child(separator); - - if (p_clickable) { - author->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); - icon->set_default_cursor_shape(CURSOR_POINTING_HAND); - icon->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_asset_clicked)); - title->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_asset_clicked)); - category->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_category_clicked)); - author->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_author_clicked)); - } else { - title->set_mouse_filter(MOUSE_FILTER_IGNORE); - category->set_mouse_filter(MOUSE_FILTER_IGNORE); - author->set_underline_mode(LinkButton::UNDERLINE_MODE_NEVER); - author->set_default_cursor_shape(CURSOR_ARROW); - } Ref label_margin; label_margin.instantiate(); label_margin->set_content_margin_all(0); - price = memnew(Label); - price->set_focus_mode(FOCUS_ACCESSIBILITY); - price->add_theme_style_override(CoreStringName(normal), label_margin); - price->set_tooltip_text(TTRC("License")); - price->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); - price->set_accessibility_name(TTRC("License")); - price->set_h_size_flags(Control::SIZE_EXPAND_FILL); - price->set_mouse_filter(MOUSE_FILTER_PASS); - author_price_hbox->add_child(price); + title = memnew(Label); + title->add_theme_style_override(CoreStringName(normal), label_margin); + title->set_accessibility_name(TTRC("Title")); + title->set_auto_translate_mode(AutoTranslateMode::AUTO_TRANSLATE_MODE_DISABLED); + title->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + title->set_mouse_filter(MOUSE_FILTER_IGNORE); + title->set_focus_mode(FOCUS_ACCESSIBILITY); + vb->add_child(title); - spacer = memnew(Control); - spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); - author_price_hbox->add_child(spacer); + author_license_hbox = memnew(HBoxContainer); + author_license_hbox->add_theme_constant_override("separation", 5 * EDSCALE); + author_license_hbox->set_mouse_filter(MOUSE_FILTER_IGNORE); + vb->add_child(author_license_hbox); + author = memnew(LinkButton); + author->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); + author->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + author->set_tooltip_text(TTRC("Author")); + author->set_accessibility_name(TTRC("Author")); + author->set_h_size_flags(Control::SIZE_EXPAND_FILL); + author_license_hbox->add_child(author); + author->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_author_clicked)); + + separator = memnew(HSeparator); + separator->set_mouse_filter(MOUSE_FILTER_IGNORE); + author_license_hbox->add_child(separator); + + license = memnew(LinkButton); + license->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); + license->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + license->set_tooltip_text(TTRC("License")); + license->set_accessibility_name(TTRC("License")); + license->set_h_size_flags(Control::SIZE_EXPAND_FILL); + author_license_hbox->add_child(license); + license->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItem::_license_clicked)); + + HBoxContainer *rating_hbox = memnew(HBoxContainer); + rating_hbox->set_mouse_filter(MOUSE_FILTER_IGNORE); + vb->add_child(rating_hbox); + + rating_icon = memnew(TextureRect); + rating_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + rating_icon->set_mouse_filter(MOUSE_FILTER_IGNORE); + rating_hbox->add_child(rating_icon); + + rating_count = memnew(Label); + rating_count->set_mouse_filter(MOUSE_FILTER_STOP); + rating_count->set_tooltip_text(TTRC("Review Score")); + rating_count->set_accessibility_name(TTRC("Review score")); + rating_hbox->add_child(rating_count); + + set_accessibility_name(TTRC("Open asset details")); set_custom_minimum_size(Size2(250, 80) * EDSCALE); - set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_h_size_flags(SIZE_EXPAND_FILL); +} + +////////////////////////////////////////////////////////////////////////////// + +Control *EditorAssetLibraryZoomMode::remove_previews() { + ERR_FAIL_NULL_V(previews, nullptr); + + remove_child(previews); + return previews; +} + +void EditorAssetLibraryZoomMode::input(const Ref &p_event) { + Ref m = p_event; + if (m.is_valid()) { + return; + } + + if (p_event->is_action_pressed(SNAME("ui_cancel"))) { + hide(); + } + + // Block inputs from going elsewhere. + get_tree()->get_root()->set_input_as_handled(); +} + +EditorAssetLibraryZoomMode::EditorAssetLibraryZoomMode(Control *p_previews) { + ERR_FAIL_COND(p_previews->get_parent()); + + ColorRect *dim = memnew(ColorRect); + dim->set_color(EditorNode::get_singleton()->get_editor_theme()->get_color(SNAME("base_color"), EditorStringName(Editor))); + dim->set_anchors_preset(Control::PRESET_FULL_RECT); + add_child(dim); + + previews = p_previews; + add_child(previews); + p_previews->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 40 * EDSCALE); + + set_process_input(true); } ////////////////////////////////////////////////////////////////////////////// void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const Ref &p_image) { switch (p_type) { - case EditorAssetLibrary::IMAGE_QUEUE_ICON: { + case EditorAssetLibrary::IMAGE_QUEUE_THUMBNAIL: { item->call("set_image", p_type, p_index, p_image); icon = p_image; } break; - case EditorAssetLibrary::IMAGE_QUEUE_THUMBNAIL: { - for (int i = 0; i < preview_images.size(); i++) { - if (preview_images[i].id == p_index) { - if (preview_images[i].is_video) { - Ref overlay = previews->get_editor_theme_icon(SNAME("PlayOverlay"))->get_image(); - Ref thumbnail = p_image->get_image(); - thumbnail = thumbnail->duplicate(); - Point2i overlay_pos = Point2i((thumbnail->get_width() - overlay->get_width()) / 2, (thumbnail->get_height() - overlay->get_height()) / 2); - // Overlay and thumbnail need the same format for `blend_rect` to work. - thumbnail->convert(Image::FORMAT_RGBA8); - thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); - preview_images[i].button->set_button_icon(ImageTexture::create_from_image(thumbnail)); - - // Make it clearer that clicking it will open an external link - preview_images[i].button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND); - } else { - preview_images[i].button->set_button_icon(p_image); - } - break; - } - } - } break; + case EditorAssetLibrary::IMAGE_QUEUE_VIDEO_THUMBNAIL: case EditorAssetLibrary::IMAGE_QUEUE_SCREENSHOT: { for (int i = 0; i < preview_images.size(); i++) { - if (preview_images[i].id == p_index) { - preview_images.write[i].image = p_image; - if (preview_images[i].button->is_pressed()) { - _preview_click(p_index); - } - break; + if (preview_images[i].id != p_index) { + continue; } + + Button *button = preview_images[i].button; + float button_texture_height = button->get_size().height - button->get_theme_stylebox(CoreStringName(normal), SNAME("Button"))->get_minimum_size().height; + float scale_ratio = button_texture_height / p_image->get_height(); + button->set_custom_minimum_size(Size2(p_image->get_width() * scale_ratio * EDSCALE, 0)); + + if (preview_images[i].is_video) { + Ref overlay = previews->get_editor_theme_icon(SNAME("PlayOverlay"))->get_image(); + Ref thumbnail = p_image->get_image()->duplicate(); + Point2i overlay_pos = Point2i((p_image->get_width() - overlay->get_width()) / 2, (p_image->get_height() - overlay->get_height()) / 2); + + // Overlay and thumbnail need the same format for `blend_rect` to work. + thumbnail->convert(Image::FORMAT_RGBA8); + thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); + button->set_button_icon(ImageTexture::create_from_image(thumbnail)); + + // Make it clearer that clicking it will open an external link. + button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND); + } else { + button->set_button_icon(p_image); + } + + preview_images.write[i].image = p_image; + if (button->is_pressed()) { + preview_click(p_index); + } + + break; } } break; } @@ -295,64 +324,230 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const void EditorAssetLibraryItemDescription::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - previews_bg->add_theme_style_override(SceneStringName(panel), previews->get_theme_stylebox(CoreStringName(normal), SNAME("TextEdit"))); + case NOTIFICATION_POSTINITIALIZE: { + connect(SceneStringName(confirmed), callable_mp(this, &EditorAssetLibraryItemDescription::_confirmed)); } break; - case NOTIFICATION_POST_POPUP: { - callable_mp(item, &EditorAssetLibraryItem::calculate_misc_links_ratio).call_deferred(); + case NOTIFICATION_THEME_CHANGED: { + version_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("bold"), EditorStringName(EditorFonts))); + Ref link_icon = get_editor_theme_icon(SNAME("ExternalLink")); + store->set_button_icon(link_icon); + source->set_button_icon(link_icon); + previous_preview->set_button_icon(get_editor_theme_icon(SNAME("Back"))); + next_preview->set_button_icon(get_editor_theme_icon(SNAME("Forward"))); + previews_bg->add_theme_style_override(SceneStringName(panel), previews->get_theme_stylebox(CoreStringName(normal), SNAME("TextEdit"))); + zoom_button->set_button_icon(get_editor_theme_icon(SNAME("DistractionFree"))); + } break; + + case NOTIFICATION_READY: { + int width = zoom_button->get_size().width; + previous_preview->set_custom_minimum_size(Size2(width, 100 * EDSCALE)); + next_preview->set_custom_minimum_size(Size2(width, 100 * EDSCALE)); } break; } } void EditorAssetLibraryItemDescription::_bind_methods() { ClassDB::bind_method(D_METHOD("set_image"), &EditorAssetLibraryItemDescription::set_image); + + ADD_SIGNAL(MethodInfo("install_requested", PropertyInfo(Variant::STRING, "asset_id"), PropertyInfo(Variant::STRING, "version"), PropertyInfo(Variant::STRING, "dpownload_url"), PropertyInfo(Variant::STRING, "sha256"))); + ADD_SIGNAL(MethodInfo("tag_clicked", PropertyInfo(Variant::STRING, "tag"))); +} + +void EditorAssetLibraryItemDescription::_confirmed() { + if (install_mode == MODE_INSTALL) { + // It will just redirect to the install dialog. + emit_signal(SNAME("install_requested"), asset_id, "", "", ""); + return; + } + + Release release = releases[version_list->get_selected()]; + emit_signal(SNAME("install_requested"), asset_id, release.version, release.url, release.sha256); +} + +void EditorAssetLibraryItemDescription::_version_selected(int p_index) { + String changes = version_list->get_item_metadata(p_index); + changelog->clear(); + changelog->append_text(changes.is_empty() ? TTRC("No changelog provided for this version.") : changes); +} + +void EditorAssetLibraryItemDescription::_store_pressed() { + OS::get_singleton()->shell_open(store_url); +} + +void EditorAssetLibraryItemDescription::_source_pressed() { + OS::get_singleton()->shell_open(source_url); } void EditorAssetLibraryItemDescription::_link_click(const String &p_url) { + if (p_url.begins_with("#")) { + emit_signal("tag_clicked", p_url); + return; + } + ERR_FAIL_COND(!p_url.begins_with("http")); OS::get_singleton()->shell_open(p_url); } -void EditorAssetLibraryItemDescription::_preview_click(int p_id) { +void EditorAssetLibraryItemDescription::preview_click(int p_id) { for (int i = 0; i < preview_images.size(); i++) { - if (preview_images[i].id == p_id) { - preview_images[i].button->set_pressed(true); - if (!preview_images[i].is_video) { - if (preview_images[i].image.is_valid()) { - preview->set_texture(preview_images[i].image); - child_controls_changed(); - } - } else { - _link_click(preview_images[i].video_link); - } - } else { - preview_images[i].button->set_pressed(false); + if (preview_images[i].id != p_id) { + continue; } + + preview_images[i].button->set_pressed(true); + if (!preview_images[i].is_video) { + if (preview_images[i].image.is_valid()) { + preview->set_texture(preview_images[i].image); + child_controls_changed(); + } + preview_images[i].button->grab_focus(true); + } else { + _link_click(preview_images[i].video_link); + } + + break; } } -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) { - asset_id = p_asset_id; - title = p_title; - download_url = p_download_url; - sha256 = p_sha256_hash; - item->configure(p_title, p_asset_id, p_category, p_category_id, p_author, p_author_id, p_cost); - description->clear(); - description->add_text(TTR("Version:") + " " + p_version_string + "\n"); - description->add_text(TTR("Contents:") + " "); - description->push_meta(p_browse_url); - description->add_text(TTR("View Files")); - description->pop(); - description->add_text("\n" + TTR("Description:") + "\n\n"); - description->append_text(p_description); - description->set_selection_enabled(true); - description->set_context_menu_enabled(true); - set_title(p_title); +void EditorAssetLibraryItemDescription::_previous_preview_pressed() { + List buttons; + preview_group->get_buttons(&buttons); + BaseButton *pressed = preview_group->get_pressed_button(); + if (pressed == buttons.front()->get()) { + preview_click(buttons.back()->get()->get_index()); + } else { + preview_click(pressed->get_index() - 1); + } } -void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, const String &p_url) { +void EditorAssetLibraryItemDescription::_next_preview_pressed() { + List buttons; + preview_group->get_buttons(&buttons); + BaseButton *pressed = preview_group->get_pressed_button(); + if (pressed == buttons.back()->get()) { + preview_click(buttons.front()->get()->get_index()); + } else { + preview_click(pressed->get_index() + 1); + } +} + +void EditorAssetLibraryItemDescription::_zoom_toggled(bool p_pressed) { + if (p_pressed) { + root->remove_child(previews_vbox); + zoom_mode = memnew(EditorAssetLibraryZoomMode(previews_vbox)); + get_tree()->get_root()->add_child(zoom_mode); + zoom_mode->connect(SceneStringName(visibility_changed), callable_mp(Object::cast_to(zoom_button), &BaseButton::set_pressed).bind(false)); + + hide(); + } else { + root->add_child(zoom_mode->remove_previews()); + zoom_mode->queue_free(); + zoom_mode = nullptr; + + show(); + } +} + +void EditorAssetLibraryItemDescription::configure(const String &p_title, const String &p_asset_id, const String &p_author, const String &p_author_id, const String &p_license_type, const String &p_license_url, int p_rating, const String &p_description, const HashMap &p_tags, const String &p_store_url, const String &p_source_url) { + asset_id = p_asset_id; + title = p_title; + item->configure(p_title, p_asset_id, p_author, p_author_id, p_license_type, p_license_url, p_rating); + + releases.clear(); + + version->show(); + version->set_text(TTRC("Loading...")); + version_list->hide(); + version_list->clear(); + + store_url = p_store_url; + + source_url = p_source_url; + source->set_visible(!p_source_url.is_empty()); + + description->clear(); + description->append_text(p_description); + + if (!p_tags.is_empty()) { + description->append_text("\n[b]" + TTR("Tags:") + "[/b]"); + for (const KeyValue &KV : p_tags) { + description->add_text(" "); + description->push_meta("#" + KV.value); + description->add_text("#" + KV.key); + description->pop(); + } + } + + changelog->set_text(TTRC("Loading...")); + + set_title(p_title); + if (install_mode == MODE_DOWNLOAD) { + get_ok_button()->set_disabled(true); + } +} + +void EditorAssetLibraryItemDescription::set_install_mode(InstallMode p_mode) { + if (p_mode == install_mode) { + return; + } + + switch (p_mode) { + case MODE_DOWNLOAD: { + set_ok_button_text(TTRC("Download")); + get_ok_button()->set_disabled(releases.is_empty()); + version_list->set_disabled(releases.is_empty()); + } break; + + case MODE_DOWNLOADING: { + set_ok_button_text(TTRC("Downloading...")); + get_ok_button()->set_disabled(true); + version_list->set_disabled(true); + } break; + + case MODE_INSTALL: { + set_ok_button_text(TTRC("Install...")); + get_ok_button()->set_disabled(false); + version_list->set_disabled(true); + } break; + } + + install_mode = p_mode; +} + +void EditorAssetLibraryItemDescription::add_release(const String &p_url, const String &p_version, const String &p_changes, const String &p_sha256) { + Release release; + release.url = p_url; + release.version = p_version; + release.sha256 = p_sha256; + + if (releases.is_empty()) { + version->set_text(p_version); + if (install_mode == MODE_DOWNLOAD) { + get_ok_button()->set_disabled(false); + } + + changelog->clear(); + changelog->append_text(p_changes.is_empty() ? TTRC("No changelog provided for this version.") : p_changes); + + } else if (releases.size() == 1) { + version->hide(); + version_list->set_text(releases[0].version); + if (install_mode == MODE_DOWNLOAD) { + version_list->set_disabled(false); + } + version_list->show(); + } + + version_list->add_item(p_version, releases.size()); + version_list->set_item_metadata(-1, p_changes); + + releases.append(release); +} + +void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, const String &p_url, const String &p_thumbnail) { if (preview_images.is_empty()) { + desc_vbox->set_h_size_flags(0); previews_vbox->show(); } @@ -362,63 +557,157 @@ void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, cons new_preview.is_video = p_video; new_preview.button = memnew(Button); new_preview.button->set_button_icon(previews->get_editor_theme_icon(SNAME("ThumbnailWait"))); - new_preview.button->set_toggle_mode(true); - new_preview.button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click).bind(p_id)); + new_preview.button->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); + new_preview.button->set_expand_icon(true); + new_preview.button->set_toggle_mode(!p_video); + new_preview.button->set_theme_type_variation(SNAME("ThumbnailButton")); + new_preview.button->set_custom_minimum_size(Size2(preview_hb->get_size().height, 0)); preview_hb->add_child(new_preview.button); + new_preview.button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::preview_click).bind(p_id)); + if (!p_video) { - new_preview.image = previews->get_editor_theme_icon(SNAME("ThumbnailWait")); + new_preview.button->set_button_group(preview_group); + // Enable the preview arrows if more than one screenshot is available. + if (previous_preview->is_disabled()) { + List buttons; + preview_group->get_buttons(&buttons); + if (buttons.size() > 1) { + previous_preview->set_disabled(false); + next_preview->set_disabled(false); + } + } + + zoom_button->set_disabled(false); } + preview_images.push_back(new_preview); - if (preview_images.size() == 1 && !p_video) { - _preview_click(p_id); - } } EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() { - HBoxContainer *hbox = memnew(HBoxContainer); - add_child(hbox); - VBoxContainer *desc_vbox = memnew(VBoxContainer); - hbox->add_child(desc_vbox); - hbox->add_theme_constant_override("separation", 15 * EDSCALE); + root = memnew(HBoxContainer); + root->add_theme_constant_override("separation", 15 * EDSCALE); + add_child(root); + + desc_vbox = memnew(VBoxContainer); + desc_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + desc_vbox->set_custom_minimum_size(Size2(440, 440) * EDSCALE); + root->add_child(desc_vbox); item = memnew(EditorAssetLibraryItem); - desc_vbox->add_child(item); - desc_vbox->set_custom_minimum_size(Size2(440 * EDSCALE, 440 * EDSCALE)); + + HBoxContainer *contents = memnew(HBoxContainer); + desc_vbox->add_child(contents); + + version_label = memnew(Label(TTRC("Version:"))); + contents->add_child(version_label); + + version = memnew(Label); + version->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + version->set_h_size_flags(Control::SIZE_EXPAND_FILL); + contents->add_child(version); + + version_list = memnew(OptionButton); + version_list->set_fit_to_longest_item(false); + version_list->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + version_list->set_tooltip_text(TTRC("Download other versions.")); + version_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + version_list->hide(); // Will be shown if multiple versions are available. + contents->add_child(version_list); + version_list->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibraryItemDescription::_version_selected)); + + store = memnew(Button); + store->set_text(TTRC("Store Page")); + store->set_tooltip_text(TTRC("Open the web browser to show the asset in the online store page.")); + store->set_theme_type_variation(SceneStringName(FlatButton)); + contents->add_child(store); + store->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::_store_pressed)); + + source = memnew(Button); + source->set_text(TTRC("View Source")); + source->set_tooltip_text(TTRC("Open the web browser to show a page with the source files.")); + source->set_theme_type_variation(SceneStringName(FlatButton)); + source->hide(); // Will be shown if the source link is available. + contents->add_child(source); + source->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::_source_pressed)); + + tabs = memnew(TabContainer); + tabs->set_theme_type_variation("TabContainerInner"); + tabs->set_v_size_flags(Control::SIZE_EXPAND_FILL); + desc_vbox->add_child(tabs); description = memnew(RichTextLabel); - desc_vbox->add_child(description); - description->set_v_size_flags(Control::SIZE_EXPAND_FILL); - description->connect("meta_clicked", callable_mp(this, &EditorAssetLibraryItemDescription::_link_click)); + description->set_selection_enabled(true); + description->set_context_menu_enabled(true); + description->set_name(TTRC("Description")); description->add_theme_constant_override(SceneStringName(line_separation), Math::round(5 * EDSCALE)); + tabs->add_child(description); + description->connect("meta_clicked", callable_mp(this, &EditorAssetLibraryItemDescription::_link_click)); + + changelog = memnew(RichTextLabel); + changelog->set_selection_enabled(true); + changelog->set_context_menu_enabled(true); + changelog->set_name(TTRC("Changelog")); + changelog->add_theme_constant_override(SceneStringName(line_separation), Math::round(5 * EDSCALE)); + tabs->add_child(changelog); previews_vbox = memnew(VBoxContainer); previews_vbox->hide(); // Will be shown if we add any previews later. - - hbox->add_child(previews_vbox); previews_vbox->add_theme_constant_override("separation", 15 * EDSCALE); previews_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); previews_vbox->set_h_size_flags(Control::SIZE_EXPAND_FILL); + root->add_child(previews_vbox); + + HBoxContainer *previews_hbox = memnew(HBoxContainer); + previews_hbox->set_v_size_flags(Control::SIZE_EXPAND_FILL); + previews_vbox->add_child(previews_hbox); + + previous_preview = memnew(Button); + previous_preview->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); + previous_preview->set_disabled(true); + previous_preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + previews_hbox->add_child(previous_preview); + previous_preview->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::_previous_preview_pressed)); preview = memnew(TextureRect); - previews_vbox->add_child(preview); preview->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); - preview->set_custom_minimum_size(Size2(640 * EDSCALE, 345 * EDSCALE)); - preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_custom_minimum_size(Size2(640, 345) * EDSCALE); preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); + previews_hbox->add_child(preview); + + MarginContainer *mc = memnew(MarginContainer); + previews_hbox->add_child(mc); + + next_preview = memnew(Button); + next_preview->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); + next_preview->set_disabled(true); + next_preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + mc->add_child(next_preview); + next_preview->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDescription::_next_preview_pressed)); + + zoom_button = memnew(Button); + zoom_button->set_toggle_mode(true); + zoom_button->set_disabled(true); + zoom_button->set_tooltip_text(TTRC("Toggle full view of preview images.")); + zoom_button->set_v_size_flags(Control::SIZE_SHRINK_END); + mc->add_child(zoom_button); + zoom_button->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetLibraryItemDescription::_zoom_toggled)); previews_bg = memnew(PanelContainer); previews_vbox->add_child(previews_bg); - previews_bg->set_custom_minimum_size(Size2(640 * EDSCALE, 101 * EDSCALE)); previews = memnew(ScrollContainer); - previews_bg->add_child(previews); + previews->set_follow_focus(true); previews->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); + previews_bg->add_child(previews); preview_hb = memnew(HBoxContainer); + preview_hb->set_custom_minimum_size(Size2(620, 90) * EDSCALE); preview_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); - previews->add_child(preview_hb); + + preview_group.instantiate(); + set_ok_button_text(TTRC("Download")); set_cancel_button_text(TTRC("Close")); } @@ -480,9 +769,9 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int } break; } - // Make the progress bar invisible but don't reflow other Controls around it. - progress->set_modulate(Color(0, 0, 0, 0)); - progress->set_indeterminate(false); + progress->hide(); + + set_process(false); if (!error_text.is_empty()) { download_error->set_text(TTR("Asset Download Error:") + "\n" + error_text); @@ -492,24 +781,26 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int return; } - install_button->set_disabled(false); - status->set_text(TTRC("Ready to install!")); + install_button->show(); + status->set_text(TTRC("Ready to install.")); - set_process(false); - - // Automatically prompt for installation once the download is completed. - install(); + // Automatically prompt for installation once the download is completed + // as long as the main window is focused, to not clash with other subwindows. + if (get_window()->has_focus()) { + install(); + } } -void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asset_id, const Ref &p_preview, const String &p_download_url, const String &p_sha256_hash) { +void EditorAssetLibraryItemDownload::configure(const String &p_title, const String &p_asset_id, const String &p_version, const Ref &p_preview, const String &p_download_url, const String &p_sha256) { title->set_text(p_title); + version->set_text(p_version); icon->set_texture(p_preview); asset_id = p_asset_id; if (p_preview.is_null()) { icon->set_texture(get_editor_theme_icon(SNAME("FileBrokenBigThumb"))); } host = p_download_url; - sha256 = p_sha256_hash; + sha256 = p_sha256; _make_request(); } @@ -517,13 +808,20 @@ void EditorAssetLibraryItemDownload::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("AssetLib"))); - status->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("status_color"), SNAME("AssetLib"))); + version->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("faded_text"), SNAME("AssetLib"))); dismiss_button->set_texture_normal(get_theme_icon(SNAME("dismiss"), SNAME("AssetLib"))); + spacer->set_custom_minimum_size(Size2(0, 8 * EDSCALE)); + + // Avoid sudden size changes by making the container have the same height as the buttons. + Ref button_style = get_theme_stylebox(CoreStringName(normal), SNAME("Button")); + Ref font = get_theme_font(SceneStringName(font), SNAME("Button")); + int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Button")); + int button_height = button_style->get_minimum_size().height + font->get_height(font_size); + progress_hbox->set_custom_minimum_size(Size2(0, button_height)); } break; case NOTIFICATION_PROCESS: { - // Make the progress bar visible again when retrying the download. - progress->set_modulate(Color(1, 1, 1, 1)); + progress->show(); if (download->get_downloaded_bytes() > 0) { progress->set_max(download->get_body_size()); @@ -580,7 +878,7 @@ void EditorAssetLibraryItemDownload::_close() { } bool EditorAssetLibraryItemDownload::can_install() const { - return !install_button->is_disabled(); + return install_button->is_visible(); } void EditorAssetLibraryItemDownload::install() { @@ -600,7 +898,7 @@ void EditorAssetLibraryItemDownload::_make_request() { retry_button->hide(); download->cancel_request(); - download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_asset_" + itos(asset_id)) + ".zip"); + download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_asset_" + asset_id) + ".zip"); Error err = download->request(host); if (err != OK) { @@ -629,47 +927,57 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { VBoxContainer *vb = memnew(VBoxContainer); hb->add_child(vb); vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vb->add_theme_constant_override("separation", 0); HBoxContainer *title_hb = memnew(HBoxContainer); vb->add_child(title_hb); title = memnew(Label); + title->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + title->set_theme_type_variation("LabelVMarginless"); title->set_focus_mode(FOCUS_ACCESSIBILITY); - title_hb->add_child(title); title->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_hb->add_child(title); dismiss_button = memnew(TextureButton); dismiss_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDownload::_close)); dismiss_button->set_accessibility_name(TTRC("Close")); title_hb->add_child(dismiss_button); - title->set_clip_text(true); + version = memnew(Label); + version->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); + version->set_theme_type_variation("LabelVMarginless"); + vb->add_child(version); - vb->add_spacer(); + spacer = memnew(Control); + vb->add_child(spacer); status = memnew(Label(TTRC("Idle"))); vb->add_child(status); + + progress_hbox = memnew(HBoxContainer); + vb->add_child(progress_hbox); + progress = memnew(ProgressBar); progress->set_editor_preview_indeterminate(true); - vb->add_child(progress); - - HBoxContainer *hb2 = memnew(HBoxContainer); - vb->add_child(hb2); - hb2->add_spacer(); - - install_button = memnew(Button); - install_button->set_text(TTRC("Install...")); - install_button->set_disabled(true); - install_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDownload::install)); + progress->hide(); + progress->set_h_size_flags(SIZE_EXPAND_FILL); + progress_hbox->add_child(progress); retry_button = memnew(Button); retry_button->set_text(TTRC("Retry")); + retry_button->hide(); // Only show the Retry button in case of a failure. + retry_button->set_h_size_flags(SIZE_EXPAND | SIZE_SHRINK_END); + progress_hbox->add_child(retry_button); retry_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDownload::_make_request)); - // Only show the Retry button in case of a failure. - retry_button->hide(); - hb2->add_child(retry_button); - hb2->add_child(install_button); - set_custom_minimum_size(Size2(310, 0) * EDSCALE); + install_button = memnew(Button); + install_button->set_text(TTRC("Install...")); + install_button->hide(); + install_button->set_h_size_flags(SIZE_EXPAND | SIZE_SHRINK_END); + progress_hbox->add_child(install_button); + install_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibraryItemDownload::install)); + + set_custom_minimum_size(Size2(400 * EDSCALE, 0)); download = memnew(HTTPRequest); panel->add_child(download); @@ -677,8 +985,8 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { setup_http_request(download); download_error = memnew(AcceptDialog); - panel->add_child(download_error); download_error->set_title(TTRC("Download Error")); + panel->add_child(download_error); asset_installer = memnew(EditorAssetInstaller); panel->add_child(asset_installer); @@ -699,7 +1007,7 @@ void EditorAssetLibrary::_notification(int p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { if (!initial_loading) { - _rerun_search(-1); + _search(); } } break; @@ -727,23 +1035,37 @@ void EditorAssetLibrary::_notification(int p_what) { } break; case NOTIFICATION_PROCESS: { - HTTPClient::Status s = request->get_http_client_status(); - const bool loading = s != HTTPClient::STATUS_DISCONNECTED; + // Check for finished image updates. + List to_delete; + for (KeyValue &E : image_queue) { + if (!E.value.update_finished) { + continue; + } - if (loading) { - library_scroll->set_modulate(Color(1, 1, 1, 0.5)); - } else { - library_scroll->set_modulate(Color(1, 1, 1, 1)); + Object *obj = ObjectDB::get_instance(E.value.target); + if (obj) { + if (E.value.texture.is_valid()) { + obj->call("set_image", E.value.image_type, E.value.image_index, E.value.texture); + } else { + obj->call("set_image", E.value.image_type, E.value.image_index, get_editor_theme_icon(SNAME("FileBrokenBigThumb"))); + } + } + + E.value.thread->wait_to_finish(); + E.value.request->queue_free(); + to_delete.push_back(E.key); + _update_image_queue(); } - const bool no_downloads = downloads_hb->get_child_count() == 0; - if (no_downloads == downloads_scroll->is_visible()) { - downloads_scroll->set_visible(!no_downloads); - - library_mc->set_theme_type_variation(no_downloads ? (Engine::get_singleton()->is_project_manager_hint() ? "NoBorderAssetLibProjectManager" : "NoBorderAssetLib") : "NoBorderHorizontal"); - library_scroll->set_scroll_hint_mode(no_downloads ? ScrollContainer::SCROLL_HINT_MODE_TOP_AND_LEFT : ScrollContainer::SCROLL_HINT_MODE_ALL); + while (to_delete.size()) { + image_queue[to_delete.front()->get()].request->queue_free(); + image_queue.erase(to_delete.front()->get()); + to_delete.pop_front(); } + if (image_queue.is_empty()) { + set_process(false); + } } break; case NOTIFICATION_RESIZED: { @@ -774,7 +1096,7 @@ void EditorAssetLibrary::_notification(int p_what) { void EditorAssetLibrary::_update_repository_options() { // TODO: Move to editor_settings.cpp Dictionary default_urls; - default_urls["godotengine.org (Official)"] = "https://godotengine.org/asset-library/api"; + default_urls["godotengine.org (Official)"] = "https://store.godotengine.org/api/v1"; Dictionary available_urls = _EDITOR_DEF("asset_library/available_urls", default_urls, true); repository->clear(); int i = 0; @@ -799,10 +1121,10 @@ void EditorAssetLibrary::shortcut_input(const Ref &p_event) { } } -void EditorAssetLibrary::_install_asset() { +void EditorAssetLibrary::_install_asset(const String &p_asset_id, const String &p_version, const String &p_download_url, const String &p_sha256) { ERR_FAIL_NULL(description); - EditorAssetLibraryItemDownload *d = _get_asset_in_progress(description->get_asset_id()); + EditorAssetLibraryItemDownload *d = _get_asset_in_progress(p_asset_id); if (d) { d->install(); return; @@ -810,7 +1132,8 @@ void EditorAssetLibrary::_install_asset() { EditorAssetLibraryItemDownload *download = memnew(EditorAssetLibraryItemDownload); downloads_hb->add_child(download); - download->configure(description->get_title(), description->get_asset_id(), description->get_preview_icon(), description->get_download_url(), description->get_sha256()); + download->configure(description->get_title(), p_asset_id, p_version, description->get_preview_icon(), p_download_url, p_sha256); + download->connect(SceneStringName(tree_exited), callable_mp(this, &EditorAssetLibrary::_update_downloads_section)); if (templates_only) { download->set_external_install(true); @@ -818,73 +1141,42 @@ void EditorAssetLibrary::_install_asset() { } } +void EditorAssetLibrary::_tag_clicked(const String &p_tag) { + description->hide(); + filter->set_text(p_tag); + _search(); +} + const char *EditorAssetLibrary::sort_key[SORT_MAX] = { - "updated", - "updated", - "name", - "name", - "cost", - "cost", + "relevance", + "updated_desc", + "updated_asc", + "reviews_desc", + "reviews_asc", + "created_desc", + "created_asc", }; const char *EditorAssetLibrary::sort_text[SORT_MAX] = { - TTRC("Recently Updated"), - TTRC("Least Recently Updated"), - TTRC("Name (A-Z)"), - TTRC("Name (Z-A)"), - TTRC("License (A-Z)"), // "cost" stores the SPDX license name in the Godot Asset Library. - TTRC("License (Z-A)"), // "cost" stores the SPDX license name in the Godot Asset Library. + TTRC("Relevance"), + TTRC("Updated (Newest First)"), + TTRC("Updated (Oldest First)"), + TTRC("Reviews (Highest Score First)"), + TTRC("Reviews (Lowest Score First)"), + TTRC("Created (Newest First)"), + TTRC("Created (Oldest First)"), }; -const char *EditorAssetLibrary::support_key[SUPPORT_MAX] = { - "official", // Former name for the Featured support level (still used on the API backend). - "community", - "testing", -}; - -const char *EditorAssetLibrary::support_text[SUPPORT_MAX] = { - TTRC("Featured"), - TTRC("Community"), - TTRC("Testing"), -}; - -void EditorAssetLibrary::_select_author(const String &p_author) { - if (!host.contains("godotengine.org")) { - // Don't open the link for alternative repositories. - return; - } - OS::get_singleton()->shell_open("https://godotengine.org/asset-library/asset?user=" + p_author.uri_encode()); +void EditorAssetLibrary::_select_asset(const String &p_id) { + _api_request("assets/" + p_id, REQUESTING_ASSET); } -void EditorAssetLibrary::_select_category(int p_id) { - for (int i = 0; i < categories->get_item_count(); i++) { - if (i == 0) { - continue; - } - int id = categories->get_item_metadata(i); - if (id == p_id) { - categories->select(i); - _search(); - break; - } - } -} +void EditorAssetLibrary::_image_update(void *p_image_queue) { + ImageQueue *iq = static_cast(p_image_queue); + PackedByteArray image_data = iq->data; -void EditorAssetLibrary::_select_asset(int p_id) { - _api_request("asset/" + itos(p_id), REQUESTING_ASSET); -} - -void EditorAssetLibrary::_image_update(bool p_use_cache, bool p_final, const PackedByteArray &p_data, int p_queue_id) { - Object *obj = ObjectDB::get_instance(image_queue[p_queue_id].target); - if (!obj) { - return; - } - - bool image_set = false; - PackedByteArray image_data = p_data; - - if (p_use_cache) { - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); + if (iq->use_cache) { + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().path_join("assetimage_" + iq->image_url.md5_text()); Ref file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ); if (file.is_valid()) { @@ -925,7 +1217,7 @@ void EditorAssetLibrary::_image_update(bool p_use_cache, bool p_final, const Pac if (parsed_image.is_null()) { if (is_print_verbose_enabled()) { - ERR_PRINT(vformat("Asset Library: Invalid image downloaded from '%s' for asset # %d", image_queue[p_queue_id].image_url, image_queue[p_queue_id].asset_id)); + ERR_PRINT(vformat("Asset Store: Invalid image downloaded from '%s' for asset # %d", iq->image_url, iq->asset_id)); } } else { image->copy_internals_from(parsed_image); @@ -933,39 +1225,30 @@ void EditorAssetLibrary::_image_update(bool p_use_cache, bool p_final, const Pac } if (!image->is_empty()) { - switch (image_queue[p_queue_id].image_type) { - case IMAGE_QUEUE_ICON: - image->resize(64 * EDSCALE, 64 * EDSCALE, Image::INTERPOLATE_LANCZOS); - break; - - case IMAGE_QUEUE_THUMBNAIL: { - float max_height = 85 * EDSCALE; - - float scale_ratio = max_height / (image->get_height() * EDSCALE); - if (scale_ratio < 1) { - image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_LANCZOS); - } + Size2 max_size; + switch (iq->image_type) { + case IMAGE_QUEUE_THUMBNAIL: + case IMAGE_QUEUE_VIDEO_THUMBNAIL: { + max_size = THUMBNAIL_SIZE; } break; case IMAGE_QUEUE_SCREENSHOT: { - float max_height = 397 * EDSCALE; - - float scale_ratio = max_height / (image->get_height() * EDSCALE); - if (scale_ratio < 1) { - image->resize(image->get_width() * EDSCALE * scale_ratio, image->get_height() * EDSCALE * scale_ratio, Image::INTERPOLATE_LANCZOS); - } + max_size.y = image->get_height(); } break; } - Ref tex = ImageTexture::create_from_image(image); + float scale_ratio = max_size.y / image->get_height(); + if (max_size.x > 0) { + scale_ratio = MIN(scale_ratio, max_size.x / image->get_width()); + } + if (scale_ratio < 1) { + image->resize(image->get_width() * scale_ratio * EDSCALE, image->get_height() * scale_ratio * EDSCALE, Image::INTERPOLATE_LANCZOS); + } - obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, tex); - image_set = true; + iq->texture = ImageTexture::create_from_image(image); } - if (!image_set && p_final) { - obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_editor_theme_icon(SNAME("FileBrokenBigThumb"))); - } + iq->update_finished = true; } void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data, int p_queue_id) { @@ -994,23 +1277,25 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons } } } - _image_update(p_code == HTTPClient::RESPONSE_NOT_MODIFIED, true, p_data, p_queue_id); + image_queue[p_queue_id].data = const_cast(p_data); + image_queue[p_queue_id].use_cache = p_code == HTTPClient::RESPONSE_NOT_MODIFIED; + set_process(true); + image_queue[p_queue_id].thread->start(_image_update, &image_queue[p_queue_id]); } else { if (is_print_verbose_enabled()) { - WARN_PRINT(vformat("Asset Library: Error getting image from '%s' for asset # %d.", image_queue[p_queue_id].image_url, image_queue[p_queue_id].asset_id)); + WARN_PRINT(vformat("Asset Store: Error getting image from '%s' for asset # %d.", image_queue[p_queue_id].image_url, image_queue[p_queue_id].asset_id)); } Object *obj = ObjectDB::get_instance(image_queue[p_queue_id].target); if (obj) { obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, get_editor_theme_icon(SNAME("FileBrokenBigThumb"))); } + + image_queue[p_queue_id].request->queue_free(); + image_queue.erase(p_queue_id); + _update_image_queue(); } - - image_queue[p_queue_id].request->queue_free(); - image_queue.erase(p_queue_id); - - _update_image_queue(); } void EditorAssetLibrary::_update_image_queue() { @@ -1036,10 +1321,9 @@ void EditorAssetLibrary::_update_image_queue() { } else { E.value.active = true; } - current_images++; - } else if (E.value.active) { - current_images++; } + + current_images++; } while (to_delete.size()) { @@ -1049,11 +1333,11 @@ void EditorAssetLibrary::_update_image_queue() { } } -void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, String p_image_url, ImageType p_type, int p_image_index) { +void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, const String &p_image_url, ImageType p_type, int p_image_index) { // Remove extra spaces around the URL. This isn't strictly valid, but recoverable. String trimmed_url = p_image_url.strip_edges(); if (trimmed_url != p_image_url && is_print_verbose_enabled()) { - WARN_PRINT(vformat("Asset Library: Badly formatted image URL '%s' for asset # %d.", p_image_url, p_asset_id)); + WARN_PRINT(vformat("Asset Store: Badly formatted image URL '%s' for asset # %d.", p_image_url, p_asset_id)); } // Validate the image URL first. @@ -1066,7 +1350,7 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, String p Error err = trimmed_url.parse_url(url_scheme, url_host, url_port, url_path, url_fragment); if (err != OK) { if (is_print_verbose_enabled()) { - ERR_PRINT(vformat("Asset Library: Invalid image URL '%s' for asset # %d.", trimmed_url, p_asset_id)); + ERR_PRINT(vformat("Asset Store: Invalid image URL '%s' for asset # %d.", trimmed_url, p_asset_id)); } Object *obj = ObjectDB::get_instance(p_for); @@ -1088,13 +1372,13 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, int p_asset_id, String p iq.asset_id = p_asset_id; iq.queue_id = ++last_queue_id; iq.active = false; + iq.thread = memnew(Thread); iq.request->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_image_request_completed).bind(iq.queue_id)); image_queue[iq.queue_id] = iq; add_child(iq.request); - _image_update(true, false, PackedByteArray(), iq.queue_id); _update_image_queue(); } @@ -1108,74 +1392,70 @@ void EditorAssetLibrary::_repository_changed(int p_repository_id) { filter->set_editable(false); sort->set_disabled(true); categories->set_disabled(true); - support->set_disabled(true); host = repository->get_item_metadata(p_repository_id); - if (templates_only) { - _api_request("configure", REQUESTING_CONFIG, "?type=project"); - } else { - _api_request("configure", REQUESTING_CONFIG); + _api_request("", REQUESTING_CHECK); +} + +void EditorAssetLibrary::_licenses_id_pressed(int p_id) { + licenses->get_popup()->set_item_checked(p_id, !licenses->get_popup()->is_item_checked(p_id)); +} + +void EditorAssetLibrary::_licenses_popup_hide() { + bool research = false; + PopupMenu *pm = licenses->get_popup(); + for (unsigned int i = 0; i < licenses_toggled.size(); i++) { + bool toggled = pm->is_item_checked(i); + if (toggled != licenses_toggled[i]) { + licenses_toggled[i] = toggled; + research = true; + } } -} -void EditorAssetLibrary::_support_toggled(int p_support) { - support->get_popup()->set_item_checked(p_support, !support->get_popup()->is_item_checked(p_support)); - _search(); -} - -void EditorAssetLibrary::_rerun_search(int p_ignore) { - _search(); + if (research) { + _search(); + } } void EditorAssetLibrary::_search(int p_page) { - String args; + ERR_FAIL_COND(p_page <= 0); + + String search = filter->get_text().to_lower(); + String args = "?query=" + search.uri_encode(); if (templates_only) { - args += "?type=project&"; - } else { - args += "?"; + args += "%23template"; + } else if (categories->get_selected() > 0) { + args = args.replace("%23template", ""); // Bad user, no templates in projects! + args += "%23" + (String)categories->get_item_metadata(categories->get_selected()); } - args += String() + "sort=" + sort_key[sort->get_selected()]; - // We use the "branch" version, i.e. major.minor, as patch releases should be compatible - args += "&godot_version=" + String(GODOT_VERSION_BRANCH); + args += "&require_release=true"; - String support_list; - for (int i = 0; i < SUPPORT_MAX; i++) { - if (support->get_popup()->is_item_checked(i)) { - support_list += String(support_key[i]) + "+"; + Dictionary version = Engine::get_singleton()->get_version_info(); + args += "&compatibility=" + (String)version["major"] + (String)version["minor"]; + if ((int)version["patch"] > 0) { + args += (String)version["patch"]; + } + + args += String() + "&sort=" + sort_key[sort->get_selected()]; + + int license_count = licenses->get_item_count(); + if (license_count > 0) { + PopupMenu *popup = licenses->get_popup(); + for (int i = 0; i < license_count; i++) { + if (popup->is_item_checked(i)) { + args += "&licenses=" + (String)popup->get_item_metadata(i); + } } } - if (!support_list.is_empty()) { - args += "&support=" + support_list.substr(0, support_list.length() - 1); - } - if (categories->get_selected() > 0) { - args += "&category=" + itos(categories->get_item_metadata(categories->get_selected())); - } - - // Sorting options with an odd index are always the reverse of the previous one - if (sort->get_selected() % 2 == 1) { - args += "&reverse=true"; - } - - if (!filter->get_text().is_empty()) { - args += "&filter=" + filter->get_text().uri_encode(); - } - - if (p_page > 0) { + current_page = p_page; + if (p_page > 1) { args += "&page=" + itos(p_page); } - _api_request("asset", REQUESTING_SEARCH, args); -} - -void EditorAssetLibrary::_search_text_changed(const String &p_text) { - filter_debounce_timer->start(); -} - -void EditorAssetLibrary::_filter_debounce_timer_timeout() { - _search(); + _api_request("search/query/" + args, REQUESTING_SEARCH); } void EditorAssetLibrary::_request_current_config() { @@ -1189,10 +1469,10 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int return hbc; } - //do the mario + // 🎜 Do the Mario! Eat your arms, and then again... 🎜 int from = p_page - (5 / EDSCALE); - if (from < 0) { - from = 0; + if (from < 1) { + from = 1; } int to = from + (10 / EDSCALE); if (to > p_page_count) { @@ -1205,8 +1485,8 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *first = memnew(Button); first->set_text(TTR("First", "Pagination")); first->set_theme_type_variation("PanelBackgroundButton"); - if (p_page != 0) { - first->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibrary::_search).bind(0)); + if (p_page != 1) { + first->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibrary::_search).bind(1)); } else { first->set_disabled(true); first->set_focus_mode(Control::FOCUS_ACCESSIBILITY); @@ -1216,7 +1496,7 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *prev = memnew(Button); prev->set_text(TTR("Previous", "Pagination")); prev->set_theme_type_variation("PanelBackgroundButton"); - if (p_page > 0) { + if (p_page > 1) { prev->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibrary::_search).bind(p_page - 1)); } else { prev->set_disabled(true); @@ -1225,10 +1505,10 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_child(prev); hbc->add_child(memnew(VSeparator)); - for (int i = from; i < to; i++) { + for (int i = from; i <= to; i++) { Button *current = memnew(Button); // Add padding to make page number buttons easier to click. - current->set_text(vformat(" %d ", i + 1)); + current->set_text(vformat(" %d ", i)); current->set_theme_type_variation("PanelBackgroundButton"); if (i == p_page) { current->set_disabled(true); @@ -1242,7 +1522,7 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *next = memnew(Button); next->set_text(TTR("Next", "Pagination")); next->set_theme_type_variation("PanelBackgroundButton"); - if (p_page < p_page_count - 1) { + if (p_page < p_page_count) { next->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibrary::_search).bind(p_page + 1)); } else { next->set_disabled(true); @@ -1254,8 +1534,8 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *last = memnew(Button); last->set_text(TTR("Last", "Pagination")); last->set_theme_type_variation("PanelBackgroundButton"); - if (p_page != p_page_count - 1) { - last->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibrary::_search).bind(p_page_count - 1)); + if (p_page != p_page_count) { + last->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetLibrary::_search).bind(p_page_count)); } else { last->set_disabled(true); last->set_focus_mode(Control::FOCUS_ACCESSIBILITY); @@ -1267,22 +1547,36 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int return hbc; } -void EditorAssetLibrary::_api_request(const String &p_request, RequestType p_request_type, const String &p_arguments) { - if (requesting != REQUESTING_NONE) { - request->cancel_request(); +void EditorAssetLibrary::_api_request(const String &p_request, RequestType p_request_type, bool p_is_parallel) { + if (!p_is_parallel) { + if ((RequestType)request->get_meta("requesting") != REQUESTING_NONE) { + request->cancel_request(); + } + error_hb->hide(); } - error_hb->hide(); if (loading_blocked) { - _set_library_message_with_action(TTRC("The Asset Library requires an online connection and involves sending data over the internet."), TTRC("Go Online"), callable_mp(this, &EditorAssetLibrary::_force_online_mode)); + _set_library_message_with_action(TTRC("The Asset Store requires an online connection and involves sending data over the internet."), TTRC("Go Online"), callable_mp(this, &EditorAssetLibrary::_force_online_mode)); return; } - requesting = p_request_type; - request->request(host + "/" + p_request + p_arguments); + HTTPRequest *requester = nullptr; + if (p_is_parallel) { + requester = memnew(HTTPRequest); + add_child(requester); + setup_http_request(requester); + requester->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_http_request_completed).bind(requester)); + } else { + requester = request; + // Make it clear that it's busy. + library_scroll->set_modulate(Color(1, 1, 1, 0.5)); + } + + requester->set_meta("requesting", p_request_type); + requester->request(host + "/" + p_request); } -void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data) { +void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data, HTTPRequest *p_requester) { String str = String::utf8((const char *)p_data.ptr(), (int)p_data.size()); bool error_abort = true; @@ -1318,51 +1612,92 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const } break; } + RequestType requested = p_requester->get_meta("requesting"); + if (p_requester != request) { + // This was done as a parallel request, so free the node. + p_requester->queue_free(); + } else { + // Not busy anymore. + library_scroll->set_modulate(Color(1, 1, 1)); + } + if (error_abort) { - if (requesting == REQUESTING_CONFIG) { - _set_library_message_with_action(TTRC("Failed to get repository configuration."), TTRC("Retry"), callable_mp(this, &EditorAssetLibrary::_request_current_config)); + if (requested == REQUESTING_CHECK) { + _set_library_message_with_action(TTRC("Failed to verify repository."), TTRC("Retry"), callable_mp(this, &EditorAssetLibrary::_request_current_config)); } error_hb->show(); return; } - Dictionary d; + Variant dt; { JSON json; json.parse(str); - d = json.get_data(); + dt = json.get_data(); } - RequestType requested = requesting; - requesting = REQUESTING_NONE; - switch (requested) { - case REQUESTING_CONFIG: { - categories->clear(); - categories->add_item(TTRC("All")); - categories->set_item_metadata(0, 0); - if (d.has("categories")) { - Array clist = d["categories"]; - for (int i = 0; i < clist.size(); i++) { - Dictionary cat = clist[i]; - if (!cat.has("name") || !cat.has("id")) { - continue; - } - String name = cat["name"]; - int id = cat["id"]; - categories->add_item(name); - categories->set_item_metadata(-1, id); - category_map[cat["id"]] = name; - } + case REQUESTING_CHECK: { + if (!templates_only) { + _api_request("tags/?featured_only=true", REQUESTING_TAGS, true); } + _api_request("licenses/", REQUESTING_LICENSES, true); filter->set_editable(true); sort->set_disabled(false); - categories->set_disabled(false); - support->set_disabled(false); _search(); } break; + + case REQUESTING_TAGS: { + categories->clear(); + + if (templates_only) { + categories->add_item(TTRC("Template")); + } else { + categories->add_item(TTRC("All")); + categories->set_disabled(false); + } + + Array arr = dt; + for (int i = arr.size() - 1; i >= 0; i--) { + Dictionary d = arr[i]; + if (!d.has("display_name") || !d.has("slug")) { + continue; + } + + String name = d["display_name"]; + String slug = d["slug"]; + + // No temolates inside projects. + if (slug == "template") { + continue; + } + + categories->add_item(name); + categories->set_item_metadata(-1, slug); + } + } break; + + case REQUESTING_LICENSES: { + licenses_toggled.clear(); + + Array arr = dt; + PopupMenu *popup = licenses->get_popup(); + for (int i = 0; i < arr.size(); i++) { + Dictionary d = arr[i]; + if (d.has("type")) { + popup->add_check_item(d["type"]); + popup->set_item_checked(-1, true); + popup->set_item_metadata(-1, String(d["type"]).uri_encode()); + + licenses_toggled.push_back(true); + } + } + + licenses->set_disabled(false); + } break; + case REQUESTING_SEARCH: { initial_loading = false; @@ -1378,29 +1713,17 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const memdelete(asset_bottom_page); } - int page = 0; - int pages = 1; - int page_len = 10; - int total_items = 1; - Array result; + Dictionary d = dt; + ERR_FAIL_COND(!d.has("count")); + ERR_FAIL_COND(!d.has("hits")); - if (d.has("page")) { - page = d["page"]; - } - if (d.has("pages")) { - pages = d["pages"]; - } - if (d.has("page_length")) { - page_len = d["page_length"]; - } - if (d.has("total")) { - total_items = d["total"]; - } - if (d.has("result")) { - result = d["result"]; - } + int page_len = 24; // API's default batch size. + int total_items = d["count"]; + int pages = total_items / page_len; + current_page = MIN(current_page, pages); + Array result = d["hits"]; - asset_top_page = _make_pages(page, pages, page_len, total_items, result.size()); + asset_top_page = _make_pages(current_page, pages, page_len, total_items, result.size()); library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); @@ -1410,58 +1733,53 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_vb->add_child(asset_items); - asset_bottom_page = _make_pages(page, pages, page_len, total_items, result.size()); + asset_bottom_page = _make_pages(current_page, pages, page_len, total_items, result.size()); library_vb->add_child(asset_bottom_page); if (result.is_empty()) { - String support_list; - for (int i = 0; i < SUPPORT_MAX; i++) { - if (support->get_popup()->is_item_checked(i)) { - if (!support_list.is_empty()) { - support_list += ", "; - } - support_list += TTRGET(support_text[i]); - } - } - if (support_list.is_empty()) { - support_list = "-"; - } - if (!filter->get_text().is_empty()) { _set_library_message( - vformat(TTR("No results for \"%s\" for support level(s): %s."), filter->get_text(), support_list)); + vformat(TTR("No results for \"%s\"."), filter->get_text())); } else { // No results, even though the user didn't search for anything specific. // This is typically because the version number changed recently // and no assets compatible with the new version have been published yet. _set_library_message( - 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(GODOT_VERSION_SHORT_NAME).capitalize(), String(GODOT_VERSION_BRANCH), support_list)); + vformat(TTR("No results compatible with %s %s."), String(GODOT_VERSION_SHORT_NAME).capitalize(), String(GODOT_VERSION_BRANCH))); } } else { library_message_box->hide(); } for (int i = 0; i < result.size(); i++) { - Dictionary r = result[i]; + ERR_CONTINUE(!Dictionary(result[i]).has("asset")); + Dictionary r = result[i].get("asset"); - ERR_CONTINUE(!r.has("title")); - ERR_CONTINUE(!r.has("asset_id")); - ERR_CONTINUE(!r.has("author")); - ERR_CONTINUE(!r.has("author_id")); - ERR_CONTINUE(!r.has("category_id")); - ERR_FAIL_COND(!category_map.has(r["category_id"])); - ERR_CONTINUE(!r.has("cost")); + ERR_CONTINUE(!r.has("name")); + ERR_CONTINUE(!r.has("slug")); + ERR_CONTINUE(!r.has("store_url")); + ERR_CONTINUE(!r.has("license_type")); + ERR_CONTINUE(!r.has("license_url")); + ERR_CONTINUE(!r.has("reviews_score")); + + ERR_CONTINUE(!r.has("publisher")); + Dictionary p = r["publisher"]; + ERR_CONTINUE(!p.has("name")); + + String author_id; + // Don't allow to open profile links for alternative repositories. + if (repository->get_selected() == 0) { + ERR_CONTINUE(!p.has("slug")); + author_id = p["slug"]; + } EditorAssetLibraryItem *item = memnew(EditorAssetLibraryItem(true)); asset_items->add_child(item); - asset_items->connect(SceneStringName(sort_children), callable_mp(item, &EditorAssetLibraryItem::calculate_misc_links_ratio)); - item->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["cost"]); + item->configure(r["name"], r["slug"], p["name"], author_id, r["license_type"], r["license_url"], r["reviews_score"]); item->connect("asset_selected", callable_mp(this, &EditorAssetLibrary::_select_asset)); - item->connect("author_selected", callable_mp(this, &EditorAssetLibrary::_select_author)); - item->connect("category_selected", callable_mp(this, &EditorAssetLibrary::_select_category)); - if (r.has("icon_url") && !r["icon_url"].operator String().is_empty()) { - _request_image(item->get_instance_id(), r["asset_id"], r["icon_url"], IMAGE_QUEUE_ICON, 0); + if (r.has("thumbnail") && !r["thumbnail"].operator String().is_empty()) { + _request_image(item->get_instance_id(), r["slug"], r["thumbnail"], IMAGE_QUEUE_THUMBNAIL, 0); } } @@ -1469,22 +1787,36 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_scroll->set_v_scroll(0); } } break; - case REQUESTING_ASSET: { - Dictionary r = d; - ERR_FAIL_COND(!r.has("title")); - ERR_FAIL_COND(!r.has("asset_id")); - ERR_FAIL_COND(!r.has("author")); - ERR_FAIL_COND(!r.has("author_id")); - ERR_FAIL_COND(!r.has("version")); - ERR_FAIL_COND(!r.has("version_string")); - ERR_FAIL_COND(!r.has("category_id")); - ERR_FAIL_COND(!category_map.has(r["category_id"])); - ERR_FAIL_COND(!r.has("cost")); - ERR_FAIL_COND(!r.has("description")); - ERR_FAIL_COND(!r.has("download_url")); - ERR_FAIL_COND(!r.has("download_hash")); - ERR_FAIL_COND(!r.has("browse_url")); + case REQUESTING_ASSET: { + Dictionary d = dt; + + ERR_FAIL_COND(!d.has("name")); + ERR_FAIL_COND(!d.has("slug")); + ERR_FAIL_COND(!d.has("store_url")); + ERR_FAIL_COND(!d.has("license_type")); + ERR_FAIL_COND(!d.has("license_url")); + ERR_FAIL_COND(!d.has("reviews_score")); + ERR_FAIL_COND(!d.has("body_bbcode")); + ERR_FAIL_COND(!d.has("source")); + ERR_FAIL_COND(!d.has("store_url")); + + ERR_FAIL_COND(!d.has("publisher")); + Dictionary p = d["publisher"]; + ERR_FAIL_COND(!p.has("name")); + ERR_FAIL_COND(!p.has("slug")); + + HashMap tags; + if (d.has("tags")) { + Array t = d["tags"]; + for (const Variant &V : t) { + const Dictionary tag = V; + ERR_FAIL_COND(!tag.has("display_name")); + ERR_FAIL_COND(!tag.has("slug")); + + tags[tag["display_name"]] = tag["slug"]; + } + } if (description) { memdelete(description); @@ -1492,57 +1824,77 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const description = memnew(EditorAssetLibraryItemDescription); add_child(description); - description->connect(SceneStringName(confirmed), callable_mp(this, &EditorAssetLibrary::_install_asset)); + description->connect(SNAME("install_requested"), callable_mp(this, &EditorAssetLibrary::_install_asset)); + description->connect(SNAME("tag_clicked"), callable_mp(this, &EditorAssetLibrary::_tag_clicked)); - 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"]); + description->configure(d["name"], d["slug"], p["name"], p["slug"], d["license_type"], d["license_url"], d["reviews_score"], d["body_bbcode"], tags, d["store_url"], d["source"]); - EditorAssetLibraryItemDownload *download_item = _get_asset_in_progress(description->get_asset_id()); + EditorAssetLibraryItemDownload *download_item = _get_asset_in_progress(d["slug"]); if (download_item) { if (download_item->can_install()) { - description->set_ok_button_text(TTRC("Install")); - description->get_ok_button()->set_disabled(false); + description->set_install_mode(EditorAssetLibraryItemDescription::MODE_INSTALL); } else { - description->set_ok_button_text(TTRC("Downloading...")); - description->get_ok_button()->set_disabled(true); + description->set_install_mode(EditorAssetLibraryItemDescription::MODE_DOWNLOADING); } - } else { - description->set_ok_button_text(TTRC("Download")); - description->get_ok_button()->set_disabled(false); } - if (r.has("icon_url") && !r["icon_url"].operator String().is_empty()) { - _request_image(description->get_instance_id(), r["asset_id"], r["icon_url"], IMAGE_QUEUE_ICON, 0); + if (d.has("thumbnail") && !d["thumbnail"].operator String().is_empty()) { + _request_image(description->get_instance_id(), d["slug"], d["thumbnail"], IMAGE_QUEUE_THUMBNAIL, 0); } - if (d.has("previews")) { - Array previews = d["previews"]; + int preview_index = 0; + if (d.has("video_playback_url")) { + String video = d["video_playback_url"]; + String thumb = d["video_thumbnail_url"]; + if (!video.is_empty() && !thumb.is_empty()) { + description->add_preview(0, true, video, thumb); + _request_image(description->get_instance_id(), d["slug"], thumb, IMAGE_QUEUE_VIDEO_THUMBNAIL, preview_index); + preview_index = 1; + } + } + if (d.has("media")) { + Array previews = d["media"]; for (int i = 0; i < previews.size(); i++) { - Dictionary p = previews[i]; - - ERR_CONTINUE(!p.has("type")); - ERR_CONTINUE(!p.has("link")); - - bool is_video = p.has("type") && String(p["type"]) == "video"; - String video_url; - if (is_video && p.has("link")) { - video_url = p["link"]; + description->add_preview(preview_index); + if (i == 0) { + description->preview_click(preview_index); } - description->add_preview(i, is_video, video_url); - - if (p.has("thumbnail")) { - _request_image(description->get_instance_id(), r["asset_id"], p["thumbnail"], IMAGE_QUEUE_THUMBNAIL, i); - } - - if (!is_video) { - _request_image(description->get_instance_id(), r["asset_id"], p["link"], IMAGE_QUEUE_SCREENSHOT, i); - } + _request_image(description->get_instance_id(), d["slug"], previews[i], IMAGE_QUEUE_SCREENSHOT, preview_index); + preview_index++; } } description->popup_centered(); + + _api_request("releases/" + String(p["slug"]) + "/" + String(d["slug"]) + "/", REQUESTING_RELEASES); } break; + + case REQUESTING_RELEASES: { + if (!description) { + return; + } + + Array arr = dt; + // Iterate backwards, so the newer releases are added first. + for (int i = arr.size() - 1; i >= 0; i--) { + Dictionary d = arr[i]; + + ERR_FAIL_COND(!d.has("download_url")); + ERR_FAIL_COND(!d.has("version")); + ERR_FAIL_COND(!d.has("stable")); + ERR_FAIL_COND(!d.has("changes_bbcode")); + + String version = d["version"]; + if (!d["stable"]) { + version += "(" + TTR("Unstable") + ")"; + } + + description->add_release(d["download_url"], version, d["changes_bbcode"], ""); + } + } break; + default: break; } @@ -1569,7 +1921,7 @@ void EditorAssetLibrary::_manage_plugins() { ProjectSettingsEditor::get_singleton()->set_plugins_page(); } -EditorAssetLibraryItemDownload *EditorAssetLibrary::_get_asset_in_progress(int p_asset_id) const { +EditorAssetLibraryItemDownload *EditorAssetLibrary::_get_asset_in_progress(const String &p_asset_id) const { for (int i = 0; i < downloads_hb->get_child_count(); i++) { EditorAssetLibraryItemDownload *d = Object::cast_to(downloads_hb->get_child(i)); if (d && d->get_asset_id() == p_asset_id) { @@ -1580,7 +1932,7 @@ EditorAssetLibraryItemDownload *EditorAssetLibrary::_get_asset_in_progress(int p return nullptr; } -void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_title) { +void EditorAssetLibrary::_install_external_asset(const String &p_zip_path, const String &p_title) { emit_signal(SNAME("install_asset"), p_zip_path, p_title); } @@ -1593,6 +1945,13 @@ void EditorAssetLibrary::_update_asset_items_columns() { } } +void EditorAssetLibrary::_update_downloads_section() { + const bool has_downloads = downloads_hb->get_child_count() > 0; + downloads_scroll->set_visible(has_downloads); + library_mc->set_theme_type_variation(has_downloads ? "NoBorderHorizontal" : (Engine::get_singleton()->is_project_manager_hint() ? "NoBorderAssetLibProjectManager" : "NoBorderAssetLib")); + library_scroll->set_scroll_hint_mode(has_downloads ? ScrollContainer::SCROLL_HINT_MODE_ALL : ScrollContainer::SCROLL_HINT_MODE_TOP_AND_LEFT); +} + void EditorAssetLibrary::_set_library_message(const String &p_message) { library_message->set_text(p_message); @@ -1626,16 +1985,11 @@ void EditorAssetLibrary::_force_online_mode() { EditorSettings::get_singleton()->save(); } -void EditorAssetLibrary::disable_community_support() { - support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, false); -} - void EditorAssetLibrary::_bind_methods() { ADD_SIGNAL(MethodInfo("install_asset", PropertyInfo(Variant::STRING, "zip_path"), PropertyInfo(Variant::STRING, "name"))); } EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { - requesting = REQUESTING_NONE; templates_only = p_templates_only; loading_blocked = ((int)EDITOR_GET("network/connection/network_mode") == EditorSettings::NETWORK_OFFLINE); @@ -1647,25 +2001,25 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_main->add_child(search_hb); library_main->add_theme_constant_override("separation", 10 * EDSCALE); - filter = memnew(LineEdit); - if (templates_only) { - filter->set_placeholder(TTRC("Search Templates, Projects, and Demos")); - } else { - filter->set_placeholder(TTRC("Search Assets (Excluding Templates, Projects, and Demos)")); - } - filter->set_clear_button_enabled(true); - search_hb->add_child(filter); - filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); - filter->connect(SceneStringName(text_changed), callable_mp(this, &EditorAssetLibrary::_search_text_changed)); - // Perform a search automatically if the user hasn't entered any text for a certain duration. // This way, the user doesn't need to press Enter to initiate their search. filter_debounce_timer = memnew(Timer); filter_debounce_timer->set_one_shot(true); filter_debounce_timer->set_wait_time(0.25); - filter_debounce_timer->connect("timeout", callable_mp(this, &EditorAssetLibrary::_filter_debounce_timer_timeout)); + filter_debounce_timer->connect("timeout", callable_mp(this, &EditorAssetLibrary::_search).bind(1)); search_hb->add_child(filter_debounce_timer); + filter = memnew(LineEdit); + if (templates_only) { + filter->set_placeholder(TTRC("Search templates. Use tags by including \"#tagname\".")); + } else { + filter->set_placeholder(TTRC("Search assets (excluding templates). Use tags by including \"#tagname\".")); + } + filter->set_clear_button_enabled(true); + search_hb->add_child(filter); + filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); + filter->connect(SceneStringName(text_changed), callable_mp(filter_debounce_timer, &Timer::start).bind(-1).unbind(1)); + if (!p_templates_only) { search_hb->add_child(memnew(VSeparator)); } @@ -1698,43 +2052,39 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { sort->set_h_size_flags(Control::SIZE_EXPAND_FILL); sort->set_clip_text(true); - sort->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibrary::_rerun_search)); - - search_hb2->add_child(memnew(VSeparator)); + sort->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibrary::_search).bind(1).unbind(1)); search_hb2->add_child(memnew(Label(TTRC("Category:")))); categories = memnew(OptionButton); - categories->add_item(TTRC("All")); - search_hb2->add_child(categories); - categories->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (p_templates_only) { + categories->add_item(TTRC("Template")); + } else { + categories->add_item(TTRC("All")); + } + categories->set_disabled(true); categories->set_clip_text(true); - categories->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibrary::_rerun_search)); + categories->set_h_size_flags(Control::SIZE_EXPAND_FILL); + search_hb2->add_child(categories); + categories->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibrary::_search).bind(1).unbind(1)); - search_hb2->add_child(memnew(VSeparator)); - - search_hb2->add_child(memnew(Label(TTRC("Site:")))); + search_hb2->add_child(memnew(Label(TTRC("Source:")))); repository = memnew(OptionButton); - - _update_repository_options(); - - repository->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibrary::_repository_changed)); - - search_hb2->add_child(repository); repository->set_h_size_flags(Control::SIZE_EXPAND_FILL); repository->set_clip_text(true); + search_hb2->add_child(repository); + repository->connect(SceneStringName(item_selected), callable_mp(this, &EditorAssetLibrary::_repository_changed)); + _update_repository_options(); search_hb2->add_child(memnew(VSeparator)); - support = memnew(MenuButton); - search_hb2->add_child(support); - support->set_text(TTRC("Support")); - support->get_popup()->set_hide_on_checkable_item_selection(false); - support->get_popup()->add_check_item(support_text[SUPPORT_FEATURED], SUPPORT_FEATURED); - support->get_popup()->add_check_item(support_text[SUPPORT_COMMUNITY], SUPPORT_COMMUNITY); - support->get_popup()->add_check_item(support_text[SUPPORT_TESTING], SUPPORT_TESTING); - support->get_popup()->set_item_checked(SUPPORT_FEATURED, true); - support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, true); - support->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &EditorAssetLibrary::_support_toggled)); + licenses = memnew(MenuButton); + licenses->set_text(TTRC("Licenses")); + licenses->set_disabled(true); + licenses->set_flat(false); + licenses->get_popup()->set_hide_on_checkable_item_selection(false); + search_hb2->add_child(licenses); + licenses->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &EditorAssetLibrary::_licenses_id_pressed)); + licenses->get_popup()->connect("popup_hide", callable_mp(this, &EditorAssetLibrary::_licenses_popup_hide)); ///////// @@ -1780,7 +2130,6 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - _update_asset_items_columns(); asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); @@ -1790,9 +2139,10 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_vb->add_child(asset_bottom_page); request = memnew(HTTPRequest); + request->set_meta("requesting", REQUESTING_NONE); add_child(request); setup_http_request(request); - request->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_http_request_completed)); + request->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_http_request_completed).bind(request)); last_queue_id = 0; @@ -1807,17 +2157,16 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { error_tr->set_v_size_flags(Control::SIZE_SHRINK_CENTER); error_hb->add_child(error_tr); - description = nullptr; - - set_process(true); set_process_shortcut_input(true); // Global shortcuts since there is no main element to be focused. downloads_scroll = memnew(ScrollContainer); downloads_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); downloads_scroll->set_theme_type_variation("ScrollContainerSecondary"); + downloads_scroll->hide(); library_main->add_child(downloads_scroll); downloads_hb = memnew(HBoxContainer); downloads_scroll->add_child(downloads_hb); + downloads_hb->connect("child_entered_tree", callable_mp(this, &EditorAssetLibrary::_update_downloads_section).unbind(1)); asset_open = memnew(EditorFileDialog); @@ -1826,15 +2175,13 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { asset_open->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); add_child(asset_open); asset_open->connect("file_selected", callable_mp(this, &EditorAssetLibrary::_asset_file_selected)); - - asset_installer = nullptr; } /////// bool AssetLibraryEditorPlugin::is_available() { #ifdef WEB_ENABLED - // Asset Library can't work on Web editor for now as most assets are sourced + // Asset Store can't work on Web editor for now as most assets are sourced // directly from GitHub which does not set CORS. return false; #else @@ -1842,6 +2189,10 @@ bool AssetLibraryEditorPlugin::is_available() { #endif } +const Ref AssetLibraryEditorPlugin::get_plugin_icon() const { + return EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("AssetStore"), EditorStringName(EditorIcons)); +} + void AssetLibraryEditorPlugin::make_visible(bool p_visible) { if (p_visible) { addon_library->show(); diff --git a/editor/asset_library/asset_library_editor_plugin.h b/editor/asset_library/asset_library_editor_plugin.h index 00e6f1c159..e4986693a6 100644 --- a/editor/asset_library/asset_library_editor_plugin.h +++ b/editor/asset_library/asset_library_editor_plugin.h @@ -44,35 +44,44 @@ #include "scene/gui/scroll_container.h" #include "scene/gui/texture_button.h" #include "scene/gui/texture_rect.h" +#include "scene/main/canvas_layer.h" #include "scene/main/http_request.h" class EditorFileDialog; class HSeparator; +class ImageTexture; class MenuButton; +class TabContainer; +class VSeparator; -class EditorAssetLibraryItem : public PanelContainer { - GDCLASS(EditorAssetLibraryItem, PanelContainer); +class EditorAssetLibraryItem : public MarginContainer { + GDCLASS(EditorAssetLibraryItem, MarginContainer); - TextureButton *icon = nullptr; - LinkButton *title = nullptr; - LinkButton *category = nullptr; + MarginContainer *margin = nullptr; + Button *button = nullptr; + TextureRect *icon = nullptr; + Label *title = nullptr; LinkButton *author = nullptr; - Label *price = nullptr; + LinkButton *license = nullptr; HSeparator *separator = nullptr; - Control *spacer = nullptr; - HBoxContainer *author_price_hbox = nullptr; + HBoxContainer *author_license_hbox = nullptr; + TextureRect *rating_icon = nullptr; + Label *rating_count = nullptr; String title_text; - int asset_id = 0; - int category_id = 0; - int author_id = 0; + String asset_id; + String author_id; + String license_url; + + bool is_hovering = false; + bool is_clickable = false; int author_width = 0; int price_width = 0; void _asset_clicked(); - void _category_clicked(); void _author_clicked(); + void _license_clicked(); void _calculate_misc_links_size(); @@ -83,58 +92,111 @@ protected: static void _bind_methods(); public: - void 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); - - void calculate_misc_links_ratio(); + void configure(const String &p_title, const String &p_asset_id, const String &p_author, const String &p_author_id, const String &p_license_type, const String &p_license_url, int p_rating); EditorAssetLibraryItem(bool p_clickable = false); }; +class EditorAssetLibraryZoomMode : public CanvasLayer { + GDCLASS(EditorAssetLibraryZoomMode, CanvasLayer); + + Control *previews = nullptr; + + virtual void input(const Ref &p_event) override; + +public: + Control *remove_previews(); + + EditorAssetLibraryZoomMode(Control *p_previews); +}; + class EditorAssetLibraryItemDescription : public ConfirmationDialog { GDCLASS(EditorAssetLibraryItemDescription, ConfirmationDialog); EditorAssetLibraryItem *item = nullptr; + HBoxContainer *root = nullptr; + TabContainer *tabs = nullptr; RichTextLabel *description = nullptr; + RichTextLabel *changelog = nullptr; + Label *version_label = nullptr; + Label *version = nullptr; + OptionButton *version_list = nullptr; + Button *store = nullptr; + Button *source = nullptr; + VBoxContainer *desc_vbox = nullptr; + VBoxContainer *previews_vbox = nullptr; + Button *previous_preview = nullptr; + Button *next_preview = nullptr; ScrollContainer *previews = nullptr; HBoxContainer *preview_hb = nullptr; PanelContainer *previews_bg = nullptr; + Button *zoom_button = nullptr; + EditorAssetLibraryZoomMode *zoom_mode = nullptr; + struct Preview { int id = 0; bool is_video = false; String video_link; + String thumbnail; Button *button = nullptr; Ref image; }; Vector preview_images; + Ref preview_group; TextureRect *preview = nullptr; void set_image(int p_type, int p_index, const Ref &p_image); - int asset_id = 0; - String download_url; + struct Release { + String url; + String version; + String sha256; + }; + + String asset_id; + Vector releases; + String store_url; + String source_url; String title; - String sha256; Ref icon; +public: + enum InstallMode { + MODE_DOWNLOAD, + MODE_DOWNLOADING, + MODE_INSTALL, + }; + +private: + InstallMode install_mode = MODE_DOWNLOAD; + + void _confirmed(); + void _version_selected(int p_index); + void _store_pressed(); + void _source_pressed(); void _link_click(const String &p_url); - void _preview_click(int p_id); + + void _previous_preview_pressed(); + void _next_preview_pressed(); + + void _zoom_toggled(bool p_pressed); protected: void _notification(int p_what); static void _bind_methods(); public: - void 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); - void add_preview(int p_id, bool p_video, const String &p_url); + void configure(const String &p_title, const String &p_asset_id, const String &p_author, const String &p_author_id, const String &p_license_type, const String &p_license_url, int p_rating, const String &p_description, const HashMap &p_tags, const String &p_store_url, const String &p_source_url); + void set_install_mode(InstallMode p_mode); + void add_release(const String &p_url, const String &p_version, const String &p_changes, const String &p_sha256); + void add_preview(int p_id, bool p_video = false, const String &p_url = "", const String &p_thumbnail = ""); + void preview_click(int p_id); String get_title() { return title; } Ref get_preview_icon() { return icon; } - String get_download_url() { return download_url; } - int get_asset_id() { return asset_id; } - String get_sha256() { return sha256; } EditorAssetLibraryItemDescription(); }; @@ -144,10 +206,13 @@ class EditorAssetLibraryItemDownload : public MarginContainer { PanelContainer *panel = nullptr; TextureRect *icon = nullptr; Label *title = nullptr; + Label *version = nullptr; ProgressBar *progress = nullptr; Button *install_button = nullptr; Button *retry_button = nullptr; TextureButton *dismiss_button = nullptr; + HBoxContainer *progress_hbox = nullptr; + Control *spacer = nullptr; AcceptDialog *download_error = nullptr; HTTPRequest *download = nullptr; @@ -157,7 +222,7 @@ class EditorAssetLibraryItemDownload : public MarginContainer { int prev_status; - int asset_id = 0; + String asset_id; bool external_install; @@ -173,8 +238,8 @@ protected: public: void set_external_install(bool p_enable) { external_install = p_enable; } - int get_asset_id() { return asset_id; } - void configure(const String &p_title, int p_asset_id, const Ref &p_preview, const String &p_download_url, const String &p_sha256_hash); + String get_asset_id() { return asset_id; } + void configure(const String &p_title, const String &p_asset_id, const String &p_version, const Ref &p_preview, const String &p_download_url, const String &p_sha256); bool can_install() const; void install(); @@ -207,13 +272,13 @@ class EditorAssetLibrary : public PanelContainer { LineEdit *filter = nullptr; Timer *filter_debounce_timer = nullptr; + OptionButton *sort = nullptr; OptionButton *categories = nullptr; OptionButton *repository = nullptr; - OptionButton *sort = nullptr; + MenuButton *licenses = nullptr; HBoxContainer *error_hb = nullptr; TextureRect *error_tr = nullptr; Label *error_label = nullptr; - MenuButton *support = nullptr; HBoxContainer *contents = nullptr; @@ -229,33 +294,30 @@ class EditorAssetLibrary : public PanelContainer { void _force_online_mode(); - enum Support { - SUPPORT_FEATURED, - SUPPORT_COMMUNITY, - SUPPORT_TESTING, - SUPPORT_MAX - }; + LocalVector licenses_toggled; + + void _licenses_id_pressed(int p_id); + void _licenses_popup_hide(); enum SortOrder { + SORT_RELEVANCE, SORT_UPDATED, SORT_UPDATED_REVERSE, - SORT_NAME, - SORT_NAME_REVERSE, - SORT_COST, - SORT_COST_REVERSE, + SORT_REVIEWS, + SORT_REVIEWS_REVERSE, + SORT_CREATED, + SORT_CREATED_REVERSE, SORT_MAX }; static const char *sort_key[SORT_MAX]; static const char *sort_text[SORT_MAX]; - static const char *support_key[SUPPORT_MAX]; - static const char *support_text[SUPPORT_MAX]; - ///MainListing + constexpr static Size2 THUMBNAIL_SIZE = Size2(114, 64); enum ImageType { - IMAGE_QUEUE_ICON, IMAGE_QUEUE_THUMBNAIL, + IMAGE_QUEUE_VIDEO_THUMBNAIL, IMAGE_QUEUE_SCREENSHOT, }; @@ -263,65 +325,69 @@ class EditorAssetLibrary : public PanelContainer { struct ImageQueue { bool active = false; int queue_id = 0; - ImageType image_type = ImageType::IMAGE_QUEUE_ICON; + ImageType image_type = ImageType::IMAGE_QUEUE_THUMBNAIL; int image_index = 0; String image_url; HTTPRequest *request = nullptr; ObjectID target; int asset_id = -1; + + Thread *thread = nullptr; + bool use_cache = false; + PackedByteArray data; + Ref texture; + bool update_finished = false; }; int last_queue_id; HashMap image_queue; - void _image_update(bool p_use_cache, bool p_final, const PackedByteArray &p_data, int p_queue_id); + static void _image_update(void *p_image_queue); void _image_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data, int p_queue_id); - void _request_image(ObjectID p_for, int p_asset_id, String p_image_url, ImageType p_type, int p_image_index); + void _request_image(ObjectID p_for, int p_asset_id, const String &p_image_url, ImageType p_type, int p_image_index); void _update_image_queue(); + int current_page = 0; + HBoxContainer *_make_pages(int p_page, int p_page_count, int p_page_len, int p_total_items, int p_current_items); - // - EditorAssetLibraryItemDescription *description = nullptr; - // - enum RequestType { REQUESTING_NONE, - REQUESTING_CONFIG, + REQUESTING_CHECK, + REQUESTING_TAGS, + REQUESTING_LICENSES, REQUESTING_SEARCH, REQUESTING_ASSET, + REQUESTING_RELEASES, }; - RequestType requesting; Dictionary category_map; ScrollContainer *downloads_scroll = nullptr; HBoxContainer *downloads_hb = nullptr; - void _install_asset(); + EditorAssetLibraryItemDescription *description = nullptr; + + void _install_asset(const String &p_asset_id, const String &p_version, const String &p_download_url, const String &p_sha256); + void _tag_clicked(const String &p_tag); void _select_author(const String &p_author); - void _select_category(int p_id); - void _select_asset(int p_id); + void _select_asset(const String &p_id); void _manage_plugins(); - void _search(int p_page = 0); - void _rerun_search(int p_ignore); - void _search_text_changed(const String &p_text = ""); - void _search_text_submitted(const String &p_text = ""); - void _api_request(const String &p_request, RequestType p_request_type, const String &p_arguments = ""); - void _http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); - void _filter_debounce_timer_timeout(); + void _search(int p_page = 1); + void _api_request(const String &p_request, RequestType p_request_type, bool p_is_parallel = false); + void _http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data, HTTPRequest *p_requester); void _request_current_config(); - EditorAssetLibraryItemDownload *_get_asset_in_progress(int p_asset_id) const; + EditorAssetLibraryItemDownload *_get_asset_in_progress(const String &p_asset_id) const; void _repository_changed(int p_repository_id); - void _support_toggled(int p_support); - void _install_external_asset(String p_zip_path, String p_title); + void _install_external_asset(const String &p_zip_path, const String &p_title); void _update_asset_items_columns(); + void _update_downloads_section(); friend class EditorAssetLibraryItemDescription; friend class EditorAssetLibraryItem; @@ -332,8 +398,6 @@ protected: virtual void shortcut_input(const Ref &p_event) override; public: - void disable_community_support(); - EditorAssetLibrary(bool p_templates_only = false); }; @@ -345,14 +409,12 @@ class AssetLibraryEditorPlugin : public EditorPlugin { public: static bool is_available(); - virtual String get_plugin_name() const override { return TTRC("AssetLib"); } + virtual String get_plugin_name() const override { return TTRC("Asset Store"); } + virtual const Ref get_plugin_icon() const override; bool has_main_screen() const override { return true; } virtual void edit(Object *p_object) override {} virtual bool handles(Object *p_object) const override { return false; } virtual void make_visible(bool p_visible) override; - //virtual bool get_remove_list(List *p_list) { return canvas_item_editor->get_remove_list(p_list); } - //virtual Dictionary get_state() const; - //virtual void set_state(const Dictionary& p_state); AssetLibraryEditorPlugin(); }; diff --git a/editor/asset_library/editor_asset_installer.cpp b/editor/asset_library/editor_asset_installer.cpp index 77cc2e24ad..02b39753ba 100644 --- a/editor/asset_library/editor_asset_installer.cpp +++ b/editor/asset_library/editor_asset_installer.cpp @@ -706,7 +706,7 @@ EditorAssetInstaller::EditorAssetInstaller() { remapping_tools->add_child(memnew(VSeparator)); skip_toplevel_check = memnew(CheckBox); - skip_toplevel_check->set_text(TTRC("Ignore asset root")); + skip_toplevel_check->set_text(TTRC("Ignore Asset Root")); skip_toplevel_check->set_tooltip_text(TTRC("Ignore the root directory when extracting files.")); skip_toplevel_check->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel)); remapping_tools->add_child(skip_toplevel_check); @@ -716,7 +716,7 @@ EditorAssetInstaller::EditorAssetInstaller() { asset_conflicts_label = memnew(Label); asset_conflicts_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY); asset_conflicts_label->set_theme_type_variation("HeaderSmall"); - asset_conflicts_label->set_text(TTRC("No files conflict with your project")); + asset_conflicts_label->set_text(TTRC("No files conflict with your project.")); remapping_tools->add_child(asset_conflicts_label); asset_conflicts_link = memnew(LinkButton); asset_conflicts_link->set_theme_type_variation("HeaderSmallLink"); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 9666732d44..81217f6f02 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -8953,7 +8953,7 @@ EditorNode::EditorNode() { ED_SHORTCUT_AND_COMMAND("editor/editor_3d", TTRC("Open 3D Workspace"), KeyModifierMask::CTRL | Key::F2); ED_SHORTCUT_AND_COMMAND("editor/editor_script", TTRC("Open Script Editor"), KeyModifierMask::CTRL | Key::F3); ED_SHORTCUT_AND_COMMAND("editor/editor_game", TTRC("Open Game View"), KeyModifierMask::CTRL | Key::F4); - ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTRC("Open Asset Library"), KeyModifierMask::CTRL | Key::F5); + ED_SHORTCUT_AND_COMMAND("editor/editor_assetlib", TTRC("Open Asset Store"), KeyModifierMask::CTRL | Key::F5); ED_SHORTCUT_OVERRIDE("editor/editor_2d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_1); ED_SHORTCUT_OVERRIDE("editor/editor_3d", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::KEY_2); @@ -9358,7 +9358,7 @@ EditorNode::EditorNode() { if (AssetLibraryEditorPlugin::is_available()) { add_editor_plugin(memnew(AssetLibraryEditorPlugin)); } else { - print_verbose("Asset Library not available (due to using Web editor, or SSL support disabled)."); + print_verbose("Asset Store not available (due to using Web editor, or SSL support disabled)."); } // More visually meaningful to have this later. diff --git a/editor/icons/AssetLib.svg b/editor/icons/AssetStore.svg similarity index 100% rename from editor/icons/AssetLib.svg rename to editor/icons/AssetStore.svg diff --git a/editor/icons/AssetThumbLoading.svg b/editor/icons/AssetThumbLoading.svg new file mode 100644 index 0000000000..190bb61e3d --- /dev/null +++ b/editor/icons/AssetThumbLoading.svg @@ -0,0 +1 @@ + diff --git a/editor/icons/PlayOverlay.svg b/editor/icons/PlayOverlay.svg index c593d0a221..693da835f4 100644 --- a/editor/icons/PlayOverlay.svg +++ b/editor/icons/PlayOverlay.svg @@ -1 +1 @@ - + diff --git a/editor/icons/ThumbsUp.svg b/editor/icons/ThumbsUp.svg new file mode 100644 index 0000000000..dceb82a545 --- /dev/null +++ b/editor/icons/ThumbsUp.svg @@ -0,0 +1 @@ + diff --git a/editor/project_manager/project_manager.cpp b/editor/project_manager/project_manager.cpp index 0366fa98f9..38a8ac8ab4 100644 --- a/editor/project_manager/project_manager.cpp +++ b/editor/project_manager/project_manager.cpp @@ -119,7 +119,7 @@ void ProjectManager::_notification(int p_what) { SceneTree::get_singleton()->get_root()->set_title(GODOT_VERSION_NAME + String(" - ") + TTR("Project Manager", "Application")); const String line1 = TTR("You don't have any projects yet."); - const String line2 = TTR("Get started by creating a new one,\nimporting one that exists, or by downloading a project template from the Asset Library!"); + const String line2 = TTR("Get started by creating a new one,\nimporting one that exists, or by downloading a project template from the Asset Store!"); empty_list_message->set_text(vformat("[center][b]%s[/b] %s[/center]", line1, line2)); _titlebar_resized(); @@ -246,7 +246,7 @@ void ProjectManager::_update_theme(bool p_skip_creation) { title_bar_logo->set_button_icon(get_editor_theme_icon("TitleBarLogo")); _set_main_view_icon(MAIN_VIEW_PROJECTS, get_editor_theme_icon("ProjectList")); - _set_main_view_icon(MAIN_VIEW_ASSETLIB, get_editor_theme_icon("AssetLib")); + _set_main_view_icon(MAIN_VIEW_ASSETLIB, get_editor_theme_icon("AssetStore")); // Project list. { @@ -299,10 +299,10 @@ void ProjectManager::_update_theme(bool p_skip_creation) { open_options_popup->set_item_icon(1, get_editor_theme_icon("NodeWarning")); } - // Dialogs + // Dialogs. migration_guide_button->set_button_icon(get_editor_theme_icon("ExternalLink")); - // Asset library popup. + // Asset store popup. if (asset_library && EDITOR_GET("interface/theme/style") == "Classic") { // Removes extra border margins. asset_library->add_theme_style_override(SceneStringName(panel), memnew(StyleBoxEmpty)); @@ -398,7 +398,6 @@ void ProjectManager::_open_asset_library_confirmed() { EditorSettings::get_singleton()->save(); } - asset_library->disable_community_support(); _select_main_view(MAIN_VIEW_ASSETLIB); } @@ -494,10 +493,10 @@ void ProjectManager::_update_list_placeholder() { const int network_mode = EDITOR_GET("network/connection/network_mode"); if (network_mode == EditorSettings::NETWORK_OFFLINE) { - empty_list_open_assetlib->set_text(TTRC("Go Online and Open Asset Library")); + empty_list_open_assetlib->set_text(TTRC("Go Online and Open Asset Store")); empty_list_online_warning->set_visible(true); } else { - empty_list_open_assetlib->set_text(TTRC("Open Asset Library")); + empty_list_open_assetlib->set_text(TTRC("Open Asset Store")); empty_list_online_warning->set_visible(false); } @@ -1634,7 +1633,7 @@ ProjectManager::ProjectManager() { empty_list_import_project->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_import_project)); empty_list_open_assetlib = memnew(Button); - empty_list_open_assetlib->set_text(TTRC("Open Asset Library")); + empty_list_open_assetlib->set_text(TTRC("Open Asset Store")); empty_list_open_assetlib->set_theme_type_variation("PanelBackgroundButton"); empty_list_actions->add_child(empty_list_open_assetlib); empty_list_open_assetlib->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_asset_library_confirmed)); @@ -1645,7 +1644,7 @@ ProjectManager::ProjectManager() { empty_list_online_warning->set_custom_minimum_size(Size2(220, 0) * EDSCALE); empty_list_online_warning->set_autowrap_mode(TextServer::AUTOWRAP_WORD); empty_list_online_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL); - empty_list_online_warning->set_text(TTRC("Note: The Asset Library requires an online connection and involves sending data over the internet.")); + empty_list_online_warning->set_text(TTRC("Note: The Asset Store requires an online connection and involves sending data over the internet.")); empty_list_placeholder->add_child(empty_list_online_warning); } @@ -1731,18 +1730,18 @@ ProjectManager::ProjectManager() { } } - // Asset library view. + // Asset store view. if (AssetLibraryEditorPlugin::is_available()) { asset_library = memnew(EditorAssetLibrary(true)); asset_library->set_name("AssetLibraryTab"); - _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref(), asset_library); + _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Store"), Ref(), asset_library); asset_library->connect("install_asset", callable_mp(this, &ProjectManager::_install_project)); } else { VBoxContainer *asset_library_filler = memnew(VBoxContainer); asset_library_filler->set_name("AssetLibraryTab"); - Button *asset_library_toggle = _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Library"), Ref(), asset_library_filler); + Button *asset_library_toggle = _add_main_view(MAIN_VIEW_ASSETLIB, TTRC("Asset Store"), Ref(), asset_library_filler); asset_library_toggle->set_disabled(true); - asset_library_toggle->set_tooltip_text(TTRC("Asset Library not available (due to using Web editor, or because SSL support disabled).")); + asset_library_toggle->set_tooltip_text(TTRC("Asset Store not available (due to using Web editor, or because SSL support disabled).")); } // Footer bar. diff --git a/editor/settings/editor_feature_profile.cpp b/editor/settings/editor_feature_profile.cpp index d4ab503b9e..f7f2aabe33 100644 --- a/editor/settings/editor_feature_profile.cpp +++ b/editor/settings/editor_feature_profile.cpp @@ -47,7 +47,7 @@ const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = { TTRC("3D Editor"), TTRC("Script Editor"), - TTRC("Asset Library"), + TTRC("Asset Store"), TTRC("Scene Tree Editing"), #ifndef DISABLE_DEPRECATED TTRC("Node Dock (deprecated)"), @@ -63,7 +63,7 @@ const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = { const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = { TTRC("Allows to view and edit 3D scenes."), TTRC("Allows to edit scripts using the integrated script editor."), - TTRC("Provides built-in access to the Asset Library."), + TTRC("Provides built-in access to the Asset Store."), TTRC("Allows editing the node hierarchy in the Scene dock."), #ifndef DISABLE_DEPRECATED TTRC("Allows to work with signals and groups of the node selected in the Scene dock."), diff --git a/editor/settings/editor_settings.cpp b/editor/settings/editor_settings.cpp index a6cc919ada..9182680ec7 100644 --- a/editor/settings/editor_settings.cpp +++ b/editor/settings/editor_settings.cpp @@ -456,7 +456,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_ENUM, "interface/editor/localization/editor_language", "auto", lang_hint, PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING); } - // Asset library + // Asset store _initial_set("asset_library/use_threads", true); /* Interface */ diff --git a/editor/themes/theme_classic.cpp b/editor/themes/theme_classic.cpp index 3c4fda3fed..9b745af1d0 100644 --- a/editor/themes/theme_classic.cpp +++ b/editor/themes/theme_classic.cpp @@ -1235,6 +1235,12 @@ void ThemeClassic::populate_standard_styles(const Ref &p_theme, Edi p_theme->set_constant("shadow_outline_size", "Label", 1 * EDSCALE); p_theme->set_constant("line_spacing", "Label", 3 * EDSCALE); p_theme->set_constant("outline_size", "Label", 0); + + // Label with no vertical margins. + + p_theme->set_type_variation("LabelVMarginless", "Label"); + Ref v_marginless_style = EditorThemeManager::make_empty_stylebox(p_config.base_empty_style->get_margin(SIDE_LEFT), 0, p_config.base_empty_style->get_margin(SIDE_RIGHT), 0); + p_theme->set_stylebox(CoreStringName(normal), "Label", v_marginless_style); } // SpinBox. @@ -1849,6 +1855,8 @@ void ThemeClassic::populate_editor_styles(const Ref &p_theme, Edito // Flat button variations. { + p_theme->set_type_variation(SceneStringName(FlatButton), "Button"); + Ref style_flat_button = EditorThemeManager::make_empty_stylebox(); Ref style_flat_button_hover = p_config.button_style_hover->duplicate(); Ref style_flat_button_pressed = p_config.button_style_pressed->duplicate(); @@ -1874,11 +1882,22 @@ void ThemeClassic::populate_editor_styles(const Ref &p_theme, Edito p_theme->set_stylebox(SceneStringName(pressed), "FlatMenuButton", style_flat_button_pressed); p_theme->set_stylebox("disabled", "FlatMenuButton", style_flat_button); + // Variation for buttons that shouldn't tint their icons. + p_theme->set_type_variation("FlatButtonNoIconTint", "FlatButton"); p_theme->set_color("icon_pressed_color", "FlatButtonNoIconTint", p_config.icon_normal_color); p_theme->set_color("icon_hover_color", "FlatButtonNoIconTint", p_config.mono_color); p_theme->set_color("icon_hover_pressed_color", "FlatButtonNoIconTint", p_config.mono_color); + // Variation for the AssetLib thumbnails. + + p_theme->set_type_variation("ThumbnailButton", SceneStringName(FlatButton)); + p_theme->set_color("icon_pressed_color", "ThumbnailButton", p_config.icon_normal_color); + p_theme->set_color("icon_hover_color", "ThumbnailButton", p_config.icon_normal_color); + p_theme->set_color("icon_hover_pressed_color", "ThumbnailButton", p_config.icon_normal_color); + + // Variation for Editor Log filter buttons. + p_theme->set_type_variation("FlatMenuButtonNoIconTint", "FlatMenuButton"); p_theme->set_color("icon_pressed_color", "FlatMenuButtonNoIconTint", p_config.icon_normal_color); p_theme->set_color("icon_hover_color", "FlatMenuButtonNoIconTint", p_config.mono_color); @@ -2398,11 +2417,11 @@ void ThemeClassic::populate_editor_styles(const Ref &p_theme, Edito p_theme->set_type_variation("EditorHelpBitTooltipContent", "EditorHelpBitContent"); } - // Asset Library. + // Asset Store. p_theme->set_stylebox("bg", "AssetLib", p_config.base_empty_style); p_theme->set_stylebox(SceneStringName(panel), "AssetLib", p_config.content_panel_style); p_theme->set_stylebox("downloads", "AssetLib", p_theme->get_stylebox(SceneStringName(panel), SNAME("Tree"))); - p_theme->set_color("status_color", "AssetLib", Color(0.5, 0.5, 0.5)); // FIXME: Use a defined color instead. + p_theme->set_color("faded_text", "AssetLib", p_config.font_disabled_color); p_theme->set_icon("dismiss", "AssetLib", p_theme->get_icon(SNAME("Close"), EditorStringName(EditorIcons))); // Debugger. diff --git a/editor/themes/theme_modern.cpp b/editor/themes/theme_modern.cpp index b8b315d7da..76f991f28b 100644 --- a/editor/themes/theme_modern.cpp +++ b/editor/themes/theme_modern.cpp @@ -1237,6 +1237,12 @@ void ThemeModern::populate_standard_styles(const Ref &p_theme, Edit p_theme->set_constant("shadow_outline_size", "Label", 1 * EDSCALE); p_theme->set_constant("line_spacing", "Label", 3 * EDSCALE); p_theme->set_constant("outline_size", "Label", 0); + + // Label with no vertical margins. + + p_theme->set_type_variation("LabelVMarginless", "Label"); + Ref v_marginless_style = EditorThemeManager::make_empty_stylebox(label_style->get_margin(SIDE_LEFT), 0, label_style->get_margin(SIDE_RIGHT), 0); + p_theme->set_stylebox(CoreStringName(normal), "Label", v_marginless_style); } // SpinBox. @@ -1912,6 +1918,7 @@ void ThemeModern::populate_editor_styles(const Ref &p_theme, Editor // Flat button variations. { + p_theme->set_type_variation(SceneStringName(FlatButton), "Button"); p_theme->set_stylebox(CoreStringName(normal), SceneStringName(FlatButton), p_config.base_empty_wide_style); p_theme->set_stylebox(SceneStringName(hover), SceneStringName(FlatButton), p_config.flat_button_hover); p_theme->set_stylebox(SceneStringName(pressed), SceneStringName(FlatButton), p_config.flat_button_pressed); @@ -1936,6 +1943,8 @@ void ThemeModern::populate_editor_styles(const Ref &p_theme, Editor p_theme->set_stylebox("hover_pressed_mirrored", "FlatMenuButton", p_config.flat_button_hover_pressed); p_theme->set_stylebox("disabled_mirrored", "FlatMenuButton", p_config.base_empty_wide_style); + // Variations for buttons that shouldn't tint their icons. + p_theme->set_type_variation("FlatButtonNoIconTint", "FlatButton"); p_theme->set_color("icon_pressed_color", "FlatButtonNoIconTint", p_config.icon_normal_color); p_theme->set_color("icon_hover_color", "FlatButtonNoIconTint", p_config.mono_color); @@ -1946,6 +1955,13 @@ void ThemeModern::populate_editor_styles(const Ref &p_theme, Editor p_theme->set_color("icon_hover_color", "FlatMenuButtonNoIconTint", p_config.mono_color); p_theme->set_color("icon_hover_pressed_color", "FlatMenuButtonNoIconTint", p_config.mono_color); + // Variation for the AssetLib thumbnails. + + p_theme->set_type_variation("ThumbnailButton", SceneStringName(FlatButton)); + p_theme->set_color("icon_pressed_color", "ThumbnailButton", p_config.icon_normal_color); + p_theme->set_color("icon_hover_color", "ThumbnailButton", p_config.icon_normal_color); + p_theme->set_color("icon_hover_pressed_color", "ThumbnailButton", p_config.icon_normal_color); + // Variation for Editor Log filter buttons. p_theme->set_type_variation("EditorLogFilterButton", "Button"); @@ -2726,12 +2742,19 @@ void ThemeModern::populate_editor_styles(const Ref &p_theme, Editor p_theme->set_stylebox(CoreStringName(normal), "EditorHelpBitTooltipContent", style); } - // Asset Library. - p_theme->set_stylebox("bg", "AssetLib", EditorThemeManager::make_empty_stylebox(p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin)); - p_theme->set_stylebox(SceneStringName(panel), "AssetLib", p_config.foreground_panel); - p_theme->set_stylebox("downloads", "AssetLib", p_theme->get_stylebox(SceneStringName(panel), SNAME("ScrollContainerSecondary"))); - p_theme->set_color("status_color", "AssetLib", Color(0.5, 0.5, 0.5)); // FIXME: Use a defined color instead. - p_theme->set_icon("dismiss", "AssetLib", p_theme->get_icon(SNAME("Close"), EditorStringName(EditorIcons))); + // Asset Store. + { + Ref assetlib_panel_style = p_config.base_style->duplicate(); + assetlib_panel_style->set_bg_color(p_config.surface_low_color); + assetlib_panel_style->set_content_margin_all(p_config.base_margin * 2 * EDSCALE); + + p_theme->set_stylebox("bg", "AssetLib", EditorThemeManager::make_empty_stylebox(p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin)); + p_theme->set_stylebox(SceneStringName(panel), "AssetLib", assetlib_panel_style); + p_theme->set_stylebox(SceneStringName(panel), "AssetLib", p_config.foreground_panel); + p_theme->set_stylebox("downloads", "AssetLib", p_theme->get_stylebox(SceneStringName(panel), SNAME("ScrollContainerSecondary"))); + p_theme->set_color("faded_text", "AssetLib", p_config.font_disabled_color); + p_theme->set_icon("dismiss", "AssetLib", p_theme->get_icon(SNAME("Close"), EditorStringName(EditorIcons))); + } // Debugger. Ref debugger_panel_style = p_config.content_panel_style->duplicate();