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]