diff --git a/core/image.cpp b/core/image.cpp index ff4937527f5..f97a88d79aa 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -1740,6 +1740,7 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + "."); ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + "."); ERR_FAIL_COND_MSG(write_lock.ptr(), "Cannot create image when it is locked."); + ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "Image format out of range, please see Image's Format enum."); int mm = 0; int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); @@ -1760,6 +1761,7 @@ void Image::create(int p_width, int p_height, bool p_use_mipmaps, Format p_forma ERR_FAIL_COND_MSG(p_height <= 0, "Image height must be greater than 0."); ERR_FAIL_COND_MSG(p_width > MAX_WIDTH, "Image width cannot be greater than " + itos(MAX_WIDTH) + "."); ERR_FAIL_COND_MSG(p_height > MAX_HEIGHT, "Image height cannot be greater than " + itos(MAX_HEIGHT) + "."); + ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "Image format out of range, please see Image's Format enum."); int mm; int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); @@ -2113,6 +2115,8 @@ Error Image::decompress() { } Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_lossy_quality) { + ERR_FAIL_INDEX_V_MSG(p_mode, COMPRESS_MAX, ERR_INVALID_PARAMETER, "Invalid compress mode."); + ERR_FAIL_INDEX_V_MSG(p_source, COMPRESS_SOURCE_MAX, ERR_INVALID_PARAMETER, "Invalid compress source."); switch (p_mode) { case COMPRESS_S3TC: { ERR_FAIL_COND_V(!_image_compress_bc_func, ERR_UNAVAILABLE); @@ -2138,6 +2142,9 @@ Error Image::compress(CompressMode p_mode, CompressSource p_source, float p_loss ERR_FAIL_COND_V(!_image_compress_bptc_func, ERR_UNAVAILABLE); _image_compress_bptc_func(this, p_lossy_quality, p_source); } break; + case COMPRESS_MAX: { + ERR_FAIL_V(ERR_INVALID_PARAMETER); + } break; } return OK; diff --git a/core/image.h b/core/image.h index c03b8982408..b54664c0afb 100644 --- a/core/image.h +++ b/core/image.h @@ -124,6 +124,7 @@ public: COMPRESS_SOURCE_SRGB, COMPRESS_SOURCE_NORMAL, COMPRESS_SOURCE_LAYERED, + COMPRESS_SOURCE_MAX, }; //some functions provided by something else @@ -304,7 +305,8 @@ public: COMPRESS_PVRTC4, COMPRESS_ETC, COMPRESS_ETC2, - COMPRESS_BPTC + COMPRESS_BPTC, + COMPRESS_MAX, }; Error compress(CompressMode p_mode = COMPRESS_S3TC, CompressSource p_source = COMPRESS_SOURCE_GENERIC, float p_lossy_quality = 0.7); diff --git a/doc/classes/CylinderMesh.xml b/doc/classes/CylinderMesh.xml index 65a9e4291ce..75c64b25d89 100644 --- a/doc/classes/CylinderMesh.xml +++ b/doc/classes/CylinderMesh.xml @@ -4,7 +4,7 @@ Class representing a cylindrical [PrimitiveMesh]. - Class representing a cylindrical [PrimitiveMesh]. This class can be used to create cones by setting either the [member top_radius] or [member bottom_radius] properties to 0.0. + Class representing a cylindrical [PrimitiveMesh]. This class can be used to create cones by setting either the [member top_radius] or [member bottom_radius] properties to [code]0.0[/code]. @@ -12,19 +12,19 @@ - Bottom radius of the cylinder. + Bottom radius of the cylinder. If set to [code]0.0[/code], the bottom faces will not be generated, resulting in a conic shape. Full height of the cylinder. - Number of radial segments on the cylinder. + Number of radial segments on the cylinder. Higher values result in a more detailed cylinder/cone at the cost of performance. - Number of edge rings along the height of the cylinder. + Number of edge rings along the height of the cylinder. Changing [member rings] does not have any visual impact unless a shader or procedural mesh tool is used to alter the vertex data. Higher values result in more subdivisions, which can be used to create smoother-looking effects with shaders or procedural mesh tools (at the cost of performance). When not altering the vertex data using a shader or procedural mesh tool, [member rings] should be kept to its default value. - Top radius of the cylinder. + Top radius of the cylinder. If set to [code]0.0[/code], the top faces will not be generated, resulting in a conic shape. diff --git a/doc/classes/File.xml b/doc/classes/File.xml index 42ec0a75dbc..0e42e7c17a3 100644 --- a/doc/classes/File.xml +++ b/doc/classes/File.xml @@ -450,6 +450,7 @@ Stores any Variant value in the file. If [code]full_objects[/code] is [code]true[/code], encoding objects is allowed (and can potentially include code). + [b]Note:[/b] Not all properties are included. Only properties that are configured with the [constant PROPERTY_USAGE_STORAGE] flag set will be serialized. You can add a new usage flag to a property by overriding the [method Object._get_property_list] method in your class. You can also check how property usage is configured by calling [method Object._get_property_list]. See [enum PropertyUsageFlags] for the possible usage flags. diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index b36b29c8acb..7b6d3ae17e8 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -48,7 +48,7 @@ If [code]true[/code], wraps the text inside the node's bounding rectangle. If you resize the node, it will change its height automatically to show all the text. - If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle. It also lets you scale the node down freely. + If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle and will clip text horizontally. The node ignores the first [code]lines_skipped[/code] lines before it starts to display text. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 140c95cabb4..0d8564a1aae 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -245,10 +245,12 @@ Icon set in [code].ico[/code] format used on Windows to set the game's icon. This is done automatically on start by calling [method OS.set_native_icon]. - If [code]true[/code], disables printing to standard error in an exported build. + If [code]true[/code], disables printing to standard error. If [code]true[/code], this also hides error and warning messages printed by [method @GDScript.push_error] and [method @GDScript.push_warning]. See also [member application/run/disable_stdout]. + Changes to this setting will only be applied upon restarting the application. - If [code]true[/code], disables printing to standard output in an exported build. + If [code]true[/code], disables printing to standard output. This is equivalent to starting the editor or project with the [code]--quiet[/code] command line argument. See also [member application/run/disable_stderr]. + Changes to this setting will only be applied upon restarting the application. If [code]true[/code], flushes the standard output stream every time a line is printed. This affects both terminal logging and file logging. diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index dc179c41b65..23f9946f03d 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1175,31 +1175,29 @@ public: p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); } break; case Animation::TYPE_VALUE: { - if (!same_key_type) { - break; - } + if (same_key_type) { + Variant v = animation->track_get_key_value(first_track, first_key); - Variant v = animation->track_get_key_value(first_track, first_key); + if (hint.type != Variant::NIL) { + PropertyInfo pi = hint; + pi.name = "value"; + p_list->push_back(pi); + } else { + PropertyHint hint = PROPERTY_HINT_NONE; + String hint_string; - if (hint.type != Variant::NIL) { - PropertyInfo pi = hint; - pi.name = "value"; - p_list->push_back(pi); - } else { - PropertyHint hint = PROPERTY_HINT_NONE; - String hint_string; - - if (v.get_type() == Variant::OBJECT) { - //could actually check the object property if exists..? yes i will! - Ref res = v; - if (res.is_valid()) { - hint = PROPERTY_HINT_RESOURCE_TYPE; - hint_string = res->get_class(); + if (v.get_type() == Variant::OBJECT) { + //could actually check the object property if exists..? yes i will! + Ref res = v; + if (res.is_valid()) { + hint = PROPERTY_HINT_RESOURCE_TYPE; + hint_string = res->get_class(); + } } - } - if (v.get_type() != Variant::NIL) { - p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string)); + if (v.get_type() != Variant::NIL) { + p_list->push_back(PropertyInfo(v.get_type(), "value", hint, hint_string)); + } } } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 3babec4e118..8da9ae221aa 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -609,6 +609,7 @@ void EditorNode::_notification(int p_what) { p->set_item_icon(p->get_item_index(HELP_DOCS), gui_base->get_icon("Instance", "EditorIcons")); p->set_item_icon(p->get_item_index(HELP_QA), gui_base->get_icon("Instance", "EditorIcons")); p->set_item_icon(p->get_item_index(HELP_REPORT_A_BUG), gui_base->get_icon("Instance", "EditorIcons")); + p->set_item_icon(p->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_icon("Instance", "EditorIcons")); p->set_item_icon(p->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_icon("Instance", "EditorIcons")); p->set_item_icon(p->get_item_index(HELP_COMMUNITY), gui_base->get_icon("Instance", "EditorIcons")); p->set_item_icon(p->get_item_index(HELP_ABOUT), gui_base->get_icon("Godot", "EditorIcons")); @@ -1516,15 +1517,6 @@ void EditorNode::_save_scene(String p_file, int idx) { return; } - // force creation of node path cache - // (hacky but needed for the tree to update properly) - Node *dummy_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!dummy_scene) { - show_accept(TTR("Couldn't save scene. Likely dependencies (instances or inheritance) couldn't be satisfied."), TTR("OK")); - return; - } - memdelete(dummy_scene); - int flg = 0; if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) { flg |= ResourceSaver::FLAG_COMPRESS; @@ -2803,6 +2795,9 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { case HELP_REPORT_A_BUG: { OS::get_singleton()->shell_open("https://github.com/godotengine/godot/issues"); } break; + case HELP_SUGGEST_A_FEATURE: { + OS::get_singleton()->shell_open("https://github.com/godotengine/godot-proposals#readme"); + } break; case HELP_SEND_DOCS_FEEDBACK: { OS::get_singleton()->shell_open("https://github.com/godotengine/godot-docs/issues"); } break; @@ -6364,6 +6359,7 @@ EditorNode::EditorNode() { p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/online_docs", TTR("Online Documentation")), HELP_DOCS); p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/q&a", TTR("Questions & Answers")), HELP_QA); p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG); + p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE); p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK); p->add_icon_shortcut(gui_base->get_icon("Instance", "EditorIcons"), ED_SHORTCUT("editor/community", TTR("Community")), HELP_COMMUNITY); p->add_separator(); diff --git a/editor/editor_node.h b/editor/editor_node.h index 0f6e82f5930..fe5fe1f7aff 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -200,6 +200,7 @@ private: HELP_DOCS, HELP_QA, HELP_REPORT_A_BUG, + HELP_SUGGEST_A_FEATURE, HELP_SEND_DOCS_FEEDBACK, HELP_COMMUNITY, HELP_ABOUT, diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 65890d7a7ec..169ed2ba3d1 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5844,7 +5844,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CTRL | KEY_G), SHOW_GRID); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CMD | KEY_G), SHOW_GRID); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES); diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index fc1d8aca638..731312e7bde 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -6345,7 +6345,7 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) { p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid")), MENU_VIEW_GRID); + p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), KEY_MASK_CMD + KEY_G), MENU_VIEW_GRID); p->add_separator(); p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 466a331304d..85c7481a989 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -103,17 +103,16 @@ void SpriteFramesEditor::_sheet_preview_draw() { split_sheet_dialog->get_ok()->set_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size())); } void SpriteFramesEditor::_sheet_preview_input(const Ref &p_event) { - Ref mb = p_event; - + const Ref mb = p_event; if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { - Size2i size = split_sheet_preview->get_size(); - int h = split_sheet_h->get_value(); - int v = split_sheet_v->get_value(); + const Size2i size = split_sheet_preview->get_size(); + const int h = split_sheet_h->get_value(); + const int v = split_sheet_v->get_value(); - int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1); - int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1); + const int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1); + const int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1); - int idx = h * y + x; + const int idx = h * y + x; if (mb->get_shift() && last_frame_selected >= 0) { //select multiple @@ -124,6 +123,9 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref &p_event) { } for (int i = from; i <= to; i++) { + // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. + frames_toggled_by_mouse_hover.insert(idx); + if (mb->get_control()) { frames_selected.erase(i); } else { @@ -131,6 +133,9 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref &p_event) { } } } else { + // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. + frames_toggled_by_mouse_hover.insert(idx); + if (frames_selected.has(idx)) { frames_selected.erase(idx); } else { @@ -141,6 +146,39 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref &p_event) { last_frame_selected = idx; split_sheet_preview->update(); } + + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + frames_toggled_by_mouse_hover.clear(); + } + + const Ref mm = p_event; + if (mm.is_valid() && mm->get_button_mask() & BUTTON_MASK_LEFT) { + // Select by holding down the mouse button on frames. + const Size2i size = split_sheet_preview->get_size(); + const int h = split_sheet_h->get_value(); + const int v = split_sheet_v->get_value(); + + const int x = CLAMP(int(mm->get_position().x) * h / size.width, 0, h - 1); + const int y = CLAMP(int(mm->get_position().y) * v / size.height, 0, v - 1); + + const int idx = h * y + x; + + if (!frames_toggled_by_mouse_hover.has(idx)) { + // Only allow toggling each tile once per mouse hold. + // Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor. + // The mouse button must be released before it can be toggled again. + frames_toggled_by_mouse_hover.insert(idx); + + if (frames_selected.has(idx)) { + frames_selected.erase(idx); + } else { + frames_selected.insert(idx); + } + + last_frame_selected = idx; + split_sheet_preview->update(); + } + } } void SpriteFramesEditor::_sheet_scroll_input(const Ref &p_event) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 77e5226ea69..920fcc526af 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -86,6 +86,7 @@ class SpriteFramesEditor : public HSplitContainer { ToolButton *split_sheet_zoom_in; EditorFileDialog *file_split_sheet; Set frames_selected; + Set frames_toggled_by_mouse_hover; int last_frame_selected; float scale_ratio; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 0befdc10626..51702b30771 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1848,9 +1848,6 @@ void ProjectManager::_unhandled_input(const Ref &p_ev) { case KEY_ENTER: { _open_selected_projects_ask(); } break; - case KEY_DELETE: { - _erase_project(); - } break; case KEY_HOME: { if (_project_list->get_project_count() > 0) { _project_list->select_project(0); @@ -2491,12 +2488,14 @@ ProjectManager::ProjectManager() { Button *open = memnew(Button); open->set_text(TTR("Edit")); + open->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTR("Edit Project"), KEY_MASK_CMD | KEY_E)); tree_vb->add_child(open); open->connect("pressed", this, "_open_selected_projects_ask"); open_btn = open; Button *run = memnew(Button); run->set_text(TTR("Run")); + run->set_shortcut(ED_SHORTCUT("project_manager/run_project", TTR("Run Project"), KEY_MASK_CMD | KEY_R)); tree_vb->add_child(run); run->connect("pressed", this, "_run_project"); run_btn = run; @@ -2505,6 +2504,7 @@ ProjectManager::ProjectManager() { Button *scan = memnew(Button); scan->set_text(TTR("Scan")); + scan->set_shortcut(ED_SHORTCUT("project_manager/scan_projects", TTR("Scan Projects"), KEY_MASK_CMD | KEY_S)); tree_vb->add_child(scan); scan->connect("pressed", this, "_scan_projects"); @@ -2520,22 +2520,26 @@ ProjectManager::ProjectManager() { Button *create = memnew(Button); create->set_text(TTR("New Project")); + create->set_shortcut(ED_SHORTCUT("project_manager/new_project", TTR("New Project"), KEY_MASK_CMD | KEY_N)); tree_vb->add_child(create); create->connect("pressed", this, "_new_project"); Button *import = memnew(Button); import->set_text(TTR("Import")); + import->set_shortcut(ED_SHORTCUT("project_manager/import_project", TTR("Import Project"), KEY_MASK_CMD | KEY_I)); tree_vb->add_child(import); import->connect("pressed", this, "_import_project"); Button *rename = memnew(Button); rename->set_text(TTR("Rename")); + rename->set_shortcut(ED_SHORTCUT("project_manager/rename_project", TTR("Rename Project"), KEY_F2)); tree_vb->add_child(rename); rename->connect("pressed", this, "_rename_project"); rename_btn = rename; Button *erase = memnew(Button); erase->set_text(TTR("Remove")); + erase->set_shortcut(ED_SHORTCUT("project_manager/remove_project", TTR("Remove Project"), KEY_DELETE)); tree_vb->add_child(erase); erase->connect("pressed", this, "_erase_project"); erase_btn = erase; diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index 9df0c6e0a71..8cdfc00eb45 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -635,8 +635,6 @@ bool EditorSpatialGizmo::intersect_ray(Camera *p_camera, const Point2 &p_point, r_normal = -p_camera->project_ray_normal(p_point); return true; } - - return false; } if (collision_segments.size()) { @@ -687,8 +685,6 @@ bool EditorSpatialGizmo::intersect_ray(Camera *p_camera, const Point2 &p_point, r_normal = -p_camera->project_ray_normal(p_point); return true; } - - return false; } if (collision_mesh.is_valid()) { diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 0cf67183057..9df15d42c94 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -187,7 +187,9 @@ Array GDScriptWorkspace::symbol(const Dictionary &p_params) { E->get()->get_symbols().symbol_tree_as_list(E->key(), script_symbols); for (int i = 0; i < script_symbols.size(); ++i) { if (query.is_subsequence_ofi(script_symbols[i].name)) { - arr.push_back(script_symbols[i].to_json()); + lsp::DocumentedSymbolInformation symbol = script_symbols[i]; + symbol.location.uri = get_file_uri(symbol.location.uri); + arr.push_back(symbol.to_json()); } } } diff --git a/modules/pvr/image_compress_pvrtc.cpp b/modules/pvr/image_compress_pvrtc.cpp index 4c67bb7a294..196026cf401 100644 --- a/modules/pvr/image_compress_pvrtc.cpp +++ b/modules/pvr/image_compress_pvrtc.cpp @@ -43,6 +43,10 @@ static void _compress_pvrtc4(Image *p_img) { if (!img->is_size_po2() || img->get_width() != img->get_height()) { make_mipmaps = img->has_mipmaps(); img->resize_to_po2(true); + // Resizing can fail for some formats + if (!img->is_size_po2() || img->get_width() != img->get_height()) { + ERR_FAIL_MSG("Failed to resize the image for compression."); + } } img->convert(Image::FORMAT_RGBA8); if (!img->has_mipmaps() && make_mipmaps) { diff --git a/modules/visual_script/visual_script_property_selector.cpp b/modules/visual_script/visual_script_property_selector.cpp index 05ca8c0f61a..65b6e256d39 100644 --- a/modules/visual_script/visual_script_property_selector.cpp +++ b/modules/visual_script/visual_script_property_selector.cpp @@ -466,10 +466,11 @@ void VisualScriptPropertySelector::_item_selected() { at_class = ClassDB::get_parent_class_nocheck(at_class); } - Map::Element *T = dd->class_list.find(class_type); + Vector functions = name.rsplit("/", false); + at_class = functions.size() > 3 ? functions[functions.size() - 2] : class_type; + Map::Element *T = dd->class_list.find(at_class); if (T) { for (int i = 0; i < T->get().methods.size(); i++) { - Vector functions = name.rsplit("/", false, 1); if (T->get().methods[i].name == functions[functions.size() - 1]) { text = T->get().methods[i].description; } diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 474f207382c..fa29d748ab3 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -89,6 +89,9 @@ When using SSL (see [member private_key] and [member ssl_certificate]), you can set this to a valid [X509Certificate] to be provided as additional CA chain information during the SSL handshake. + + The time in seconds before a pending client (i.e. a client that has not yet finished the HTTP handshake) is considered stale and forcefully disconnected. + When set to a valid [CryptoKey] (along with [member ssl_certificate]) will cause the server to require SSL instead of regular TCP (i.e. the [code]wss://[/code] protocol). diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp index 222ce205a7b..dc3047c8929 100644 --- a/modules/websocket/websocket_client.cpp +++ b/modules/websocket/websocket_client.cpp @@ -43,9 +43,9 @@ Error WebSocketClient::connect_to_url(String p_url, const Vector p_proto _is_multiplayer = gd_mp_api; String host = p_url; - String path = "/"; - String scheme = ""; - int port = 80; + String path; + String scheme; + int port = 0; Error err = p_url.parse_url(scheme, host, port, path); ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid URL: " + p_url); @@ -56,6 +56,9 @@ Error WebSocketClient::connect_to_url(String p_url, const Vector p_proto if (port == 0) { port = ssl ? 443 : 80; } + if (path.empty()) { + path = "/"; + } return connect_to_host(host, path, port, ssl, p_protocols, p_custom_headers); } diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp index f57e8d959cc..69c53db6d84 100644 --- a/modules/websocket/websocket_server.cpp +++ b/modules/websocket/websocket_server.cpp @@ -65,6 +65,10 @@ void WebSocketServer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_ca_chain"), &WebSocketServer::set_ca_chain); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "ca_chain", PROPERTY_HINT_RESOURCE_TYPE, "X509Certificate", 0), "set_ca_chain", "get_ca_chain"); + ClassDB::bind_method(D_METHOD("get_handshake_timeout"), &WebSocketServer::get_handshake_timeout); + ClassDB::bind_method(D_METHOD("set_handshake_timeout", "timeout"), &WebSocketServer::set_handshake_timeout); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "handshake_timeout"), "set_handshake_timeout", "get_handshake_timeout"); + ADD_SIGNAL(MethodInfo("client_close_request", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "code"), PropertyInfo(Variant::STRING, "reason"))); ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::BOOL, "was_clean_close"))); ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); @@ -108,6 +112,15 @@ void WebSocketServer::set_ca_chain(Ref p_ca_chain) { ca_chain = p_ca_chain; } +float WebSocketServer::get_handshake_timeout() const { + return handshake_timeout / 1000.0; +} + +void WebSocketServer::set_handshake_timeout(float p_timeout) { + ERR_FAIL_COND(p_timeout <= 0.0); + handshake_timeout = p_timeout * 1000; +} + NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const { if (is_listening()) { return CONNECTION_CONNECTED; diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h index 451fe7e5210..6f7101b2ff7 100644 --- a/modules/websocket/websocket_server.h +++ b/modules/websocket/websocket_server.h @@ -48,6 +48,7 @@ protected: Ref private_key; Ref ssl_cert; Ref ca_chain; + uint32_t handshake_timeout = 3000; public: virtual void poll() = 0; @@ -80,6 +81,9 @@ public: Ref get_ca_chain() const; void set_ca_chain(Ref p_ca_chain); + float get_handshake_timeout() const; + void set_handshake_timeout(float p_timeout); + virtual Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) = 0; WebSocketServer(); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp index 59c8b80a2ec..3125d5ab7f9 100644 --- a/modules/websocket/wsl_client.cpp +++ b/modules/websocket/wsl_client.cpp @@ -158,6 +158,7 @@ bool WSLClient::_verify_headers(String &r_protocol) { Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector p_protocols, const Vector p_custom_headers) { ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(p_path.empty(), ERR_INVALID_PARAMETER); _peer = Ref(memnew(WSLPeer)); IP_Address addr; diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp index 3283c665098..6f27925a065 100644 --- a/modules/websocket/wsl_server.cpp +++ b/modules/websocket/wsl_server.cpp @@ -104,8 +104,8 @@ bool WSLServer::PendingPeer::_parse_request(const Vector p_protocols) { return true; } -Error WSLServer::PendingPeer::do_handshake(const Vector p_protocols) { - if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) { +Error WSLServer::PendingPeer::do_handshake(const Vector p_protocols, uint64_t p_timeout) { + if (OS::get_singleton()->get_ticks_msec() - time > p_timeout) { return ERR_TIMEOUT; } if (use_ssl) { @@ -197,7 +197,7 @@ void WSLServer::poll() { List> remove_peers; for (List>::Element *E = _pending.front(); E; E = E->next()) { Ref ppeer = E->get(); - Error err = ppeer->do_handshake(_protocols); + Error err = ppeer->do_handshake(_protocols, handshake_timeout); if (err == ERR_BUSY) { continue; } else if (err != OK) { diff --git a/modules/websocket/wsl_server.h b/modules/websocket/wsl_server.h index 8b2d4d3a040..49a0b383e46 100644 --- a/modules/websocket/wsl_server.h +++ b/modules/websocket/wsl_server.h @@ -40,8 +40,6 @@ #include "core/io/stream_peer_tcp.h" #include "core/io/tcp_server.h" -#define WSL_SERVER_TIMEOUT 1000 - class WSLServer : public WebSocketServer { GDCIIMPL(WSLServer, WebSocketServer); @@ -66,7 +64,7 @@ private: PendingPeer(); - Error do_handshake(const Vector p_protocols); + Error do_handshake(const Vector p_protocols, uint64_t p_timeout); }; int _in_buf_size; diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp index 6d386350fef..6ab393e87e0 100644 --- a/platform/android/export/export.cpp +++ b/platform/android/export/export.cpp @@ -1910,7 +1910,7 @@ public: err = OS::get_singleton()->execute(adb, args, true, nullptr, &output, &rv, true); print_verbose(output); if (err || rv != 0) { - EditorNode::add_io_error("Could not install to device."); + EditorNode::add_io_error("Could not install to device: " + output); CLEANUP_AND_RETURN(ERR_CANT_CREATE); } diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index ebe568b6ee8..9bf69bd59ac 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -9,7 +9,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() //CHUNK_BUILDSCRIPT_REPOSITORIES_BEGIN //CHUNK_BUILDSCRIPT_REPOSITORIES_END } @@ -25,9 +25,8 @@ apply plugin: 'com.android.application' allprojects { repositories { - mavenCentral() google() - jcenter() + mavenCentral() //CHUNK_ALLPROJECTS_REPOSITORIES_BEGIN //CHUNK_ALLPROJECTS_REPOSITORIES_END diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index a2c32552d9a..81fc87b7ef7 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,11 +1,11 @@ ext.versions = [ - androidGradlePlugin: '4.0.1', + androidGradlePlugin: '4.2.1', compileSdk : 29, minSdk : 18, targetSdk : 29, buildTools : '30.0.3', supportCoreUtils : '1.0.0', - kotlinVersion : '1.4.10', + kotlinVersion : '1.5.10', v4Support : '1.0.0', javaVersion : 1.8, ndkVersion : '21.4.7075529' // Also update 'platform/android/detect.py#get_project_ndk_version()' when this is updated. diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index a28888d80d3..ee24a46d9fb 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -5,7 +5,7 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { classpath libraries.androidGradlePlugin @@ -16,7 +16,6 @@ buildscript { allprojects { repositories { google() - jcenter() mavenCentral() } } diff --git a/platform/android/java/gradle/wrapper/gradle-wrapper.properties b/platform/android/java/gradle/wrapper/gradle-wrapper.properties index a7d8a0f3107..74c5636f8ac 100644 --- a/platform/android/java/gradle/wrapper/gradle-wrapper.properties +++ b/platform/android/java/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Sep 02 02:44:30 PDT 2019 +#Wed Jun 23 23:42:22 PDT 2021 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index b509cdfd537..c384cc26d8a 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -136,8 +136,11 @@ public: // Wrong protocol ERR_FAIL_COND_MSG(req[0] != "GET" || req[2] != "HTTP/1.1", "Invalid method or HTTP version."); - const String req_file = req[1].get_file(); - const String req_ext = req[1].get_extension(); + const int query_index = req[1].find_char('?'); + const String path = (query_index == -1) ? req[1] : req[1].substr(0, query_index); + + const String req_file = path.get_file(); + const String req_ext = path.get_extension(); const String cache_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("web"); const String filepath = cache_path.plus_file(req_file); @@ -446,6 +449,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector &p_html, const Re } config["canvasResizePolicy"] = p_preset->get("html/canvas_resize_policy"); config["experimentalVK"] = p_preset->get("html/experimental_virtual_keyboard"); + config["focusCanvas"] = p_preset->get("html/focus_canvas_on_start"); config["gdnativeLibs"] = libs; config["executable"] = p_name; config["args"] = args; @@ -648,6 +652,7 @@ void EditorExportPlatformJavaScript::get_export_options(List *r_op r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_FILE, "*.html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "html/canvas_resize_policy", PROPERTY_HINT_ENUM, "None,Project,Adaptive"), 2)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/focus_canvas_on_start"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "html/experimental_virtual_keyboard"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "progressive_web_app/enabled"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "progressive_web_app/offline_page", PROPERTY_HINT_FILE, "*.html"), "")); diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js index 60727828757..ba61b14eb7f 100644 --- a/platform/javascript/js/engine/config.js +++ b/platform/javascript/js/engine/config.js @@ -90,6 +90,14 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- * @default */ args: [], + /** + * When enabled, the game canvas will automatically grab the focus when the engine starts. + * + * @memberof EngineConfig + * @type {boolean} + * @default + */ + focusCanvas: true, /** * When enabled, this will turn on experimental virtual keyboard support on mobile. * @@ -238,6 +246,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- this.persistentPaths = parse('persistentPaths', this.persistentPaths); this.persistentDrops = parse('persistentDrops', this.persistentDrops); this.experimentalVK = parse('experimentalVK', this.experimentalVK); + this.focusCanvas = parse('focusCanvas', this.focusCanvas); this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs); this.fileSizes = parse('fileSizes', this.fileSizes); this.args = parse('args', this.args); @@ -324,6 +333,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused- 'locale': locale, 'persistentDrops': this.persistentDrops, 'virtualKeyboard': this.experimentalVK, + 'focusCanvas': this.focusCanvas, 'onExecute': this.onExecute, 'onExit': function (p_code) { cleanup(); // We always need to call the cleanup callback to free memory. diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js index 7414b8cc476..5aa750757c5 100644 --- a/platform/javascript/js/libs/library_godot_os.js +++ b/platform/javascript/js/libs/library_godot_os.js @@ -72,6 +72,9 @@ const GodotConfig = { GodotConfig.persistent_drops = !!p_opts['persistentDrops']; GodotConfig.on_execute = p_opts['onExecute']; GodotConfig.on_exit = p_opts['onExit']; + if (p_opts['focusCanvas']) { + GodotConfig.canvas.focus(); + } }, locate_file: function (file) { diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index 3e425c08978..1505a6063bd 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -3295,6 +3295,12 @@ void OS_OSX::set_mouse_mode(MouseMode p_mode) { ignore_warp = true; warp_events.clear(); mouse_mode = p_mode; + + if (mouse_mode == MOUSE_MODE_VISIBLE || mouse_mode == MOUSE_MODE_CONFINED) { + CursorShape p_shape = cursor_shape; + cursor_shape = OS::CURSOR_MAX; + set_cursor_shape(p_shape); + } } OS::MouseMode OS_OSX::get_mouse_mode() const { diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index 44017edac92..294b5f3d215 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -1056,19 +1056,19 @@ public: // Capabilities const char **basic = uwp_capabilities; while (*basic) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*basic).camelcase_to_underscore(false)), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*basic)), false)); basic++; } const char **uap = uwp_uap_capabilities; while (*uap) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*uap).camelcase_to_underscore(false)), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*uap)), false)); uap++; } const char **device = uwp_device_capabilities; while (*device) { - r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*device).camelcase_to_underscore(false)), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/" + String(*device)), false)); device++; } } diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index ff2b83bcf0a..dce47ed7fa8 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -310,7 +310,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p args.push_back(p_path); #ifndef WINDOWS_ENABLED args.push_back("-out"); - args.push_back(p_path); + args.push_back(p_path + "_signed"); #endif String str; @@ -326,6 +326,16 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p return FAILED; } +#ifndef WINDOWS_ENABLED + DirAccessRef tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); + + err = tmp_dir->remove(p_path); + ERR_FAIL_COND_V(err != OK, err); + + err = tmp_dir->rename(p_path + "_signed", p_path); + ERR_FAIL_COND_V(err != OK, err); +#endif + return OK; } diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 5f771b16a18..e7666f76ca2 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -575,7 +575,7 @@ void GraphNode::_connpos_update() { continue; } - Size2i size = c->get_combined_minimum_size(); + Size2i size = c->get_rect().size; int y = sb->get_margin(MARGIN_TOP) + vofs; int h = size.y; diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp index 092d7cdf368..b46878fb0e1 100644 --- a/scene/gui/texture_progress.cpp +++ b/scene/gui/texture_progress.cpp @@ -221,43 +221,87 @@ void TextureProgress::draw_nine_patch_stretched(const Ref &p_texture, F double width_texture = 0.0; double first_section_size = 0.0; double last_section_size = 0.0; - switch (mode) { - case FILL_LEFT_TO_RIGHT: - case FILL_RIGHT_TO_LEFT: { + switch (p_mode) { + case FILL_LEFT_TO_RIGHT: { width_total = dst_rect.size.x; width_texture = texture_size.x; first_section_size = topleft.x; last_section_size = bottomright.x; } break; - case FILL_TOP_TO_BOTTOM: - case FILL_BOTTOM_TO_TOP: { + case FILL_RIGHT_TO_LEFT: { + width_total = dst_rect.size.x; + width_texture = texture_size.x; + // In contrast to `FILL_LEFT_TO_RIGHT`, `first_section_size` and `last_section_size` should switch value. + first_section_size = bottomright.x; + last_section_size = topleft.x; + } break; + case FILL_TOP_TO_BOTTOM: { width_total = dst_rect.size.y; width_texture = texture_size.y; first_section_size = topleft.y; last_section_size = bottomright.y; } break; + case FILL_BOTTOM_TO_TOP: { + width_total = dst_rect.size.y; + width_texture = texture_size.y; + // Similar to `FILL_RIGHT_TO_LEFT`. + first_section_size = bottomright.y; + last_section_size = topleft.y; + } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { - // TODO: Implement + width_total = dst_rect.size.x; + width_texture = texture_size.x; + first_section_size = topleft.x; + last_section_size = bottomright.x; } break; case FILL_BILINEAR_TOP_AND_BOTTOM: { - // TODO: Implement + width_total = dst_rect.size.y; + width_texture = texture_size.y; + first_section_size = topleft.y; + last_section_size = bottomright.y; } break; case FILL_CLOCKWISE: case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: { - // Those modes are circular, not relevant for nine patch + // Those modes are circular, not relevant for nine patch. } break; + case FILL_MODE_MAX: + break; } double width_filled = width_total * p_ratio; double middle_section_size = MAX(0.0, width_texture - first_section_size - last_section_size); - middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size))); - last_section_size = MAX(0.0, last_section_size - (width_total - width_filled)); - first_section_size = MIN(first_section_size, width_filled); - width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); + // Maximum middle texture size. + double max_middle_texture_size = middle_section_size; - switch (mode) { + // Maximum real middle texture size. + double max_middle_real_size = MAX(0.0, width_total - (first_section_size + last_section_size)); + + switch (p_mode) { + case FILL_BILINEAR_LEFT_AND_RIGHT: + case FILL_BILINEAR_TOP_AND_BOTTOM: { + last_section_size = MAX(0.0, last_section_size - (width_total - width_filled) * 0.5); + first_section_size = MAX(0.0, first_section_size - (width_total - width_filled) * 0.5); + + // When `width_filled` increases, `middle_section_size` only increases when either of `first_section_size` and `last_section_size` is zero. + // Also, it should always be smaller than or equal to `(width_total - (first_section_size + last_section_size))`. + double real_middle_size = width_filled - first_section_size - last_section_size; + middle_section_size *= MIN(max_middle_real_size, real_middle_size) / max_middle_real_size; + + width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); + } break; + case FILL_MODE_MAX: + break; + default: { + middle_section_size *= MIN(1.0, (MAX(0.0, width_filled - first_section_size) / MAX(1.0, width_total - first_section_size - last_section_size))); + last_section_size = MAX(0.0, last_section_size - (width_total - width_filled)); + first_section_size = MIN(first_section_size, width_filled); + width_texture = MIN(width_texture, first_section_size + middle_section_size + last_section_size); + } + } + + switch (p_mode) { case FILL_LEFT_TO_RIGHT: { src_rect.size.x = width_texture; dst_rect.size.x = width_filled; @@ -287,16 +331,32 @@ void TextureProgress::draw_nine_patch_stretched(const Ref &p_texture, F bottomright.y = first_section_size; } break; case FILL_BILINEAR_LEFT_AND_RIGHT: { - // TODO: Implement + double center_mapped_from_real_width = (width_total * 0.5 - topleft.x) / max_middle_real_size * max_middle_texture_size + topleft.x; + double drift_from_unscaled_center = (src_rect.size.x * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.x - topleft.x); + src_rect.position.x += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5; + src_rect.size.x = width_texture; + dst_rect.position.x += (width_total - width_filled) * 0.5; + dst_rect.size.x = width_filled; + topleft.x = first_section_size; + bottomright.x = last_section_size; } break; case FILL_BILINEAR_TOP_AND_BOTTOM: { - // TODO: Implement + double center_mapped_from_real_width = (width_total * 0.5 - topleft.y) / max_middle_real_size * max_middle_texture_size + topleft.y; + double drift_from_unscaled_center = (src_rect.size.y * 0.5 - center_mapped_from_real_width) * (last_section_size - first_section_size) / (bottomright.y - topleft.y); + src_rect.position.y += center_mapped_from_real_width + drift_from_unscaled_center - width_texture * 0.5; + src_rect.size.y = width_texture; + dst_rect.position.y += (width_total - width_filled) * 0.5; + dst_rect.size.y = width_filled; + topleft.y = first_section_size; + bottomright.y = last_section_size; } break; case FILL_CLOCKWISE: case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: case FILL_COUNTER_CLOCKWISE: { - // Those modes are circular, not relevant for nine patch + // Those modes are circular, not relevant for nine patch. } break; + case FILL_MODE_MAX: + break; } } @@ -310,19 +370,34 @@ void TextureProgress::_notification(int p_what) { const float corners[12] = { -0.125, -0.375, -0.625, -0.875, 0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875 }; switch (p_what) { case NOTIFICATION_DRAW: { - if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP)) { + if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP || mode == FILL_BILINEAR_LEFT_AND_RIGHT || mode == FILL_BILINEAR_TOP_AND_BOTTOM)) { if (under.is_valid()) { - draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0, tint_under); + draw_nine_patch_stretched(under, mode, 1.0, tint_under); } if (progress.is_valid()) { draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress); } if (over.is_valid()) { - draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0, tint_over); + draw_nine_patch_stretched(over, mode, 1.0, tint_over); } } else { if (under.is_valid()) { - draw_texture(under, Point2(), tint_under); + switch (mode) { + case FILL_CLOCKWISE: + case FILL_COUNTER_CLOCKWISE: + case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { + if (nine_patch_stretch) { + Rect2 region = Rect2(Point2(), get_size()); + draw_texture_rect(under, region, false, tint_under); + } else { + draw_texture(under, Point2(), tint_under); + } + } break; + case FILL_MODE_MAX: + break; + default: + draw_texture(under, Point2(), tint_under); + } } if (progress.is_valid()) { Size2 s = progress->get_size(); @@ -353,7 +428,7 @@ void TextureProgress::_notification(int p_what) { float val = get_as_ratio() * rad_max_degrees / 360; if (val == 1) { Rect2 region = Rect2(Point2(), s); - draw_texture_rect_region(progress, region, region, tint_progress); + draw_texture_rect(progress, region, false, tint_progress); } else if (val != 0) { Array pts; float direction = mode == FILL_COUNTER_CLOCKWISE ? -1 : 1; @@ -416,12 +491,29 @@ void TextureProgress::_notification(int p_what) { Rect2 region = Rect2(Point2(0, s.y / 2 - s.y * get_as_ratio() / 2), Size2(s.x, s.y * get_as_ratio())); draw_texture_rect_region(progress, region, region, tint_progress); } break; + case FILL_MODE_MAX: + break; default: draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress); } } if (over.is_valid()) { - draw_texture(over, Point2(), tint_over); + switch (mode) { + case FILL_CLOCKWISE: + case FILL_COUNTER_CLOCKWISE: + case FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE: { + if (nine_patch_stretch) { + Rect2 region = Rect2(Point2(), get_size()); + draw_texture_rect(over, region, false, tint_over); + } else { + draw_texture(over, Point2(), tint_over); + } + } break; + case FILL_MODE_MAX: + break; + default: + draw_texture(over, Point2(), tint_over); + } } } @@ -430,7 +522,7 @@ void TextureProgress::_notification(int p_what) { } void TextureProgress::set_fill_mode(int p_fill) { - ERR_FAIL_INDEX(p_fill, 9); + ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX); mode = (FillMode)p_fill; update(); } @@ -513,7 +605,7 @@ void TextureProgress::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_under", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_under_texture", "get_under_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_over_texture", "get_over_texture"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_progress_texture", "get_progress_texture"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom), Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise,Bilinear (Left and Right),Bilinear (Top and Bottom),Clockwise and Counter Clockwise"), "set_fill_mode", "get_fill_mode"); ADD_GROUP("Tint", "tint_"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under"), "set_tint_under", "get_tint_under"); ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over"), "set_tint_over", "get_tint_over"); diff --git a/scene/gui/texture_progress.h b/scene/gui/texture_progress.h index 247653e2417..5022b813781 100644 --- a/scene/gui/texture_progress.h +++ b/scene/gui/texture_progress.h @@ -54,7 +54,8 @@ public: FILL_COUNTER_CLOCKWISE, FILL_BILINEAR_LEFT_AND_RIGHT, FILL_BILINEAR_TOP_AND_BOTTOM, - FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE + FILL_CLOCKWISE_AND_COUNTER_CLOCKWISE, + FILL_MODE_MAX, }; void set_fill_mode(int p_fill); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index b03dbd0318b..070b5e61cca 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -471,7 +471,9 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Map StringName type = p_node->get_class(); Ref