diff options
25 files changed, 1375 insertions, 172 deletions
diff --git a/core/image.cpp b/core/image.cpp index 790f17a9d6..2ac8ffea56 100644 --- a/core/image.cpp +++ b/core/image.cpp @@ -2239,6 +2239,7 @@ void Image::_bind_methods() {  	ClassDB::bind_method(D_METHOD("premultiply_alpha"), &Image::premultiply_alpha);  	ClassDB::bind_method(D_METHOD("srgb_to_linear"), &Image::srgb_to_linear);  	ClassDB::bind_method(D_METHOD("normalmap_to_xy"), &Image::normalmap_to_xy); +	ClassDB::bind_method(D_METHOD("bumpmap_to_normalmap", "bump_scale"), &Image::bumpmap_to_normalmap, DEFVAL(1.0));  	ClassDB::bind_method(D_METHOD("blit_rect", "src", "src_rect", "dst"), &Image::blit_rect);  	ClassDB::bind_method(D_METHOD("blit_rect_mask", "src", "mask", "src_rect", "dst"), &Image::blit_rect_mask); @@ -2347,6 +2348,47 @@ void Image::normalmap_to_xy() {  	convert(Image::FORMAT_LA8);  } +void Image::bumpmap_to_normalmap(float bump_scale) { +	ERR_FAIL_COND(!_can_modify(format)); +	convert(Image::FORMAT_RF); + +	PoolVector<uint8_t> result_image; //rgba output +	result_image.resize(width * height * 4); + +	{ +		PoolVector<uint8_t>::Read rp = data.read(); +		PoolVector<uint8_t>::Write wp = result_image.write(); + +		unsigned char *write_ptr = wp.ptr(); +		float *read_ptr = (float *)rp.ptr(); + +		for (int ty = 0; ty < height; ty++) { +			int py = ty + 1; +			if (py >= height) py -= height; + +			for (int tx = 0; tx < width; tx++) { +				int px = tx + 1; +				if (px >= width) px -= width; +				float here = read_ptr[ty * width + tx]; +				float to_right = read_ptr[ty * width + px]; +				float above = read_ptr[py * width + tx]; +				Vector3 up = Vector3(0, 1, (here - above) * bump_scale); +				Vector3 across = Vector3(1, 0, (to_right - here) * bump_scale); + +				Vector3 normal = across.cross(up); +				normal.normalize(); + +				write_ptr[((ty * width + tx) << 2) + 0] = (127.5 + normal.x * 127.5); +				write_ptr[((ty * width + tx) << 2) + 1] = (127.5 + normal.y * 127.5); +				write_ptr[((ty * width + tx) << 2) + 2] = (127.5 + normal.z * 127.5); +				write_ptr[((ty * width + tx) << 2) + 3] = 255; +			} +		} +	} +	format = FORMAT_RGBA8; +	data = result_image; +} +  void Image::srgb_to_linear() {  	if (data.size() == 0) diff --git a/core/image.h b/core/image.h index e962787ae9..17477d88ea 100644 --- a/core/image.h +++ b/core/image.h @@ -284,6 +284,7 @@ public:  	void premultiply_alpha();  	void srgb_to_linear();  	void normalmap_to_xy(); +	void bumpmap_to_normalmap(float bump_scale = 1.0);  	void blit_rect(const Ref<Image> &p_src, const Rect2 &p_src_rect, const Point2 &p_dest);  	void blit_rect_mask(const Ref<Image> &p_src, const Ref<Image> &p_mask, const Rect2 &p_src_rect, const Point2 &p_dest); diff --git a/core/math/geometry.h b/core/math/geometry.h index ca4363e129..73a53c53b6 100644 --- a/core/math/geometry.h +++ b/core/math/geometry.h @@ -502,16 +502,15 @@ public:  	}  	static bool is_point_in_triangle(const Vector2 &s, const Vector2 &a, const Vector2 &b, const Vector2 &c) { -		int as_x = s.x - a.x; -		int as_y = s.y - a.y; +		Vector2 an = a - s; +		Vector2 bn = b - s; +		Vector2 cn = c - s; -		bool s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0; +		bool orientation = an.cross(bn) > 0; -		if (((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0) == s_ab) return false; +		if ((bn.cross(cn) > 0) != orientation) return false; -		if (((c.x - b.x) * (s.y - b.y) - (c.y - b.y) * (s.x - b.x) > 0) != s_ab) return false; - -		return true; +		return (cn.cross(an) > 0) == orientation;  	}  	static bool is_point_in_polygon(const Vector2 &p_point, const Vector<Vector2> &p_polygon); diff --git a/doc/classes/SurfaceTool.xml b/doc/classes/SurfaceTool.xml index d8644c5419..7d78d71330 100644 --- a/doc/classes/SurfaceTool.xml +++ b/doc/classes/SurfaceTool.xml @@ -196,8 +196,11 @@  		<method name="generate_normals">  			<return type="void">  			</return> +			<argument index="0" name="flip" type="bool" default="false"> +			</argument>  			<description>  				Generates normals from Vertices so you do not have to do it manually. +				Setting "flip" [code]true[/code] inverts resulting normals.  			</description>  		</method>  		<method name="generate_tangents"> diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index a287dca1ed..f91ed35331 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -29,6 +29,7 @@  /*************************************************************************/  #include "rasterizer_storage_gles3.h" +#include "engine.h"  #include "project_settings.h"  #include "rasterizer_canvas_gles3.h"  #include "rasterizer_scene_gles3.h" @@ -5855,6 +5856,8 @@ void RasterizerStorageGLES3::update_particles() {  		shaders.particles.set_uniform(ParticlesShaderGLES3::EMITTING, particles->emitting);  		shaders.particles.set_uniform(ParticlesShaderGLES3::RANDOMNESS, particles->randomness); +		bool zero_time_scale = Engine::get_singleton()->get_time_scale() <= 0.0; +  		if (particles->clear && particles->pre_process_time > 0.0) {  			float frame_time; @@ -5872,7 +5875,15 @@ void RasterizerStorageGLES3::update_particles() {  		}  		if (particles->fixed_fps > 0) { -			float frame_time = 1.0 / particles->fixed_fps; +			float frame_time; +			float decr; +			if (zero_time_scale) { +				frame_time = 0.0; +				decr = 1.0 / particles->fixed_fps; +			} else { +				frame_time = 1.0 / particles->fixed_fps; +				decr = frame_time; +			}  			float delta = frame.delta;  			if (delta > 0.1) { //avoid recursive stalls if fps goes below 10  				delta = 0.1; @@ -5883,13 +5894,16 @@ void RasterizerStorageGLES3::update_particles() {  			while (todo >= frame_time) {  				_particles_process(particles, frame_time); -				todo -= frame_time; +				todo -= decr;  			}  			particles->frame_remainder = todo;  		} else { -			_particles_process(particles, frame.delta); +			if (zero_time_scale) +				_particles_process(particles, 0.0); +			else +				_particles_process(particles, frame.delta);  		}  		particle_update_list.remove(particle_update_list.first()); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 16223dbb16..d4c7d7483e 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -533,6 +533,7 @@ void FileSystemDock::_update_files(bool p_keep_selection) {  			filelist.push_back(fi);  		} +		filelist.sort();  	}  	String oi = "Object"; diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp new file mode 100644 index 0000000000..9442bbc0e8 --- /dev/null +++ b/editor/find_in_files.cpp @@ -0,0 +1,829 @@ +/*************************************************************************/ +/*  find_in_files.cpp                                                    */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#include "find_in_files.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/check_box.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/grid_container.h" +#include "scene/gui/item_list.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/progress_bar.h" + +#define ROOT_PREFIX "res://" + +const char *FindInFiles::SIGNAL_RESULT_FOUND = "result_found"; +const char *FindInFiles::SIGNAL_FINISHED = "finished"; + +// TODO Would be nice in Vector and PoolVectors +template <typename T> +inline void pop_back(T &container) { +	container.resize(container.size() - 1); +} + +// TODO Copied from TextEdit private, would be nice to extract it in a single place +static bool is_text_char(CharType c) { +	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; +} + +FindInFiles::FindInFiles() { +	_root_prefix = ROOT_PREFIX; +	_extension_filter.insert("gd"); +	_extension_filter.insert("cs"); +	_searching = false; +	_whole_words = true; +	_match_case = true; +} + +void FindInFiles::set_search_text(String p_pattern) { +	_pattern = p_pattern; +} + +void FindInFiles::set_whole_words(bool p_whole_word) { +	_whole_words = p_whole_word; +} + +void FindInFiles::set_match_case(bool p_match_case) { +	_match_case = p_match_case; +} + +void FindInFiles::set_folder(String folder) { +	_root_dir = folder; +} + +void FindInFiles::set_filter(const Set<String> &exts) { +	_extension_filter = exts; +} + +void FindInFiles::_notification(int p_notification) { +	if (p_notification == NOTIFICATION_PROCESS) { +		_process(); +	} +} + +void FindInFiles::start() { +	if (_pattern == "") { +		print_line("Nothing to search, pattern is empty"); +		emit_signal(SIGNAL_FINISHED); +		return; +	} +	if (_extension_filter.size() == 0) { +		print_line("Nothing to search, filter matches no files"); +		emit_signal(SIGNAL_FINISHED); +		return; +	} + +	// Init search +	_current_dir = ""; +	PoolStringArray init_folder; +	init_folder.append(_root_dir); +	_folders_stack.push_back(init_folder); + +	_initial_files_count = 0; + +	_searching = true; +	set_process(true); +} + +void FindInFiles::stop() { +	_searching = false; +	_current_dir = ""; +	set_process(false); +} + +void FindInFiles::_process() { +	// This part can be moved to a thread if needed + +	OS &os = *OS::get_singleton(); +	float duration = 0.0; +	while (duration < 1.0 / 120.0) { +		float time_before = os.get_ticks_msec(); +		_iterate(); +		duration += (os.get_ticks_msec() - time_before); +	} +} + +void FindInFiles::_iterate() { + +	if (_folders_stack.size() != 0) { + +		// Scan folders first so we can build a list of files and have progress info later + +		PoolStringArray &folders_to_scan = _folders_stack[_folders_stack.size() - 1]; + +		if (folders_to_scan.size() != 0) { +			// Scan one folder below + +			String folder_name = folders_to_scan[folders_to_scan.size() - 1]; +			pop_back(folders_to_scan); + +			_current_dir = _current_dir.plus_file(folder_name); + +			PoolStringArray sub_dirs; +			_scan_dir(_root_prefix + _current_dir, sub_dirs); + +			if (sub_dirs.size() != 0) { +				_folders_stack.push_back(sub_dirs); +			} + +		} else { +			// Go back one level + +			pop_back(_folders_stack); +			_current_dir = _current_dir.get_base_dir(); + +			if (_folders_stack.size() == 0) { +				// All folders scanned +				_initial_files_count = _files_to_scan.size(); +			} +		} + +	} else if (_files_to_scan.size() != 0) { + +		// Then scan files + +		String fpath = _files_to_scan[_files_to_scan.size() - 1]; +		pop_back(_files_to_scan); +		_scan_file(_root_prefix + fpath); + +	} else { +		print_line("Search complete"); +		set_process(false); +		_current_dir = ""; +		_searching = false; +		emit_signal(SIGNAL_FINISHED); +	} +} + +float FindInFiles::get_progress() const { +	if (_initial_files_count != 0) { +		return static_cast<float>(_initial_files_count - _files_to_scan.size()) / static_cast<float>(_initial_files_count); +	} +	return 0; +} + +void FindInFiles::_scan_dir(String path, PoolStringArray &out_folders) { + +	DirAccess *dir = DirAccess::open(path); +	if (dir == NULL) { +		print_line("Cannot open directory! " + path); +		return; +	} + +	//print_line(String("Scanning ") + path); + +	dir->list_dir_begin(); + +	for (int i = 0; i < 1000; ++i) { +		String file = dir->get_next(); + +		if (file == "") +			break; + +		// Ignore special dirs and hidden dirs (such as .git and .import) +		if (file == "." || file == ".." || file.begins_with(".")) +			continue; + +		if (dir->current_is_dir()) +			out_folders.append(file); + +		else { +			String file_ext = file.get_extension(); +			if (_extension_filter.has(file_ext)) { +				_files_to_scan.push_back(file); +			} +		} +	} +} + +void FindInFiles::_scan_file(String fpath) { + +	FileAccess *f = FileAccess::open(fpath, FileAccess::READ); +	if (f == NULL) { +		f->close(); +		print_line(String("Cannot open file ") + fpath); +		return; +	} + +	int line_number = 0; + +	while (!f->eof_reached()) { + +		// line number starts at 1 +		++line_number; + +		int begin = 0; +		int end = 0; + +		String line = f->get_line(); + +		// Find all occurrences in the current line +		while (true) { +			begin = _match_case ? line.find(_pattern, end) : line.findn(_pattern, end); + +			if (begin == -1) +				break; + +			end = begin + _pattern.length(); + +			if (_whole_words) { +				if (begin > 0 && is_text_char(line[begin - 1])) { +					continue; +				} +				if (end < line.size() && is_text_char(line[end])) { +					continue; +				} +			} + +			emit_signal(SIGNAL_RESULT_FOUND, fpath, line_number, begin, end, line); +		} +	} + +	f->close(); +} + +void FindInFiles::_bind_methods() { + +	ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_FOUND, +			PropertyInfo(Variant::STRING, "path"), +			PropertyInfo(Variant::INT, "line_number"), +			PropertyInfo(Variant::INT, "begin"), +			PropertyInfo(Variant::INT, "end"), +			PropertyInfo(Variant::STRING, "text"))); + +	ADD_SIGNAL(MethodInfo(SIGNAL_FINISHED)); +} + +//----------------------------------------------------------------------------- +const char *FindInFilesDialog::SIGNAL_FIND_REQUESTED = "find_requested"; +const char *FindInFilesDialog::SIGNAL_REPLACE_REQUESTED = "replace_requested"; + +FindInFilesDialog::FindInFilesDialog() { + +	set_custom_minimum_size(Size2(400, 190)); +	set_resizable(true); +	set_title(TTR("Find in files")); + +	VBoxContainer *vbc = memnew(VBoxContainer); +	vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 8 * EDSCALE); +	vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 8 * EDSCALE); +	vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, -8 * EDSCALE); +	vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, -8 * EDSCALE); +	add_child(vbc); + +	GridContainer *gc = memnew(GridContainer); +	gc->set_columns(2); +	vbc->add_child(gc); + +	Label *find_label = memnew(Label); +	find_label->set_text(TTR("Find: ")); +	gc->add_child(find_label); + +	_search_text_line_edit = memnew(LineEdit); +	_search_text_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); +	_search_text_line_edit->connect("text_changed", this, "_on_search_text_modified"); +	_search_text_line_edit->connect("text_entered", this, "_on_search_text_entered"); +	gc->add_child(_search_text_line_edit); + +	{ +		Control *placeholder = memnew(Control); +		gc->add_child(placeholder); +	} + +	{ +		HBoxContainer *hbc = memnew(HBoxContainer); + +		_whole_words_checkbox = memnew(CheckBox); +		_whole_words_checkbox->set_text(TTR("Whole words")); +		_whole_words_checkbox->set_pressed(true); +		hbc->add_child(_whole_words_checkbox); + +		_match_case_checkbox = memnew(CheckBox); +		_match_case_checkbox->set_text(TTR("Match case")); +		_match_case_checkbox->set_pressed(true); +		hbc->add_child(_match_case_checkbox); + +		gc->add_child(hbc); +	} + +	Label *folder_label = memnew(Label); +	folder_label->set_text(TTR("Folder: ")); +	gc->add_child(folder_label); + +	{ +		HBoxContainer *hbc = memnew(HBoxContainer); + +		Label *prefix_label = memnew(Label); +		prefix_label->set_text(ROOT_PREFIX); +		hbc->add_child(prefix_label); + +		_folder_line_edit = memnew(LineEdit); +		_folder_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); +		hbc->add_child(_folder_line_edit); + +		Button *folder_button = memnew(Button); +		folder_button->set_text("..."); +		folder_button->connect("pressed", this, "_on_folder_button_pressed"); +		hbc->add_child(folder_button); + +		_folder_dialog = memnew(FileDialog); +		_folder_dialog->set_mode(FileDialog::MODE_OPEN_DIR); +		_folder_dialog->connect("dir_selected", this, "_on_folder_selected"); +		add_child(_folder_dialog); + +		gc->add_child(hbc); +	} + +	Label *filter_label = memnew(Label); +	filter_label->set_text(TTR("Filter: ")); +	gc->add_child(filter_label); + +	{ +		HBoxContainer *hbc = memnew(HBoxContainer); + +		Vector<String> exts; +		exts.push_back("gd"); +		exts.push_back("cs"); + +		for (int i = 0; i < exts.size(); ++i) { +			CheckBox *cb = memnew(CheckBox); +			cb->set_text(exts[i]); +			cb->set_pressed(true); +			hbc->add_child(cb); +			_filters.push_back(cb); +		} + +		gc->add_child(hbc); +	} + +	{ +		Control *placeholder = memnew(Control); +		placeholder->set_custom_minimum_size(Size2(0, EDSCALE * 16)); +		vbc->add_child(placeholder); +	} + +	{ +		HBoxContainer *hbc = memnew(HBoxContainer); +		hbc->set_alignment(HBoxContainer::ALIGN_CENTER); + +		_find_button = memnew(Button); +		_find_button->set_text(TTR("Find...")); +		_find_button->connect("pressed", this, "_on_find_button_pressed"); +		_find_button->set_disabled(true); +		hbc->add_child(_find_button); + +		{ +			Control *placeholder = memnew(Control); +			placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0)); +			hbc->add_child(placeholder); +		} + +		_replace_button = memnew(Button); +		_replace_button->set_text(TTR("Replace...")); +		_replace_button->connect("pressed", this, "_on_replace_button_pressed"); +		_replace_button->set_disabled(true); +		hbc->add_child(_replace_button); + +		{ +			Control *placeholder = memnew(Control); +			placeholder->set_custom_minimum_size(Size2(EDSCALE * 16, 0)); +			hbc->add_child(placeholder); +		} + +		Button *cancel_button = memnew(Button); +		cancel_button->set_text(TTR("Cancel")); +		cancel_button->connect("pressed", this, "hide"); +		hbc->add_child(cancel_button); + +		vbc->add_child(hbc); +	} +} + +void FindInFilesDialog::set_search_text(String text) { +	_search_text_line_edit->set_text(text); +} + +String FindInFilesDialog::get_search_text() const { +	String text = _search_text_line_edit->get_text(); +	return text.strip_edges(); +} + +bool FindInFilesDialog::is_match_case() const { +	return _match_case_checkbox->is_pressed(); +} + +bool FindInFilesDialog::is_whole_words() const { +	return _whole_words_checkbox->is_pressed(); +} + +String FindInFilesDialog::get_folder() const { +	String text = _folder_line_edit->get_text(); +	return text.strip_edges(); +} + +Set<String> FindInFilesDialog::get_filter() const { +	Set<String> filters; +	for (int i = 0; i < _filters.size(); ++i) { +		CheckBox *cb = _filters[i]; +		if (cb->is_pressed()) { +			filters.insert(_filters[i]->get_text()); +		} +	} +	return filters; +} + +void FindInFilesDialog::_notification(int p_what) { +	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { +		if (is_visible()) { +			// Doesn't work more than once if not deferred... +			_search_text_line_edit->call_deferred("grab_focus"); +			_search_text_line_edit->select_all(); +		} +	} +} + +void FindInFilesDialog::_on_folder_button_pressed() { +	_folder_dialog->popup_centered_ratio(); +} + +void FindInFilesDialog::_on_find_button_pressed() { +	emit_signal(SIGNAL_FIND_REQUESTED); +	hide(); +} + +void FindInFilesDialog::_on_replace_button_pressed() { +	emit_signal(SIGNAL_REPLACE_REQUESTED); +	hide(); +} + +void FindInFilesDialog::_on_search_text_modified(String text) { + +	ERR_FAIL_COND(!_find_button); +	ERR_FAIL_COND(!_replace_button); + +	_find_button->set_disabled(get_search_text().empty()); +	_replace_button->set_disabled(get_search_text().empty()); +} + +void FindInFilesDialog::_on_search_text_entered(String text) { +	// This allows to trigger a global search without leaving the keyboard +	if (!_find_button->is_disabled()) +		_on_find_button_pressed(); +} + +void FindInFilesDialog::_on_folder_selected(String path) { +	int i = path.find("://"); +	if (i != -1) +		path = path.right(i + 3); +	_folder_line_edit->set_text(path); +} + +void FindInFilesDialog::_bind_methods() { + +	ClassDB::bind_method("_on_folder_button_pressed", &FindInFilesDialog::_on_folder_button_pressed); +	ClassDB::bind_method("_on_find_button_pressed", &FindInFilesDialog::_on_find_button_pressed); +	ClassDB::bind_method("_on_replace_button_pressed", &FindInFilesDialog::_on_replace_button_pressed); +	ClassDB::bind_method("_on_folder_selected", &FindInFilesDialog::_on_folder_selected); +	ClassDB::bind_method("_on_search_text_modified", &FindInFilesDialog::_on_search_text_modified); +	ClassDB::bind_method("_on_search_text_entered", &FindInFilesDialog::_on_search_text_entered); + +	ADD_SIGNAL(MethodInfo(SIGNAL_FIND_REQUESTED)); +	ADD_SIGNAL(MethodInfo(SIGNAL_REPLACE_REQUESTED)); +} + +//----------------------------------------------------------------------------- +const char *FindInFilesPanel::SIGNAL_RESULT_SELECTED = "result_selected"; +const char *FindInFilesPanel::SIGNAL_FILES_MODIFIED = "files_modified"; + +FindInFilesPanel::FindInFilesPanel() { + +	_finder = memnew(FindInFiles); +	_finder->connect(FindInFiles::SIGNAL_RESULT_FOUND, this, "_on_result_found"); +	_finder->connect(FindInFiles::SIGNAL_FINISHED, this, "_on_finished"); +	add_child(_finder); + +	VBoxContainer *vbc = memnew(VBoxContainer); +	vbc->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_BEGIN, 0); +	vbc->set_anchor_and_margin(MARGIN_TOP, ANCHOR_BEGIN, 0); +	vbc->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0); +	vbc->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0); +	add_child(vbc); + +	{ +		HBoxContainer *hbc = memnew(HBoxContainer); + +		Label *find_label = memnew(Label); +		find_label->set_text(TTR("Find: ")); +		hbc->add_child(find_label); + +		_search_text_label = memnew(Label); +		_search_text_label->add_font_override("font", get_font("source", "EditorFonts")); +		hbc->add_child(_search_text_label); + +		_progress_bar = memnew(ProgressBar); +		_progress_bar->set_h_size_flags(SIZE_EXPAND_FILL); +		hbc->add_child(_progress_bar); +		set_progress_visible(false); + +		_status_label = memnew(Label); +		hbc->add_child(_status_label); + +		_cancel_button = memnew(Button); +		_cancel_button->set_text(TTR("Cancel")); +		_cancel_button->connect("pressed", this, "_on_cancel_button_clicked"); +		_cancel_button->set_disabled(true); +		hbc->add_child(_cancel_button); + +		vbc->add_child(hbc); +	} + +	// In the future, this should be replaced by a more specific list container, +	// which can highlight text regions and change opacity for enabled/disabled states +	_results_display = memnew(ItemList); +	_results_display->add_font_override("font", get_font("source", "EditorFonts")); +	_results_display->set_v_size_flags(SIZE_EXPAND_FILL); +	_results_display->connect("item_selected", this, "_on_result_selected"); +	vbc->add_child(_results_display); + +	{ +		_replace_container = memnew(HBoxContainer); + +		Label *replace_label = memnew(Label); +		replace_label->set_text(TTR("Replace: ")); +		_replace_container->add_child(replace_label); + +		_replace_line_edit = memnew(LineEdit); +		_replace_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); +		_replace_line_edit->connect("text_changed", this, "_on_replace_text_changed"); +		_replace_container->add_child(_replace_line_edit); + +		_replace_all_button = memnew(Button); +		_replace_all_button->set_text(TTR("Replace all (no undo)")); +		_replace_all_button->connect("pressed", this, "_on_replace_all_clicked"); +		_replace_container->add_child(_replace_all_button); + +		_replace_container->hide(); + +		vbc->add_child(_replace_container); +	} +} + +void FindInFilesPanel::set_with_replace(bool with_replace) { + +	_replace_container->set_visible(with_replace); +} + +void FindInFilesPanel::start_search() { + +	_results_display->clear(); +	_status_label->set_text(TTR("Searching...")); +	_search_text_label->set_text(_finder->get_search_text()); + +	set_process(true); +	set_progress_visible(true); + +	_finder->start(); + +	update_replace_buttons(); +	_cancel_button->set_disabled(false); +} + +void FindInFilesPanel::stop_search() { + +	_finder->stop(); + +	_status_label->set_text(""); +	update_replace_buttons(); +	set_progress_visible(false); +	_cancel_button->set_disabled(true); +} + +void FindInFilesPanel::_notification(int p_what) { +	if (p_what == NOTIFICATION_PROCESS) { +		_progress_bar->set_as_ratio(_finder->get_progress()); +	} +} + +void FindInFilesPanel::_on_result_found(String fpath, int line_number, int begin, int end, String text) { + +	int i = _results_display->get_item_count(); +	_results_display->add_item(fpath + ": " + String::num(line_number) + ":        " + text.replace("\t", "    ")); +	_results_display->set_item_metadata(i, varray(fpath, line_number, begin, end)); +} + +void FindInFilesPanel::_on_finished() { + +	_status_label->set_text(TTR("Search complete")); +	update_replace_buttons(); +	set_progress_visible(false); +	_cancel_button->set_disabled(true); +} + +void FindInFilesPanel::_on_cancel_button_clicked() { +	stop_search(); +} + +void FindInFilesPanel::_on_result_selected(int i) { + +	Array meta = _results_display->get_item_metadata(i); +	emit_signal(SIGNAL_RESULT_SELECTED, meta[0], meta[1], meta[2], meta[3]); +} + +void FindInFilesPanel::_on_replace_text_changed(String text) { +	update_replace_buttons(); +} + +void FindInFilesPanel::_on_replace_all_clicked() { + +	String replace_text = get_replace_text(); +	ERR_FAIL_COND(replace_text.empty()); + +	String last_fpath; +	PoolIntArray locations; +	PoolStringArray modified_files; + +	for (int i = 0; i < _results_display->get_item_count(); ++i) { + +		Array meta = _results_display->get_item_metadata(i); + +		String fpath = meta[0]; + +		// Results are sorted by file, so we can batch replaces +		if (fpath != last_fpath) { +			if (locations.size() != 0) { +				apply_replaces_in_file(last_fpath, locations, replace_text); +				modified_files.append(last_fpath); +				locations.resize(0); +			} +		} + +		locations.append(meta[1]); // line_number +		locations.append(meta[2]); // begin +		locations.append(meta[3]); // end + +		last_fpath = fpath; +	} + +	if (locations.size() != 0) { +		apply_replaces_in_file(last_fpath, locations, replace_text); +		modified_files.append(last_fpath); +	} + +	// Hide replace bar so we can't trigger the action twice without doing a new search +	set_with_replace(false); + +	emit_signal(SIGNAL_FILES_MODIFIED, modified_files); +} + +// Same as get_line, but preserves line ending characters +class ConservativeGetLine { +public: +	String get_line(FileAccess *f) { + +		_line_buffer.clear(); + +		CharType c = f->get_8(); + +		while (!f->eof_reached()) { + +			if (c == '\n') { +				_line_buffer.push_back(c); +				_line_buffer.push_back(0); +				return String::utf8(_line_buffer.ptr()); + +			} else if (c == '\0') { +				_line_buffer.push_back(c); +				return String::utf8(_line_buffer.ptr()); + +			} else if (c != '\r') { +				_line_buffer.push_back(c); +			} + +			c = f->get_8(); +		} + +		_line_buffer.push_back(0); +		return String::utf8(_line_buffer.ptr()); +	} + +private: +	Vector<char> _line_buffer; +}; + +void FindInFilesPanel::apply_replaces_in_file(String fpath, PoolIntArray locations, String text) { + +	ERR_FAIL_COND(locations.size() % 3 != 0); + +	//print_line(String("Replacing {0} occurrences in {1}").format(varray(fpath, locations.size() / 3))); + +	// If the file is already open, I assume the editor will reload it. +	// If there are unsaved changes, the user will be asked on focus, +	// however that means either loosing changes or loosing replaces. + +	FileAccess *f = FileAccess::open(fpath, FileAccess::READ); +	ERR_FAIL_COND(f == NULL); + +	String buffer; +	int current_line = 1; + +	ConservativeGetLine conservative; + +	String line = conservative.get_line(f); + +	PoolIntArray::Read locations_read = locations.read(); +	for (int i = 0; i < locations.size(); i += 3) { + +		int repl_line_number = locations_read[i]; +		int repl_begin = locations_read[i + 1]; +		int repl_end = locations_read[i + 2]; + +		while (current_line < repl_line_number) { +			buffer += line; +			line = conservative.get_line(f); +			++current_line; +		} + +		line = line.left(repl_begin) + text + line.right(repl_end); +	} + +	buffer += line; + +	while (!f->eof_reached()) { +		buffer += conservative.get_line(f); +	} + +	// Now the modified contents are in the buffer, rewrite the file with our changes + +	Error err = f->reopen(fpath, FileAccess::WRITE); +	ERR_FAIL_COND(err != OK); + +	f->store_string(buffer); + +	f->close(); +} + +String FindInFilesPanel::get_replace_text() { +	return _replace_line_edit->get_text().strip_edges(); +} + +void FindInFilesPanel::update_replace_buttons() { + +	String text = get_replace_text(); +	bool disabled = text.empty() || _finder->is_searching(); + +	_replace_all_button->set_disabled(disabled); +} + +void FindInFilesPanel::set_progress_visible(bool visible) { +	_progress_bar->set_self_modulate(Color(1, 1, 1, visible ? 1 : 0)); +} + +void FindInFilesPanel::_bind_methods() { + +	ClassDB::bind_method("_on_result_found", &FindInFilesPanel::_on_result_found); +	ClassDB::bind_method("_on_finished", &FindInFilesPanel::_on_finished); +	ClassDB::bind_method("_on_cancel_button_clicked", &FindInFilesPanel::_on_cancel_button_clicked); +	ClassDB::bind_method("_on_result_selected", &FindInFilesPanel::_on_result_selected); +	ClassDB::bind_method("_on_replace_text_changed", &FindInFilesPanel::_on_replace_text_changed); +	ClassDB::bind_method("_on_replace_all_clicked", &FindInFilesPanel::_on_replace_all_clicked); + +	ADD_SIGNAL(MethodInfo(SIGNAL_RESULT_SELECTED, +			PropertyInfo(Variant::STRING, "path"), +			PropertyInfo(Variant::INT, "line_number"), +			PropertyInfo(Variant::INT, "begin"), +			PropertyInfo(Variant::INT, "end"))); + +	ADD_SIGNAL(MethodInfo(SIGNAL_FILES_MODIFIED, PropertyInfo(Variant::STRING, "paths"))); +} diff --git a/editor/find_in_files.h b/editor/find_in_files.h new file mode 100644 index 0000000000..d57184960b --- /dev/null +++ b/editor/find_in_files.h @@ -0,0 +1,184 @@ +/*************************************************************************/ +/*  find_in_files.h                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md)    */ +/*                                                                       */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the       */ +/* "Software"), to deal in the Software without restriction, including   */ +/* without limitation the rights to use, copy, modify, merge, publish,   */ +/* distribute, sublicense, and/or sell copies of the Software, and to    */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions:                                             */ +/*                                                                       */ +/* The above copyright notice and this permission notice shall be        */ +/* included in all copies or substantial portions of the Software.       */ +/*                                                                       */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ +/*************************************************************************/ + +#ifndef FIND_IN_FILES_H +#define FIND_IN_FILES_H + +#include "scene/gui/dialogs.h" + +// Performs the actual search +class FindInFiles : public Node { +	GDCLASS(FindInFiles, Node) +public: +	static const char *SIGNAL_RESULT_FOUND; +	static const char *SIGNAL_FINISHED; + +	FindInFiles(); + +	void set_search_text(String p_pattern); +	void set_whole_words(bool p_whole_word); +	void set_match_case(bool p_match_case); +	void set_folder(String folder); +	void set_filter(const Set<String> &exts); + +	String get_search_text() const { return _pattern; } + +	bool is_whole_words() const { return _whole_words; } +	bool is_match_case() const { return _match_case; } + +	void start(); +	void stop(); + +	bool is_searching() const { return _searching; } +	float get_progress() const; + +protected: +	void _notification(int p_notification); + +	static void _bind_methods(); + +private: +	void _process(); +	void _iterate(); +	void _scan_dir(String path, PoolStringArray &out_folders); +	void _scan_file(String fpath); + +	// Config +	String _pattern; +	Set<String> _extension_filter; +	String _root_prefix; +	String _root_dir; +	bool _whole_words; +	bool _match_case; + +	// State +	bool _searching; +	String _current_dir; +	Vector<PoolStringArray> _folders_stack; +	Vector<String> _files_to_scan; +	int _initial_files_count; +}; + +class LineEdit; +class CheckBox; +class FileDialog; + +// Prompts search parameters +class FindInFilesDialog : public WindowDialog { +	GDCLASS(FindInFilesDialog, WindowDialog) +public: +	static const char *SIGNAL_FIND_REQUESTED; +	static const char *SIGNAL_REPLACE_REQUESTED; + +	FindInFilesDialog(); + +	void set_search_text(String text); + +	String get_search_text() const; +	bool is_match_case() const; +	bool is_whole_words() const; +	String get_folder() const; +	Set<String> get_filter() const; + +protected: +	static void _bind_methods(); + +	void _notification(int p_what); + +private: +	void _on_folder_button_pressed(); +	void _on_find_button_pressed(); +	void _on_replace_button_pressed(); +	void _on_folder_selected(String path); +	void _on_search_text_modified(String text); +	void _on_search_text_entered(String text); + +	LineEdit *_search_text_line_edit; +	LineEdit *_folder_line_edit; +	Vector<CheckBox *> _filters; +	CheckBox *_match_case_checkbox; +	CheckBox *_whole_words_checkbox; +	Button *_find_button; +	Button *_replace_button; +	FileDialog *_folder_dialog; +}; + +class Button; +class ItemList; +class ProgressBar; + +// Display search results +class FindInFilesPanel : public Control { +	GDCLASS(FindInFilesPanel, Control) +public: +	static const char *SIGNAL_RESULT_SELECTED; +	static const char *SIGNAL_FILES_MODIFIED; + +	FindInFilesPanel(); + +	FindInFiles *get_finder() const { return _finder; } + +	void set_with_replace(bool with_replace); + +	void start_search(); +	void stop_search(); + +protected: +	static void _bind_methods(); + +	void _notification(int p_what); + +private: +	void _on_result_found(String fpath, int line_number, int begin, int end, String text); +	void _on_finished(); +	void _on_cancel_button_clicked(); +	void _on_result_selected(int i); +	void _on_replace_text_changed(String text); +	void _on_replace_all_clicked(); + +	void apply_replaces_in_file(String fpath, PoolIntArray locations, String text); + +	void update_replace_buttons(); +	String get_replace_text(); +	void set_progress_visible(bool visible); + +	FindInFiles *_finder; +	Label *_search_text_label; +	ItemList *_results_display; +	Label *_status_label; +	Button *_cancel_button; +	ProgressBar *_progress_bar; + +	HBoxContainer *_replace_container; +	LineEdit *_replace_line_edit; +	Button *_replace_all_button; +}; + +#endif // FIND_IN_FILES_H diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index 03155b3a48..debdeb1c4a 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -304,17 +304,23 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s  	int limit_rate_hz = p_options["force/max_rate_hz"];  	if (limit_rate && rate > limit_rate_hz && rate > 0 && frames > 0) {  		//resampleeee!!! -		int new_data_frames = frames * limit_rate_hz / rate; +		int new_data_frames = (int)(frames * (float)limit_rate_hz / (float)rate); + +		print_line("\tresampling ratio: " + rtos((float)limit_rate_hz / (float)rate)); +		print_line("\tnew frames: " + itos(new_data_frames)); +  		Vector<float> new_data;  		new_data.resize(new_data_frames * format_channels);  		for (int c = 0; c < format_channels; c++) { +			float frac = .0f; +			int ipos = 0; +  			for (int i = 0; i < new_data_frames; i++) {  				//simple cubic interpolation should be enough. -				float pos = float(i) * frames / new_data_frames; -				float mu = pos - Math::floor(pos); -				int ipos = int(Math::floor(pos)); + +				float mu = frac;  				float y0 = data[MAX(0, ipos - 1) * format_channels + c];  				float y1 = data[ipos * format_channels + c]; @@ -330,14 +336,22 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s  				float res = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3);  				new_data[i * format_channels + c] = res; + +				// update position and always keep fractional part within ]0...1] +				// in order to avoid 32bit floating point precision errors + +				frac += (float)rate / (float)limit_rate_hz; +				int tpos = (int)Math::floor(frac); +				ipos += tpos; +				frac -= tpos;  			}  		}  		if (loop) { - -			loop_begin = loop_begin * new_data_frames / frames; -			loop_end = loop_end * new_data_frames / frames; +			loop_begin = (int)(loop_begin * (float)new_data_frames / (float)frames); +			loop_end = (int)(loop_end * (float)new_data_frames / (float)frames);  		} +  		data = new_data;  		rate = limit_rate_hz;  		frames = new_data_frames; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 2ce36ee8d5..9193b3fbbf 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -39,9 +39,11 @@  #include "core/project_settings.h"  #include "editor/editor_node.h"  #include "editor/editor_settings.h" +#include "editor/find_in_files.h"  #include "editor/node_dock.h"  #include "editor/script_editor_debugger.h"  #include "scene/main/viewport.h" +#include "script_text_editor.h"  /*** SCRIPT EDITOR ****/ @@ -54,6 +56,8 @@ void ScriptEditorBase::_bind_methods() {  	ADD_SIGNAL(MethodInfo("request_open_script_at_line", PropertyInfo(Variant::OBJECT, "script"), PropertyInfo(Variant::INT, "line")));  	ADD_SIGNAL(MethodInfo("request_save_history"));  	ADD_SIGNAL(MethodInfo("go_to_help", PropertyInfo(Variant::STRING, "what"))); +	// TODO This signal is no use for VisualScript... +	ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text")));  }  static bool _can_open_in_editor(Script *p_script) { @@ -298,15 +302,9 @@ void ScriptEditor::_script_created(Ref<Script> p_script) {  void ScriptEditor::_goto_script_line2(int p_line) { -	int selected = tab_container->get_current_tab(); -	if (selected < 0 || selected >= tab_container->get_child_count()) -		return; - -	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); -	if (!current) -		return; - -	current->goto_line(p_line); +	ScriptEditorBase *current = _get_current_editor(); +	if (current) +		current->goto_line(p_line);  }  void ScriptEditor::_goto_script_line(REF p_script, int p_line) { @@ -316,19 +314,22 @@ void ScriptEditor::_goto_script_line(REF p_script, int p_line) {  		if (edit(p_script, p_line, 0)) {  			editor->push_item(p_script.ptr()); -			int selected = tab_container->get_current_tab(); -			if (selected < 0 || selected >= tab_container->get_child_count()) -				return; - -			ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); -			if (!current) -				return; - -			current->goto_line(p_line, true); +			ScriptEditorBase *current = _get_current_editor(); +			if (current) +				current->goto_line(p_line, true);  		}  	}  } +ScriptEditorBase *ScriptEditor::_get_current_editor() const { + +	int selected = tab_container->get_current_tab(); +	if (selected < 0 || selected >= tab_container->get_child_count()) +		return NULL; + +	return Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); +} +  void ScriptEditor::_update_history_arrows() {  	script_back->set_disabled(history_pos <= 0); @@ -587,7 +588,7 @@ void ScriptEditor::_close_docs_tab() {  }  void ScriptEditor::_copy_script_path() { -	ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab())); +	ScriptEditorBase *se = _get_current_editor();  	Ref<Script> script = se->get_edited_script();  	OS::get_singleton()->set_clipboard(script->get_path());  } @@ -819,11 +820,8 @@ void ScriptEditor::_file_dialog_action(String p_file) {  Ref<Script> ScriptEditor::_get_current_script() { -	int selected = tab_container->get_current_tab(); -	if (selected < 0 || selected >= tab_container->get_child_count()) -		return NULL; +	ScriptEditorBase *current = _get_current_editor(); -	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));  	if (current) {  		return current->get_edited_script();  	} else { @@ -939,11 +937,7 @@ void ScriptEditor::_menu_option(int p_option) {  		}  	} -	int selected = tab_container->get_current_tab(); -	if (selected < 0 || selected >= tab_container->get_child_count()) -		return; - -	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); +	ScriptEditorBase *current = _get_current_editor();  	if (current) {  		switch (p_option) { @@ -1033,7 +1027,7 @@ void ScriptEditor::_menu_option(int p_option) {  				_copy_script_path();  			} break;  			case SHOW_IN_FILE_SYSTEM: { -				ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_current_tab())); +				ScriptEditorBase *se = _get_current_editor();  				Ref<Script> script = se->get_edited_script();  				FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock();  				file_system_dock->navigate_to_path(script->get_path()); @@ -1223,6 +1217,17 @@ void ScriptEditor::_notification(int p_what) {  			recent_scripts->set_as_minsize();  		} break; +		case CanvasItem::NOTIFICATION_VISIBILITY_CHANGED: { + +			if (is_visible()) { +				find_in_files_button->show(); +			} else { +				find_in_files->hide(); +				find_in_files_button->hide(); +			} + +		} break; +  		default:  			break;  	} @@ -1230,15 +1235,11 @@ void ScriptEditor::_notification(int p_what) {  bool ScriptEditor::can_take_away_focus() const { -	int selected = tab_container->get_current_tab(); -	if (selected < 0 || selected >= tab_container->get_child_count()) -		return true; - -	ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); -	if (!current) +	ScriptEditorBase *current = _get_current_editor(); +	if (current) +		return current->can_lose_focus_on_node_selection(); +	else  		return true; - -	return current->can_lose_focus_on_node_selection();  }  void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { @@ -1315,20 +1316,13 @@ void ScriptEditor::ensure_focus_current() {  	if (!is_inside_tree())  		return; -	int cidx = tab_container->get_current_tab(); -	if (cidx < 0 || cidx >= tab_container->get_tab_count()) -		return; - -	Control *c = Object::cast_to<Control>(tab_container->get_child(cidx)); -	ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(c); -	if (!se) -		return; -	se->ensure_focus(); +	ScriptEditorBase *current = _get_current_editor(); +	if (current) +		current->ensure_focus();  }  void ScriptEditor::_members_overview_selected(int p_idx) { -	Node *current = tab_container->get_child(tab_container->get_current_tab()); -	ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); +	ScriptEditorBase *se = _get_current_editor();  	if (!se) {  		return;  	} @@ -1362,18 +1356,12 @@ void ScriptEditor::ensure_select_current() {  	if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) { -		Node *current = tab_container->get_child(tab_container->get_current_tab()); - -		ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); +		ScriptEditorBase *se = _get_current_editor();  		if (se) { -			Ref<Script> script = se->get_edited_script(); -  			if (!grab_focus_block && is_visible_in_tree())  				se->ensure_focus();  		} - -		EditorHelp *eh = Object::cast_to<EditorHelp>(current);  	}  	_update_selected_editor_menu(); @@ -1413,12 +1401,7 @@ struct _ScriptEditorItemData {  void ScriptEditor::_update_members_overview_visibility() { -	int selected = tab_container->get_current_tab(); -	if (selected < 0 || selected >= tab_container->get_child_count()) -		return; - -	Node *current = tab_container->get_child(tab_container->get_current_tab()); -	ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); +	ScriptEditorBase *se = _get_current_editor();  	if (!se) {  		members_overview->set_visible(false);  		return; @@ -1434,12 +1417,7 @@ void ScriptEditor::_update_members_overview_visibility() {  void ScriptEditor::_update_members_overview() {  	members_overview->clear(); -	int selected = tab_container->get_current_tab(); -	if (selected < 0 || selected >= tab_container->get_child_count()) -		return; - -	Node *current = tab_container->get_child(tab_container->get_current_tab()); -	ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(current); +	ScriptEditorBase *se = _get_current_editor();  	if (!se) {  		return;  	} @@ -1813,6 +1791,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool  	se->connect("request_open_script_at_line", this, "_goto_script_line");  	se->connect("go_to_help", this, "_help_class_goto");  	se->connect("request_save_history", this, "_save_history"); +	se->connect("search_in_files_requested", this, "_on_find_in_files_requested");  	//test for modification, maybe the script was not edited but was loaded @@ -2530,6 +2509,48 @@ void ScriptEditor::_script_changed() {  	NodeDock::singleton->update_lists();  } +void ScriptEditor::_on_find_in_files_requested(String text) { + +	find_in_files_dialog->set_search_text(text); +	find_in_files_dialog->popup_centered_minsize(); +} + +void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_number, int begin, int end) { + +	Ref<Resource> res = ResourceLoader::load(fpath); +	edit(res); + +	ScriptEditorBase *seb = _get_current_editor(); + +	ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(seb); +	if (ste) { +		ste->goto_line_selection(line_number - 1, begin, end); +	} +} + +void ScriptEditor::_start_find_in_files(bool with_replace) { + +	FindInFiles *f = find_in_files->get_finder(); + +	f->set_search_text(find_in_files_dialog->get_search_text()); +	f->set_match_case(find_in_files_dialog->is_match_case()); +	f->set_whole_words(find_in_files_dialog->is_match_case()); +	f->set_folder(find_in_files_dialog->get_folder()); +	f->set_filter(find_in_files_dialog->get_filter()); + +	find_in_files->set_with_replace(with_replace); +	find_in_files->start_search(); + +	find_in_files_button->set_pressed(true); +	find_in_files->show(); +} + +void ScriptEditor::_on_find_in_files_modified_files(PoolStringArray paths) { + +	_test_script_times_on_disk(); +	_update_modified_scripts_for_external_editor(); +} +  void ScriptEditor::_bind_methods() {  	ClassDB::bind_method("_file_dialog_action", &ScriptEditor::_file_dialog_action); @@ -2577,6 +2598,10 @@ void ScriptEditor::_bind_methods() {  	ClassDB::bind_method("_script_list_gui_input", &ScriptEditor::_script_list_gui_input);  	ClassDB::bind_method("_script_changed", &ScriptEditor::_script_changed);  	ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); +	ClassDB::bind_method("_on_find_in_files_requested", &ScriptEditor::_on_find_in_files_requested); +	ClassDB::bind_method("_start_find_in_files", &ScriptEditor::_start_find_in_files); +	ClassDB::bind_method("_on_find_in_files_result_selected", &ScriptEditor::_on_find_in_files_result_selected); +	ClassDB::bind_method("_on_find_in_files_modified_files", &ScriptEditor::_on_find_in_files_modified_files);  	ClassDB::bind_method(D_METHOD("get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw);  	ClassDB::bind_method(D_METHOD("can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw); @@ -2838,6 +2863,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {  	add_child(help_index);  	help_index->connect("open_class", this, "_help_class_open"); +	find_in_files_dialog = memnew(FindInFilesDialog); +	find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, this, "_start_find_in_files", varray(false)); +	find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, this, "_start_find_in_files", varray(true)); +	add_child(find_in_files_dialog); +	find_in_files = memnew(FindInFilesPanel); +	find_in_files_button = editor->add_bottom_panel_item(TTR("Search results"), find_in_files); +	find_in_files_button->set_tooltip(TTR("Search in files")); +	find_in_files->set_custom_minimum_size(Size2(0, 200)); +	find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, this, "_on_find_in_files_result_selected"); +	find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, this, "_on_find_in_files_modified_files"); +	find_in_files->hide(); +	find_in_files_button->hide(); +  	history_pos = -1;  	//debugger_gui->hide(); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index f947351089..9f37b18d7d 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -119,6 +119,8 @@ typedef SyntaxHighlighter *(*CreateSyntaxHighlighterFunc)();  typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Script> &p_script);  class EditorScriptCodeCompletionCache; +class FindInFilesDialog; +class FindInFilesPanel;  class ScriptEditor : public PanelContainer { @@ -217,6 +219,10 @@ class ScriptEditor : public PanelContainer {  	ToolButton *script_back;  	ToolButton *script_forward; +	FindInFilesDialog *find_in_files_dialog; +	FindInFilesPanel *find_in_files; +	Button *find_in_files_button; +  	enum {  		SCRIPT_EDITOR_FUNC_MAX = 32,  		SYNTAX_HIGHLIGHTER_FUNC_MAX = 32 @@ -304,6 +310,8 @@ class ScriptEditor : public PanelContainer {  	void _update_window_menu();  	void _script_created(Ref<Script> p_script); +	ScriptEditorBase *_get_current_editor() const; +  	void _save_layout();  	void _editor_settings_changed();  	void _autosave_scripts(); @@ -359,6 +367,11 @@ class ScriptEditor : public PanelContainer {  	Ref<Script> _get_current_script();  	Array _get_open_scripts() const; +	void _on_find_in_files_requested(String text); +	void _on_find_in_files_result_selected(String fpath, int line_number, int begin, int end); +	void _start_find_in_files(bool with_replace); +	void _on_find_in_files_modified_files(PoolStringArray paths); +  	static void _open_script_request(const String &p_path);  	static ScriptEditor *script_editor; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 711a313902..bcc575a7ac 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -529,6 +529,14 @@ void ScriptTextEditor::goto_line(int p_line, bool p_with_error) {  	tx->call_deferred("cursor_set_line", p_line);  } +void ScriptTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) { +	TextEdit *tx = code_editor->get_text_edit(); +	tx->unfold_line(p_line); +	tx->call_deferred("cursor_set_line", p_line); +	tx->call_deferred("cursor_set_column", p_begin); +	tx->select(p_line, p_begin, p_line, p_end); +} +  void ScriptTextEditor::ensure_focus() {  	code_editor->get_text_edit()->grab_focus(); @@ -1173,6 +1181,15 @@ void ScriptTextEditor::_edit_option(int p_op) {  			code_editor->get_find_replace_bar()->popup_replace();  		} break; +		case SEARCH_IN_FILES: { + +			String selected_text = code_editor->get_text_edit()->get_selection_text(); + +			// Yep, because it doesn't make sense to instance this dialog for every single script open... +			// So this will be delegated to the ScriptEditor +			emit_signal("search_in_files_requested", selected_text); + +		} break;  		case SEARCH_LOCATE_FUNCTION: {  			quick_open->popup(get_functions()); @@ -1660,6 +1677,8 @@ ScriptTextEditor::ScriptTextEditor() {  	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);  	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);  	search_menu->get_popup()->add_separator(); +	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES); +	search_menu->get_popup()->add_separator();  	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);  	search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);  	search_menu->get_popup()->add_separator(); @@ -1739,7 +1758,9 @@ void ScriptTextEditor::register_editor() {  	ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3);  	ED_SHORTCUT("script_text_editor/replace", TTR("Replace.."), KEY_MASK_CMD | KEY_R); -	ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_F); +	ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F); + +	ED_SHORTCUT("script_text_editor/goto_function", TTR("Goto Function.."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F);  	ED_SHORTCUT("script_text_editor/goto_line", TTR("Goto Line.."), KEY_MASK_CMD | KEY_L);  	ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_SHIFT | KEY_F1); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index eb52d2593a..a93e1a6fa8 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -106,6 +106,7 @@ class ScriptTextEditor : public ScriptEditorBase {  		SEARCH_REPLACE,  		SEARCH_LOCATE_FUNCTION,  		SEARCH_GOTO_LINE, +		SEARCH_IN_FILES,  		DEBUG_TOGGLE_BREAKPOINT,  		DEBUG_REMOVE_ALL_BREAKPOINTS,  		DEBUG_GOTO_NEXT_BREAKPOINT, @@ -170,6 +171,7 @@ public:  	virtual void tag_saved_version();  	virtual void goto_line(int p_line, bool p_with_error = false); +	void goto_line_selection(int p_line, int p_begin, int p_end);  	virtual void reload(bool p_soft);  	virtual void get_breakpoints(List<int> *p_breakpoints); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 87d8fe1bf5..0d52f0a995 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1333,13 +1333,23 @@ static void _find_identifiers_in_block(GDScriptCompletionContext &context, int p  	for (int i = 0; i < context.block->statements.size(); i++) { -		if (context.block->statements[i]->line > p_line) +		GDScriptParser::Node *statement = context.block->statements[i]; +		if (statement->line > p_line)  			continue; -		if (context.block->statements[i]->type == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { +		GDScriptParser::BlockNode::Type statementType = statement->type; +		if (statementType == GDScriptParser::BlockNode::TYPE_LOCAL_VAR) { -			const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(context.block->statements[i]); +			const GDScriptParser::LocalVarNode *lv = static_cast<const GDScriptParser::LocalVarNode *>(statement);  			result.insert(lv->name.operator String()); +		} else if (statementType == GDScriptParser::BlockNode::TYPE_CONTROL_FLOW) { + +			const GDScriptParser::ControlFlowNode *cf = static_cast<const GDScriptParser::ControlFlowNode *>(statement); +			if (cf->cf_type == GDScriptParser::ControlFlowNode::CF_FOR) { + +				const GDScriptParser::IdentifierNode *id = static_cast<const GDScriptParser::IdentifierNode *>(cf->arguments[0]); +				result.insert(id->name.operator String()); +			}  		}  	}  } diff --git a/modules/gdscript/gdscript_highlighter.cpp b/modules/gdscript/gdscript_highlighter.cpp index 5b8b652c29..4e89851bf2 100644 --- a/modules/gdscript/gdscript_highlighter.cpp +++ b/modules/gdscript/gdscript_highlighter.cpp @@ -71,24 +71,8 @@ Map<int, TextEdit::HighlighterInfo> GDScriptSyntaxHighlighter::_get_line_syntax_  	Color keyword_color;  	Color color; -	int in_region = -1; +	int in_region = text_editor->_is_line_in_region(p_line);  	int deregion = 0; -	for (int i = 0; i < p_line; i++) { -		int ending_color_region = text_editor->_get_line_ending_color_region(i); -		if (in_region == -1) { -			in_region = ending_color_region; -		} else if (in_region == ending_color_region) { -			in_region = -1; -		} else { -			const Map<int, TextEdit::Text::ColorRegionInfo> &cri_map = text_editor->_get_line_color_region_info(i); -			for (const Map<int, TextEdit::Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { -				const TextEdit::Text::ColorRegionInfo &cri = E->get(); -				if (cri.region == in_region) { -					in_region = -1; -				} -			} -		} -	}  	const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text_editor->_get_line_color_region_info(p_line);  	const String &str = text_editor->get_line(p_line); diff --git a/modules/mono/editor/mono_bottom_panel.cpp b/modules/mono/editor/mono_bottom_panel.cpp index f1cf0bcdf5..1b5a303835 100644 --- a/modules/mono/editor/mono_bottom_panel.cpp +++ b/modules/mono/editor/mono_bottom_panel.cpp @@ -407,9 +407,14 @@ void MonoBuildTab::stop_build() {  void MonoBuildTab::_issue_activated(int p_idx) { -	ERR_FAIL_INDEX(p_idx, issues.size()); +	ERR_FAIL_INDEX(p_idx, issues_list->get_item_count()); -	const BuildIssue &issue = issues[p_idx]; +	// Get correct issue idx from issue list +	int issue_idx = this->issues_list->get_item_metadata(p_idx); + +	ERR_FAIL_INDEX(issue_idx, issues.size()); + +	const BuildIssue &issue = issues[issue_idx];  	if (issue.project_file.empty() && issue.file.empty())  		return; diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 232855c978..bc44c91f64 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -366,11 +366,17 @@ void Sprite3D::_draw() {  		final_rect.position * pixel_size,  	}; + +	// Properly setup UVs for impostor textures (AtlasTexture). +	RID texture_rid = texture->get_rid(); +	Vector2 src_tsize = Vector2( +			VS::get_singleton()->texture_get_width(texture_rid), +			VS::get_singleton()->texture_get_height(texture_rid));  	Vector2 uvs[4] = { -		final_src_rect.position / tsize, -		(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize, -		(final_src_rect.position + final_src_rect.size) / tsize, -		(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / tsize, +		final_src_rect.position / src_tsize, +		(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, +		(final_src_rect.position + final_src_rect.size) / src_tsize, +		(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize,  	};  	if (is_flipped_h()) { @@ -649,18 +655,23 @@ void AnimatedSprite3D::_draw() {  	float pixel_size = get_pixel_size();  	Vector2 vertices[4] = { -  		(final_rect.position + Vector2(0, final_rect.size.y)) * pixel_size,  		(final_rect.position + final_rect.size) * pixel_size,  		(final_rect.position + Vector2(final_rect.size.x, 0)) * pixel_size,  		final_rect.position * pixel_size,  	}; + +	// Properly setup UVs for impostor textures (AtlasTexture). +	RID texture_rid = texture->get_rid(); +	Vector2 src_tsize = Vector2( +			VS::get_singleton()->texture_get_width(texture_rid), +			VS::get_singleton()->texture_get_height(texture_rid));  	Vector2 uvs[4] = { -		final_src_rect.position / tsize, -		(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / tsize, -		(final_src_rect.position + final_src_rect.size) / tsize, -		(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / tsize, +		final_src_rect.position / src_tsize, +		(final_src_rect.position + Vector2(final_src_rect.size.x, 0)) / src_tsize, +		(final_src_rect.position + final_src_rect.size) / src_tsize, +		(final_src_rect.position + Vector2(0, final_src_rect.size.y)) / src_tsize,  	};  	if (is_flipped_h()) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index a5883863cd..c71320f207 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1281,22 +1281,24 @@ void Control::_size_changed() {  	Size2 minimum_size = get_combined_minimum_size(); -	if (data.h_grow == GROW_DIRECTION_BEGIN) { -		if (minimum_size.width > new_size_cache.width) { -			new_pos_cache.x = new_pos_cache.x + new_size_cache.width - minimum_size.width; -			new_size_cache.width = minimum_size.width; +	if (minimum_size.width > new_size_cache.width) { +		if (data.h_grow == GROW_DIRECTION_BEGIN) { +			new_pos_cache.x += new_size_cache.width - minimum_size.width; +		} else if (data.h_grow == GROW_DIRECTION_BOTH) { +			new_pos_cache.x += 0.5 * (new_size_cache.width - minimum_size.width);  		} -	} else { -		new_size_cache.width = MAX(minimum_size.width, new_size_cache.width); + +		new_size_cache.width = minimum_size.width;  	} -	if (data.v_grow == GROW_DIRECTION_BEGIN) { -		if (minimum_size.height > new_size_cache.height) { -			new_pos_cache.y = new_pos_cache.y + new_size_cache.height - minimum_size.height; -			new_size_cache.height = minimum_size.height; +	if (minimum_size.height > new_size_cache.height) { +		if (data.v_grow == GROW_DIRECTION_BEGIN) { +			new_pos_cache.y += new_size_cache.height - minimum_size.height; +		} else if (data.v_grow == GROW_DIRECTION_BOTH) { +			new_pos_cache.y += 0.5 * (new_size_cache.height - minimum_size.height);  		} -	} else { -		new_size_cache.height = MAX(minimum_size.height, new_size_cache.height); + +		new_size_cache.height = minimum_size.height;  	}  	// We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot() @@ -2838,8 +2840,8 @@ void Control::_bind_methods() {  	ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "margin_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_BOTTOM);  	ADD_GROUP("Grow Direction", "grow_"); -	ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End"), "set_h_grow_direction", "get_h_grow_direction"); -	ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End"), "set_v_grow_direction", "get_v_grow_direction"); +	ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction"); +	ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction");  	ADD_GROUP("Rect", "rect_");  	ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position"); @@ -2939,6 +2941,7 @@ void Control::_bind_methods() {  	BIND_ENUM_CONSTANT(GROW_DIRECTION_BEGIN);  	BIND_ENUM_CONSTANT(GROW_DIRECTION_END); +	BIND_ENUM_CONSTANT(GROW_DIRECTION_BOTH);  	BIND_ENUM_CONSTANT(ANCHOR_BEGIN);  	BIND_ENUM_CONSTANT(ANCHOR_END); diff --git a/scene/gui/control.h b/scene/gui/control.h index 51325f27b5..65f75c8a66 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -60,7 +60,8 @@ public:  	enum GrowDirection {  		GROW_DIRECTION_BEGIN, -		GROW_DIRECTION_END +		GROW_DIRECTION_END, +		GROW_DIRECTION_BOTH  	};  	enum FocusMode { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e214a020d5..cc6a677ec8 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -145,7 +145,6 @@ void TextEdit::Text::_update_line_cache(int p_line) const {  	text[p_line].region_info.clear(); -	int ending_color_region = -1;  	for (int i = 0; i < len; i++) {  		if (!_is_symbol(str[i])) @@ -186,11 +185,6 @@ void TextEdit::Text::_update_line_cache(int p_line) const {  				text[p_line].region_info[i] = cri;  				i += lr - 1; -				if (ending_color_region == -1 && !cr.line_only) { -					ending_color_region = j; -				} else if (ending_color_region == j) { -					ending_color_region = -1; -				}  				break;  			} @@ -219,15 +213,10 @@ void TextEdit::Text::_update_line_cache(int p_line) const {  				text[p_line].region_info[i] = cri;  				i += lr - 1; -				if (ending_color_region == j) { -					ending_color_region = -1; -				} -  				break;  			}  		}  	} -	text[p_line].ending_color_region = ending_color_region;  }  const Map<int, TextEdit::Text::ColorRegionInfo> &TextEdit::Text::get_color_region_info(int p_line) const { @@ -569,7 +558,6 @@ void TextEdit::_notification(int p_what) {  			}  		} break;  		case NOTIFICATION_DRAW: { -  			if ((!has_focus() && !menu->has_focus()) || !window_has_focus) {  				draw_caret = false;  			} @@ -3196,6 +3184,7 @@ void TextEdit::_base_insert_text(int p_line, int p_char, const String &p_text, i  			MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");  		text_changed_dirty = true;  	} +	_line_edited_from(p_line);  }  String TextEdit::_base_get_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) const { @@ -3246,6 +3235,7 @@ void TextEdit::_base_remove_text(int p_from_line, int p_from_column, int p_to_li  			MessageQueue::get_singleton()->push_call(this, "_text_changed_emit");  		text_changed_dirty = true;  	} +	_line_edited_from(p_from_line);  }  void TextEdit::_insert_text(int p_line, int p_char, const String &p_text, int *r_end_line, int *r_end_char) { @@ -3368,6 +3358,13 @@ void TextEdit::_insert_text_at_cursor(const String &p_text) {  	update();  } +void TextEdit::_line_edited_from(int p_line) { +	int cache_size = color_region_cache.size(); +	for (int i = p_line; i < cache_size; i++) { +		color_region_cache.erase(i); +	} +} +  int TextEdit::get_char_count() {  	int totalsize = 0; @@ -4009,15 +4006,49 @@ void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter)  	update();  } -int TextEdit::_get_line_ending_color_region(int p_line) const { -	if (p_line < 0 || p_line > text.size() - 1) { -		return -1; +int TextEdit::_is_line_in_region(int p_line) { + +	// do we have in cache? +	if (color_region_cache.has(p_line)) { +		return color_region_cache[p_line]; +	} + +	// if not find the closest line we have +	int previous_line = p_line - 1; +	for (previous_line; previous_line > -1; previous_line--) { +		if (color_region_cache.has(p_line)) { +			break; +		} +	} + +	// calculate up to line we need and update the cache along the way. +	int in_region = color_region_cache[previous_line]; +	for (int i = previous_line; i < p_line; i++) { +		const Map<int, Text::ColorRegionInfo> &cri_map = _get_line_color_region_info(i); +		for (const Map<int, Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { +			const Text::ColorRegionInfo &cri = E->get(); +			if (in_region == -1) { +				if (!cri.end) { +					in_region = cri.region; +				} +			} else if (in_region == cri.region && !_get_color_region(cri.region).line_only) { +				if (cri.end || _get_color_region(cri.region).eq) { +					in_region = -1; +				} +			} +		} + +		if (in_region >= 0 && _get_color_region(in_region).line_only) { +			in_region = -1; +		} + +		color_region_cache[i + 1] = in_region;  	} -	return text.get_line_ending_color_region(p_line); +	return in_region;  }  TextEdit::ColorRegion TextEdit::_get_color_region(int p_region) const { -	if (p_region < 0 || p_region > color_regions.size()) { +	if (p_region < 0 || p_region >= color_regions.size()) {  		return ColorRegion();  	}  	return color_regions[p_region]; @@ -4034,6 +4065,7 @@ void TextEdit::clear_colors() {  	keywords.clear();  	color_regions.clear(); +	color_region_cache.clear();  	text.clear_caches();  } @@ -5777,24 +5809,8 @@ Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int  	Color keyword_color;  	Color color; -	int in_region = -1; +	int in_region = _is_line_in_region(p_line);  	int deregion = 0; -	for (int i = 0; i < p_line; i++) { -		int ending_color_region = text.get_line_ending_color_region(i); -		if (in_region == -1) { -			in_region = ending_color_region; -		} else if (in_region == ending_color_region) { -			in_region = -1; -		} else { -			const Map<int, TextEdit::Text::ColorRegionInfo> &cri_map = text.get_color_region_info(i); -			for (const Map<int, TextEdit::Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) { -				const TextEdit::Text::ColorRegionInfo &cri = E->get(); -				if (cri.region == in_region) { -					in_region = -1; -				} -			} -		} -	}  	const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text.get_color_region_info(p_line);  	const String &str = text[p_line]; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 2360ce79db..30e70bfd0b 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -76,7 +76,6 @@ public:  			bool marked : 1;  			bool breakpoint : 1;  			bool hidden : 1; -			int ending_color_region;  			Map<int, ColorRegionInfo> region_info;  			String data;  		}; @@ -103,7 +102,6 @@ public:  		bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }  		void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; }  		bool is_hidden(int p_line) const { return text[p_line].hidden; } -		int get_line_ending_color_region(int p_line) const { return text[p_line].ending_color_region; }  		void insert(int p_at, const String &p_text);  		void remove(int p_at);  		int size() const { return text.size(); } @@ -189,6 +187,8 @@ private:  		Size2 size;  	} cache; +	Map<int, int> color_region_cache; +  	struct TextOperation {  		enum Type { @@ -368,6 +368,7 @@ private:  	void _update_caches();  	void _cursor_changed_emit();  	void _text_changed_emit(); +	void _line_edited_from(int p_line);  	void _push_current_op(); @@ -407,7 +408,7 @@ public:  	SyntaxHighlighter *_get_syntax_highlighting();  	void _set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter); -	int _get_line_ending_color_region(int p_line) const; +	int _is_line_in_region(int p_line);  	ColorRegion _get_color_region(int p_region) const;  	Map<int, Text::ColorRegionInfo> _get_line_color_region_info(int p_line) const; diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 07c1036a10..5a42873d79 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -861,7 +861,7 @@ void SurfaceTool::generate_tangents() {  	}  } -void SurfaceTool::generate_normals() { +void SurfaceTool::generate_normals(bool p_flip) {  	ERR_FAIL_COND(primitive != Mesh::PRIMITIVE_TRIANGLES); @@ -887,7 +887,11 @@ void SurfaceTool::generate_normals() {  		ERR_FAIL_COND(!v[2]);  		E = v[2]->next(); -		Vector3 normal = Plane(v[0]->get().vertex, v[1]->get().vertex, v[2]->get().vertex).normal; +		Vector3 normal; +		if (!p_flip) +			normal = Plane(v[0]->get().vertex, v[1]->get().vertex, v[2]->get().vertex).normal; +		else +			normal = Plane(v[2]->get().vertex, v[1]->get().vertex, v[0]->get().vertex).normal;  		if (smooth) { @@ -980,7 +984,7 @@ void SurfaceTool::_bind_methods() {  	ClassDB::bind_method(D_METHOD("index"), &SurfaceTool::index);  	ClassDB::bind_method(D_METHOD("deindex"), &SurfaceTool::deindex); -	ClassDB::bind_method(D_METHOD("generate_normals"), &SurfaceTool::generate_normals); +	ClassDB::bind_method(D_METHOD("generate_normals", "flip"), &SurfaceTool::generate_normals, DEFVAL(false));  	ClassDB::bind_method(D_METHOD("generate_tangents"), &SurfaceTool::generate_tangents);  	ClassDB::bind_method(D_METHOD("add_to_format", "flags"), &SurfaceTool::add_to_format); diff --git a/scene/resources/surface_tool.h b/scene/resources/surface_tool.h index 7a9aa349bb..459d399380 100644 --- a/scene/resources/surface_tool.h +++ b/scene/resources/surface_tool.h @@ -116,7 +116,7 @@ public:  	void index();  	void deindex(); -	void generate_normals(); +	void generate_normals(bool p_flip = false);  	void generate_tangents();  	void add_to_format(int p_flags) { format |= p_flags; } diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp index 7fdc296bb4..bebbf6e238 100644 --- a/scene/resources/tile_set.cpp +++ b/scene/resources/tile_set.cpp @@ -919,6 +919,8 @@ void TileSet::_bind_methods() {  	ClassDB::bind_method(D_METHOD("tile_get_shape_count", "id"), &TileSet::tile_get_shape_count);  	ClassDB::bind_method(D_METHOD("tile_set_shapes", "id", "shapes"), &TileSet::_tile_set_shapes);  	ClassDB::bind_method(D_METHOD("tile_get_shapes", "id"), &TileSet::_tile_get_shapes); +	ClassDB::bind_method(D_METHOD("tile_set_tile_mode", "id", "tilemode"), &TileSet::tile_set_tile_mode); +	ClassDB::bind_method(D_METHOD("tile_get_tile_mode", "id"), &TileSet::tile_get_tile_mode);  	ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon", "id", "navigation_polygon"), &TileSet::tile_set_navigation_polygon);  	ClassDB::bind_method(D_METHOD("tile_get_navigation_polygon", "id"), &TileSet::tile_get_navigation_polygon);  	ClassDB::bind_method(D_METHOD("tile_set_navigation_polygon_offset", "id", "navigation_polygon_offset"), &TileSet::tile_set_navigation_polygon_offset); @@ -948,6 +950,10 @@ void TileSet::_bind_methods() {  	BIND_ENUM_CONSTANT(BIND_BOTTOMLEFT);  	BIND_ENUM_CONSTANT(BIND_BOTTOM);  	BIND_ENUM_CONSTANT(BIND_BOTTOMRIGHT); + +	BIND_ENUM_CONSTANT(SINGLE_TILE); +	BIND_ENUM_CONSTANT(AUTO_TILE); +	BIND_ENUM_CONSTANT(ANIMATED_TILE);  }  TileSet::TileSet() { diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h index 46f34b6252..706d04998f 100644 --- a/scene/resources/tile_set.h +++ b/scene/resources/tile_set.h @@ -238,5 +238,6 @@ public:  VARIANT_ENUM_CAST(TileSet::AutotileBindings);  VARIANT_ENUM_CAST(TileSet::BitmaskMode); +VARIANT_ENUM_CAST(TileSet::TileMode);  #endif // TILE_SET_H  |