diff options
Diffstat (limited to 'editor')
31 files changed, 666 insertions, 310 deletions
| diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index ab8ae71904..67b52bf0ca 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -762,7 +762,7 @@ void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) {  			undo_redo->create_action(TTR("Add Bezier Point"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, time); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time);  			undo_redo->commit_action();  			//then attempt to move @@ -857,7 +857,7 @@ void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) {  					continue; //already in selection, don't save  				} -				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", track, newtime); +				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, newtime);  				AnimMoveRestore amr;  				amr.key = animation->track_get_key_value(track, idx); @@ -888,7 +888,7 @@ void AnimationBezierTrackEdit::_gui_input(const Ref<InputEvent> &p_event) {  				if (newpos<0)  					continue; //no remove what no inserted  				*/ -				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, newpos); +				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, newpos);  			}  			// 5-(undo) reinsert keys @@ -1038,7 +1038,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {  			undo_redo->create_action(TTR("Add Bezier Point"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, time); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time);  			undo_redo->commit_action();  		} break; @@ -1074,7 +1074,7 @@ void AnimationBezierTrackEdit::duplicate_selection() {  		int existing_idx = animation->track_find_key(track, dst_time, true);  		undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get())); -		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, dst_time); +		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, dst_time);  		Pair<int, float> p;  		p.first = track; diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 9db2f0a287..89f77f3c77 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -134,7 +134,7 @@ public:  			undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, key);  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, val, trans);  			undo_redo->add_do_method(this, "_key_ofs_changed", animation, key_ofs, new_time); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, new_time); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, new_time);  			undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_ofs, val, trans);  			undo_redo->add_undo_method(this, "_key_ofs_changed", animation, new_time, key_ofs); @@ -758,7 +758,7 @@ public:  					undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, key);  					undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, new_time, val, trans);  					undo_redo->add_do_method(this, "_key_ofs_changed", animation, key_ofs, new_time); -					undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", track, new_time); +					undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, new_time);  					undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, key_ofs, val, trans);  					undo_redo->add_undo_method(this, "_key_ofs_changed", animation, new_time, key_ofs); @@ -3979,7 +3979,7 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD  		undo_redo->add_undo_method(animation.ptr(), "remove_track", animation->get_track_count());  		p_next_tracks.normal++;  	} else { -		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_id.track_idx, time); +		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_id.track_idx, time);  		int existing = animation->track_find_key(p_id.track_idx, time, true);  		if (existing != -1) {  			Variant v = animation->track_get_key_value(p_id.track_idx, existing); @@ -4568,7 +4568,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {  			undo_redo->create_action(TTR("Add Transform Track Key"));  			undo_redo->add_do_method(animation.ptr(), "transform_track_insert_key", p_track, p_ofs, loc, rot, scale); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);  			undo_redo->commit_action();  		} break; @@ -4580,7 +4580,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {  			undo_redo->create_action(TTR("Add Track Key"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, value);  			undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);  			undo_redo->commit_action();  		} break; @@ -4611,7 +4611,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {  			undo_redo->create_action(TTR("Add Track Key"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, arr); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);  			undo_redo->commit_action();  		} break; @@ -4623,7 +4623,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {  			undo_redo->create_action(TTR("Add Track Key"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, ak); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);  			undo_redo->commit_action();  		} break;  		case Animation::TYPE_ANIMATION: { @@ -4631,7 +4631,7 @@ void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) {  			undo_redo->create_action(TTR("Add Track Key"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_track, p_ofs, anim); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", p_track, p_ofs); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", p_track, p_ofs);  			undo_redo->commit_action();  		} break;  	} @@ -4669,7 +4669,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) {  			undo_redo->create_action(TTR("Add Method Track Key"));  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", insert_key_from_track_call_track, insert_key_from_track_call_ofs, d); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", insert_key_from_track_call_track, insert_key_from_track_call_ofs); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", insert_key_from_track_call_track, insert_key_from_track_call_ofs);  			undo_redo->commit_action();  			return; @@ -4879,7 +4879,7 @@ void AnimationTrackEditor::_move_selection_commit() {  			continue; //already in selection, don't save  		} -		undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime); +		undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime);  		_AnimMoveRestore amr;  		amr.key = animation->track_get_key_value(E->key().track, idx); @@ -4899,7 +4899,7 @@ void AnimationTrackEditor::_move_selection_commit() {  	// 4 - (undo) remove inserted keys  	for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {  		float newpos = snap_time(E->get().pos + motion); -		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos); +		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);  	}  	// 5 - (undo) reinsert keys @@ -5096,7 +5096,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(bool transpose) {  			int existing_idx = animation->track_find_key(dst_track, dst_time, true);  			undo_redo->add_do_method(animation.ptr(), "track_insert_key", dst_track, dst_time, animation->track_get_key_value(E->key().track, E->key().key), animation->track_get_key_transition(E->key().track, E->key().key)); -			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", dst_track, dst_time); +			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", dst_track, dst_time);  			Pair<int, float> p;  			p.first = dst_track; @@ -5344,7 +5344,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {  					continue; //already in selection, don't save  				} -				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newtime); +				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newtime);  				_AnimMoveRestore amr;  				amr.key = animation->track_get_key_value(E->key().track, idx); @@ -5365,7 +5365,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {  			// 4-(undo) remove inserted keys  			for (Map<SelectedKey, KeyInfo>::Element *E = selection.back(); E; E = E->prev()) {  				float newpos = _NEW_POS(E->get().pos); -				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_position", E->key().track, newpos); +				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->key().track, newpos);  			}  			// 5-(undo) reinsert keys diff --git a/editor/animation_track_editor_plugins.cpp b/editor/animation_track_editor_plugins.cpp index 506a327ffc..ae79299331 100644 --- a/editor/animation_track_editor_plugins.cpp +++ b/editor/animation_track_editor_plugins.cpp @@ -1024,7 +1024,7 @@ void AnimationTrackEditTypeAudio::drop_data(const Point2 &p_point, const Variant  			get_undo_redo()->create_action(TTR("Add Audio Track Clip"));  			get_undo_redo()->add_do_method(get_animation().ptr(), "audio_track_insert_key", get_track(), ofs, stream); -			get_undo_redo()->add_undo_method(get_animation().ptr(), "track_remove_key_at_position", get_track(), ofs); +			get_undo_redo()->add_undo_method(get_animation().ptr(), "track_remove_key_at_time", get_track(), ofs);  			get_undo_redo()->commit_action();  			update(); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index d962658484..7527514dca 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -37,6 +37,9 @@  #include "core/version.h"  #include "core/version_hash.gen.h" +// The metadata key used to store and retrieve the version text to copy to the clipboard. +static const String META_TEXT_TO_COPY = "text_to_copy"; +  void EditorAbout::_theme_changed() {  	const Ref<Font> font = get_theme_font("source", "EditorFonts");  	const int font_size = get_theme_font_size("source_size", "EditorFonts"); @@ -63,7 +66,12 @@ void EditorAbout::_license_tree_selected() {  	_tpl_text->set_text(selected->get_metadata(0));  } +void EditorAbout::_version_button_pressed() { +	DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY)); +} +  void EditorAbout::_bind_methods() { +	ClassDB::bind_method(D_METHOD("_version_button_pressed"), &EditorAbout::_version_button_pressed);  }  TextureRect *EditorAbout::get_logo() const { @@ -124,17 +132,32 @@ EditorAbout::EditorAbout() {  	_logo = memnew(TextureRect);  	hbc->add_child(_logo); +	VBoxContainer *version_info_vbc = memnew(VBoxContainer); + +	// Add a dummy control node for spacing. +	Control *v_spacer = memnew(Control); +	version_info_vbc->add_child(v_spacer); + +	version_btn = memnew(LinkButton);  	String hash = String(VERSION_HASH);  	if (hash.length() != 0) {  		hash = "." + hash.left(9);  	} +	version_btn->set_text(VERSION_FULL_NAME + hash); +	// Set the text to copy in metadata as it slightly differs from the button's text. +	version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash); +	version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); +	version_btn->set_tooltip(TTR("Click to copy.")); +	version_btn->connect("pressed", callable_mp(this, &EditorAbout::_version_button_pressed)); +	version_info_vbc->add_child(version_btn);  	Label *about_text = memnew(Label);  	about_text->set_v_size_flags(Control::SIZE_SHRINK_CENTER); -	about_text->set_text(VERSION_FULL_NAME + hash + -						 String::utf8("\n\xc2\xa9 2007-2021 Juan Linietsky, Ariel Manzur.\n\xc2\xa9 2014-2021 ") + +	about_text->set_text(String::utf8("\xc2\xa9 2007-2021 Juan Linietsky, Ariel Manzur.\n\xc2\xa9 2014-2021 ") +  						 TTR("Godot Engine contributors") + "\n"); -	hbc->add_child(about_text); +	version_info_vbc->add_child(about_text); + +	hbc->add_child(version_info_vbc);  	TabContainer *tc = memnew(TabContainer);  	tc->set_custom_minimum_size(Size2(950, 400) * EDSCALE); diff --git a/editor/editor_about.h b/editor/editor_about.h index 2823220a8a..b76a2ada34 100644 --- a/editor/editor_about.h +++ b/editor/editor_about.h @@ -34,6 +34,7 @@  #include "scene/gui/control.h"  #include "scene/gui/dialogs.h"  #include "scene/gui/item_list.h" +#include "scene/gui/link_button.h"  #include "scene/gui/rich_text_label.h"  #include "scene/gui/scroll_container.h"  #include "scene/gui/separator.h" @@ -53,8 +54,10 @@ class EditorAbout : public AcceptDialog {  private:  	void _license_tree_selected(); +	void _version_button_pressed();  	ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_flag_single_column = 0); +	LinkButton *version_btn;  	Tree *_tpl_tree;  	RichTextLabel *_license_text;  	RichTextLabel *_tpl_text; diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index fa4703d425..6405af3876 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -426,6 +426,18 @@ UndoRedo &EditorData::get_undo_redo() {  	return undo_redo;  } +void EditorData::add_undo_redo_inspector_hook_callback(Callable p_callable) { +	undo_redo_callbacks.push_back(p_callable); +} + +void EditorData::remove_undo_redo_inspector_hook_callback(Callable p_callable) { +	undo_redo_callbacks.erase(p_callable); +} + +const Vector<Callable> EditorData::get_undo_redo_inspector_hook_callback() { +	return undo_redo_callbacks; +} +  void EditorData::remove_editor_plugin(EditorPlugin *p_plugin) {  	p_plugin->undo_redo = nullptr;  	editor_plugins.erase(p_plugin); diff --git a/editor/editor_data.h b/editor/editor_data.h index dbe729d9d9..2ece94d6a3 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -132,6 +132,7 @@ private:  	List<PropertyData> clipboard;  	UndoRedo undo_redo; +	Vector<Callable> undo_redo_callbacks;  	void _cleanup_history(); @@ -166,6 +167,9 @@ public:  	EditorPlugin *get_editor_plugin(int p_idx);  	UndoRedo &get_undo_redo(); +	void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks shoud have 4 args: (Object* undo_redo, Object *modified_object, String property, Varian new_value) +	void remove_undo_redo_inspector_hook_callback(Callable p_callable); +	const Vector<Callable> get_undo_redo_inspector_hook_callback();  	void save_editor_global_states();  	void restore_editor_global_states(); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 59d3b09678..495bdd42f7 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -68,6 +68,11 @@ int EditorFileSystemDirectory::find_dir_index(const String &p_dir) const {  	return -1;  } +void EditorFileSystemDirectory::force_update() { +	// We set modified_time to 0 to force `EditorFileSystem::_scan_fs_changes` to search changes in the directory +	modified_time = 0; +} +  int EditorFileSystemDirectory::get_subdir_count() const {  	return subdirs.size();  } @@ -854,9 +859,11 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const  		//then scan files and directories and check what's different -		DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES); +		DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + +		Error ret = da->change_dir(cd); +		ERR_FAIL_COND_MSG(ret != OK, "Cannot change to '" + cd + "' folder."); -		da->change_dir(cd);  		da->list_dir_begin();  		while (true) {  			String f = da->get_next(); @@ -944,7 +951,6 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const  		}  		da->list_dir_end(); -		memdelete(da);  	}  	for (int i = 0; i < p_dir->files.size(); i++) { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 9c9076106c..855c320856 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -102,6 +102,8 @@ public:  	int find_file_index(const String &p_file) const;  	int find_dir_index(const String &p_dir) const; +	void force_update(); +  	EditorFileSystemDirectory();  	~EditorFileSystemDirectory();  }; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 738b2f9f82..5bb3c8b4d0 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -2266,6 +2266,22 @@ void EditorInspector::_edit_set(const String &p_name, const Variant &p_value, bo  		undo_redo->add_do_property(object, p_name, p_value);  		undo_redo->add_undo_property(object, p_name, object->get(p_name)); +		Variant v_undo_redo = (Object *)undo_redo; +		Variant v_object = object; +		Variant v_name = p_name; +		for (int i = 0; i < EditorNode::get_singleton()->get_editor_data().get_undo_redo_inspector_hook_callback().size(); i++) { +			const Callable &callback = EditorNode::get_singleton()->get_editor_data().get_undo_redo_inspector_hook_callback()[i]; + +			const Variant *p_arguments[] = { &v_undo_redo, &v_object, &v_name, &p_value }; +			Variant return_value; +			Callable::CallError call_error; + +			callback.call(p_arguments, 4, return_value, call_error); +			if (call_error.error != Callable::CallError::CALL_OK) { +				ERR_PRINT("Invalid UndoRedo callback."); +			} +		} +  		if (p_refresh_all) {  			undo_redo->add_do_method(this, "_edit_request_change", object, "");  			undo_redo->add_undo_method(this, "_edit_request_change", object, ""); diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 7b94016fb6..9f188b53c4 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -63,6 +63,18 @@ void EditorLog::_notification(int p_what) {  		log->add_theme_font_override("normal_font", get_theme_font("output_source", "EditorFonts"));  		log->add_theme_font_size_override("normal_font_size", get_theme_font_size("output_source_size", "EditorFonts"));  		log->add_theme_color_override("selection_color", get_theme_color("accent_color", "Editor") * Color(1, 1, 1, 0.4)); +		log->add_theme_font_override("bold_font", get_theme_font("bold", "EditorFonts")); + +		type_filter_map[MSG_TYPE_STD]->toggle_button->set_icon(get_theme_icon("Popup", "EditorIcons")); +		type_filter_map[MSG_TYPE_ERROR]->toggle_button->set_icon(get_theme_icon("StatusError", "EditorIcons")); +		type_filter_map[MSG_TYPE_WARNING]->toggle_button->set_icon(get_theme_icon("StatusWarning", "EditorIcons")); +		type_filter_map[MSG_TYPE_EDITOR]->toggle_button->set_icon(get_theme_icon("Edit", "EditorIcons")); + +		clear_button->set_icon(get_theme_icon("Clear", "EditorIcons")); +		copy_button->set_icon(get_theme_icon("ActionCopy", "EditorIcons")); +		collapse_button->set_icon(get_theme_icon("CombineLines", "EditorIcons")); +		show_search_button->set_icon(get_theme_icon("Search", "EditorIcons")); +  	} else if (p_what == NOTIFICATION_THEME_CHANGED) {  		Ref<Font> df_output_code = get_theme_font("output_source", "EditorFonts");  		if (df_output_code.is_valid()) { @@ -75,8 +87,15 @@ void EditorLog::_notification(int p_what) {  	}  } +void EditorLog::_set_collapse(bool p_collapse) { +	collapse = p_collapse; +	_rebuild_log(); +} +  void EditorLog::_clear_request() {  	log->clear(); +	messages.clear(); +	_reset_message_counts();  	tool_button->set_icon(Ref<Texture2D>());  } @@ -96,13 +115,83 @@ void EditorLog::clear() {  	_clear_request();  } -void EditorLog::copy() { -	_copy_request(); +void EditorLog::_process_message(const String &p_msg, MessageType p_type) { +	if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg) { +		// If previous message is the same as the new one, increase previous count rather than adding another +		// instance to the messages list. +		LogMessage &previous = messages.write[messages.size() - 1]; +		previous.count++; + +		_add_log_line(previous, collapse); +	} else { +		// Different message to the previous one received. +		LogMessage message(p_msg, p_type); +		_add_log_line(message); +		messages.push_back(message); +	} + +	type_filter_map[p_type]->set_message_count(type_filter_map[p_type]->get_message_count() + 1);  }  void EditorLog::add_message(const String &p_msg, MessageType p_type) { -	bool restore = p_type != MSG_TYPE_STD; -	switch (p_type) { +	// Make text split by new lines their own message. +	// See #41321 for reasoning. At time of writing, multiple print()'s in running projects +	// get grouped together and sent to the editor log as one message. This can mess with the +	// search functionality (see the comments on the PR above for more details). This behaviour +	// also matches that of other IDE's. +	Vector<String> lines = p_msg.split("\n", false); + +	for (int i = 0; i < lines.size(); i++) { +		_process_message(lines[i], p_type); +	} +} + +void EditorLog::set_tool_button(Button *p_tool_button) { +	tool_button = p_tool_button; +} + +void EditorLog::_undo_redo_cbk(void *p_self, const String &p_name) { +	EditorLog *self = (EditorLog *)p_self; +	self->add_message(p_name, EditorLog::MSG_TYPE_EDITOR); +} + +void EditorLog::_rebuild_log() { +	log->clear(); + +	for (int msg_idx = 0; msg_idx < messages.size(); msg_idx++) { +		LogMessage msg = messages[msg_idx]; + +		if (collapse) { +			// If collapsing, only log one instance of the message. +			_add_log_line(msg); +		} else { +			// If not collapsing, log each instance on a line. +			for (int i = 0; i < msg.count; i++) { +				_add_log_line(msg); +			} +		} +	} +} + +void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { +	// Only add the message to the log if it passes the filters. +	bool filter_active = type_filter_map[p_message.type]->active; +	String search_text = search_box->get_text(); +	bool search_match = search_text == String() || p_message.text.findn(search_text) > -1; + +	if (!filter_active || !search_match) { +		return; +	} + +	if (p_replace_previous) { +		// Remove last line if replacing, as it will be replace by the next added line. +		log->remove_line(log->get_line_count() - 1); +		log->increment_line_count(); +	} else { +		log->add_newline(); +	} + +	switch (p_message.type) {  		case MSG_TYPE_STD: {  		} break;  		case MSG_TYPE_ERROR: { @@ -125,21 +214,42 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) {  		} break;  	} -	log->add_text(p_msg); -	log->add_newline(); +	// If collapsing, add the count of this message in bold at the start of the line. +	if (collapse && p_message.count > 1) { +		log->push_bold(); +		log->add_text(vformat("(%s) ", itos(p_message.count))); +		log->pop(); +	} -	if (restore) { +	log->add_text(p_message.text); + +	// Need to use pop() to exit out of the RichTextLabels current "push" stack. +	// We only "push" in the above switch when message type != STD, so only pop when that is the case. +	if (p_message.type != MSG_TYPE_STD) {  		log->pop();  	}  } -void EditorLog::set_tool_button(Button *p_tool_button) { -	tool_button = p_tool_button; +void EditorLog::_set_filter_active(bool p_active, MessageType p_message_type) { +	type_filter_map[p_message_type]->active = p_active; +	_rebuild_log();  } -void EditorLog::_undo_redo_cbk(void *p_self, const String &p_name) { -	EditorLog *self = (EditorLog *)p_self; -	self->add_message(p_name, EditorLog::MSG_TYPE_EDITOR); +void EditorLog::_set_search_visible(bool p_visible) { +	search_box->set_visible(p_visible); +	if (p_visible) { +		search_box->grab_focus(); +	} +} + +void EditorLog::_search_changed(const String &p_text) { +	_rebuild_log(); +} + +void EditorLog::_reset_message_counts() { +	for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { +		E->value()->set_message_count(0); +	}  }  void EditorLog::_bind_methods() { @@ -148,37 +258,108 @@ void EditorLog::_bind_methods() {  }  EditorLog::EditorLog() { -	VBoxContainer *vb = this; - -	HBoxContainer *hb = memnew(HBoxContainer); -	vb->add_child(hb); -	title = memnew(Label); -	title->set_text(TTR("Output:")); -	title->set_h_size_flags(SIZE_EXPAND_FILL); -	hb->add_child(title); - -	copybutton = memnew(Button); -	hb->add_child(copybutton); -	copybutton->set_text(TTR("Copy")); -	copybutton->set_shortcut(ED_SHORTCUT("editor/copy_output", TTR("Copy Selection"), KEY_MASK_CMD | KEY_C)); -	copybutton->set_shortcut_context(this); -	copybutton->connect("pressed", callable_mp(this, &EditorLog::_copy_request)); - -	clearbutton = memnew(Button); -	hb->add_child(clearbutton); -	clearbutton->set_text(TTR("Clear")); -	clearbutton->set_shortcut(ED_SHORTCUT("editor/clear_output", TTR("Clear Output"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_K)); -	clearbutton->set_shortcut_context(this); -	clearbutton->connect("pressed", callable_mp(this, &EditorLog::_clear_request)); +	HBoxContainer *hb = this; +	VBoxContainer *vb_left = memnew(VBoxContainer); +	vb_left->set_custom_minimum_size(Size2(0, 180) * EDSCALE); +	vb_left->set_v_size_flags(SIZE_EXPAND_FILL); +	vb_left->set_h_size_flags(SIZE_EXPAND_FILL); +	hb->add_child(vb_left); + +	// Log - Rich Text Label.  	log = memnew(RichTextLabel);  	log->set_scroll_follow(true);  	log->set_selection_enabled(true);  	log->set_focus_mode(FOCUS_CLICK); -	log->set_custom_minimum_size(Size2(0, 180) * EDSCALE);  	log->set_v_size_flags(SIZE_EXPAND_FILL);  	log->set_h_size_flags(SIZE_EXPAND_FILL); -	vb->add_child(log); +	vb_left->add_child(log); + +	// Search box +	search_box = memnew(LineEdit); +	search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); +	search_box->set_placeholder(TTR("Filter messages")); +	search_box->set_right_icon(get_theme_icon("Search", "EditorIcons")); +	search_box->set_clear_button_enabled(true); +	search_box->set_visible(true); +	search_box->connect("text_changed", callable_mp(this, &EditorLog::_search_changed)); +	vb_left->add_child(search_box); + +	VBoxContainer *vb_right = memnew(VBoxContainer); +	hb->add_child(vb_right); + +	// Tools grid +	HBoxContainer *hb_tools = memnew(HBoxContainer); +	hb_tools->set_h_size_flags(SIZE_SHRINK_CENTER); +	vb_right->add_child(hb_tools); + +	// Clear. +	clear_button = memnew(Button); +	clear_button->set_flat(true); +	clear_button->set_focus_mode(FOCUS_NONE); +	clear_button->set_shortcut(ED_SHORTCUT("editor/clear_output", TTR("Clear Output"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_K)); +	clear_button->set_shortcut_context(this); +	clear_button->connect("pressed", callable_mp(this, &EditorLog::_clear_request)); +	hb_tools->add_child(clear_button); + +	// Copy. +	copy_button = memnew(Button); +	copy_button->set_flat(true); +	copy_button->set_focus_mode(FOCUS_NONE); +	copy_button->set_shortcut(ED_SHORTCUT("editor/copy_output", TTR("Copy Selection"), KEY_MASK_CMD | KEY_C)); +	copy_button->set_shortcut_context(this); +	copy_button->connect("pressed", callable_mp(this, &EditorLog::_copy_request)); +	hb_tools->add_child(copy_button); + +	// A second hbox to make a 2x2 grid of buttons. +	HBoxContainer *hb_tools2 = memnew(HBoxContainer); +	hb_tools2->set_h_size_flags(SIZE_SHRINK_CENTER); +	vb_right->add_child(hb_tools2); + +	// Collapse. +	collapse_button = memnew(Button); +	collapse_button->set_flat(true); +	collapse_button->set_focus_mode(FOCUS_NONE); +	collapse_button->set_tooltip(TTR("Collapse duplicate messages into one log entry. Shows number of occurences.")); +	collapse_button->set_toggle_mode(true); +	collapse_button->set_pressed(false); +	collapse_button->connect("toggled", callable_mp(this, &EditorLog::_set_collapse)); +	hb_tools2->add_child(collapse_button); + +	// Show Search. +	show_search_button = memnew(Button); +	show_search_button->set_flat(true); +	show_search_button->set_focus_mode(FOCUS_NONE); +	show_search_button->set_toggle_mode(true); +	show_search_button->set_pressed(true); +	show_search_button->set_shortcut(ED_SHORTCUT("editor/open_search", TTR("Open the search box."), KEY_MASK_CMD | KEY_F)); +	show_search_button->set_shortcut_context(this); +	show_search_button->connect("toggled", callable_mp(this, &EditorLog::_set_search_visible)); +	hb_tools2->add_child(show_search_button); + +	// Message Type Filters. +	vb_right->add_child(memnew(HSeparator)); + +	LogFilter *std_filter = memnew(LogFilter(MSG_TYPE_STD)); +	std_filter->initialize_button("Toggle visibility of standard output messages.", callable_mp(this, &EditorLog::_set_filter_active)); +	vb_right->add_child(std_filter->toggle_button); +	type_filter_map.insert(MSG_TYPE_STD, std_filter); + +	LogFilter *error_filter = memnew(LogFilter(MSG_TYPE_ERROR)); +	error_filter->initialize_button("Toggle visibility of errors.", callable_mp(this, &EditorLog::_set_filter_active)); +	vb_right->add_child(error_filter->toggle_button); +	type_filter_map.insert(MSG_TYPE_ERROR, error_filter); + +	LogFilter *warning_filter = memnew(LogFilter(MSG_TYPE_WARNING)); +	warning_filter->initialize_button("Toggle visibility of warnings.", callable_mp(this, &EditorLog::_set_filter_active)); +	vb_right->add_child(warning_filter->toggle_button); +	type_filter_map.insert(MSG_TYPE_WARNING, warning_filter); + +	LogFilter *editor_filter = memnew(LogFilter(MSG_TYPE_EDITOR)); +	editor_filter->initialize_button("Toggle visibility of editor messages.", callable_mp(this, &EditorLog::_set_filter_active)); +	vb_right->add_child(editor_filter->toggle_button); +	type_filter_map.insert(MSG_TYPE_EDITOR, editor_filter); +  	add_message(VERSION_FULL_NAME " (c) 2007-2021 Juan Linietsky, Ariel Manzur & Godot Contributors.");  	eh.errfunc = _error_handler; @@ -187,8 +368,6 @@ EditorLog::EditorLog() {  	current = Thread::get_caller_id(); -	add_theme_constant_override("separation", get_theme_constant("separation", "VBoxContainer")); -  	EditorNode::get_undo_redo()->set_commit_notify_callback(_undo_redo_cbk, this);  } @@ -197,4 +376,7 @@ void EditorLog::deinit() {  }  EditorLog::~EditorLog() { +	for (Map<MessageType, LogFilter *>::Element *E = type_filter_map.front(); E; E = E->next()) { +		memdelete(E->get()); +	}  } diff --git a/editor/editor_log.h b/editor/editor_log.h index 79dfb3ffaa..89d00d0fa0 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -36,19 +36,92 @@  #include "scene/gui/button.h"  #include "scene/gui/control.h"  #include "scene/gui/label.h" +#include "scene/gui/line_edit.h"  #include "scene/gui/panel_container.h"  #include "scene/gui/rich_text_label.h"  #include "scene/gui/texture_button.h"  #include "scene/gui/texture_rect.h" -class EditorLog : public VBoxContainer { -	GDCLASS(EditorLog, VBoxContainer); +class EditorLog : public HBoxContainer { +	GDCLASS(EditorLog, HBoxContainer); + +public: +	enum MessageType { +		MSG_TYPE_STD, +		MSG_TYPE_ERROR, +		MSG_TYPE_WARNING, +		MSG_TYPE_EDITOR, +	}; + +private: +	struct LogMessage { +		String text; +		MessageType type; +		int count = 1; + +		LogMessage() {} + +		LogMessage(const String p_text, MessageType p_type) : +				text(p_text), +				type(p_type) { +		} +	}; + +	// Encapsulates all data and functionality regarding filters. +	struct LogFilter { +	private: +		// Force usage of set method since it has functionality built-in. +		int message_count = 0; + +	public: +		MessageType type; +		Button *toggle_button = nullptr; +		bool active = true; + +		void initialize_button(const String &p_tooltip, Callable p_toggled_callback) { +			toggle_button = memnew(Button); +			toggle_button->set_toggle_mode(true); +			toggle_button->set_pressed(true); +			toggle_button->set_text(itos(message_count)); +			toggle_button->set_tooltip(TTR(p_tooltip)); +			// Don't tint the icon even when in "pressed" state. +			toggle_button->add_theme_color_override("icon_color_pressed", Color(1, 1, 1, 1)); +			toggle_button->set_focus_mode(FOCUS_NONE); +			// When toggled call the callback and pass the MessageType this button is for. +			toggle_button->connect("toggled", p_toggled_callback, varray(type)); +		} + +		int get_message_count() { +			return message_count; +		} + +		void set_message_count(int p_count) { +			message_count = p_count; +			toggle_button->set_text(itos(message_count)); +		} + +		LogFilter(MessageType p_type) : +				type(p_type) { +		} +	}; + +	Vector<LogMessage> messages; +	// Maps MessageTypes to LogFilters for convenient access and storage (don't need 1 member per filter). +	Map<MessageType, LogFilter *> type_filter_map; -	Button *clearbutton; -	Button *copybutton; -	Label *title;  	RichTextLabel *log; -	HBoxContainer *title_hb; + +	Button *clear_button; +	Button *copy_button; + +	Button *collapse_button; +	bool collapse = false; + +	Button *show_search_button; +	LineEdit *search_box; + +	// Reference to the "Output" button on the toolbar so we can update it's icon when +	// Warnings or Errors are encounetered.  	Button *tool_button;  	static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type); @@ -62,26 +135,33 @@ class EditorLog : public VBoxContainer {  	void _copy_request();  	static void _undo_redo_cbk(void *p_self, const String &p_name); +	void _rebuild_log(); +	void _add_log_line(LogMessage &p_message, bool p_replace_previous = false); + +	void _set_filter_active(bool p_active, MessageType p_message_type); +	void _set_search_visible(bool p_visible); +	void _search_changed(const String &p_text); + +	void _process_message(const String &p_msg, MessageType p_type); +	void _reset_message_counts(); + +	void _set_collapse(bool p_collapse); +  protected:  	static void _bind_methods();  	void _notification(int p_what);  public: -	enum MessageType { -		MSG_TYPE_STD, -		MSG_TYPE_ERROR, -		MSG_TYPE_WARNING, -		MSG_TYPE_EDITOR -	}; -  	void add_message(const String &p_msg, MessageType p_type = MSG_TYPE_STD);  	void set_tool_button(Button *p_tool_button);  	void deinit();  	void clear(); -	void copy(); +  	EditorLog();  	~EditorLog();  }; +VARIANT_ENUM_CAST(EditorLog::MessageType); +  #endif // EDITOR_LOG_H diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6137617564..8eeabf9cfd 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -46,11 +46,13 @@  #include "core/string/print_string.h"  #include "core/string/translation.h"  #include "core/version.h" +#include "core/version_hash.gen.h"  #include "main/main.h"  #include "scene/gui/center_container.h"  #include "scene/gui/control.h"  #include "scene/gui/dialogs.h"  #include "scene/gui/file_dialog.h" +#include "scene/gui/link_button.h"  #include "scene/gui/menu_button.h"  #include "scene/gui/panel.h"  #include "scene/gui/panel_container.h" @@ -184,6 +186,9 @@  EditorNode *EditorNode::singleton = nullptr; +// The metadata key used to store and retrieve the version text to copy to the clipboard. +static const String META_TEXT_TO_COPY = "text_to_copy"; +  void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vector<String> &r_filenames) {  	// Keep track of a list of "index sets," i.e. sets of indices  	// within disambiguated_scene_names which contain the same name. @@ -989,6 +994,10 @@ void EditorNode::_reload_project_settings() {  void EditorNode::_vp_resized() {  } +void EditorNode::_version_button_pressed() { +	DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY)); +} +  void EditorNode::_node_renamed() {  	if (get_inspector()) {  		get_inspector()->update_tree(); @@ -1399,8 +1408,9 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {  		} else {  			// The 3D editor may be disabled as a feature, but scenes can still be opened.  			// This check prevents the preview from regenerating in case those scenes are then saved. +			// The preview will be generated if no feature profile is set (as the 3D editor is enabled by default).  			Ref<EditorFeatureProfile> profile = feature_profile_manager->get_current_profile(); -			if (profile.is_valid() && !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D)) { +			if (!profile.is_valid() || !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D)) {  				img = Node3DEditor::get_singleton()->get_editor_viewport(0)->get_viewport_node()->get_texture()->get_image();  			}  		} @@ -4855,7 +4865,7 @@ void EditorNode::_scene_tab_input(const Ref<InputEvent> &p_input) {  				_scene_tab_closed(scene_tabs->get_hovered_tab());  			}  		} else { -			if ((mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_doubleclick()) || (mb->get_button_index() == MOUSE_BUTTON_MIDDLE && mb->is_pressed())) { +			if ((mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_double_click()) || (mb->get_button_index() == MOUSE_BUTTON_MIDDLE && mb->is_pressed())) {  				_menu_option_confirm(FILE_NEW_SCENE, true);  			}  		} @@ -5559,6 +5569,8 @@ void EditorNode::_bind_methods() {  	ClassDB::bind_method("_screenshot", &EditorNode::_screenshot);  	ClassDB::bind_method("_save_screenshot", &EditorNode::_save_screenshot); +	ClassDB::bind_method("_version_button_pressed", &EditorNode::_version_button_pressed); +  	ADD_SIGNAL(MethodInfo("play_pressed"));  	ADD_SIGNAL(MethodInfo("pause_pressed"));  	ADD_SIGNAL(MethodInfo("stop_pressed")); @@ -6617,11 +6629,31 @@ EditorNode::EditorNode() {  	bottom_panel_hb_editors->set_h_size_flags(Control::SIZE_EXPAND_FILL);  	bottom_panel_hb->add_child(bottom_panel_hb_editors); -	version_label = memnew(Label); -	version_label->set_text(VERSION_FULL_CONFIG); +	VBoxContainer *version_info_vbc = memnew(VBoxContainer); +	bottom_panel_hb->add_child(version_info_vbc); + +	// Add a dummy control node for vertical spacing. +	Control *v_spacer = memnew(Control); +	version_info_vbc->add_child(v_spacer); + +	version_btn = memnew(LinkButton); +	version_btn->set_text(VERSION_FULL_CONFIG); +	String hash = String(VERSION_HASH); +	if (hash.length() != 0) { +		hash = "." + hash.left(9); +	} +	// Set the text to copy in metadata as it slightly differs from the button's text. +	version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash);  	// Fade out the version label to be less prominent, but still readable -	version_label->set_self_modulate(Color(1, 1, 1, 0.6)); -	bottom_panel_hb->add_child(version_label); +	version_btn->set_self_modulate(Color(1, 1, 1, 0.65)); +	version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); +	version_btn->set_tooltip(TTR("Click to copy.")); +	version_btn->connect("pressed", callable_mp(this, &EditorNode::_version_button_pressed)); +	version_info_vbc->add_child(version_btn); + +	// Add a dummy control node for horizontal spacing. +	Control *h_spacer = memnew(Control); +	bottom_panel_hb->add_child(h_spacer);  	bottom_panel_raise = memnew(Button);  	bottom_panel_raise->set_flat(true); diff --git a/editor/editor_node.h b/editor/editor_node.h index 7e16936f5d..d06851cb4f 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -40,6 +40,7 @@  #include "editor/inspector_dock.h"  #include "editor/property_editor.h"  #include "editor/scene_tree_dock.h" +#include "scene/gui/link_button.h"  typedef void (*EditorNodeInitCallback)();  typedef void (*EditorPluginInitializeCallback)(); @@ -424,7 +425,7 @@ private:  	HBoxContainer *bottom_panel_hb;  	HBoxContainer *bottom_panel_hb_editors;  	VBoxContainer *bottom_panel_vb; -	Label *version_label; +	LinkButton *version_btn;  	Button *bottom_panel_raise;  	Tree *disk_changed_list; @@ -477,6 +478,7 @@ private:  	void _close_messages();  	void _show_messages();  	void _vp_resized(); +	void _version_button_pressed();  	int _save_external_resources(); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index eabcbacd9a..6b96cb0f5c 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -703,6 +703,14 @@ bool EditorPlugin::get_remove_list(List<Node *> *p_list) {  void EditorPlugin::restore_global_state() {}  void EditorPlugin::save_global_state() {} +void EditorPlugin::add_undo_redo_inspector_hook_callback(Callable p_callable) { +	EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(p_callable); +} + +void EditorPlugin::remove_undo_redo_inspector_hook_callback(Callable p_callable) { +	EditorNode::get_singleton()->get_editor_data().remove_undo_redo_inspector_hook_callback(p_callable); +} +  void EditorPlugin::add_translation_parser_plugin(const Ref<EditorTranslationParserPlugin> &p_parser) {  	EditorTranslationParser::get_singleton()->add_parser(p_parser, EditorTranslationParser::CUSTOM);  } @@ -862,6 +870,8 @@ void EditorPlugin::_bind_methods() {  	ClassDB::bind_method(D_METHOD("hide_bottom_panel"), &EditorPlugin::hide_bottom_panel);  	ClassDB::bind_method(D_METHOD("get_undo_redo"), &EditorPlugin::_get_undo_redo); +	ClassDB::bind_method(D_METHOD("add_undo_redo_inspector_hook_callback", "callable"), &EditorPlugin::add_undo_redo_inspector_hook_callback); +	ClassDB::bind_method(D_METHOD("remove_undo_redo_inspector_hook_callback", "callable"), &EditorPlugin::remove_undo_redo_inspector_hook_callback);  	ClassDB::bind_method(D_METHOD("queue_save_layout"), &EditorPlugin::queue_save_layout);  	ClassDB::bind_method(D_METHOD("add_translation_parser_plugin", "parser"), &EditorPlugin::add_translation_parser_plugin);  	ClassDB::bind_method(D_METHOD("remove_translation_parser_plugin", "parser"), &EditorPlugin::remove_translation_parser_plugin); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 67b163eabf..37412e5ebe 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -225,6 +225,9 @@ public:  	EditorInterface *get_editor_interface();  	ScriptCreateDialog *get_script_create_dialog(); +	void add_undo_redo_inspector_hook_callback(Callable p_callable); +	void remove_undo_redo_inspector_hook_callback(Callable p_callable); +  	int update_overlays() const;  	void queue_save_layout(); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 652deb1804..47c0e31da6 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -929,7 +929,7 @@ EditorPropertyFloat::EditorPropertyFloat() {  void EditorPropertyEasing::_drag_easing(const Ref<InputEvent> &p_ev) {  	const Ref<InputEventMouseButton> mb = p_ev;  	if (mb.is_valid()) { -		if (mb->is_doubleclick() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { +		if (mb->is_double_click() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {  			_setup_spin();  		} diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 8577ccb9db..dba53a9708 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -70,7 +70,6 @@ void EditorSpinSlider::_gui_input(const Ref<InputEvent> &p_event) {  					grabbing_spinner_dist_cache = 0;  					pre_grab_value = get_value();  					grabbing_spinner = false; -					grabbing_spinner_mouse_pos = Input::get_singleton()->get_mouse_position();  				}  			} else {  				if (grabbing_spinner_attempt) { @@ -283,6 +282,8 @@ void EditorSpinSlider::_notification(int p_what) {  			Rect2 grabber_rect = Rect2(ofs + gofs, svofs + 1, grabber_w, 2 * EDSCALE);  			draw_rect(grabber_rect, c); +			grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.position + grabber_rect.size * 0.5; +  			bool display_grabber = (mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !value_input_popup->is_visible();  			if (grabber->is_visible() != display_grabber) {  				if (display_grabber) { diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 899070f036..09424647fe 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1465,6 +1465,10 @@ void FileSystemDock::_folder_removed(String p_folder) {  	}  	current_path->set_text(path); +	EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(path); +	if (efd) { +		efd->force_update(); +	}  }  void FileSystemDock::_rename_operation_confirm() { diff --git a/editor/icons/CombineLines.svg b/editor/icons/CombineLines.svg new file mode 100644 index 0000000000..124814ae88 --- /dev/null +++ b/editor/icons/CombineLines.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M1 2v2h14V2zm7 5v2h7V7zm0 5v2h7v-2zM4.976 14V9h2l-1.5-2-1.5-2-1.5 2-1.5 2h2v5z" fill="#e0e0e0"/></svg> diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 4bb56beaeb..96002400f3 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -1136,7 +1136,7 @@ Ref<Animation> ResourceImporterScene::import_animation_from_other_importer(Edito  	return importer->import_animation(p_path, p_flags, p_bake_fps);  } -void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<uint8_t> &r_dst_lightmap_cache) { +void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches) {  	EditorSceneImporterMeshNode3D *src_mesh_node = Object::cast_to<EditorSceneImporterMeshNode3D>(p_node);  	if (src_mesh_node) {  		//is mesh @@ -1216,7 +1216,28 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m  						n = n->get_parent_spatial();  					} -					//use xf as transform for mesh, and bake it +					Vector<uint8_t> lightmap_cache; +					src_mesh_node->get_mesh()->lightmap_unwrap_cached(xf, p_lightmap_texel_size, p_src_lightmap_cache, lightmap_cache); + +					if (!lightmap_cache.is_empty()) { +						if (r_lightmap_caches.is_empty()) { +							r_lightmap_caches.push_back(lightmap_cache); +						} else { +							String new_md5 = String::md5(lightmap_cache.ptr()); // MD5 is stored at the beginning of the cache data + +							for (int i = 0; i < r_lightmap_caches.size(); i++) { +								String md5 = String::md5(r_lightmap_caches[i].ptr()); +								if (new_md5 < md5) { +									r_lightmap_caches.insert(i, lightmap_cache); +									break; +								} + +								if (new_md5 == md5) { +									break; +								} +							} +						} +					}  				}  				if (save_to_file != String()) { @@ -1265,7 +1286,7 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m  	}  	for (int i = 0; i < p_node->get_child_count(); i++) { -		_generate_meshes(p_node->get_child(i), p_mesh_data, p_generate_lods, p_create_shadow_meshes, p_light_bake_mode, p_lightmap_texel_size, p_src_lightmap_cache, r_dst_lightmap_cache); +		_generate_meshes(p_node->get_child(i), p_mesh_data, p_generate_lods, p_create_shadow_meshes, p_light_bake_mode, p_lightmap_texel_size, p_src_lightmap_cache, r_lightmap_caches);  	}  } @@ -1433,7 +1454,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p  	float lightmap_texel_size = MAX(0.001, texel_size);  	Vector<uint8_t> src_lightmap_cache; -	Vector<uint8_t> dst_lightmap_cache; +	Vector<Vector<uint8_t>> mesh_lightmap_caches;  	{  		src_lightmap_cache = FileAccess::get_file_as_array(p_source_file + ".unwrap_cache", &err); @@ -1446,124 +1467,20 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p  	if (subresources.has("meshes")) {  		mesh_data = subresources["meshes"];  	} -	_generate_meshes(scene, mesh_data, gen_lods, create_shadow_meshes, LightBakeMode(light_bake_mode), lightmap_texel_size, src_lightmap_cache, dst_lightmap_cache); +	_generate_meshes(scene, mesh_data, gen_lods, create_shadow_meshes, LightBakeMode(light_bake_mode), lightmap_texel_size, src_lightmap_cache, mesh_lightmap_caches); -	if (dst_lightmap_cache.size()) { +	if (mesh_lightmap_caches.size()) {  		FileAccessRef f = FileAccess::open(p_source_file + ".unwrap_cache", FileAccess::WRITE);  		if (f) { -			f->store_buffer(dst_lightmap_cache.ptr(), dst_lightmap_cache.size()); -		} -	} -	err = OK; - -#if 0 -	if (light_bake_mode == 2 /* || generate LOD */) { -		Map<Ref<ArrayMesh>, Transform> meshes; -		_find_meshes(scene, meshes); - -		String file_id = src_path.get_file(); -		String cache_file_path = base_path.plus_file(file_id + ".unwrap_cache"); - -		Vector<unsigned char> cache_data; - -		if (FileAccess::exists(cache_file_path)) { -			Error err2; -			FileAccess *file = FileAccess::open(cache_file_path, FileAccess::READ, &err2); - -			if (err2) { -				if (file) { -					memdelete(file); -				} -			} else { -				int cache_size = file->get_len(); -				cache_data.resize(cache_size); -				file->get_buffer(cache_data.ptrw(), cache_size); +			f->store_32(mesh_lightmap_caches.size()); +			for (int i = 0; i < mesh_lightmap_caches.size(); i++) { +				String md5 = String::md5(mesh_lightmap_caches[i].ptr()); +				f->store_buffer(mesh_lightmap_caches[i].ptr(), mesh_lightmap_caches[i].size());  			} -		} - -		Map<String, unsigned int> used_unwraps; - -		EditorProgress progress2("gen_lightmaps", TTR("Generating Lightmaps"), meshes.size()); -		int step = 0; -		for (Map<Ref<ArrayMesh>, Transform>::Element *E = meshes.front(); E; E = E->next()) { -			Ref<ArrayMesh> mesh = E->key(); -			String name = mesh->get_name(); -			if (name == "") { //should not happen but.. -				name = "Mesh " + itos(step); -			} - -			progress2.step(TTR("Generating for Mesh: ") + name + " (" + itos(step) + "/" + itos(meshes.size()) + ")", step); - -			int *ret_cache_data = (int *)cache_data.ptrw(); -			unsigned int ret_cache_size = cache_data.size(); -			bool ret_used_cache = true; // Tell the unwrapper to use the cache -			Error err2 = mesh->lightmap_unwrap_cached(ret_cache_data, ret_cache_size, ret_used_cache, E->get(), texel_size); - -			if (err2 != OK) { -				EditorNode::add_io_error("Mesh '" + name + "' failed lightmap generation. Please fix geometry."); -			} else { -`				String hash = String::md5((unsigned char *)ret_cache_data); -				used_unwraps.insert(hash, ret_cache_size); - -				if (!ret_used_cache) { -					// Cache was not used, add the generated entry to the current cache -					if (cache_data.is_empty()) { -						cache_data.resize(4 + ret_cache_size); -						int *data = (int *)cache_data.ptrw(); -						data[0] = 1; -						memcpy(&data[1], ret_cache_data, ret_cache_size); -					} else { -						int current_size = cache_data.size(); -						cache_data.resize(cache_data.size() + ret_cache_size); -							unsigned char *ptrw = cache_data.ptrw(); -						memcpy(&ptrw[current_size], ret_cache_data, ret_cache_size); -						int *data = (int *)ptrw; -						data[0] += 1; -					} -				} -			} -			step++; -		} - -		Error err2; -		FileAccess *file = FileAccess::open(cache_file_path, FileAccess::WRITE, &err2); - -		if (err2) { -			if (file) { -				memdelete(file); -			} -		} else { -			// Store number of entries -			file->store_32(used_unwraps.size()); - -			// Store cache entries -			const int *cache = (int *)cache_data.ptr(); -			unsigned int r_idx = 1; -			for (int i = 0; i < cache[0]; ++i) { -				unsigned char *entry_start = (unsigned char *)&cache[r_idx]; -				String entry_hash = String::md5(entry_start); -				if (used_unwraps.has(entry_hash)) { -					unsigned int entry_size = used_unwraps[entry_hash]; -					file->store_buffer(entry_start, entry_size); -				} - -				r_idx += 4; // hash -				r_idx += 2; // size hint - -				int vertex_count = cache[r_idx]; -				r_idx += 1; // vertex count -				r_idx += vertex_count; // vertex -				r_idx += vertex_count * 2; // uvs - -				int index_count = cache[r_idx]; -				r_idx += 1; // index count -				r_idx += index_count; // indices -			} - -			file->close(); +			f->close();  		}  	} -#endif +	err = OK;  	progress.step(TTR("Running Custom Script..."), 2); diff --git a/editor/import/resource_importer_scene.h b/editor/import/resource_importer_scene.h index 00039f2ac6..8cb84abce2 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/resource_importer_scene.h @@ -119,7 +119,7 @@ class ResourceImporterScene : public ResourceImporter {  	};  	void _replace_owner(Node *p_node, Node *p_scene, Node *p_new_owner); -	void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<uint8_t> &r_dst_lightmap_cache); +	void _generate_meshes(Node *p_node, const Dictionary &p_mesh_data, bool p_generate_lods, bool p_create_shadow_meshes, LightBakeMode p_light_bake_mode, float p_lightmap_texel_size, const Vector<uint8_t> &p_src_lightmap_cache, Vector<Vector<uint8_t>> &r_lightmap_caches);  	void _add_shapes(Node *p_node, const List<Ref<Shape3D>> &p_shapes);  public: diff --git a/editor/import/scene_importer_mesh.cpp b/editor/import/scene_importer_mesh.cpp index 28fdd4ddbd..bc7e8a1626 100644 --- a/editor/import/scene_importer_mesh.cpp +++ b/editor/import/scene_importer_mesh.cpp @@ -583,7 +583,7 @@ Ref<NavigationMesh> EditorSceneImporterMesh::create_navigation_mesh() {  	return nm;  } -extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y, int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache); +extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y);  struct EditorSceneImporterMeshLightmapSurface {  	Ref<Material> material; @@ -593,22 +593,24 @@ struct EditorSceneImporterMeshLightmapSurface {  	String name;  }; -Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size) { +Error EditorSceneImporterMesh::lightmap_unwrap_cached(const Transform &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache) {  	ERR_FAIL_COND_V(!array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED);  	ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); -	Vector<float> vertices; -	Vector<float> normals; -	Vector<int> indices; -	Vector<float> uv; -	Vector<Pair<int, int>> uv_indices; +	LocalVector<float> vertices; +	LocalVector<float> normals; +	LocalVector<int> indices; +	LocalVector<float> uv; +	LocalVector<Pair<int, int>> uv_indices;  	Vector<EditorSceneImporterMeshLightmapSurface> lightmap_surfaces;  	// Keep only the scale -	Transform transform = p_base_transform; -	transform.origin = Vector3(); -	transform.looking_at(Vector3(1, 0, 0), Vector3(0, 1, 0)); +	Basis basis = p_base_transform.get_basis(); +	Vector3 scale = Vector3(basis.get_axis(0).length(), basis.get_axis(1).length(), basis.get_axis(2).length()); + +	Transform transform; +	transform.scale(scale);  	Basis normal_basis = transform.basis.inverse().transposed(); @@ -623,15 +625,10 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  		SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); -		Vector<Vector3> rvertices = arrays[Mesh::ARRAY_VERTEX]; +		PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX];  		int vc = rvertices.size(); -		const Vector3 *r = rvertices.ptr(); - -		Vector<Vector3> rnormals = arrays[Mesh::ARRAY_NORMAL]; - -		ERR_FAIL_COND_V_MSG(rnormals.size() == 0, ERR_UNAVAILABLE, "Normals are required for lightmap unwrap."); -		const Vector3 *rn = rnormals.ptr(); +		PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL];  		int vertex_ofs = vertices.size() / 3; @@ -640,24 +637,29 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  		uv_indices.resize(vertex_ofs + vc);  		for (int j = 0; j < vc; j++) { -			Vector3 v = transform.xform(r[j]); -			Vector3 n = normal_basis.xform(rn[j]).normalized(); - -			vertices.write[(j + vertex_ofs) * 3 + 0] = v.x; -			vertices.write[(j + vertex_ofs) * 3 + 1] = v.y; -			vertices.write[(j + vertex_ofs) * 3 + 2] = v.z; -			normals.write[(j + vertex_ofs) * 3 + 0] = n.x; -			normals.write[(j + vertex_ofs) * 3 + 1] = n.y; -			normals.write[(j + vertex_ofs) * 3 + 2] = n.z; -			uv_indices.write[j + vertex_ofs] = Pair<int, int>(i, j); +			Vector3 v = transform.xform(rvertices[j]); +			Vector3 n = normal_basis.xform(rnormals[j]).normalized(); + +			vertices[(j + vertex_ofs) * 3 + 0] = v.x; +			vertices[(j + vertex_ofs) * 3 + 1] = v.y; +			vertices[(j + vertex_ofs) * 3 + 2] = v.z; +			normals[(j + vertex_ofs) * 3 + 0] = n.x; +			normals[(j + vertex_ofs) * 3 + 1] = n.y; +			normals[(j + vertex_ofs) * 3 + 2] = n.z; +			uv_indices[j + vertex_ofs] = Pair<int, int>(i, j);  		} -		Vector<int> rindices = arrays[Mesh::ARRAY_INDEX]; +		PackedInt32Array rindices = arrays[Mesh::ARRAY_INDEX];  		int ic = rindices.size(); +		float eps = 1.19209290e-7F; // Taken from xatlas.h  		if (ic == 0) {  			for (int j = 0; j < vc / 3; j++) { -				if (Face3(r[j * 3 + 0], r[j * 3 + 1], r[j * 3 + 2]).is_degenerate()) { +				Vector3 p0 = transform.xform(rvertices[j * 3 + 0]); +				Vector3 p1 = transform.xform(rvertices[j * 3 + 1]); +				Vector3 p2 = transform.xform(rvertices[j * 3 + 2]); + +				if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) {  					continue;  				} @@ -667,15 +669,18 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  			}  		} else { -			const int *ri = rindices.ptr(); -  			for (int j = 0; j < ic / 3; j++) { -				if (Face3(r[ri[j * 3 + 0]], r[ri[j * 3 + 1]], r[ri[j * 3 + 2]]).is_degenerate()) { +				Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]); +				Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]); +				Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]); + +				if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) {  					continue;  				} -				indices.push_back(vertex_ofs + ri[j * 3 + 0]); -				indices.push_back(vertex_ofs + ri[j * 3 + 1]); -				indices.push_back(vertex_ofs + ri[j * 3 + 2]); + +				indices.push_back(vertex_ofs + rindices[j * 3 + 0]); +				indices.push_back(vertex_ofs + rindices[j * 3 + 1]); +				indices.push_back(vertex_ofs + rindices[j * 3 + 2]);  			}  		} @@ -684,6 +689,9 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  	//unwrap +	bool use_cache = true; // Used to request cache generation and to know if cache was used +	uint8_t *gen_cache; +	int gen_cache_size;  	float *gen_uvs;  	int *gen_vertices;  	int *gen_indices; @@ -692,7 +700,7 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  	int size_x;  	int size_y; -	bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y, r_cache_data, r_cache_size, r_used_cache); +	bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y);  	if (!ok) {  		return ERR_CANT_CREATE; @@ -702,7 +710,7 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  	clear();  	//create surfacetools for each surface.. -	Vector<Ref<SurfaceTool>> surfaces_tools; +	LocalVector<Ref<SurfaceTool>> surfaces_tools;  	for (int i = 0; i < lightmap_surfaces.size(); i++) {  		Ref<SurfaceTool> st; @@ -714,11 +722,12 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  	}  	print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); +  	//go through all indices  	for (int i = 0; i < gen_index_count; i += 3) { -		ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], uv_indices.size(), ERR_BUG); -		ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], uv_indices.size(), ERR_BUG); -		ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], uv_indices.size(), ERR_BUG); +		ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); +		ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); +		ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG);  		ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); @@ -728,49 +737,54 @@ Error EditorSceneImporterMesh::lightmap_unwrap_cached(int *&r_cache_data, unsign  			SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second];  			if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) { -				surfaces_tools.write[surface]->set_color(v.color); +				surfaces_tools[surface]->set_color(v.color);  			}  			if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) { -				surfaces_tools.write[surface]->set_uv(v.uv); +				surfaces_tools[surface]->set_uv(v.uv);  			}  			if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) { -				surfaces_tools.write[surface]->set_normal(v.normal); +				surfaces_tools[surface]->set_normal(v.normal);  			}  			if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TANGENT) {  				Plane t;  				t.normal = v.tangent;  				t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; -				surfaces_tools.write[surface]->set_tangent(t); +				surfaces_tools[surface]->set_tangent(t);  			}  			if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) { -				surfaces_tools.write[surface]->set_bones(v.bones); +				surfaces_tools[surface]->set_bones(v.bones);  			}  			if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) { -				surfaces_tools.write[surface]->set_weights(v.weights); +				surfaces_tools[surface]->set_weights(v.weights);  			}  			Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); -			surfaces_tools.write[surface]->set_uv2(uv2); +			surfaces_tools[surface]->set_uv2(uv2); -			surfaces_tools.write[surface]->add_vertex(v.vertex); +			surfaces_tools[surface]->add_vertex(v.vertex);  		}  	}  	//generate surfaces - -	for (int i = 0; i < surfaces_tools.size(); i++) { -		surfaces_tools.write[i]->index(); -		Array arrays = surfaces_tools.write[i]->commit_to_arrays(); -		add_surface(surfaces_tools.write[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools.write[i]->get_material(), surfaces_tools.write[i]->get_meta("name")); +	for (unsigned int i = 0; i < surfaces_tools.size(); i++) { +		surfaces_tools[i]->index(); +		Array arrays = surfaces_tools[i]->commit_to_arrays(); +		add_surface(surfaces_tools[i]->get_primitive(), arrays, Array(), Dictionary(), surfaces_tools[i]->get_material(), surfaces_tools[i]->get_meta("name"));  	}  	set_lightmap_size_hint(Size2(size_x, size_y)); -	if (!r_used_cache) { -		//free stuff -		::free(gen_vertices); -		::free(gen_indices); -		::free(gen_uvs); +	if (gen_cache_size > 0) { +		r_dst_cache.resize(gen_cache_size); +		memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size); +		memfree(gen_cache); +	} + +	if (!use_cache) { +		// Cache was not used, free the buffers +		memfree(gen_vertices); +		memfree(gen_indices); +		memfree(gen_uvs);  	}  	return OK; diff --git a/editor/import/scene_importer_mesh.h b/editor/import/scene_importer_mesh.h index 3326fab55d..b3e8137e0a 100644 --- a/editor/import/scene_importer_mesh.h +++ b/editor/import/scene_importer_mesh.h @@ -105,7 +105,7 @@ public:  	Vector<Ref<Shape3D>> convex_decompose() const;  	Ref<Shape3D> create_trimesh_shape() const;  	Ref<NavigationMesh> create_navigation_mesh(); -	Error lightmap_unwrap_cached(int *&r_cache_data, unsigned int &r_cache_size, bool &r_used_cache, const Transform &p_base_transform, float p_texel_size); +	Error lightmap_unwrap_cached(const Transform &p_base_transform, float p_texel_size, const Vector<uint8_t> &p_src_cache, Vector<uint8_t> &r_dst_cache);  	void set_lightmap_size_hint(const Size2i &p_size);  	Size2i get_lightmap_size_hint() const; diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp index 3553450672..3b54b30b54 100644 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -32,6 +32,7 @@  #include "core/config/project_settings.h"  #include "core/io/resource_loader.h" +#include "core/os/keyboard.h"  #include "editor/audio_stream_preview.h"  #include "editor/editor_scale.h"  #include "editor/editor_settings.h" @@ -144,23 +145,26 @@ void AudioStreamEditor::_draw_indicator() {  	Rect2 rect = _preview->get_rect();  	float len = stream->get_length();  	float ofs_x = _current / len * rect.size.width; -	_indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), get_theme_color("accent_color", "Editor"), 1); +	const Color color = get_theme_color("accent_color", "Editor"); +	_indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), color, Math::round(2 * EDSCALE)); +	_indicator->draw_texture( +			get_theme_icon("TimelineIndicator", "EditorIcons"), +			Point2(ofs_x - get_theme_icon("TimelineIndicator", "EditorIcons")->get_width() * 0.5, 0), +			color);  	_current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /");  }  void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) { -	Ref<InputEventMouseButton> mb = p_event; - -	if (mb.is_valid()) { +	const Ref<InputEventMouseButton> mb = p_event; +	if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {  		if (mb->is_pressed()) {  			_seek_to(mb->get_position().x);  		}  		_dragging = mb->is_pressed();  	} -	Ref<InputEventMouseMotion> mm = p_event; - +	const Ref<InputEventMouseMotion> mm = p_event;  	if (mm.is_valid()) {  		if (_dragging) {  			_seek_to(mm->get_position().x); @@ -228,6 +232,7 @@ AudioStreamEditor::AudioStreamEditor() {  	hbox->add_child(_play_button);  	_play_button->set_focus_mode(Control::FOCUS_NONE);  	_play_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_play)); +	_play_button->set_shortcut(ED_SHORTCUT("inspector/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), KEY_SPACE));  	_stop_button = memnew(Button);  	_stop_button->set_flat(true); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 6ac47595dc..261621e10a 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -1654,7 +1654,7 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven  	Ref<InputEventMouseButton> b = p_event;  	// Open a sub-scene on double-click -	if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_doubleclick() && tool == TOOL_SELECT) { +	if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) {  		List<CanvasItem *> selection = _get_edited_canvas_items();  		if (selection.size() == 1) {  			CanvasItem *canvas_item = selection[0]; diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 87eeb82101..a319a595c7 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -504,6 +504,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size  	Color keyword_color = EditorSettings::get_singleton()->get("text_editor/highlighting/keyword_color");  	Color text_color = EditorSettings::get_singleton()->get("text_editor/highlighting/text_color");  	Color symbol_color = EditorSettings::get_singleton()->get("text_editor/highlighting/symbol_color"); +	Color comment_color = EditorSettings::get_singleton()->get("text_editor/highlighting/comment_color");  	if (bg_color.a == 0) {  		bg_color = Color(0, 0, 0, 0); @@ -523,33 +524,42 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size  	bool prev_is_text = false;  	bool in_keyword = false; +	bool in_comment = false;  	for (int i = 0; i < code.length(); i++) {  		char32_t c = code[i];  		if (c > 32) {  			if (col < thumbnail_size) {  				Color color = text_color; -				if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) { -					//make symbol a little visible -					color = symbol_color; -					in_keyword = false; -				} else if (!prev_is_text && _is_text_char(c)) { -					int pos = i; +				if (c == '#') { +					in_comment = true; +				} -					while (_is_text_char(code[pos])) { -						pos++; -					} -					String word = code.substr(i, pos - i); -					if (keywords.has(word)) { -						in_keyword = true; +				if (in_comment) { +					color = comment_color; +				} else { +					if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) { +						//make symbol a little visible +						color = symbol_color; +						in_keyword = false; +					} else if (!prev_is_text && _is_text_char(c)) { +						int pos = i; + +						while (_is_text_char(code[pos])) { +							pos++; +						} +						String word = code.substr(i, pos - i); +						if (keywords.has(word)) { +							in_keyword = true; +						} + +					} else if (!_is_text_char(c)) { +						in_keyword = false;  					} -				} else if (!_is_text_char(c)) { -					in_keyword = false; -				} - -				if (in_keyword) { -					color = keyword_color; +					if (in_keyword) { +						color = keyword_color; +					}  				}  				Color ul = color; @@ -559,11 +569,14 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size  				prev_is_text = _is_text_char(c);  			} +			col++;  		} else {  			prev_is_text = false;  			in_keyword = false;  			if (c == '\n') { +				in_comment = false; +  				col = x0;  				line++;  				if (line >= available_height / 2) { @@ -571,9 +584,10 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size  				}  			} else if (c == '\t') {  				col += 3; +			} else { +				col++;  			}  		} -		col++;  	}  	post_process_preview(img); diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp index bd721244ea..1d6ff92e0c 100644 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ b/editor/plugins/tile_map_editor_plugin.cpp @@ -573,6 +573,7 @@ void TileMapEditor::_update_palette() {  		entries2.sort_custom<SwapComparator>();  		Ref<Texture2D> tex = tileset->tile_get_texture(sel_tile); +		Color modulate = tileset->tile_get_modulate(sel_tile);  		for (int i = 0; i < entries2.size(); i++) {  			manual_palette->add_item(String()); @@ -588,6 +589,7 @@ void TileMapEditor::_update_palette() {  				}  				manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex); +				manual_palette->set_item_icon_modulate(manual_palette->get_item_count() - 1, modulate);  			}  			manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]); @@ -658,11 +660,15 @@ Vector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool erase,  	}  	// Check if the tile variation is the same -	Vector2 prev_position = node->get_cell_autotile_coord(p_start.x, p_start.y);  	if (ids.size() == 1 && ids[0] == prev_id) {  		int current = manual_palette->get_current(); -		Vector2 position = manual_palette->get_item_metadata(current); -		if (prev_position == position) { +		if (current == -1) { +			// Same ID, no variation selected, nothing to change +			return Vector<Vector2>(); +		} +		Vector2 prev_autotile_coord = node->get_cell_autotile_coord(p_start.x, p_start.y); +		Vector2 autotile_coord = manual_palette->get_item_metadata(current); +		if (autotile_coord == prev_autotile_coord) {  			// Same ID and variation, nothing to change  			return Vector<Vector2>();  		} diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index e51e8ee82e..2b75144ac7 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1762,7 +1762,7 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {  		emit_signal(SIGNAL_SELECTION_CHANGED); -		if (!mb->get_control() && mb->is_doubleclick()) { +		if (!mb->get_control() && mb->is_double_click()) {  			emit_signal(SIGNAL_PROJECT_ASK_OPEN);  		}  	} @@ -2371,6 +2371,7 @@ void ProjectManager::_on_search_term_changed(const String &p_term) {  void ProjectManager::_bind_methods() {  	ClassDB::bind_method("_unhandled_key_input", &ProjectManager::_unhandled_key_input);  	ClassDB::bind_method("_update_project_buttons", &ProjectManager::_update_project_buttons); +	ClassDB::bind_method("_version_button_pressed", &ProjectManager::_version_button_pressed);  }  void ProjectManager::_open_asset_library() { @@ -2378,6 +2379,10 @@ void ProjectManager::_open_asset_library() {  	tabs->set_current_tab(1);  } +void ProjectManager::_version_button_pressed() { +	DisplayServer::get_singleton()->clipboard_set(version_btn->get_text()); +} +  ProjectManager::ProjectManager() {  	// load settings  	if (!EditorSettings::get_singleton()) { @@ -2601,15 +2606,30 @@ ProjectManager::ProjectManager() {  		settings_hb->set_h_grow_direction(Control::GROW_DIRECTION_BEGIN);  		settings_hb->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT); -		Label *version_label = memnew(Label); +		// A VBoxContainer that contains a dummy Control node to adjust the LinkButton's vertical position. +		VBoxContainer *spacer_vb = memnew(VBoxContainer); +		settings_hb->add_child(spacer_vb); + +		Control *v_spacer = memnew(Control); +		spacer_vb->add_child(v_spacer); + +		version_btn = memnew(LinkButton);  		String hash = String(VERSION_HASH);  		if (hash.length() != 0) {  			hash = "." + hash.left(9);  		} -		version_label->set_text("v" VERSION_FULL_BUILD "" + hash); -		version_label->set_self_modulate(Color(1, 1, 1, 0.6)); -		version_label->set_align(Label::ALIGN_CENTER); -		settings_hb->add_child(version_label); +		version_btn->set_text("v" VERSION_FULL_BUILD + hash); +		// Fade the version label to be less prominent, but still readable. +		version_btn->set_self_modulate(Color(1, 1, 1, 0.6)); +		version_btn->set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER); +		version_btn->set_tooltip(TTR("Click to copy.")); +		version_btn->connect("pressed", callable_mp(this, &ProjectManager::_version_button_pressed)); +		spacer_vb->add_child(version_btn); + +		// Add a small horizontal spacer between the version and language buttons +		// to distinguish them. +		Control *h_spacer = memnew(Control); +		settings_hb->add_child(h_spacer);  		language_btn = memnew(OptionButton);  		language_btn->set_flat(true); diff --git a/editor/project_manager.h b/editor/project_manager.h index a66b7c4ab6..0fc69bd313 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -89,6 +89,7 @@ class ProjectManager : public Control {  	ProjectDialog *npdialog;  	OptionButton *language_btn; +	LinkButton *version_btn;  	void _open_asset_library();  	void _scan_projects(); @@ -123,6 +124,7 @@ class ProjectManager : public Control {  	void _unhandled_key_input(const Ref<InputEvent> &p_ev);  	void _files_dropped(PackedStringArray p_files, int p_screen); +	void _version_button_pressed();  	void _on_order_option_changed(int p_idx);  	void _on_tab_changed(int p_tab);  	void _on_search_term_changed(const String &p_term); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index 81af4996ed..541ec224b9 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -270,16 +270,17 @@ void EditorSettingsDialog::_update_shortcuts() {  		Array events; // Need to get the list of events into an array so it can be set as metadata on the item.  		Vector<String> event_strings; -		List<Ref<InputEvent>> defaults = InputMap::get_singleton()->get_builtins().find(action_name).value(); -		// Remove all non-key events from the defaults. -		for (List<Ref<InputEvent>>::Element *I = defaults.front(); I; I = I->next()) { +		List<Ref<InputEvent>> all_default_events = InputMap::get_singleton()->get_builtins().find(action_name).value(); +		List<Ref<InputEventKey>> key_default_events; +		// Remove all non-key events from the defaults. Only check keys, since we are in the editor. +		for (List<Ref<InputEvent>>::Element *I = all_default_events.front(); I; I = I->next()) {  			Ref<InputEventKey> k = I->get(); -			if (k.is_null()) { -				I->erase(); +			if (k.is_valid()) { +				key_default_events.push_back(k);  			}  		} -		bool same_as_defaults = defaults.size() == action.inputs.size(); // Initially this is set to just whether the arrays are equal. Later we check the events if needed. +		bool same_as_defaults = key_default_events.size() == action.inputs.size(); // Initially this is set to just whether the arrays are equal. Later we check the events if needed.  		int count = 0;  		for (List<Ref<InputEvent>>::Element *I = action.inputs.front(); I; I = I->next()) { @@ -288,12 +289,8 @@ void EditorSettingsDialog::_update_shortcuts() {  			event_strings.push_back(I->get()->as_text());  			// Only check if the events have been the same so far - once one fails, we don't need to check any more. -			if (same_as_defaults) { -				Ref<InputEventKey> k = defaults[count]; -				// Only check keys, since we are in the editor. -				if (k.is_valid() && !defaults[count]->shortcut_match(I->get())) { -					same_as_defaults = false; -				} +			if (same_as_defaults && !key_default_events[count]->shortcut_match(I->get())) { +				same_as_defaults = false;  			}  			count++;  		} |