diff options
46 files changed, 1198 insertions, 639 deletions
diff --git a/core/class_db.cpp b/core/class_db.cpp index 794d990083..49e3f94d8f 100644 --- a/core/class_db.cpp +++ b/core/class_db.cpp @@ -480,6 +480,7 @@ uint64_t ClassDB::get_api_hash(APIType p_api) { for (List<StringName>::Element *F = snames.front(); F; F = F->next()) { PropertySetGet *psg = t->property_setget.getptr(F->get()); + ERR_FAIL_COND_V(!psg, 0); hash = hash_djb2_one_64(F->get().hash(), hash); hash = hash_djb2_one_64(psg->setter.hash(), hash); diff --git a/core/io/packet_peer.cpp b/core/io/packet_peer.cpp index c77c81f9e2..1e4ea715b3 100644 --- a/core/io/packet_peer.cpp +++ b/core/io/packet_peer.cpp @@ -110,10 +110,11 @@ Error PacketPeer::put_var(const Variant &p_packet, bool p_full_objects) { Variant PacketPeer::_bnd_get_var(bool p_allow_objects) { Variant var; - get_var(var, p_allow_objects); + Error err = get_var(var, p_allow_objects); + ERR_FAIL_COND_V(err != OK, Variant()); return var; -}; +} Error PacketPeer::_put_packet(const PoolVector<uint8_t> &p_buffer) { return put_packet_buffer(p_buffer); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 38bef2768e..146480e5a2 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -718,8 +718,8 @@ Error ResourceInteractiveLoaderBinary::poll() { Resource *r = Object::cast_to<Resource>(obj); if (!r) { error = ERR_FILE_CORRUPT; - memdelete(obj); //bye ERR_EXPLAIN(local_path + ":Resource type in resource field not a resource, type is: " + obj->get_class()); + memdelete(obj); //bye ERR_FAIL_V(ERR_FILE_CORRUPT); } diff --git a/core/make_binders.py b/core/make_binders.py index 24901c42a1..c38db5cef4 100644 --- a/core/make_binders.py +++ b/core/make_binders.py @@ -334,9 +334,6 @@ def make_version(template, nargs, argmax, const, ret): elif (cmd == "noarg"): for i in range(nargs + 1, argmax + 1): outtext += data.replace("@", str(i)) - elif (cmd == "noarg"): - for i in range(nargs + 1, argmax + 1): - outtext += data.replace("@", str(i)) from_pos = end + 1 diff --git a/core/map.h b/core/map.h index a701ba36f7..c8197639f2 100644 --- a/core/map.h +++ b/core/map.h @@ -38,7 +38,7 @@ */ // based on the very nice implementation of rb-trees by: -// http://web.mit.edu/~emin/www/source_code/red_black_tree/index.html +// https://web.archive.org/web/20120507164830/http://web.mit.edu/~emin/www/source_code/red_black_tree/index.html template <class K, class V, class C = Comparator<K>, class A = DefaultAllocator> class Map { diff --git a/core/math/camera_matrix.cpp b/core/math/camera_matrix.cpp index 8b3b6c82f3..30c0cab909 100644 --- a/core/math/camera_matrix.cpp +++ b/core/math/camera_matrix.cpp @@ -302,8 +302,8 @@ Vector<Plane> CameraMatrix::get_projection_planes(const Transform &p_transform) /** Fast Plane Extraction from combined modelview/projection matrices. * References: - * http://www.markmorley.com/opengl/frustumculling.html - * http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf + * https://web.archive.org/web/20011221205252/http://www.markmorley.com/opengl/frustumculling.html + * https://web.archive.org/web/20061020020112/http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf */ Vector<Plane> planes; diff --git a/core/set.h b/core/set.h index 81250068af..b2c717880d 100644 --- a/core/set.h +++ b/core/set.h @@ -39,7 +39,7 @@ */ // based on the very nice implementation of rb-trees by: -// http://web.mit.edu/~emin/www/source_code/red_black_tree/index.html +// https://web.archive.org/web/20120507164830/http://web.mit.edu/~emin/www/source_code/red_black_tree/index.html template <class T, class C = Comparator<T>, class A = DefaultAllocator> class Set { diff --git a/core/ustring.cpp b/core/ustring.cpp index 75e3b6f22e..ed401c3763 100644 --- a/core/ustring.cpp +++ b/core/ustring.cpp @@ -2729,6 +2729,51 @@ bool String::is_quoted() const { return is_enclosed_in("\"") || is_enclosed_in("'"); } +int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const { + if (p_string.empty()) { + return 0; + } + int len = length(); + int slen = p_string.length(); + if (len < slen) { + return 0; + } + String str; + if (p_from >= 0 && p_to >= 0) { + if (p_to == 0) { + p_to = len; + } else if (p_from >= p_to) { + return 0; + } + if (p_from == 0 && p_to == len) { + str = String(); + str.copy_from_unchecked(&c_str()[0], len); + } else { + str = substr(p_from, p_to - p_from); + } + } else { + return 0; + } + int c = 0; + int idx = -1; + do { + idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string); + if (idx != -1) { + str = str.substr(idx + slen, str.length() - slen); + ++c; + } + } while (idx != -1); + return c; +} + +int String::count(const String &p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, false); +} + +int String::countn(const String &p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, true); +} + bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const { int len = length(); diff --git a/core/ustring.h b/core/ustring.h index 8a52c53238..3eb5c47b3a 100644 --- a/core/ustring.h +++ b/core/ustring.h @@ -137,6 +137,7 @@ class String { void copy_from(const CharType &p_char); void copy_from_unchecked(const CharType *p_char, const int p_length); bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; + int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; public: enum { @@ -279,6 +280,9 @@ public: String to_upper() const; String to_lower() const; + int count(const String &p_string, int p_from = 0, int p_to = 0) const; + int countn(const String &p_string, int p_from = 0, int p_to = 0) const; + String left(int p_pos) const; String right(int p_pos) const; String dedent() const; diff --git a/core/variant_call.cpp b/core/variant_call.cpp index b637e745af..377cc889b8 100644 --- a/core/variant_call.cpp +++ b/core/variant_call.cpp @@ -237,6 +237,8 @@ struct _VariantCall { VCALL_LOCALMEM1R(String, casecmp_to); VCALL_LOCALMEM1R(String, nocasecmp_to); VCALL_LOCALMEM0R(String, length); + VCALL_LOCALMEM3R(String, count); + VCALL_LOCALMEM3R(String, countn); VCALL_LOCALMEM2R(String, substr); VCALL_LOCALMEM2R(String, find); VCALL_LOCALMEM1R(String, find_last); @@ -1502,6 +1504,9 @@ void register_variant_methods() { ADDFUNC2R(STRING, INT, String, find, STRING, "what", INT, "from", varray(0)); + ADDFUNC3R(STRING, INT, String, count, STRING, "what", INT, "from", INT, "to", varray(0, 0)); + ADDFUNC3R(STRING, INT, String, countn, STRING, "what", INT, "from", INT, "to", varray(0, 0)); + ADDFUNC1R(STRING, INT, String, find_last, STRING, "what", varray()); ADDFUNC2R(STRING, INT, String, findn, STRING, "what", INT, "from", varray(0)); ADDFUNC2R(STRING, INT, String, rfind, STRING, "what", INT, "from", varray(-1)); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index e513a44b1d..6dc3e35558 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -272,6 +272,34 @@ Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]+1[/code] if greater than, or [code]0[/code] if equal. </description> </method> + <method name="count"> + <return type="int"> + </return> + <argument index="0" name="what" type="String"> + </argument> + <argument index="1" name="from" type="int" default="0"> + </argument> + <argument index="2" name="to" type="int" default="0"> + </argument> + </argument> + <description> + Returns the number of occurrences of substring [code]what[/code] between [code]from[/code] and [code]to[/code] positions. If [code]from[/code] and [code]to[/code] equals 0 the whole string will be used. If only [code]to[/code] equals 0 the remained substring will be used. + </description> + </method> + <method name="countn"> + <return type="int"> + </return> + <argument index="0" name="what" type="String"> + </argument> + <argument index="1" name="from" type="int" default="0"> + </argument> + <argument index="2" name="to" type="int" default="0"> + </argument> + </argument> + <description> + Returns the number of occurrences of substring [code]what[/code] (ignoring case) between [code]from[/code] and [code]to[/code] positions. If [code]from[/code] and [code]to[/code] equals 0 the whole string will be used. If only [code]to[/code] equals 0 the remained substring will be used. + </description> + </method> <method name="dedent"> <return type="String"> </return> diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index d710b0959f..f3ba29a0e7 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -2365,7 +2365,7 @@ void RasterizerSceneGLES3::_add_geometry_with_material(RasterizerStorageGLES3::G if (p_depth_pass) { - if (has_blend_alpha || p_material->shader->spatial.uses_depth_texture || (has_base_alpha && p_material->shader->spatial.depth_draw_mode != RasterizerStorageGLES3::Shader::Spatial::DEPTH_DRAW_ALPHA_PREPASS) || p_material->shader->spatial.depth_draw_mode == RasterizerStorageGLES3::Shader::Spatial::DEPTH_DRAW_NEVER || p_material->shader->spatial.no_depth_test) + if (has_blend_alpha || p_material->shader->spatial.uses_depth_texture || (has_base_alpha && p_material->shader->spatial.depth_draw_mode != RasterizerStorageGLES3::Shader::Spatial::DEPTH_DRAW_ALPHA_PREPASS) || p_material->shader->spatial.depth_draw_mode == RasterizerStorageGLES3::Shader::Spatial::DEPTH_DRAW_NEVER || p_material->shader->spatial.no_depth_test || p_instance->cast_shadows == VS::SHADOW_CASTING_SETTING_OFF) return; //bye if (!p_material->shader->spatial.uses_alpha_scissor && !p_material->shader->spatial.writes_modelview_or_projection && !p_material->shader->spatial.uses_vertex && !p_material->shader->spatial.uses_discard && p_material->shader->spatial.depth_draw_mode != RasterizerStorageGLES3::Shader::Spatial::DEPTH_DRAW_ALPHA_PREPASS) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index b59d42a5f7..f1944bf63f 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2488,12 +2488,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { screenshot_timer->start(); } break; - case EDITOR_OPEN_SCREENSHOT: { - - bool is_checked = settings_menu->get_popup()->is_item_checked(settings_menu->get_popup()->get_item_index(EDITOR_OPEN_SCREENSHOT)); - settings_menu->get_popup()->set_item_checked(settings_menu->get_popup()->get_item_index(EDITOR_OPEN_SCREENSHOT), !is_checked); - EditorSettings::get_singleton()->set_project_metadata("screenshot_options", "open_screenshot", !is_checked); - } break; case SETTINGS_PICK_MAIN_SCENE: { file->set_mode(EditorFileDialog::MODE_OPEN_FILE); @@ -2553,7 +2547,7 @@ void EditorNode::_screenshot(bool p_use_utc) { String name = "editor_screenshot_" + OS::get_singleton()->get_iso_date_time(p_use_utc).replace(":", "") + ".png"; NodePath path = String("user://") + name; _save_screenshot(path); - if (EditorSettings::get_singleton()->get_project_metadata("screenshot_options", "open_screenshot", true)) { + if (EditorSettings::get_singleton()->get("interface/editor/automatically_open_screenshots")) { OS::get_singleton()->shell_open(String("file://") + ProjectSettings::get_singleton()->globalize_path(path)); } } @@ -6000,16 +5994,13 @@ EditorNode::EditorNode() { p->add_child(editor_layouts); editor_layouts->connect("id_pressed", this, "_layout_menu_option"); p->add_submenu_item(TTR("Editor Layout"), "Layouts"); + p->add_separator(); #ifdef OSX_ENABLED p->add_shortcut(ED_SHORTCUT("editor/take_screenshot", TTR("Take Screenshot"), KEY_MASK_CMD | KEY_F12), EDITOR_SCREENSHOT); #else p->add_shortcut(ED_SHORTCUT("editor/take_screenshot", TTR("Take Screenshot"), KEY_MASK_CTRL | KEY_F12), EDITOR_SCREENSHOT); #endif p->set_item_tooltip(p->get_item_count() - 1, TTR("Screenshots are stored in the Editor Data/Settings Folder.")); - p->add_check_shortcut(ED_SHORTCUT("editor/open_screenshot", TTR("Automatically Open Screenshots")), EDITOR_OPEN_SCREENSHOT); - bool is_open_screenshot = EditorSettings::get_singleton()->get_project_metadata("screenshot_options", "open_screenshot", true); - p->set_item_checked(p->get_item_count() - 1, is_open_screenshot); - p->set_item_tooltip(p->get_item_count() - 1, TTR("Open in an external image editor.")); #ifdef OSX_ENABLED p->add_shortcut(ED_SHORTCUT("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KEY_MASK_CMD | KEY_MASK_CTRL | KEY_F), SETTINGS_TOGGLE_FULLSCREEN); #else diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 223ca7a108..b4c1fc3463 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -345,6 +345,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/unfocused_low_processor_mode_sleep_usec", 50000); // 20 FPS hints["interface/editor/unfocused_low_processor_mode_sleep_usec"] = PropertyInfo(Variant::REAL, "interface/editor/unfocused_low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "1,100000,1", PROPERTY_USAGE_DEFAULT); _initial_set("interface/editor/separate_distraction_mode", false); + _initial_set("interface/editor/automatically_open_screenshots", true); _initial_set("interface/editor/hide_console_window", false); _initial_set("interface/editor/save_each_scene_on_quit", true); // Regression _initial_set("interface/editor/quit_confirmation", true); diff --git a/editor/import/editor_import_collada.cpp b/editor/import/editor_import_collada.cpp index e152827c63..1c4a8c43a9 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/editor_import_collada.cpp @@ -1175,35 +1175,33 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres morph = &collada.state.morph_controller_data_map[ngsource]; meshid = morph->mesh; - Vector<String> targets; - - morph->targets.has("MORPH_TARGET"); - String target = morph->targets["MORPH_TARGET"]; - bool valid = false; - if (morph->sources.has(target)) { - valid = true; - Vector<String> names = morph->sources[target].sarray; - for (int i = 0; i < names.size(); i++) { - - String meshid2 = names[i]; - if (collada.state.mesh_data_map.has(meshid2)) { - Ref<ArrayMesh> mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); - const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid2]; - mesh->set_name(meshdata.name); - Error err = _create_mesh_surfaces(false, mesh, ng2->material_map, meshdata, apply_xform, bone_remap, skin, NULL, Vector<Ref<ArrayMesh> >(), false); - ERR_FAIL_COND_V(err, err); - - morphs.push_back(mesh); - } else { - valid = false; + if (morph->targets.has("MORPH_TARGET")) { + String target = morph->targets["MORPH_TARGET"]; + bool valid = false; + if (morph->sources.has(target)) { + valid = true; + Vector<String> names = morph->sources[target].sarray; + for (int i = 0; i < names.size(); i++) { + + String meshid2 = names[i]; + if (collada.state.mesh_data_map.has(meshid2)) { + Ref<ArrayMesh> mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); + const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid2]; + mesh->set_name(meshdata.name); + Error err = _create_mesh_surfaces(false, mesh, ng2->material_map, meshdata, apply_xform, bone_remap, skin, NULL, Vector<Ref<ArrayMesh> >(), false); + ERR_FAIL_COND_V(err, err); + + morphs.push_back(mesh); + } else { + valid = false; + } } } - } - - if (!valid) - morphs.clear(); - ngsource = ""; + if (!valid) + morphs.clear(); + ngsource = ""; + } } if (ngsource != "") { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 53b67c46b0..8e6a56a929 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -435,6 +435,7 @@ Node *ResourceImporterScene::_fix_node(Node *p_node, Node *p_root, Map<Ref<Mesh> Object::cast_to<Spatial>(sb)->set_transform(Object::cast_to<Spatial>(p_node)->get_transform()); p_node->replace_by(sb); memdelete(p_node); + p_node = NULL; CollisionShape *colshape = memnew(CollisionShape); if (empty_draw_type == "CUBE") { BoxShape *boxShape = memnew(BoxShape); diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index e582f6ded2..1fc6dae978 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -201,6 +201,8 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { ERR_FAIL_COND(ps.is_null()); Node *scene = ps->instance(); + ERR_FAIL_COND(!scene); + _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE); memdelete(scene); diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index b475eee920..52fadf1e71 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -69,7 +69,7 @@ #define FREELOOK_SPEED_MULTIPLIER 1.08 #define MIN_Z 0.01 -#define MAX_Z 10000 +#define MAX_Z 1000000.0 #define MIN_FOV 0.01 #define MAX_FOV 179 @@ -645,7 +645,7 @@ bool SpatialEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_hig Vector3 r; - if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * 10000.0, grabber_pos, grabber_radius, &r)) { + if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { float d = r.distance_to(ray_pos); if (d < col_d) { col_d = d; @@ -753,7 +753,7 @@ bool SpatialEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_hig Vector3 r; - if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * 10000.0, grabber_pos, grabber_radius, &r)) { + if (Geometry::segment_intersects_sphere(ray_pos, ray_pos + ray * MAX_Z, grabber_pos, grabber_radius, &r)) { float d = r.distance_to(ray_pos); if (d < col_d) { col_d = d; @@ -1835,8 +1835,11 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL); _update_name(); } - if (ED_IS_SHORTCUT("spatial_editor/align_selection_with_view", p_event)) { - _menu_option(VIEW_ALIGN_SELECTION_WITH_VIEW); + if (ED_IS_SHORTCUT("spatial_editor/align_transform_with_view", p_event)) { + _menu_option(VIEW_ALIGN_TRANSFORM_WITH_VIEW); + } + if (ED_IS_SHORTCUT("spatial_editor/align_rotation_with_view", p_event)) { + _menu_option(VIEW_ALIGN_ROTATION_WITH_VIEW); } if (ED_IS_SHORTCUT("spatial_editor/insert_anim_key", p_event)) { if (!get_selected_count() || _edit.mode != TRANSFORM_NONE) @@ -2562,7 +2565,7 @@ void SpatialEditorViewport::_menu_option(int p_option) { focus_selection(); } break; - case VIEW_ALIGN_SELECTION_WITH_VIEW: { + case VIEW_ALIGN_TRANSFORM_WITH_VIEW: { if (!get_selected_count()) break; @@ -2571,7 +2574,8 @@ void SpatialEditorViewport::_menu_option(int p_option) { List<Node *> &selection = editor_selection->get_selected_node_list(); - undo_redo->create_action(TTR("Align with View")); + undo_redo->create_action(TTR("Align Transform with View")); + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { Spatial *sp = Object::cast_to<Spatial>(E->get()); @@ -2595,6 +2599,34 @@ void SpatialEditorViewport::_menu_option(int p_option) { undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); } undo_redo->commit_action(); + focus_selection(); + + } break; + case VIEW_ALIGN_ROTATION_WITH_VIEW: { + + if (!get_selected_count()) + break; + + Transform camera_transform = camera->get_global_transform(); + + List<Node *> &selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Align Rotation with View")); + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + + Spatial *sp = Object::cast_to<Spatial>(E->get()); + if (!sp) + continue; + + SpatialEditorSelectedItem *se = editor_selection->get_node_editor_data<SpatialEditorSelectedItem>(sp); + if (!se) + continue; + + undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_rotation()); + undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation()); + } + undo_redo->commit_action(); + } break; case VIEW_ENVIRONMENT: { @@ -3544,7 +3576,8 @@ SpatialEditorViewport::SpatialEditorViewport(SpatialEditor *p_spatial_editor, Ed view_menu->get_popup()->add_separator(); view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_origin"), VIEW_CENTER_TO_ORIGIN); view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/focus_selection"), VIEW_CENTER_TO_SELECTION); - view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_selection_with_view"), VIEW_ALIGN_SELECTION_WITH_VIEW); + view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_transform_with_view"), VIEW_ALIGN_TRANSFORM_WITH_VIEW); + view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/align_rotation_with_view"), VIEW_ALIGN_ROTATION_WITH_VIEW); view_menu->get_popup()->connect("id_pressed", this, "_menu_option"); view_menu->set_disable_shortcuts(true); @@ -5601,7 +5634,8 @@ SpatialEditor::SpatialEditor(EditorNode *p_editor) { ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), KEY_K); ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), KEY_O); ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), KEY_F); - ED_SHORTCUT("spatial_editor/align_selection_with_view", TTR("Align Selection With View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_F); + ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_M); + ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_F); ED_SHORTCUT("spatial_editor/tool_select", TTR("Tool Select"), KEY_Q); ED_SHORTCUT("spatial_editor/tool_move", TTR("Tool Move"), KEY_W); diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index 1a32d6e047..dde402c0ff 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -153,7 +153,8 @@ class SpatialEditorViewport : public Control { VIEW_REAR, VIEW_CENTER_TO_ORIGIN, VIEW_CENTER_TO_SELECTION, - VIEW_ALIGN_SELECTION_WITH_VIEW, + VIEW_ALIGN_TRANSFORM_WITH_VIEW, + VIEW_ALIGN_ROTATION_WITH_VIEW, VIEW_PERSPECTIVE, VIEW_ENVIRONMENT, VIEW_ORTHOGONAL, diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index e013aae164..48a587d4db 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -52,6 +52,10 @@ #include "scene/gui/texture_rect.h" #include "scene/gui/tool_button.h" +static inline String get_project_key_from_path(const String &dir) { + return dir.replace("/", "::"); +} + class ProjectDialog : public ConfirmationDialog { GDCLASS(ProjectDialog, ConfirmationDialog); @@ -606,7 +610,7 @@ private: dir = dir.replace("\\", "/"); if (dir.ends_with("/")) dir = dir.substr(0, dir.length() - 1); - String proj = dir.replace("/", "::"); + String proj = get_project_key_from_path(dir); EditorSettings::get_singleton()->set("projects/" + proj, dir); EditorSettings::get_singleton()->save(); @@ -918,596 +922,960 @@ public: } }; -struct ProjectItem { - String project; - String project_name; - String path; - String conf; - String icon; - String main_scene; - uint64_t last_modified; - bool favorite; - bool grayed; - ProjectListFilter::FilterOption filter_order_option; - ProjectItem() {} - ProjectItem(const String &p_project, const String &p_name, const String &p_path, const String &p_conf, const String &p_icon, const String &p_main_scene, uint64_t p_last_modified, bool p_favorite = false, bool p_grayed = false, const ProjectListFilter::FilterOption p_filter_order_option = ProjectListFilter::FILTER_NAME) { - project = p_project; - project_name = p_name; - path = p_path; - conf = p_conf; - icon = p_icon; - main_scene = p_main_scene; - last_modified = p_last_modified; - favorite = p_favorite; - grayed = p_grayed; - filter_order_option = p_filter_order_option; - } - _FORCE_INLINE_ bool operator<(const ProjectItem &l) const { - switch (filter_order_option) { +class ProjectListItemControl : public HBoxContainer { + GDCLASS(ProjectListItemControl, HBoxContainer) +public: + TextureButton *favorite_button; + TextureRect *icon; + bool icon_needs_reload; + + ProjectListItemControl() { + favorite_button = NULL; + icon = NULL; + icon_needs_reload = true; + } + + void set_is_favorite(bool fav) { + favorite_button->set_modulate(fav ? Color(1, 1, 1, 1) : Color(1, 1, 1, 0.2)); + } +}; + +class ProjectList : public ScrollContainer { + GDCLASS(ProjectList, ScrollContainer) +public: + static const char *SIGNAL_SELECTION_CHANGED; + static const char *SIGNAL_PROJECT_ASK_OPEN; + + // Can often be passed by copy + struct Item { + String project_key; + String project_name; + String path; + String icon; + String main_scene; + uint64_t last_modified; + bool favorite; + bool grayed; + bool missing; + int version; + + ProjectListItemControl *control; + + Item() {} + + Item(const String &p_project, + const String &p_name, + const String &p_path, + const String &p_icon, + const String &p_main_scene, + uint64_t p_last_modified, + bool p_favorite, + bool p_grayed, + bool p_missing, + int p_version) { + + project_key = p_project; + project_name = p_name; + path = p_path; + icon = p_icon; + main_scene = p_main_scene; + last_modified = p_last_modified; + favorite = p_favorite; + grayed = p_grayed; + missing = p_missing; + version = p_version; + control = NULL; + } + + _FORCE_INLINE_ bool operator==(const Item &l) const { + return project_key == l.project_key; + } + }; + + ProjectList(); + ~ProjectList(); + + void load_projects(); + void set_search_term(String p_search_term); + void set_filter_option(ProjectListFilter::FilterOption p_option); + void set_order_option(ProjectListFilter::FilterOption p_option); + void sort_projects(); + int get_project_count() const; + void select_project(int p_index); + void erase_selected_projects(); + Vector<Item> get_selected_projects() const; + const Set<String> &get_selected_project_keys() const; + void ensure_project_visible(int p_index); + int get_single_selected_index() const; + bool is_any_project_missing() const; + void erase_missing_projects(); + int refresh_project(const String &dir_path); + +private: + static void _bind_methods(); + void _notification(int p_what); + + void _panel_draw(Node *p_hb); + void _panel_input(const Ref<InputEvent> &p_ev, Node *p_hb); + void _favorite_pressed(Node *p_hb); + void _show_project(const String &p_path); + + void select_range(int p_begin, int p_end); + void toggle_select(int p_index); + void create_project_item_control(int p_index); + void remove_project(int p_index, bool p_update_settings); + void update_icons_async(); + void load_project_icon(int p_index); + + static void load_project_data(const String &p_property_key, Item &p_item, bool p_favorite); + + String _search_term; + ProjectListFilter::FilterOption _filter_option; + ProjectListFilter::FilterOption _order_option; + Set<String> _selected_project_keys; + String _last_clicked; // Project key + VBoxContainer *_scroll_children; + int _icon_load_index; + + Vector<Item> _projects; +}; + +struct ProjectListComparator { + ProjectListFilter::FilterOption order_option; + + // operator< + _FORCE_INLINE_ bool operator()(const ProjectList::Item &a, const ProjectList::Item &b) const { + if (a.favorite && !b.favorite) { + return true; + } + if (b.favorite && !a.favorite) { + return false; + } + switch (order_option) { case ProjectListFilter::FILTER_PATH: - return project < l.project; + return a.project_key < b.project_key; case ProjectListFilter::FILTER_MODIFIED: - return last_modified > l.last_modified; + return a.last_modified > b.last_modified; default: - return project_name < l.project_name; + return a.project_name < b.project_name; } } - _FORCE_INLINE_ bool operator==(const ProjectItem &l) const { return project == l.project; } }; -void ProjectManager::_notification(int p_what) { +ProjectList::ProjectList() { + _filter_option = ProjectListFilter::FILTER_NAME; + _order_option = ProjectListFilter::FILTER_MODIFIED; - switch (p_what) { - case NOTIFICATION_ENTER_TREE: { + _scroll_children = memnew(VBoxContainer); + _scroll_children->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(_scroll_children); - Engine::get_singleton()->set_editor_hint(false); - } break; - case NOTIFICATION_READY: { + _icon_load_index = 0; +} - if (scroll_children->get_child_count() == 0 && StreamPeerSSL::is_available()) - open_templates->popup_centered_minsize(); - } break; - case NOTIFICATION_VISIBILITY_CHANGED: { +ProjectList::~ProjectList() { +} - set_process_unhandled_input(is_visible_in_tree()); - } break; - case NOTIFICATION_WM_QUIT_REQUEST: { +void ProjectList::update_icons_async() { + _icon_load_index = 0; + set_process(true); +} - _dim_window(); - } break; +void ProjectList::_notification(int p_what) { + if (p_what == NOTIFICATION_PROCESS) { + + // Load icons as a coroutine to speed up launch when you have hundreds of projects + if (_icon_load_index < _projects.size()) { + Item &item = _projects.write[_icon_load_index]; + if (item.control->icon_needs_reload) { + load_project_icon(_icon_load_index); + } + _icon_load_index++; + + } else { + set_process(false); + } } } -void ProjectManager::_dim_window() { - - // This method must be called before calling `get_tree()->quit()`. - // Otherwise, its effect won't be visible +void ProjectList::load_project_icon(int p_index) { + Item &item = _projects.write[p_index]; + + Ref<Texture> default_icon = get_icon("DefaultProjectIcon", "EditorIcons"); + Ref<Texture> icon; + if (item.icon != "") { + Ref<Image> img; + img.instance(); + Error err = img->load(item.icon.replace_first("res://", item.path + "/")); + if (err == OK) { + + img->resize(default_icon->get_width(), default_icon->get_height()); + Ref<ImageTexture> it = memnew(ImageTexture); + it->create_from_image(img); + icon = it; + } + } + if (icon.is_null()) { + icon = default_icon; + } - // Dim the project manager window while it's quitting to make it clearer that it's busy. - // No transition is applied, as the effect needs to be visible immediately - float c = 1.0f - float(EDITOR_GET("interface/editor/dim_amount")); - Color dim_color = Color(c, c, c); - gui_base->set_modulate(dim_color); + item.control->icon->set_texture(icon); + item.control->icon_needs_reload = false; } -void ProjectManager::_panel_draw(Node *p_hb) { +void ProjectList::load_project_data(const String &p_property_key, Item &p_item, bool p_favorite) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(p_hb); + String path = EditorSettings::get_singleton()->get(p_property_key); + String conf = path.plus_file("project.godot"); + bool grayed = false; + bool missing = false; - hb->draw_line(Point2(0, hb->get_size().y + 1), Point2(hb->get_size().x - 10, hb->get_size().y + 1), get_color("guide_color", "Tree")); + Ref<ConfigFile> cf = memnew(ConfigFile); + Error cf_err = cf->load(conf); - if (selected_list.has(hb->get_meta("name"))) { - hb->draw_style_box(gui_base->get_stylebox("selected", "Tree"), Rect2(Point2(), hb->get_size() - Size2(10, 0) * EDSCALE)); + int config_version = 0; + String project_name = TTR("Unnamed Project"); + if (cf_err == OK) { + String cf_project_name = static_cast<String>(cf->get_value("application", "config/name", "")); + if (cf_project_name != "") + project_name = cf_project_name.xml_unescape(); + config_version = (int)cf->get_value("", "config_version", 0); } + + if (config_version > ProjectSettings::CONFIG_VERSION) { + // Comes from an incompatible (more recent) Godot version, grey it out + grayed = true; + } + + String icon = cf->get_value("application", "config/icon", ""); + String main_scene = cf->get_value("application", "run/main_scene", ""); + + uint64_t last_modified = 0; + if (FileAccess::exists(conf)) { + last_modified = FileAccess::get_modified_time(conf); + + String fscache = path.plus_file(".fscache"); + if (FileAccess::exists(fscache)) { + uint64_t cache_modified = FileAccess::get_modified_time(fscache); + if (cache_modified > last_modified) + last_modified = cache_modified; + } + } else { + grayed = true; + missing = true; + print_line("Project is missing: " + conf); + } + + String project_key = p_property_key.get_slice("/", 1); + + p_item = Item(project_key, project_name, path, icon, main_scene, last_modified, p_favorite, grayed, missing, config_version); } -void ProjectManager::_update_project_buttons() { - for (int i = 0; i < scroll_children->get_child_count(); i++) { +void ProjectList::load_projects() { + // This is a full, hard reload of the list. Don't call this unless really required, it's expensive. + // If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons. - CanvasItem *item = Object::cast_to<CanvasItem>(scroll_children->get_child(i)); - item->update(); + // Clear whole list + for (int i = 0; i < _projects.size(); ++i) { + Item &project = _projects.write[i]; + CRASH_COND(project.control == NULL); + memdelete(project.control); // Why not queue_free()? } + _projects.clear(); + _last_clicked = ""; + _selected_project_keys.clear(); - bool empty_selection = selected_list.empty(); - erase_btn->set_disabled(empty_selection); - open_btn->set_disabled(empty_selection); - rename_btn->set_disabled(empty_selection); - run_btn->set_disabled(empty_selection); + // Load data + // TODO Would be nice to change how projects and favourites are stored... it complicates things a bit. + // Use a dictionary associating project path to metadata (like is_favorite). + + List<PropertyInfo> properties; + EditorSettings::get_singleton()->get_property_list(&properties); - bool missing_projects = false; - Map<String, String> list_all_projects; - for (int i = 0; i < scroll_children->get_child_count(); i++) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (hb) { - list_all_projects.insert(hb->get_meta("name"), hb->get_meta("main_scene")); + Set<String> favorites; + // Find favourites... + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + String property_key = E->get().name; + if (property_key.begins_with("favorite_projects/")) { + favorites.insert(property_key); } } - for (Map<String, String>::Element *E = list_all_projects.front(); E; E = E->next()) { - String project_name = E->key().replace(":::", ":/").replace("::", "/") + "/project.godot"; - if (!FileAccess::exists(project_name)) { - missing_projects = true; - break; - } + + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + // This is actually something like "projects/C:::Documents::Godot::Projects::MyGame" + String property_key = E->get().name; + if (!property_key.begins_with("projects/")) + continue; + + String project_key = property_key.get_slice("/", 1); + bool favorite = favorites.has("favorite_projects/" + project_key); + + Item item; + load_project_data(property_key, item, favorite); + + _projects.push_back(item); + } + + // Create controls + for (int i = 0; i < _projects.size(); ++i) { + create_project_item_control(i); } - erase_missing_btn->set_visible(missing_projects); + sort_projects(); + + set_v_scroll(0); + + update_icons_async(); } -void ProjectManager::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) { +void ProjectList::create_project_item_control(int p_index) { - Ref<InputEventMouseButton> mb = p_ev; + // Will be added last in the list, so make sure indexes match + ERR_FAIL_COND(p_index != _scroll_children->get_child_count()); - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + Item &item = _projects.write[p_index]; + ERR_FAIL_COND(item.control != NULL); // Already created - String clicked = p_hb->get_meta("name"); - String clicked_main_scene = p_hb->get_meta("main_scene"); + Ref<Texture> favorite_icon = get_icon("Favorites", "EditorIcons"); + Color font_color = get_color("font_color", "Tree"); + + ProjectListItemControl *hb = memnew(ProjectListItemControl); + hb->connect("draw", this, "_panel_draw", varray(hb)); + hb->connect("gui_input", this, "_panel_input", varray(hb)); + hb->add_constant_override("separation", 10 * EDSCALE); + + VBoxContainer *favorite_box = memnew(VBoxContainer); + favorite_box->set_name("FavoriteBox"); + TextureButton *favorite = memnew(TextureButton); + favorite->set_name("FavoriteButton"); + favorite->set_normal_texture(favorite_icon); + favorite->connect("pressed", this, "_favorite_pressed", varray(hb)); + favorite_box->add_child(favorite); + favorite_box->set_alignment(BoxContainer::ALIGN_CENTER); + hb->add_child(favorite_box); + hb->favorite_button = favorite; + hb->set_is_favorite(item.favorite); + + TextureRect *tf = memnew(TextureRect); + tf->set_texture(get_icon("DefaultProjectIcon", "EditorIcons")); + hb->add_child(tf); + hb->icon = tf; - if (mb->get_shift() && selected_list.size() > 0 && last_clicked != "" && clicked != last_clicked) { + VBoxContainer *vb = memnew(VBoxContainer); + if (item.grayed) + vb->set_modulate(Color(0.5, 0.5, 0.5)); + vb->set_h_size_flags(SIZE_EXPAND_FILL); + hb->add_child(vb); + Control *ec = memnew(Control); + ec->set_custom_minimum_size(Size2(0, 1)); + ec->set_mouse_filter(MOUSE_FILTER_PASS); + vb->add_child(ec); + Label *title = memnew(Label(item.project_name)); + title->add_font_override("font", get_font("title", "EditorFonts")); + title->add_color_override("font_color", font_color); + title->set_clip_text(true); + vb->add_child(title); + + HBoxContainer *path_hb = memnew(HBoxContainer); + path_hb->set_h_size_flags(SIZE_EXPAND_FILL); + vb->add_child(path_hb); + + Button *show = memnew(Button); + show->set_icon(get_icon("Load", "EditorIcons")); // Folder icon + show->set_flat(true); + show->set_modulate(Color(1, 1, 1, 0.5)); + path_hb->add_child(show); + show->connect("pressed", this, "_show_project", varray(item.path)); + show->set_tooltip(TTR("Show in File Manager")); + + Label *fpath = memnew(Label(item.path)); + path_hb->add_child(fpath); + fpath->set_h_size_flags(SIZE_EXPAND_FILL); + fpath->set_modulate(Color(1, 1, 1, 0.5)); + fpath->add_color_override("font_color", font_color); + fpath->set_clip_text(true); + + _scroll_children->add_child(hb); + item.control = hb; +} - int clicked_id = -1; - int last_clicked_id = -1; - for (int i = 0; i < scroll_children->get_child_count(); i++) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (!hb) continue; - if (hb->get_meta("name") == clicked) clicked_id = i; - if (hb->get_meta("name") == last_clicked) last_clicked_id = i; - } +void ProjectList::set_search_term(String p_search_term) { + _search_term = p_search_term; +} - if (last_clicked_id != -1 && clicked_id != -1) { - int min = clicked_id < last_clicked_id ? clicked_id : last_clicked_id; - int max = clicked_id > last_clicked_id ? clicked_id : last_clicked_id; - for (int i = 0; i < scroll_children->get_child_count(); ++i) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (!hb) continue; - if (i != clicked_id && (i < min || i > max) && !mb->get_control()) { - selected_list.erase(hb->get_meta("name")); - } else if (i >= min && i <= max) { - selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene")); - } - } - } +void ProjectList::set_filter_option(ProjectListFilter::FilterOption p_option) { + if (_filter_option != p_option) { + _filter_option = p_option; + } +} + +void ProjectList::set_order_option(ProjectListFilter::FilterOption p_option) { + if (_order_option != p_option) { + _order_option = p_option; + EditorSettings::get_singleton()->set("project_manager/sorting_order", (int)_filter_option); + EditorSettings::get_singleton()->save(); + } +} - } else if (selected_list.has(clicked) && mb->get_control()) { +void ProjectList::sort_projects() { - selected_list.erase(clicked); + SortArray<Item, ProjectListComparator> sorter; + sorter.compare.order_option = _order_option; + sorter.sort(_projects.ptrw(), _projects.size()); - } else { + for (int i = 0; i < _projects.size(); ++i) { + Item &item = _projects.write[i]; - last_clicked = clicked; - if (mb->get_control() || selected_list.size() == 0) { - selected_list.insert(clicked, clicked_main_scene); - } else { - selected_list.clear(); - selected_list.insert(clicked, clicked_main_scene); + bool visible = true; + if (_search_term != "") { + if (_filter_option == ProjectListFilter::FILTER_PATH) { + visible = item.path.findn(_search_term) != -1; + } else if (_filter_option == ProjectListFilter::FILTER_NAME) { + visible = item.project_name.findn(_search_term) != -1; } } - _update_project_buttons(); + item.control->set_visible(visible); + } - if (mb->is_doubleclick()) - _open_selected_projects_ask(); //open if doubleclicked + for (int i = 0; i < _projects.size(); ++i) { + Item &item = _projects.write[i]; + if (item.control->is_visible()) { + item.control->get_parent()->move_child(item.control, i); + } } + + // Rewind the coroutine because order of projects changed + update_icons_async(); } -void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) { +const Set<String> &ProjectList::get_selected_project_keys() const { + // Faster if that's all you need + return _selected_project_keys; +} - Ref<InputEventKey> k = p_ev; +Vector<ProjectList::Item> ProjectList::get_selected_projects() const { + Vector<Item> items; + if (_selected_project_keys.size() == 0) { + return items; + } + items.resize(_selected_project_keys.size()); + int j = 0; + for (int i = 0; i < _projects.size(); ++i) { + const Item &item = _projects[i]; + if (_selected_project_keys.has(item.project_key)) { + items.write[j++] = item; + } + } + ERR_FAIL_COND_V(j != items.size(), items); + return items; +} - if (k.is_valid()) { +void ProjectList::ensure_project_visible(int p_index) { + const Item &item = _projects[p_index]; - if (!k->is_pressed()) - return; + int item_top = item.control->get_position().y; + int item_bottom = item.control->get_position().y + item.control->get_size().y; - if (tabs->get_current_tab() != 0) - return; + if (item_top < get_v_scroll()) { + set_v_scroll(item_top); - bool scancode_handled = true; + } else if (item_bottom > get_v_scroll() + get_size().y) { + set_v_scroll(item_bottom - get_size().y); + } +} - switch (k->get_scancode()) { +int ProjectList::get_single_selected_index() const { + if (_selected_project_keys.size() == 0) { + // Default selection + return 0; + } + String key; + if (_selected_project_keys.size() == 1) { + // Only one selected + key = _selected_project_keys.front()->get(); + } else { + // Multiple selected, consider the last clicked one as "main" + key = _last_clicked; + } + for (int i = 0; i < _projects.size(); ++i) { + if (_projects[i].project_key == key) { + return i; + } + } + return 0; +} - case KEY_ENTER: { +void ProjectList::remove_project(int p_index, bool p_update_settings) { + const Item item = _projects[p_index]; // Take a copy - _open_selected_projects_ask(); - } break; - case KEY_DELETE: { + _selected_project_keys.erase(item.project_key); - _erase_project(); - } break; - case KEY_HOME: { + if (_last_clicked == item.project_key) { + _last_clicked = ""; + } - for (int i = 0; i < scroll_children->get_child_count(); i++) { + memdelete(item.control); + _projects.remove(p_index); - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (hb) { - selected_list.clear(); - selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene")); - scroll->set_v_scroll(0); - _update_project_buttons(); - break; - } - } + if (p_update_settings) { + EditorSettings::get_singleton()->erase("projects/" + item.project_key); + EditorSettings::get_singleton()->erase("favorite_projects/" + item.project_key); + // Not actually saving the file, in case you are doing more changes to settings + } +} - } break; - case KEY_END: { +bool ProjectList::is_any_project_missing() const { + for (int i = 0; i < _projects.size(); ++i) { + if (_projects[i].missing) { + return true; + } + } + return false; +} - for (int i = scroll_children->get_child_count() - 1; i >= 0; i--) { +void ProjectList::erase_missing_projects() { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (hb) { - selected_list.clear(); - selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene")); - scroll->set_v_scroll(scroll_children->get_size().y); - _update_project_buttons(); - break; - } - } + if (_projects.empty()) { + return; + } - } break; - case KEY_UP: { + int deleted_count = 0; + int remaining_count = 0; - if (k->get_shift()) - break; + for (int i = 0; i < _projects.size(); ++i) { + const Item &item = _projects[i]; + + if (item.missing) { + remove_project(i, true); + --i; + ++deleted_count; - if (selected_list.size()) { + } else { + ++remaining_count; + } + } - bool found = false; + print_line("Removed " + itos(deleted_count) + " projects from the list, remaining " + itos(remaining_count) + " projects"); - for (int i = scroll_children->get_child_count() - 1; i >= 0; i--) { + EditorSettings::get_singleton()->save(); +} - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (!hb) continue; +int ProjectList::refresh_project(const String &dir_path) { + // Reads editor settings and reloads information about a specific project. + // If it wasn't loaded and should be in the list, it is added (i.e new project). + // If it isn't in the list anymore, it is removed. + // If it is in the list but doesn't exist anymore, it is marked as missing. - String current = hb->get_meta("name"); + String project_key = get_project_key_from_path(dir_path); - if (found) { - selected_list.clear(); - selected_list.insert(current, hb->get_meta("main_scene")); + // Read project manager settings + bool is_favourite = false; + bool should_be_in_list = false; + String property_key = "projects/" + project_key; + { + List<PropertyInfo> properties; + EditorSettings::get_singleton()->get_property_list(&properties); + String favorite_property_key = "favorite_projects/" + project_key; + + bool found = false; + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + String prop = E->get().name; + if (!found && prop == property_key) { + found = true; + } else if (!is_favourite && prop == favorite_property_key) { + is_favourite = true; + } + } - int offset_diff = scroll->get_v_scroll() - hb->get_position().y; + should_be_in_list = found; + } - if (offset_diff > 0) - scroll->set_v_scroll(scroll->get_v_scroll() - offset_diff); + bool was_selected = _selected_project_keys.has(project_key); - _update_project_buttons(); + // Remove item in any case + for (int i = 0; i < _projects.size(); ++i) { + const Item &existing_item = _projects[i]; + if (existing_item.path == dir_path) { + remove_project(i, false); + break; + } + } - break; + int index = -1; + if (should_be_in_list) { + // Recreate it with updated info - } else if (current == selected_list.back()->key()) { + Item item; + load_project_data(property_key, item, is_favourite); - found = true; - } - } + _projects.push_back(item); + create_project_item_control(_projects.size() - 1); - break; + sort_projects(); + + for (int i = 0; i < _projects.size(); ++i) { + if (_projects[i].project_key == project_key) { + if (was_selected) { + select_project(i); + ensure_project_visible(i); } - FALLTHROUGH; + load_project_icon(i); + index = i; + break; } - case KEY_DOWN: { + } + } - if (k->get_shift()) - break; + return index; +} - bool found = selected_list.empty(); +int ProjectList::get_project_count() const { + return _projects.size(); +} - for (int i = 0; i < scroll_children->get_child_count(); i++) { +void ProjectList::select_project(int p_index) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (!hb) continue; + Vector<Item> previous_selected_items = get_selected_projects(); + _selected_project_keys.clear(); - String current = hb->get_meta("name"); + for (int i = 0; i < previous_selected_items.size(); ++i) { + previous_selected_items[i].control->update(); + } - if (found) { - selected_list.clear(); - selected_list.insert(current, hb->get_meta("main_scene")); + toggle_select(p_index); +} - int last_y_visible = scroll->get_v_scroll() + scroll->get_size().y; - int offset_diff = (hb->get_position().y + hb->get_size().y) - last_y_visible; +inline void sort(int &a, int &b) { + if (a > b) { + int temp = a; + a = b; + b = temp; + } +} - if (offset_diff > 0) - scroll->set_v_scroll(scroll->get_v_scroll() + offset_diff); +void ProjectList::select_range(int p_begin, int p_end) { + sort(p_begin, p_end); + select_project(p_begin); + for (int i = p_begin + 1; i <= p_end; ++i) { + toggle_select(i); + } +} - _update_project_buttons(); +void ProjectList::toggle_select(int p_index) { + Item &item = _projects.write[p_index]; + if (_selected_project_keys.has(item.project_key)) { + _selected_project_keys.erase(item.project_key); + } else { + _selected_project_keys.insert(item.project_key); + } + item.control->update(); +} - break; +void ProjectList::erase_selected_projects() { - } else if (current == selected_list.back()->key()) { + if (_selected_project_keys.size() == 0) { + return; + } - found = true; - } + for (int i = 0; i < _projects.size(); ++i) { + Item &item = _projects.write[i]; + if (_selected_project_keys.has(item.project_key) && item.control->is_visible()) { + + EditorSettings::get_singleton()->erase("projects/" + item.project_key); + EditorSettings::get_singleton()->erase("favorite_projects/" + item.project_key); + + memdelete(item.control); + _projects.remove(i); + --i; + } + } + + EditorSettings::get_singleton()->save(); + + _selected_project_keys.clear(); + _last_clicked = ""; +} + +// Draws selected project highlight +void ProjectList::_panel_draw(Node *p_hb) { + Control *hb = Object::cast_to<Control>(p_hb); + + hb->draw_line(Point2(0, hb->get_size().y + 1), Point2(hb->get_size().x - 10, hb->get_size().y + 1), get_color("guide_color", "Tree")); + + String key = _projects[p_hb->get_index()].project_key; + + if (_selected_project_keys.has(key)) { + hb->draw_style_box(get_stylebox("selected", "Tree"), Rect2(Point2(), hb->get_size() - Size2(10, 0) * EDSCALE)); + } +} + +// Input for each item in the list +void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) { + + Ref<InputEventMouseButton> mb = p_ev; + int clicked_index = p_hb->get_index(); + const Item &clicked_project = _projects[clicked_index]; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { + + if (mb->get_shift() && _selected_project_keys.size() > 0 && _last_clicked != "" && clicked_project.project_key != _last_clicked) { + + int anchor_index = -1; + for (int i = 0; i < _projects.size(); ++i) { + const Item &p = _projects[i]; + if (p.project_key == _last_clicked) { + anchor_index = p.control->get_index(); + break; } + } + CRASH_COND(anchor_index == -1); + select_range(anchor_index, clicked_index); - } break; - case KEY_F: { - if (k->get_command()) - this->project_filter->search_box->grab_focus(); - else - scancode_handled = false; - } break; - default: { - scancode_handled = false; - } break; + } else if (mb->get_control()) { + toggle_select(clicked_index); + + } else { + _last_clicked = clicked_project.project_key; + select_project(clicked_index); } - if (scancode_handled) { - accept_event(); + emit_signal(SIGNAL_SELECTION_CHANGED); + + if (mb->is_doubleclick()) { + emit_signal(SIGNAL_PROJECT_ASK_OPEN); } } } -void ProjectManager::_favorite_pressed(Node *p_hb) { +void ProjectList::_favorite_pressed(Node *p_hb) { + + ProjectListItemControl *control = Object::cast_to<ProjectListItemControl>(p_hb); + + int index = control->get_index(); + Item item = _projects.write[index]; // Take copy - String clicked = p_hb->get_meta("name"); - bool favorite = !p_hb->get_meta("favorite"); - String proj = clicked.replace(":::", ":/"); - proj = proj.replace("::", "/"); + item.favorite = !item.favorite; - if (favorite) { - EditorSettings::get_singleton()->set("favorite_projects/" + clicked, proj); + if (item.favorite) { + EditorSettings::get_singleton()->set("favorite_projects/" + item.project_key, item.path); } else { - EditorSettings::get_singleton()->erase("favorite_projects/" + clicked); + EditorSettings::get_singleton()->erase("favorite_projects/" + item.project_key); } EditorSettings::get_singleton()->save(); - call_deferred("_load_recent_projects"); -} -void ProjectManager::_load_recent_projects() { + _projects.write[index] = item; + + control->set_is_favorite(item.favorite); - ProjectListFilter::FilterOption filter_option = project_filter->get_filter_option(); - String search_term = project_filter->get_search_term(); + sort_projects(); - while (scroll_children->get_child_count() > 0) { - memdelete(scroll_children->get_child(0)); + if (item.favorite) { + for (int i = 0; i < _projects.size(); ++i) { + if (_projects[i].project_key == item.project_key) { + ensure_project_visible(i); + break; + } + } } +} - Map<String, String> selected_list_copy = selected_list; +void ProjectList::_show_project(const String &p_path) { - List<PropertyInfo> properties; - EditorSettings::get_singleton()->get_property_list(&properties); + OS::get_singleton()->shell_open(String("file://") + p_path); +} - Color font_color = gui_base->get_color("font_color", "Tree"); +const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed"; +const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open"; - ProjectListFilter::FilterOption filter_order_option = project_order_filter->get_filter_option(); - EditorSettings::get_singleton()->set("project_manager/sorting_order", (int)filter_order_option); +void ProjectList::_bind_methods() { - List<ProjectItem> projects; - List<ProjectItem> favorite_projects; + ClassDB::bind_method("_panel_draw", &ProjectList::_panel_draw); + ClassDB::bind_method("_panel_input", &ProjectList::_panel_input); + ClassDB::bind_method("_favorite_pressed", &ProjectList::_favorite_pressed); + ClassDB::bind_method("_show_project", &ProjectList::_show_project); + //ClassDB::bind_method("_unhandled_input", &ProjectList::_unhandled_input); - for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED)); + ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN)); +} - String _name = E->get().name; - if (!_name.begins_with("projects/") && !_name.begins_with("favorite_projects/")) - continue; +void ProjectManager::_notification(int p_what) { - String path = EditorSettings::get_singleton()->get(_name); - if (filter_option == ProjectListFilter::FILTER_PATH && search_term != "" && path.findn(search_term) == -1) - continue; + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { - String project = _name.get_slice("/", 1); - String conf = path.plus_file("project.godot"); - bool favorite = (_name.begins_with("favorite_projects/")) ? true : false; - bool grayed = false; + Engine::get_singleton()->set_editor_hint(false); + } break; + case NOTIFICATION_READY: { - Ref<ConfigFile> cf = memnew(ConfigFile); - Error cf_err = cf->load(conf); + if (_project_list->get_project_count() == 0 && StreamPeerSSL::is_available()) + open_templates->popup_centered_minsize(); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { - int config_version = 0; - String project_name = TTR("Unnamed Project"); - if (cf_err == OK) { + set_process_unhandled_input(is_visible_in_tree()); + } break; + case NOTIFICATION_WM_QUIT_REQUEST: { - String cf_project_name = static_cast<String>(cf->get_value("application", "config/name", "")); - if (cf_project_name != "") - project_name = cf_project_name.xml_unescape(); - config_version = (int)cf->get_value("", "config_version", 0); - } + _dim_window(); + } break; + } +} - if (config_version > ProjectSettings::CONFIG_VERSION) { - // Comes from an incompatible (more recent) Godot version, grey it out - grayed = true; - } +void ProjectManager::_dim_window() { - String icon = cf->get_value("application", "config/icon", ""); - String main_scene = cf->get_value("application", "run/main_scene", ""); + // This method must be called before calling `get_tree()->quit()`. + // Otherwise, its effect won't be visible - uint64_t last_modified = 0; - if (FileAccess::exists(conf)) { - last_modified = FileAccess::get_modified_time(conf); + // Dim the project manager window while it's quitting to make it clearer that it's busy. + // No transition is applied, as the effect needs to be visible immediately + float c = 1.0f - float(EDITOR_GET("interface/editor/dim_amount")); + Color dim_color = Color(c, c, c); + gui_base->set_modulate(dim_color); +} - String fscache = path.plus_file(".fscache"); - if (FileAccess::exists(fscache)) { - uint64_t cache_modified = FileAccess::get_modified_time(fscache); - if (cache_modified > last_modified) - last_modified = cache_modified; - } - } else { - grayed = true; - } +void ProjectManager::_update_project_buttons() { - ProjectItem item(project, project_name, path, conf, icon, main_scene, last_modified, favorite, grayed, filter_order_option); - if (favorite) - favorite_projects.push_back(item); - else - projects.push_back(item); - } - projects.sort(); - favorite_projects.sort(); + Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects(); + bool empty_selection = selected_projects.empty(); - for (List<ProjectItem>::Element *E = projects.front(); E;) { - List<ProjectItem>::Element *next = E->next(); - if (favorite_projects.find(E->get()) != NULL) - projects.erase(E->get()); - E = next; - } - for (List<ProjectItem>::Element *E = favorite_projects.back(); E; E = E->prev()) { - projects.push_front(E->get()); - } + erase_btn->set_disabled(empty_selection); + open_btn->set_disabled(empty_selection); + rename_btn->set_disabled(empty_selection); + run_btn->set_disabled(empty_selection); - Ref<Texture> favorite_icon = get_icon("Favorites", "EditorIcons"); + erase_missing_btn->set_visible(_project_list->is_any_project_missing()); +} - for (List<ProjectItem>::Element *E = projects.front(); E; E = E->next()) { +void ProjectManager::_unhandled_input(const Ref<InputEvent> &p_ev) { - ProjectItem &item = E->get(); - String project = item.project; - String path = item.path; - String conf = item.conf; + Ref<InputEventKey> k = p_ev; - if (filter_option == ProjectListFilter::FILTER_NAME && search_term != "" && item.project_name.findn(search_term) == -1) - continue; + if (k.is_valid()) { + + if (!k->is_pressed()) + return; + + if (tabs->get_current_tab() != 0) + return; + + bool scancode_handled = true; + + switch (k->get_scancode()) { - Ref<Texture> icon; + case KEY_ENTER: { - if (item.icon != "") { - Ref<Image> img; - img.instance(); - Error err = img->load(item.icon.replace_first("res://", path + "/")); - if (err == OK) { + _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); + _update_project_buttons(); + } + + } break; + case KEY_END: { - Ref<Texture> default_icon = get_icon("DefaultProjectIcon", "EditorIcons"); - img->resize(default_icon->get_width(), default_icon->get_height()); - Ref<ImageTexture> it = memnew(ImageTexture); - it->create_from_image(img); - icon = it; + if (_project_list->get_project_count() > 0) { + _project_list->select_project(_project_list->get_project_count() - 1); + _update_project_buttons(); + } + + } break; + case KEY_UP: { + + if (k->get_shift()) + break; + + int index = _project_list->get_single_selected_index(); + if (index - 1 > 0) { + _project_list->select_project(index - 1); + _project_list->ensure_project_visible(index - 1); + _update_project_buttons(); + } + + break; } + case KEY_DOWN: { + + if (k->get_shift()) + break; + + int index = _project_list->get_single_selected_index(); + if (index + 1 < _project_list->get_project_count()) { + _project_list->select_project(index + 1); + _project_list->ensure_project_visible(index + 1); + _update_project_buttons(); + } + + } break; + case KEY_F: { + if (k->get_command()) + this->project_filter->search_box->grab_focus(); + else + scancode_handled = false; + } break; + default: { + scancode_handled = false; + } break; } - if (icon.is_null()) { - icon = get_icon("DefaultProjectIcon", "EditorIcons"); + if (scancode_handled) { + accept_event(); } + } +} - selected_list_copy.erase(project); - - bool is_favorite = item.favorite; - bool is_grayed = item.grayed; - - HBoxContainer *hb = memnew(HBoxContainer); - hb->set_meta("name", project); - hb->set_meta("main_scene", item.main_scene); - hb->set_meta("favorite", is_favorite); - hb->connect("draw", this, "_panel_draw", varray(hb)); - hb->connect("gui_input", this, "_panel_input", varray(hb)); - hb->add_constant_override("separation", 10 * EDSCALE); - - VBoxContainer *favorite_box = memnew(VBoxContainer); - TextureButton *favorite = memnew(TextureButton); - favorite->set_normal_texture(favorite_icon); - if (!is_favorite) - favorite->set_modulate(Color(1, 1, 1, 0.2)); - favorite->connect("pressed", this, "_favorite_pressed", varray(hb)); - favorite_box->add_child(favorite); - favorite_box->set_alignment(BoxContainer::ALIGN_CENTER); - hb->add_child(favorite_box); - - TextureRect *tf = memnew(TextureRect); - tf->set_texture(icon); - hb->add_child(tf); +void ProjectManager::_load_recent_projects() { - VBoxContainer *vb = memnew(VBoxContainer); - if (is_grayed) - vb->set_modulate(Color(0.5, 0.5, 0.5)); - vb->set_name("project"); - vb->set_h_size_flags(SIZE_EXPAND_FILL); - hb->add_child(vb); - Control *ec = memnew(Control); - ec->set_custom_minimum_size(Size2(0, 1)); - ec->set_mouse_filter(MOUSE_FILTER_PASS); - vb->add_child(ec); - Label *title = memnew(Label(item.project_name)); - title->add_font_override("font", gui_base->get_font("title", "EditorFonts")); - title->add_color_override("font_color", font_color); - title->set_clip_text(true); - vb->add_child(title); - - HBoxContainer *path_hb = memnew(HBoxContainer); - path_hb->set_name("path_box"); - path_hb->set_h_size_flags(SIZE_EXPAND_FILL); - vb->add_child(path_hb); - - Button *show = memnew(Button); - show->set_name("show"); - show->set_icon(get_icon("Filesystem", "EditorIcons")); - show->set_flat(true); - show->set_modulate(Color(1, 1, 1, 0.5)); - path_hb->add_child(show); - show->connect("pressed", this, "_show_project", varray(path)); - show->set_tooltip(TTR("Show in File Manager")); - - Label *fpath = memnew(Label(path)); - fpath->set_name("path"); - path_hb->add_child(fpath); - fpath->set_h_size_flags(SIZE_EXPAND_FILL); - fpath->set_modulate(Color(1, 1, 1, 0.5)); - fpath->add_color_override("font_color", font_color); - fpath->set_clip_text(true); - - scroll_children->add_child(hb); - } - - for (Map<String, String>::Element *E = selected_list_copy.front(); E; E = E->next()) { - String key = E->key(); - selected_list.erase(key); - } - - scroll->set_v_scroll(0); + _project_list->set_filter_option(project_filter->get_filter_option()); + _project_list->set_order_option(project_order_filter->get_filter_option()); + _project_list->set_search_term(project_filter->get_search_term()); + _project_list->load_projects(); _update_project_buttons(); - EditorSettings::get_singleton()->save(); - tabs->set_current_tab(0); } void ProjectManager::_on_projects_updated() { - _load_recent_projects(); + Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects(); + int index = 0; + for (int i = 0; i < selected_projects.size(); ++i) { + index = _project_list->refresh_project(selected_projects[i].path); + } + if (index != -1) { + _project_list->ensure_project_visible(index); + } } void ProjectManager::_on_project_created(const String &dir) { project_filter->clear(); - bool has_already = false; - for (int i = 0; i < scroll_children->get_child_count(); i++) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - Label *fpath = Object::cast_to<Label>(hb->get_node(NodePath("project/path_box/path"))); - if (fpath->get_text() == dir) { - has_already = true; - break; - } - } - if (has_already) { - _update_scroll_position(dir); - } else { - _load_recent_projects(); - _update_scroll_position(dir); - } + int i = _project_list->refresh_project(dir); + _project_list->select_project(i); + _project_list->ensure_project_visible(i); _open_selected_projects_ask(); } -void ProjectManager::_update_scroll_position(const String &dir) { - for (int i = 0; i < scroll_children->get_child_count(); i++) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - Label *fpath = Object::cast_to<Label>(hb->get_node(NodePath("project/path_box/path"))); - if (fpath->get_text() == dir) { - last_clicked = hb->get_meta("name"); - selected_list.clear(); - selected_list.insert(hb->get_meta("name"), hb->get_meta("main_scene")); - _update_project_buttons(); - int last_y_visible = scroll->get_v_scroll() + scroll->get_size().y; - int offset_diff = (hb->get_position().y + hb->get_size().y) - last_y_visible; - - if (offset_diff > 0) - scroll->set_v_scroll(scroll->get_v_scroll() + offset_diff); - break; - } - } -} - void ProjectManager::_confirm_update_settings() { _open_selected_projects(); } void ProjectManager::_open_selected_projects() { - for (const Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) { - const String &selected = E->key(); + const Set<String> &selected_list = _project_list->get_selected_project_keys(); + + for (const Set<String>::Element *E = selected_list.front(); E; E = E->next()) { + const String &selected = E->get(); String path = EditorSettings::get_singleton()->get("projects/" + selected); String conf = path.plus_file("project.godot"); + if (!FileAccess::exists(conf)) { dialog_error->set_text(vformat(TTR("Can't open project at '%s'."), path)); dialog_error->popup_centered_minsize(); @@ -1540,6 +1908,8 @@ void ProjectManager::_open_selected_projects() { void ProjectManager::_open_selected_projects_ask() { + const Set<String> &selected_list = _project_list->get_selected_project_keys(); + if (selected_list.size() < 1) { return; } @@ -1550,22 +1920,11 @@ void ProjectManager::_open_selected_projects_ask() { return; } - // Update the project settings or don't open - String path = EditorSettings::get_singleton()->get("projects/" + selected_list.front()->key()); - String conf = path.plus_file("project.godot"); + ProjectList::Item project = _project_list->get_selected_projects()[0]; - // FIXME: We already parse those in _load_recent_projects, we could instead make - // its `projects` list global and reuse its parsed metadata here. - Ref<ConfigFile> cf = memnew(ConfigFile); - Error cf_err = cf->load(conf); - - if (cf_err != OK) { - dialog_error->set_text(vformat(TTR("Can't open project at '%s'."), path)); - dialog_error->popup_centered_minsize(); - return; - } - - int config_version = (int)cf->get_value("", "config_version", 0); + // Update the project settings or don't open + String conf = project.path.plus_file("project.godot"); + int config_version = project.version; // Check if the config_version property was empty or 0 if (config_version == 0) { @@ -1581,7 +1940,7 @@ void ProjectManager::_open_selected_projects_ask() { } // Check if the file was generated by a newer, incompatible engine version if (config_version > ProjectSettings::CONFIG_VERSION) { - dialog_error->set_text(vformat(TTR("Can't open project at '%s'.") + "\n" + TTR("The project settings were created by a newer engine version, whose settings are not compatible with this version."), path)); + dialog_error->set_text(vformat(TTR("Can't open project at '%s'.") + "\n" + TTR("The project settings were created by a newer engine version, whose settings are not compatible with this version."), project.path)); dialog_error->popup_centered_minsize(); return; } @@ -1592,16 +1951,18 @@ void ProjectManager::_open_selected_projects_ask() { void ProjectManager::_run_project_confirm() { - for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) { + Vector<ProjectList::Item> selected_list = _project_list->get_selected_projects(); + + for (int i = 0; i < selected_list.size(); ++i) { - const String &selected_main = E->get(); + const String &selected_main = selected_list[i].main_scene; if (selected_main == "") { run_error_diag->set_text(TTR("Can't run project: no main scene defined.\nPlease edit the project and set the main scene in the Project Settings under the \"Application\" category.")); run_error_diag->popup_centered(); return; } - const String &selected = E->key(); + const String &selected = selected_list[i].project_key; String path = EditorSettings::get_singleton()->get("projects/" + selected); if (!DirAccess::exists(path + "/.import")) { @@ -1629,8 +1990,11 @@ void ProjectManager::_run_project_confirm() { } } +// When you press the "Run" button void ProjectManager::_run_project() { + const Set<String> &selected_list = _project_list->get_selected_project_keys(); + if (selected_list.size() < 1) { return; } @@ -1643,11 +2007,6 @@ void ProjectManager::_run_project() { } } -void ProjectManager::_show_project(const String &p_path) { - - OS::get_singleton()->shell_open(String("file://") + p_path); -} - void ProjectManager::_scan_dir(const String &path, List<String> *r_projects) { DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); da->change_dir(path); @@ -1673,7 +2032,7 @@ void ProjectManager::_scan_begin(const String &p_base) { print_line("Found " + itos(projects.size()) + " projects."); for (List<String>::Element *E = projects.front(); E; E = E->next()) { - String proj = E->get().replace("/", "::"); + String proj = get_project_key_from_path(E->get()); EditorSettings::get_singleton()->set("projects/" + proj, E->get()); } EditorSettings::get_singleton()->save(); @@ -1699,12 +2058,14 @@ void ProjectManager::_import_project() { void ProjectManager::_rename_project() { + const Set<String> &selected_list = _project_list->get_selected_project_keys(); + if (selected_list.size() == 0) { return; } - for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) { - const String &selected = E->key(); + for (Set<String>::Element *E = selected_list.front(); E; E = E->next()) { + const String &selected = E->get(); String path = EditorSettings::get_singleton()->get("projects/" + selected); npdialog->set_project_path(path); npdialog->set_mode(ProjectDialog::MODE_RENAME); @@ -1713,55 +2074,17 @@ void ProjectManager::_rename_project() { } void ProjectManager::_erase_project_confirm() { - - if (selected_list.size() == 0) { - return; - } - for (Map<String, String>::Element *E = selected_list.front(); E; E = E->next()) { - EditorSettings::get_singleton()->erase("projects/" + E->key()); - EditorSettings::get_singleton()->erase("favorite_projects/" + E->key()); - } - EditorSettings::get_singleton()->save(); - selected_list.clear(); - last_clicked = ""; - _load_recent_projects(); + _project_list->erase_selected_projects(); } void ProjectManager::_erase_missing_projects_confirm() { - - Map<String, String> list_all_projects; - for (int i = 0; i < scroll_children->get_child_count(); i++) { - HBoxContainer *hb = Object::cast_to<HBoxContainer>(scroll_children->get_child(i)); - if (hb) { - list_all_projects.insert(hb->get_meta("name"), hb->get_meta("main_scene")); - } - } - - if (list_all_projects.size() == 0) { - return; - } - - int deleted_projects = 0; - int remaining_projects = 0; - for (Map<String, String>::Element *E = list_all_projects.front(); E; E = E->next()) { - String project_name = E->key().replace(":::", ":/").replace("::", "/") + "/project.godot"; - if (!FileAccess::exists(project_name)) { - deleted_projects++; - EditorSettings::get_singleton()->erase("projects/" + E->key()); - EditorSettings::get_singleton()->erase("favorite_projects/" + E->key()); - } else { - remaining_projects++; - } - } - print_line("Deleted " + itos(deleted_projects) + " projects, remaining " + itos(remaining_projects) + " projects"); - EditorSettings::get_singleton()->save(); - selected_list.clear(); - last_clicked = ""; - _load_recent_projects(); + _project_list->erase_missing_projects(); } void ProjectManager::_erase_project() { + const Set<String> &selected_list = _project_list->get_selected_project_keys(); + if (selected_list.size() == 0) return; @@ -1867,13 +2190,23 @@ void ProjectManager::_scan_multiple_folders(PoolStringArray p_files) { } } +void ProjectManager::_on_order_option_changed() { + _project_list->set_order_option(project_order_filter->get_filter_option()); + _project_list->sort_projects(); +} + +void ProjectManager::_on_filter_option_changed() { + _project_list->set_filter_option(project_filter->get_filter_option()); + _project_list->set_search_term(project_filter->get_search_term()); + _project_list->sort_projects(); +} + void ProjectManager::_bind_methods() { ClassDB::bind_method("_open_selected_projects_ask", &ProjectManager::_open_selected_projects_ask); ClassDB::bind_method("_open_selected_projects", &ProjectManager::_open_selected_projects); ClassDB::bind_method("_run_project", &ProjectManager::_run_project); ClassDB::bind_method("_run_project_confirm", &ProjectManager::_run_project_confirm); - ClassDB::bind_method("_show_project", &ProjectManager::_show_project); ClassDB::bind_method("_scan_projects", &ProjectManager::_scan_projects); ClassDB::bind_method("_scan_begin", &ProjectManager::_scan_begin); ClassDB::bind_method("_import_project", &ProjectManager::_import_project); @@ -1886,18 +2219,16 @@ void ProjectManager::_bind_methods() { ClassDB::bind_method("_language_selected", &ProjectManager::_language_selected); ClassDB::bind_method("_restart_confirm", &ProjectManager::_restart_confirm); ClassDB::bind_method("_exit_dialog", &ProjectManager::_exit_dialog); - ClassDB::bind_method("_load_recent_projects", &ProjectManager::_load_recent_projects); + ClassDB::bind_method("_on_order_option_changed", &ProjectManager::_on_order_option_changed); + ClassDB::bind_method("_on_filter_option_changed", &ProjectManager::_on_filter_option_changed); ClassDB::bind_method("_on_projects_updated", &ProjectManager::_on_projects_updated); ClassDB::bind_method("_on_project_created", &ProjectManager::_on_project_created); - ClassDB::bind_method("_update_scroll_position", &ProjectManager::_update_scroll_position); - ClassDB::bind_method("_panel_draw", &ProjectManager::_panel_draw); - ClassDB::bind_method("_panel_input", &ProjectManager::_panel_input); ClassDB::bind_method("_unhandled_input", &ProjectManager::_unhandled_input); - ClassDB::bind_method("_favorite_pressed", &ProjectManager::_favorite_pressed); ClassDB::bind_method("_install_project", &ProjectManager::_install_project); ClassDB::bind_method("_files_dropped", &ProjectManager::_files_dropped); ClassDB::bind_method("_open_asset_library", &ProjectManager::_open_asset_library); ClassDB::bind_method("_confirm_update_settings", &ProjectManager::_confirm_update_settings); + ClassDB::bind_method("_update_project_buttons", &ProjectManager::_update_project_buttons); ClassDB::bind_method(D_METHOD("_scan_multiple_folders", "files"), &ProjectManager::_scan_multiple_folders); } @@ -1925,29 +2256,12 @@ ProjectManager::ProjectManager() { editor_set_scale(OS::get_singleton()->get_screen_dpi(screen) >= 192 && OS::get_singleton()->get_screen_size(screen).x > 2000 ? 2.0 : 1.0); } break; - case 1: { - editor_set_scale(0.75); - } break; - - case 2: { - editor_set_scale(1.0); - } break; - - case 3: { - editor_set_scale(1.25); - } break; - - case 4: { - editor_set_scale(1.5); - } break; - - case 5: { - editor_set_scale(1.75); - } break; - - case 6: { - editor_set_scale(2.0); - } break; + case 1: editor_set_scale(0.75); break; + case 2: editor_set_scale(1.0); break; + case 3: editor_set_scale(1.25); break; + case 4: editor_set_scale(1.5); break; + case 5: editor_set_scale(1.75); break; + case 6: editor_set_scale(2.0); break; default: { editor_set_scale(custom_display_scale); @@ -2030,7 +2344,7 @@ ProjectManager::ProjectManager() { project_order_filter->_setup_filters(sort_filter_titles); project_order_filter->set_filter_size(150); sort_filters->add_child(project_order_filter); - project_order_filter->connect("filter_changed", this, "_load_recent_projects"); + project_order_filter->connect("filter_changed", this, "_on_order_option_changed"); project_order_filter->set_custom_minimum_size(Size2(180, 10) * EDSCALE); int projects_sorting_order = (int)EditorSettings::get_singleton()->get("project_manager/sorting_order"); @@ -2049,7 +2363,7 @@ ProjectManager::ProjectManager() { project_filter->_setup_filters(vec2); project_filter->add_search_box(); search_filters->add_child(project_filter); - project_filter->connect("filter_changed", this, "_load_recent_projects"); + project_filter->connect("filter_changed", this, "_on_filter_option_changed"); project_filter->set_custom_minimum_size(Size2(280, 10) * EDSCALE); sort_filters->add_child(search_filters); @@ -2060,15 +2374,14 @@ ProjectManager::ProjectManager() { search_tree_vb->add_child(pc); pc->set_v_size_flags(SIZE_EXPAND_FILL); - scroll = memnew(ScrollContainer); - pc->add_child(scroll); - scroll->set_enable_h_scroll(false); + _project_list = memnew(ProjectList); + _project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, this, "_update_project_buttons"); + _project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, this, "_open_selected_projects_ask"); + pc->add_child(_project_list); + _project_list->set_enable_h_scroll(false); VBoxContainer *tree_vb = memnew(VBoxContainer); tree_hb->add_child(tree_vb); - scroll_children = memnew(VBoxContainer); - scroll_children->set_h_size_flags(SIZE_EXPAND_FILL); - scroll->add_child(scroll_children); Button *open = memnew(Button); open->set_text(TTR("Edit")); @@ -2238,14 +2551,13 @@ ProjectManager::ProjectManager() { npdialog->connect("projects_updated", this, "_on_projects_updated"); npdialog->connect("project_created", this, "_on_project_created"); + _load_recent_projects(); if (EditorSettings::get_singleton()->get("filesystem/directories/autoscan_project_path")) { _scan_begin(EditorSettings::get_singleton()->get("filesystem/directories/autoscan_project_path")); } - last_clicked = ""; - SceneTree::get_singleton()->connect("files_dropped", this, "_files_dropped"); run_error_diag = memnew(AcceptDialog); diff --git a/editor/project_manager.h b/editor/project_manager.h index d75d7164cc..4ccb99d6bd 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -39,6 +39,7 @@ #include "scene/gui/tree.h" class ProjectDialog; +class ProjectList; class ProjectListFilter; class ProjectManager : public Control { @@ -68,16 +69,13 @@ class ProjectManager : public Control { AcceptDialog *dialog_error; ProjectDialog *npdialog; - ScrollContainer *scroll; - VBoxContainer *scroll_children; HBoxContainer *projects_hb; TabContainer *tabs; + ProjectList *_project_list; OptionButton *language_btn; Control *gui_base; - Map<String, String> selected_list; // name -> main_scene - String last_clicked; bool importing; void _open_asset_library(); @@ -86,7 +84,6 @@ class ProjectManager : public Control { void _run_project_confirm(); void _open_selected_projects(); void _open_selected_projects_ask(); - void _show_project(const String &p_path); void _import_project(); void _new_project(); void _rename_project(); @@ -111,13 +108,13 @@ class ProjectManager : public Control { void _install_project(const String &p_zip_path, const String &p_title); void _dim_window(); - void _panel_draw(Node *p_hb); - void _panel_input(const Ref<InputEvent> &p_ev, Node *p_hb); void _unhandled_input(const Ref<InputEvent> &p_ev); - void _favorite_pressed(Node *p_hb); void _files_dropped(PoolStringArray p_files, int p_screen); void _scan_multiple_folders(PoolStringArray p_files); + void _on_order_option_changed(); + void _on_filter_option_changed(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 009ac603e2..aeee829de2 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -301,6 +301,8 @@ bool SceneTreeDock::_track_inherit(const String &p_target_scene_path, Node *p_de Ref<PackedScene> data = ResourceLoader::load(path); if (data.is_valid()) { p = data->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!p) + continue; instances.push_back(p); } else break; diff --git a/editor/script_editor_debugger.h b/editor/script_editor_debugger.h index 505eab266f..947b0cca52 100644 --- a/editor/script_editor_debugger.h +++ b/editor/script_editor_debugger.h @@ -173,7 +173,7 @@ class ScriptEditorDebugger : public Control { void _set_reason_text(const String &p_reason, MessageType p_type); void _scene_tree_property_select_object(ObjectID p_object); void _scene_tree_property_value_edited(const String &p_prop, const Variant &p_value); - int _update_scene_tree(TreeItem *parent, const Array &items, int current_index); + int _update_scene_tree(TreeItem *parent, const Array &nodes, int current_index); void _video_mem_request(); diff --git a/main/tests/test_string.cpp b/main/tests/test_string.cpp index 05df888f40..ab5fb64252 100644 --- a/main/tests/test_string.cpp +++ b/main/tests/test_string.cpp @@ -1078,6 +1078,44 @@ bool test_34() { return state; } +bool test_35() { +#define COUNT_TEST(x) \ + { \ + bool success = x; \ + state = state && success; \ + if (!success) { \ + OS::get_singleton()->print("\tfailed at: %s\n", #x); \ + } \ + } + + OS::get_singleton()->print("\n\nTest 35: count and countn function\n"); + bool state = true; + + COUNT_TEST(String("").count("Test") == 0); + COUNT_TEST(String("Test").count("") == 0); + COUNT_TEST(String("Test").count("test") == 0); + COUNT_TEST(String("Test").count("TEST") == 0); + COUNT_TEST(String("TEST").count("TEST") == 1); + COUNT_TEST(String("Test").count("Test") == 1); + COUNT_TEST(String("aTest").count("Test") == 1); + COUNT_TEST(String("Testa").count("Test") == 1); + COUNT_TEST(String("TestTestTest").count("Test") == 3); + COUNT_TEST(String("TestTestTest").count("TestTest") == 1); + COUNT_TEST(String("TestGodotTestGodotTestGodot").count("Test") == 3); + + COUNT_TEST(String("TestTestTestTest").count("Test", 4, 8) == 1); + COUNT_TEST(String("TestTestTestTest").count("Test", 4, 12) == 2); + COUNT_TEST(String("TestTestTestTest").count("Test", 4, 16) == 3); + COUNT_TEST(String("TestTestTestTest").count("Test", 4) == 3); + + COUNT_TEST(String("Test").countn("test") == 1); + COUNT_TEST(String("Test").countn("TEST") == 1); + COUNT_TEST(String("testTest-Testatest").countn("tEst") == 4); + COUNT_TEST(String("testTest-TeStatest").countn("tEsT", 4, 16) == 2); + + return state; +} + typedef bool (*TestFunc)(void); TestFunc test_funcs[] = { @@ -1116,6 +1154,7 @@ TestFunc test_funcs[] = { test_32, test_33, test_34, + test_35, 0 }; diff --git a/modules/bullet/rigid_body_bullet.h b/modules/bullet/rigid_body_bullet.h index 2c9bdb8b0b..f63148092f 100644 --- a/modules/bullet/rigid_body_bullet.h +++ b/modules/bullet/rigid_body_bullet.h @@ -305,7 +305,7 @@ public: void reload_axis_lock(); /// Doc: - /// http://www.bulletphysics.org/mediawiki-1.5.8/index.php?title=Anti_tunneling_by_Motion_Clamping + /// https://web.archive.org/web/20180404091446/http://www.bulletphysics.org/mediawiki-1.5.8/index.php/Anti_tunneling_by_Motion_Clamping void set_continuous_collision_detection(bool p_enable); bool is_continuous_collision_detection_enabled() const; diff --git a/modules/bullet/space_bullet.cpp b/modules/bullet/space_bullet.cpp index 738b415d16..9d632aaf83 100644 --- a/modules/bullet/space_bullet.cpp +++ b/modules/bullet/space_bullet.cpp @@ -581,6 +581,10 @@ void SpaceBullet::create_empty_world(bool p_create_soft_world) { } else { world_mem = malloc(sizeof(btDiscreteDynamicsWorld)); } + if (!world_mem) { + ERR_EXPLAIN("Out of memory"); + ERR_FAIL(); + } if (p_create_soft_world) { collisionConfiguration = bulletnew(GodotSoftCollisionConfiguration(static_cast<btDiscreteDynamicsWorld *>(world_mem))); diff --git a/modules/gdnative/gdnative/string.cpp b/modules/gdnative/gdnative/string.cpp index 913c57eb56..9086121940 100644 --- a/modules/gdnative/gdnative/string.cpp +++ b/modules/gdnative/gdnative/string.cpp @@ -186,6 +186,20 @@ godot_bool GDAPI godot_string_ends_with(const godot_string *p_self, const godot_ return self->ends_with(*string); } +godot_int GDAPI godot_string_count(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to) { + const String *self = (const String *)p_self; + String *what = (String *)&p_what; + + return self->count(*what, p_from, p_to); +} + +godot_int GDAPI godot_string_countn(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to) { + const String *self = (const String *)p_self; + String *what = (String *)&p_what; + + return self->countn(*what, p_from, p_to); +} + godot_int GDAPI godot_string_find(const godot_string *p_self, godot_string p_what) { const String *self = (const String *)p_self; String *what = (String *)&p_what; diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 6c12ee6534..7372990d5f 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -44,6 +44,26 @@ ["const godot_vector2 *", "p_to"], ["const godot_real", "p_delta"] ] + }, + { + "name": "godot_string_count", + "return_type": "godot_int", + "arguments": [ + ["const godot_string *", "p_self"], + ["godot_string", "p_what"], + ["godot_int", "p_from"], + ["godot_int", "p_to"] + ] + }, + { + "name": "godot_string_countn", + "return_type": "godot_int", + "arguments": [ + ["const godot_string *", "p_self"], + ["godot_string", "p_what"], + ["godot_int", "p_from"], + ["godot_int", "p_to"] + ] } ] }, diff --git a/modules/gdnative/include/gdnative/string.h b/modules/gdnative/include/gdnative/string.h index f045ac9d58..7500d70f20 100644 --- a/modules/gdnative/include/gdnative/string.h +++ b/modules/gdnative/include/gdnative/string.h @@ -102,6 +102,8 @@ godot_bool GDAPI godot_string_begins_with_char_array(const godot_string *p_self, godot_array GDAPI godot_string_bigrams(const godot_string *p_self); godot_string GDAPI godot_string_chr(wchar_t p_character); godot_bool GDAPI godot_string_ends_with(const godot_string *p_self, const godot_string *p_string); +godot_int GDAPI godot_string_count(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to); +godot_int GDAPI godot_string_countn(const godot_string *p_self, godot_string p_what, godot_int p_from, godot_int p_to); godot_int GDAPI godot_string_find(const godot_string *p_self, godot_string p_what); godot_int GDAPI godot_string_find_from(const godot_string *p_self, godot_string p_what, godot_int p_from); godot_int GDAPI godot_string_findmk(const godot_string *p_self, const godot_array *p_keys); diff --git a/modules/mono/glue/Managed/Files/StringExtensions.cs b/modules/mono/glue/Managed/Files/StringExtensions.cs index b43034fbb5..6045c83e95 100644 --- a/modules/mono/glue/Managed/Files/StringExtensions.cs +++ b/modules/mono/glue/Managed/Files/StringExtensions.cs @@ -98,6 +98,66 @@ namespace Godot } // <summary> + // Return the amount of substrings in string. + // </summary> + public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0) + { + if (what.Length == 0) + { + return 0; + } + + int len = instance.Length; + int slen = what.Length; + + if (len < slen) + { + return 0; + } + + string str; + + if (from >= 0 && to >= 0) + { + if (to == 0) + { + to = len; + } + else if (from >= to) + { + return 0; + } + if (from == 0 && to == len) + { + str = instance; + } + else + { + str = instance.Substring(from, to - from); + } + } + else + { + return 0; + } + + int c = 0; + int idx; + + do + { + idx = str.IndexOf(what, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + if (idx != -1) + { + str = str.Substring(idx + slen); + ++c; + } + } while (idx != -1); + + return c; + } + + // <summary> // Return a copy of the string with special characters escaped using the C language standard. // </summary> public static string CEscape(this string instance) diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp index abb7b391d3..75ce422e9e 100644 --- a/platform/uwp/export/export.cpp +++ b/platform/uwp/export/export.cpp @@ -519,7 +519,9 @@ Error AppxPackager::add_file(String p_file_name, const uint8_t *p_buffer, size_t int total_out_before = strm.total_out; - deflate(&strm, Z_FULL_FLUSH); + int err = deflate(&strm, Z_FULL_FLUSH); + ERR_FAIL_COND_V(err >= 0, ERR_BUG); // Negative means bug + bh.compressed_size = strm.total_out - total_out_before; //package->store_buffer(strm_out.ptr(), strm.total_out - total_out_before); diff --git a/platform/x11/joypad_linux.cpp b/platform/x11/joypad_linux.cpp index e6328ee14d..4242952374 100644 --- a/platform/x11/joypad_linux.cpp +++ b/platform/x11/joypad_linux.cpp @@ -513,6 +513,8 @@ void JoypadLinux::process_joypads() { break; default: + if (ev.code >= MAX_ABS) + return; if (joy->abs_map[ev.code] != -1 && joy->abs_info[ev.code]) { InputDefault::JoyAxis value = axis_correct(joy->abs_info[ev.code], ev.value); joy->curr_axis[joy->abs_map[ev.code]] = value; diff --git a/platform/x11/power_x11.cpp b/platform/x11/power_x11.cpp index 758bd84114..c33c77e16b 100644 --- a/platform/x11/power_x11.cpp +++ b/platform/x11/power_x11.cpp @@ -268,9 +268,7 @@ bool PowerX11::GetPowerInfo_Linux_proc_acpi() { check_proc_acpi_battery(node.utf8().get_data(), &have_battery, &charging /*, seconds, percent*/); node = dirp->get_next(); } - memdelete(dirp); } - dirp->change_dir(proc_acpi_ac_adapter_path); err = dirp->list_dir_begin(); if (err != OK) { @@ -281,7 +279,6 @@ bool PowerX11::GetPowerInfo_Linux_proc_acpi() { check_proc_acpi_ac_adapter(node.utf8().get_data(), &have_ac); node = dirp->get_next(); } - memdelete(dirp); } if (!have_battery) { @@ -294,6 +291,7 @@ bool PowerX11::GetPowerInfo_Linux_proc_acpi() { this->power_state = OS::POWERSTATE_ON_BATTERY; } + memdelete(dirp); return true; /* definitive answer. */ } diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index 71addd6fea..99ecc8bc37 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -92,6 +92,8 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene if (!ps.is_valid()) return NULL; Node *scene = ps->instance(); + if (!scene) + return NULL; scene->set_name(get_name()); int pos = get_position_in_parent(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 2badf19f2b..dbf3150ae0 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -1507,8 +1507,11 @@ void SceneTree::_live_edit_instance_node_func(const NodePath &p_parent, const St Node *n2 = n->get_node(p_parent); Node *no = ps->instance(); - no->set_name(p_name); + if (!no) { + continue; + } + no->set_name(p_name); n2->add_child(no); } } diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index cafc7a0f00..4585e7fdcc 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -1624,13 +1624,13 @@ public: virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty - void set_comparsion_type(ComparsionType p_func); + void set_comparsion_type(ComparsionType p_type); ComparsionType get_comparsion_type() const; void set_function(Function p_func); Function get_function() const; - void set_condition(Condition p_mode); + void set_condition(Condition p_cond); Condition get_condition() const; virtual Vector<StringName> get_editable_properties() const; diff --git a/servers/audio/effects/audio_effect_pitch_shift.cpp b/servers/audio/effects/audio_effect_pitch_shift.cpp index c250f2e2bd..ec3182685f 100644 --- a/servers/audio/effects/audio_effect_pitch_shift.cpp +++ b/servers/audio/effects/audio_effect_pitch_shift.cpp @@ -70,7 +70,7 @@ * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice and this license appear in all source copies. * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF -* ANY KIND. See http://www.dspguru.com/wol.htm for more information. +* ANY KIND. See https://dspguru.com/wide-open-license/ for more information. * *****************************************************************************/ diff --git a/servers/physics/collision_solver_sat.cpp b/servers/physics/collision_solver_sat.cpp index a13fa65009..d0f8fd8aff 100644 --- a/servers/physics/collision_solver_sat.cpp +++ b/servers/physics/collision_solver_sat.cpp @@ -274,8 +274,8 @@ static void _generate_contacts_from_supports(const Vector3 *p_points_A, int p_po points_B = p_points_B; } - int version_A = (pointcount_A > 3 ? 3 : pointcount_A) - 1; - int version_B = (pointcount_B > 3 ? 3 : pointcount_B) - 1; + int version_A = (pointcount_A > 2 ? 2 : pointcount_A) - 1; + int version_B = (pointcount_B > 2 ? 2 : pointcount_B) - 1; GenerateContactsFunc contacts_func = generate_contacts_func_table[version_A][version_B]; ERR_FAIL_COND(!contacts_func); diff --git a/servers/physics/collision_solver_sw.cpp b/servers/physics/collision_solver_sw.cpp index 0d10dae8cc..d970dd39fb 100644 --- a/servers/physics/collision_solver_sw.cpp +++ b/servers/physics/collision_solver_sw.cpp @@ -233,8 +233,6 @@ bool CollisionSolverSW::solve_static(const ShapeSW *p_shape_A, const Transform & return collision_solver(p_shape_A, p_transform_A, p_shape_B, p_transform_B, p_result_callback, p_userdata, false, r_sep_axis, p_margin_A, p_margin_B); } - - return false; } void CollisionSolverSW::concave_distance_callback(void *p_userdata, ShapeSW *p_convex) { @@ -371,6 +369,4 @@ bool CollisionSolverSW::solve_distance(const ShapeSW *p_shape_A, const Transform return gjk_epa_calculate_distance(p_shape_A, p_transform_A, p_shape_B, p_transform_B, r_point_A, r_point_B); //should pass sepaxis.. } - - return false; } diff --git a/servers/physics/joints/hinge_joint_sw.cpp b/servers/physics/joints/hinge_joint_sw.cpp index 1d1b30286e..209cddda5e 100644 --- a/servers/physics/joints/hinge_joint_sw.cpp +++ b/servers/physics/joints/hinge_joint_sw.cpp @@ -198,7 +198,6 @@ bool HingeJointSW::setup(real_t p_step) { plane_space(m_rbAFrame.basis.get_axis(2), jointAxis0local, jointAxis1local); - A->get_transform().basis.xform(m_rbAFrame.basis.get_axis(2)); Vector3 jointAxis0 = A->get_transform().basis.xform(jointAxis0local); Vector3 jointAxis1 = A->get_transform().basis.xform(jointAxis1local); Vector3 hingeAxisWorld = A->get_transform().basis.xform(m_rbAFrame.basis.get_axis(2)); diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp index 60bbcef4b6..5dff655ea1 100644 --- a/servers/physics_2d/body_2d_sw.cpp +++ b/servers/physics_2d/body_2d_sw.cpp @@ -185,28 +185,28 @@ real_t Body2DSW::get_param(Physics2DServer::BodyParameter p_param) const { case Physics2DServer::BODY_PARAM_BOUNCE: { return bounce; - } break; + } case Physics2DServer::BODY_PARAM_FRICTION: { return friction; - } break; + } case Physics2DServer::BODY_PARAM_MASS: { return mass; - } break; + } case Physics2DServer::BODY_PARAM_INERTIA: { return _inv_inertia == 0 ? 0 : 1.0 / _inv_inertia; - } break; + } case Physics2DServer::BODY_PARAM_GRAVITY_SCALE: { return gravity_scale; - } break; + } case Physics2DServer::BODY_PARAM_LINEAR_DAMP: { return linear_damp; - } break; + } case Physics2DServer::BODY_PARAM_ANGULAR_DAMP: { return angular_damp; - } break; + } default: { } } @@ -343,19 +343,19 @@ Variant Body2DSW::get_state(Physics2DServer::BodyState p_state) const { switch (p_state) { case Physics2DServer::BODY_STATE_TRANSFORM: { return get_transform(); - } break; + } case Physics2DServer::BODY_STATE_LINEAR_VELOCITY: { return linear_velocity; - } break; + } case Physics2DServer::BODY_STATE_ANGULAR_VELOCITY: { return angular_velocity; - } break; + } case Physics2DServer::BODY_STATE_SLEEPING: { return !is_active(); - } break; + } case Physics2DServer::BODY_STATE_CAN_SLEEP: { return can_sleep; - } break; + } } return Variant(); diff --git a/servers/physics_2d/broad_phase_2d_hash_grid.cpp b/servers/physics_2d/broad_phase_2d_hash_grid.cpp index 1bbb50c974..6dd19c2868 100644 --- a/servers/physics_2d/broad_phase_2d_hash_grid.cpp +++ b/servers/physics_2d/broad_phase_2d_hash_grid.cpp @@ -673,7 +673,7 @@ public IEnumerable<Point3D> GetCellsOnRay(Ray ray, int maxDepth) // "A Fast Voxel Traversal Algorithm for Ray Tracing" // John Amanatides, Andrew Woo // http://www.cse.yorku.ca/~amana/research/grid.pdf - // http://www.devmaster.net/articles/raytracing_series/A%20faster%20voxel%20traversal%20algorithm%20for%20ray%20tracing.pdf + // https://web.archive.org/web/20100616193049/http://www.devmaster.net/articles/raytracing_series/A%20faster%20voxel%20traversal%20algorithm%20for%20ray%20tracing.pdf // NOTES: // * This code assumes that the ray's position and direction are in 'cell coordinates', which means diff --git a/servers/physics_2d/collision_solver_2d_sat.cpp b/servers/physics_2d/collision_solver_2d_sat.cpp index 9d75f71ff0..19e4b8c1d9 100644 --- a/servers/physics_2d/collision_solver_2d_sat.cpp +++ b/servers/physics_2d/collision_solver_2d_sat.cpp @@ -172,8 +172,8 @@ static void _generate_contacts_from_supports(const Vector2 *p_points_A, int p_po points_B = p_points_B; } - int version_A = (pointcount_A > 3 ? 3 : pointcount_A) - 1; - int version_B = (pointcount_B > 3 ? 3 : pointcount_B) - 1; + int version_A = (pointcount_A > 2 ? 2 : pointcount_A) - 1; + int version_B = (pointcount_B > 2 ? 2 : pointcount_B) - 1; GenerateContactsFunc contacts_func = generate_contacts_func_table[version_A][version_B]; ERR_FAIL_COND(!contacts_func); diff --git a/servers/physics_2d/collision_solver_2d_sw.cpp b/servers/physics_2d/collision_solver_2d_sw.cpp index e49961c048..03c0fd5981 100644 --- a/servers/physics_2d/collision_solver_2d_sw.cpp +++ b/servers/physics_2d/collision_solver_2d_sw.cpp @@ -249,6 +249,4 @@ bool CollisionSolver2DSW::solve(const Shape2DSW *p_shape_A, const Transform2D &p return collision_solver(p_shape_A, p_transform_A, p_motion_A, p_shape_B, p_transform_B, p_motion_B, p_result_callback, p_userdata, false, sep_axis, margin_A, margin_B); } - - return false; } diff --git a/servers/visual/shader_language.cpp b/servers/visual/shader_language.cpp index 14fefbf195..25973aa295 100644 --- a/servers/visual/shader_language.cpp +++ b/servers/visual/shader_language.cpp @@ -967,7 +967,7 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, const Map<String bool ShaderLanguage::_validate_operator(OperatorNode *p_op, DataType *r_ret_type) { bool valid = false; - DataType ret_type; + DataType ret_type = TYPE_VOID; switch (p_op->op) { case OP_EQUAL: @@ -3059,7 +3059,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons String ident = identifier; bool ok = true; - DataType member_type; + DataType member_type = TYPE_VOID; switch (dt) { case TYPE_BVEC2: case TYPE_IVEC2: diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp index c6468694fd..25e18d0623 100644 --- a/servers/visual_server.cpp +++ b/servers/visual_server.cpp @@ -1118,7 +1118,7 @@ void VisualServer::mesh_add_surface_from_arrays(RID p_mesh, PrimitiveType p_prim } offsets[i] = elem_size; continue; - } break; + } default: { ERR_FAIL(); } @@ -1286,7 +1286,7 @@ Array VisualServer::_get_array_from_surface(uint32_t p_format, PoolVector<uint8_ } offsets[i] = elem_size; continue; - } break; + } default: { ERR_FAIL_V(Array()); } |