diff options
53 files changed, 512 insertions, 542 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp index f3f51e57ee..7a2e6765b8 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -456,10 +456,6 @@ String _OS::get_user_data_dir() const {  	return OS::get_singleton()->get_user_data_dir();  } -String _OS::get_external_data_dir() const { -	return OS::get_singleton()->get_external_data_dir(); -} -  String _OS::get_config_dir() const {  	// Exposed as `get_config_dir()` instead of `get_config_path()` for consistency with other exposed OS methods.  	return OS::get_singleton()->get_config_path(); @@ -483,8 +479,8 @@ bool _OS::is_debug_build() const {  #endif  } -String _OS::get_system_dir(SystemDir p_dir) const { -	return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir)); +String _OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { +	return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir), p_shared_storage);  }  String _OS::get_keycode_string(uint32_t p_code) const { @@ -567,8 +563,7 @@ void _OS::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &_OS::get_static_memory_peak_usage);  	ClassDB::bind_method(D_METHOD("get_user_data_dir"), &_OS::get_user_data_dir); -	ClassDB::bind_method(D_METHOD("get_external_data_dir"), &_OS::get_external_data_dir); -	ClassDB::bind_method(D_METHOD("get_system_dir", "dir"), &_OS::get_system_dir); +	ClassDB::bind_method(D_METHOD("get_system_dir", "dir", "shared_storage"), &_OS::get_system_dir, DEFVAL(true));  	ClassDB::bind_method(D_METHOD("get_config_dir"), &_OS::get_config_dir);  	ClassDB::bind_method(D_METHOD("get_data_dir"), &_OS::get_data_dir);  	ClassDB::bind_method(D_METHOD("get_cache_dir"), &_OS::get_cache_dir); @@ -1560,15 +1555,17 @@ Error _Directory::copy(String p_from, String p_to) {  Error _Directory::rename(String p_from, String p_to) {  	ERR_FAIL_COND_V_MSG(!is_open(), ERR_UNCONFIGURED, "Directory must be opened before use."); +	ERR_FAIL_COND_V_MSG(p_from.is_empty() || p_from == "." || p_from == "..", ERR_INVALID_PARAMETER, "Invalid path to rename."); +  	if (!p_from.is_rel_path()) {  		DirAccess *d = DirAccess::create_for_path(p_from); -		ERR_FAIL_COND_V_MSG(!d->file_exists(p_from), ERR_DOES_NOT_EXIST, "File does not exist."); +		ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist.");  		Error err = d->rename(p_from, p_to);  		memdelete(d);  		return err;  	} -	ERR_FAIL_COND_V_MSG(!d->file_exists(p_from), ERR_DOES_NOT_EXIST, "File does not exist."); +	ERR_FAIL_COND_V_MSG(!d->file_exists(p_from) && !d->dir_exists(p_from), ERR_DOES_NOT_EXIST, "File or directory does not exist.");  	return d->rename(p_from, p_to);  } diff --git a/core/core_bind.h b/core/core_bind.h index 9060aff340..94e8ab2a34 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -229,10 +229,9 @@ public:  		SYSTEM_DIR_RINGTONES,  	}; -	String get_system_dir(SystemDir p_dir) const; +	String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;  	String get_user_data_dir() const; -	String get_external_data_dir() const;  	String get_config_dir() const;  	String get_data_dir() const;  	String get_cache_dir() const; diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index adf068eb2f..b7d2bac96d 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -405,11 +405,11 @@ String UndoRedo::get_current_action_name() const {  	return actions[current_action].name;  } -bool UndoRedo::has_undo() { +bool UndoRedo::has_undo() const {  	return current_action >= 0;  } -bool UndoRedo::has_redo() { +bool UndoRedo::has_redo() const {  	return (current_action + 1) < actions.size();  } diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index 8f009830e3..d1ce252d86 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -121,8 +121,8 @@ public:  	String get_action_name(int p_id);  	void clear_history(bool p_increase_version = true); -	bool has_undo(); -	bool has_redo(); +	bool has_undo() const; +	bool has_redo() const;  	uint64_t get_version() const; diff --git a/core/os/os.cpp b/core/os/os.cpp index 76a6da51e1..63390919f4 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -277,18 +277,13 @@ String OS::get_user_data_dir() const {  	return ".";  } -// Android OS path to app's external data storage -String OS::get_external_data_dir() const { -	return get_user_data_dir(); -}; -  // Absolute path to res://  String OS::get_resource_dir() const {  	return ProjectSettings::get_singleton()->get_resource_path();  }  // Access system-specific dirs like Documents, Downloads, etc. -String OS::get_system_dir(SystemDir p_dir) const { +String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {  	return ".";  } diff --git a/core/os/os.h b/core/os/os.h index 0466d94acd..55b21266fc 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -250,7 +250,6 @@ public:  	virtual String get_bundle_resource_dir() const;  	virtual String get_user_data_dir() const; -	virtual String get_external_data_dir() const;  	virtual String get_resource_dir() const;  	enum SystemDir { @@ -264,7 +263,7 @@ public:  		SYSTEM_DIR_RINGTONES,  	}; -	virtual String get_system_dir(SystemDir p_dir) const; +	virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;  	virtual Error move_to_trash(const String &p_path) { return FAILED; } diff --git a/doc/classes/Directory.xml b/doc/classes/Directory.xml index 919960cc9f..e8e5a286b4 100644 --- a/doc/classes/Directory.xml +++ b/doc/classes/Directory.xml @@ -187,7 +187,7 @@  			<argument index="0" name="from" type="String" />  			<argument index="1" name="to" type="String" />  			<description> -				Renames (move) the [code]from[/code] file to the [code]to[/code] destination. Both arguments should be paths to files, either relative or absolute. If the destination file exists and is not access-protected, it will be overwritten. +				Renames (move) the [code]from[/code] file or directory to the [code]to[/code] destination. Both arguments should be paths to files or directories, either relative or absolute. If the destination file or directory exists and is not access-protected, it will be overwritten.  				Returns one of the [enum Error] code constants ([code]OK[/code] on success).  			</description>  		</method> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index e5d45189c7..757730f6c8 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -197,12 +197,6 @@  				Returns the path to the current engine executable.  			</description>  		</method> -		<method name="get_external_data_dir" qualifiers="const"> -			<return type="String" /> -			<description> -				On Android, returns the absolute directory path where user data can be written to external storage if available. On all other platforms, this will return the same location as [method get_user_data_dir]. -			</description> -		</method>  		<method name="get_granted_permissions" qualifiers="const">  			<return type="PackedStringArray" />  			<description> @@ -270,9 +264,11 @@  		<method name="get_system_dir" qualifiers="const">  			<return type="String" />  			<argument index="0" name="dir" type="int" enum="OS.SystemDir" /> +			<argument index="1" name="shared_storage" type="bool" default="true" />  			<description>  				Returns the actual path to commonly used folders across different platforms. Available locations are specified in [enum SystemDir].  				[b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows. +				[b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories. Shared directories have additional restrictions on Android.  			</description>  		</method>  		<method name="get_thread_caller_id" qualifiers="const"> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 68d20e41d0..7aa627b7d0 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -464,12 +464,24 @@  				Returns if the user has IME text.  			</description>  		</method> +		<method name="has_redo" qualifiers="const"> +			<return type="bool" /> +			<description> +				Returns [code]true[/code] if a "redo" action is available. +			</description> +		</method>  		<method name="has_selection" qualifiers="const">  			<return type="bool" />  			<description>  				Returns [code]true[/code] if the user has selected text.  			</description>  		</method> +		<method name="has_undo" qualifiers="const"> +			<return type="bool" /> +			<description> +				Returns [code]true[/code] if an "undo" action is available. +			</description> +		</method>  		<method name="insert_line_at">  			<return type="void" />  			<argument index="0" name="line" type="int" /> diff --git a/doc/classes/Theme.xml b/doc/classes/Theme.xml index 1124fb8084..edf5874432 100644 --- a/doc/classes/Theme.xml +++ b/doc/classes/Theme.xml @@ -81,19 +81,6 @@  				Unmarks [code]theme_type[/code] as being a variation of any other type.  			</description>  		</method> -		<method name="copy_default_theme"> -			<return type="void" /> -			<description> -				Sets the theme's values to a copy of the default theme values. -			</description> -		</method> -		<method name="copy_theme"> -			<return type="void" /> -			<argument index="0" name="other" type="Theme" /> -			<description> -				Sets the theme's values to a copy of a given theme. -			</description> -		</method>  		<method name="get_color" qualifiers="const">  			<return type="Color" />  			<argument index="0" name="name" type="StringName" /> @@ -340,6 +327,14 @@  				Returns [code]true[/code] if [code]theme_type[/code] is marked as a variation of [code]base_type[/code] in this theme.  			</description>  		</method> +		<method name="merge_with"> +			<return type="void" /> +			<argument index="0" name="other" type="Theme" /> +			<description> +				Adds missing and overrides existing definitions with values from the [code]other[/code] [Theme]. +				[b]Note:[/b] This modifies the current theme. If you want to merge two themes together without modifying either one, create a new empty theme and merge the other two into it one after another. +			</description> +		</method>  		<method name="rename_color">  			<return type="void" />  			<argument index="0" name="old_name" type="StringName" /> diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml index 4417447891..def6fe5d1f 100644 --- a/doc/classes/UndoRedo.xml +++ b/doc/classes/UndoRedo.xml @@ -166,13 +166,13 @@  				This is useful mostly to check if something changed from a saved version.  			</description>  		</method> -		<method name="has_redo"> +		<method name="has_redo" qualifiers="const">  			<return type="bool" />  			<description>  				Returns [code]true[/code] if a "redo" action is available.  			</description>  		</method> -		<method name="has_undo"> +		<method name="has_undo" qualifiers="const">  			<return type="bool" />  			<description>  				Returns [code]true[/code] if an "undo" action is available. diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 87749450c4..625c222b67 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -976,37 +976,35 @@ Error VulkanContext::_create_device() {  		sdevice.queueCreateInfoCount = 2;  	} -#ifdef VK_VERSION_1_2  	VkPhysicalDeviceVulkan11Features vulkan11features; - -	vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; -	vulkan11features.pNext = nullptr; -	// !BAS! Need to figure out which ones of these we want enabled... -	vulkan11features.storageBuffer16BitAccess = 0; -	vulkan11features.uniformAndStorageBuffer16BitAccess = 0; -	vulkan11features.storagePushConstant16 = 0; -	vulkan11features.storageInputOutput16 = 0; -	vulkan11features.multiview = multiview_capabilities.is_supported; -	vulkan11features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; -	vulkan11features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; -	vulkan11features.variablePointersStorageBuffer = 0; -	vulkan11features.variablePointers = 0; -	vulkan11features.protectedMemory = 0; -	vulkan11features.samplerYcbcrConversion = 0; -	vulkan11features.shaderDrawParameters = 0; - -	sdevice.pNext = &vulkan11features; -#elif VK_VERSION_1_1  	VkPhysicalDeviceMultiviewFeatures multiview_features; +	if (vulkan_major > 1 || vulkan_minor >= 2) { +		vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; +		vulkan11features.pNext = nullptr; +		// !BAS! Need to figure out which ones of these we want enabled... +		vulkan11features.storageBuffer16BitAccess = 0; +		vulkan11features.uniformAndStorageBuffer16BitAccess = 0; +		vulkan11features.storagePushConstant16 = 0; +		vulkan11features.storageInputOutput16 = 0; +		vulkan11features.multiview = multiview_capabilities.is_supported; +		vulkan11features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; +		vulkan11features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; +		vulkan11features.variablePointersStorageBuffer = 0; +		vulkan11features.variablePointers = 0; +		vulkan11features.protectedMemory = 0; +		vulkan11features.samplerYcbcrConversion = 0; +		vulkan11features.shaderDrawParameters = 0; + +		sdevice.pNext = &vulkan11features; +	} else if (vulkan_major == 1 && vulkan_minor == 1) { +		multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; +		multiview_features.pNext = nullptr; +		multiview_features.multiview = multiview_capabilities.is_supported; +		multiview_features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; +		multiview_features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; -	multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; -	multiview_features.pNext = nullptr; -	multiview_features.multiview = multiview_capabilities.is_supported; -	multiview_features.multiviewGeometryShader = multiview_capabilities.geometry_shader_is_supported; -	multiview_features.multiviewTessellationShader = multiview_capabilities.tessellation_shader_is_supported; - -	sdevice.pNext = &multiview_features; -#endif +		sdevice.pNext = &multiview_features; +	}  	err = vkCreateDevice(gpu, &sdevice, nullptr, &device);  	ERR_FAIL_COND_V(err, ERR_CANT_CREATE); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 48f2be450b..39095c42a4 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1429,13 +1429,13 @@ void CodeTextEditor::toggle_inline_comment(const String &delimiter) {  void CodeTextEditor::goto_line(int p_line) {  	text_editor->deselect();  	text_editor->unfold_line(p_line); -	text_editor->call_deferred(SNAME("cursor_set_line"), p_line); +	text_editor->call_deferred(SNAME("set_caret_line"), p_line);  }  void CodeTextEditor::goto_line_selection(int p_line, int p_begin, int p_end) {  	text_editor->unfold_line(p_line); -	text_editor->call_deferred(SNAME("cursor_set_line"), p_line); -	text_editor->call_deferred(SNAME("cursor_set_column"), p_begin); +	text_editor->call_deferred(SNAME("set_caret_line"), p_line); +	text_editor->call_deferred(SNAME("set_caret_column"), p_begin);  	text_editor->select(p_line, p_begin, p_line, p_end);  } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 4cd2e8bdd0..3adb7688b0 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -2592,26 +2592,26 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {  		case EDIT_UNDO: {  			if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { -				log->add_message("Can't undo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR); +				log->add_message(TTR("Can't undo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR);  			} else {  				String action = editor_data.get_undo_redo().get_current_action_name();  				if (!editor_data.get_undo_redo().undo()) { -					log->add_message("Nothing to undo.", EditorLog::MSG_TYPE_EDITOR); +					log->add_message(TTR("Nothing to undo."), EditorLog::MSG_TYPE_EDITOR);  				} else if (action != "") { -					log->add_message("Undo: " + action, EditorLog::MSG_TYPE_EDITOR); +					log->add_message(vformat(TTR("Undo: %s"), action), EditorLog::MSG_TYPE_EDITOR);  				}  			}  		} break;  		case EDIT_REDO: {  			if (Input::get_singleton()->get_mouse_button_mask() & 0x7) { -				log->add_message("Can't redo while mouse buttons are pressed.", EditorLog::MSG_TYPE_EDITOR); +				log->add_message(TTR("Can't redo while mouse buttons are pressed."), EditorLog::MSG_TYPE_EDITOR);  			} else {  				if (!editor_data.get_undo_redo().redo()) { -					log->add_message("Nothing to redo.", EditorLog::MSG_TYPE_EDITOR); +					log->add_message(TTR("Nothing to redo."), EditorLog::MSG_TYPE_EDITOR);  				} else {  					String action = editor_data.get_undo_redo().get_current_action_name(); -					log->add_message("Redo: " + action, EditorLog::MSG_TYPE_EDITOR); +					log->add_message(vformat(TTR("Redo: %s"), action), EditorLog::MSG_TYPE_EDITOR);  				}  			}  		} break; @@ -3014,8 +3014,13 @@ void EditorNode::_update_file_menu_opened() {  	close_scene_sc->set_name(TTR("Close Scene"));  	Ref<Shortcut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene");  	reopen_closed_scene_sc->set_name(TTR("Reopen Closed Scene")); +  	PopupMenu *pop = file_menu->get_popup();  	pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty()); + +	const UndoRedo &undo_redo = editor_data.get_undo_redo(); +	pop->set_item_disabled(pop->get_item_index(EDIT_UNDO), !undo_redo.has_undo()); +	pop->set_item_disabled(pop->get_item_index(EDIT_REDO), !undo_redo.has_redo());  }  void EditorNode::_update_file_menu_closed() { diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index a802afda0f..91f00deeaa 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -195,11 +195,11 @@ void EditorSpinSlider::_update_value_input_stylebox() {  	if (!value_input) {  		return;  	} +  	// Add a left margin to the stylebox to make the number align with the Label  	// when it's edited. The LineEdit "focus" stylebox uses the "normal" stylebox's  	// default margins. -	Ref<StyleBoxFlat> stylebox = -			EditorNode::get_singleton()->get_theme_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->duplicate(); +	Ref<StyleBox> stylebox = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))->duplicate();  	// EditorSpinSliders with a label have more space on the left, so add an  	// higher margin to match the location where the text begins.  	// The margin values below were determined by empirical testing. @@ -210,188 +210,197 @@ void EditorSpinSlider::_update_value_input_stylebox() {  		stylebox->set_default_margin(SIDE_LEFT, (get_label() != String() ? 23 : 16) * EDSCALE);  		stylebox->set_default_margin(SIDE_RIGHT, 0);  	} +  	value_input->add_theme_style_override("normal", stylebox);  } -void EditorSpinSlider::_notification(int p_what) { -	if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_OUT || -			p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN || -			p_what == NOTIFICATION_EXIT_TREE) { -		if (grabbing_spinner) { -			grabber->hide(); -			Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); -			grabbing_spinner = false; -			grabbing_spinner_attempt = false; -		} -	} -	if (p_what == NOTIFICATION_READY || p_what == NOTIFICATION_THEME_CHANGED) { -		_update_value_input_stylebox(); -	} +void EditorSpinSlider::_draw_spin_slider() { +	updown_offset = -1; -	if (p_what == NOTIFICATION_DRAW) { -		updown_offset = -1; +	RID ci = get_canvas_item(); +	bool rtl = is_layout_rtl(); +	Vector2 size = get_size(); -		RID ci = get_canvas_item(); -		bool rtl = is_layout_rtl(); -		Vector2 size = get_size(); +	Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); +	if (!flat) { +		draw_style_box(sb, Rect2(Vector2(), size)); +	} +	Ref<Font> font = get_theme_font(SNAME("font"), SNAME("LineEdit")); +	int font_size = get_theme_font_size(SNAME("font_size"), SNAME("LineEdit")); +	int sep_base = 4 * EDSCALE; +	int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better -		Ref<StyleBox> sb = get_theme_stylebox(SNAME("normal"), SNAME("LineEdit")); -		if (!flat) { -			draw_style_box(sb, Rect2(Vector2(), size)); -		} -		Ref<Font> font = get_theme_font(SNAME("font"), SNAME("LineEdit")); -		int font_size = get_theme_font_size(SNAME("font_size"), SNAME("LineEdit")); -		int sep_base = 4 * EDSCALE; -		int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better +	int label_width = font->get_string_size(label, font_size).width; +	int number_width = size.width - sb->get_minimum_size().width - label_width - sep; -		int label_width = font->get_string_size(label, font_size).width; -		int number_width = size.width - sb->get_minimum_size().width - label_width - sep; +	Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); -		Ref<Texture2D> updown = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); +	if (get_step() == 1) { +		number_width -= updown->get_width(); +	} -		if (get_step() == 1) { -			number_width -= updown->get_width(); -		} +	String numstr = get_text_value(); -		String numstr = get_text_value(); +	int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); -		int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); +	Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit")); +	Color lc; +	if (use_custom_label_color) { +		lc = custom_label_color; +	} else { +		lc = fc; +	} -		Color fc = get_theme_color(SNAME("font_color"), SNAME("LineEdit")); -		Color lc; -		if (use_custom_label_color) { -			lc = custom_label_color; +	if (flat && label != String()) { +		Color label_bg_color = get_theme_color(SNAME("dark_color_3"), SNAME("Editor")); +		if (rtl) { +			draw_rect(Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color);  		} else { -			lc = fc; +			draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color);  		} +	} -		if (flat && label != String()) { -			Color label_bg_color = get_theme_color(SNAME("dark_color_3"), SNAME("Editor")); -			if (rtl) { -				draw_rect(Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color); -			} else { -				draw_rect(Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)), label_bg_color); -			} -		} +	if (has_focus()) { +		Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit")); +		draw_style_box(focus, Rect2(Vector2(), size)); +	} + +	if (rtl) { +		draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HALIGN_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5)); +	} else { +		draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HALIGN_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5)); +	} -		if (has_focus()) { -			Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit")); -			draw_style_box(focus, Rect2(Vector2(), size)); +	int suffix_start = numstr.length(); +	RID num_rid = TS->create_shaped_text(); +	TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size); + +	float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep); +	Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs); +	const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid); +	int v_size = visual.size(); +	const TextServer::Glyph *glyphs = visual.ptr(); +	for (int i = 0; i < v_size; i++) { +		for (int j = 0; j < glyphs[i].repeat; j++) { +			if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) { +				Color color = fc; +				if (glyphs[i].start >= suffix_start) { +					color.a *= 0.4; +				} +				if (glyphs[i].font_rid != RID()) { +					TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); +				} else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { +					TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); +				} +			} +			text_ofs.x += glyphs[i].advance;  		} +	} +	TS->free(num_rid); +	if (get_step() == 1) { +		Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); +		int updown_vofs = (size.height - updown2->get_height()) / 2;  		if (rtl) { -			draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HALIGN_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5)); +			updown_offset = sb->get_margin(SIDE_LEFT);  		} else { -			draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HALIGN_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5)); +			updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();  		} - -		int suffix_start = numstr.length(); -		RID num_rid = TS->create_shaped_text(); -		TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size); - -		float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep); -		Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs); -		const Vector<TextServer::Glyph> visual = TS->shaped_text_get_glyphs(num_rid); -		int v_size = visual.size(); -		const TextServer::Glyph *glyphs = visual.ptr(); -		for (int i = 0; i < v_size; i++) { -			for (int j = 0; j < glyphs[i].repeat; j++) { -				if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) { -					Color color = fc; -					if (glyphs[i].start >= suffix_start) { -						color.a *= 0.4; -					} -					if (glyphs[i].font_rid != RID()) { -						TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); -					} else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { -						TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); -					} -				} -				text_ofs.x += glyphs[i].advance; -			} +		Color c(1, 1, 1); +		if (hover_updown) { +			c *= Color(1.2, 1.2, 1.2);  		} -		TS->free(num_rid); - -		if (get_step() == 1) { -			Ref<Texture2D> updown2 = get_theme_icon(SNAME("updown"), SNAME("SpinBox")); -			int updown_vofs = (size.height - updown2->get_height()) / 2; -			if (rtl) { -				updown_offset = sb->get_margin(SIDE_LEFT); +		draw_texture(updown2, Vector2(updown_offset, updown_vofs), c); +		if (grabber->is_visible()) { +			grabber->hide(); +		} +	} else if (!hide_slider) { +		int grabber_w = 4 * EDSCALE; +		int width = size.width - sb->get_minimum_size().width - grabber_w; +		int ofs = sb->get_offset().x; +		int svofs = (size.height + vofs) / 2 - 1; +		Color c = fc; +		c.a = 0.2; + +		draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c); +		int gofs = get_as_ratio() * width; +		c.a = 0.9; +		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 && value_input_popup->is_visible()); +		if (grabber->is_visible() != display_grabber) { +			if (display_grabber) { +				grabber->show();  			} else { -				updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width(); -			} -			Color c(1, 1, 1); -			if (hover_updown) { -				c *= Color(1.2, 1.2, 1.2); -			} -			draw_texture(updown2, Vector2(updown_offset, updown_vofs), c); -			if (grabber->is_visible()) {  				grabber->hide();  			} -		} else if (!hide_slider) { -			int grabber_w = 4 * EDSCALE; -			int width = size.width - sb->get_minimum_size().width - grabber_w; -			int ofs = sb->get_offset().x; -			int svofs = (size.height + vofs) / 2 - 1; -			Color c = fc; -			c.a = 0.2; - -			draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c); -			int gofs = get_as_ratio() * width; -			c.a = 0.9; -			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 && value_input_popup->is_visible()); -			if (grabber->is_visible() != display_grabber) { -				if (display_grabber) { -					grabber->show(); -				} else { -					grabber->hide(); -				} -			} - -			if (display_grabber) { -				Ref<Texture2D> grabber_tex; -				if (mouse_over_grabber) { -					grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider")); -				} else { -					grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider")); -				} +		} -				if (grabber->get_texture() != grabber_tex) { -					grabber->set_texture(grabber_tex); -				} +		if (display_grabber) { +			Ref<Texture2D> grabber_tex; +			if (mouse_over_grabber) { +				grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider")); +			} else { +				grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider")); +			} -				Vector2 scale = get_global_transform_with_canvas().get_scale(); -				grabber->set_scale(scale); -				grabber->set_size(Size2(0, 0)); -				grabber->set_position(get_global_position() + (grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5) * scale); +			if (grabber->get_texture() != grabber_tex) { +				grabber->set_texture(grabber_tex); +			} -				if (mousewheel_over_grabber) { -					Input::get_singleton()->warp_mouse_position(grabber->get_position() + grabber_rect.size); -				} +			Vector2 scale = get_global_transform_with_canvas().get_scale(); +			grabber->set_scale(scale); +			grabber->set_size(Size2(0, 0)); +			grabber->set_position(get_global_position() + (grabber_rect.position + grabber_rect.size * 0.5 - grabber->get_size() * 0.5) * scale); -				grabber_range = width; +			if (mousewheel_over_grabber) { +				Input::get_singleton()->warp_mouse_position(grabber->get_position() + grabber_rect.size);  			} + +			grabber_range = width;  		}  	} +} -	if (p_what == NOTIFICATION_MOUSE_ENTER) { -		mouse_over_spin = true; -		update(); -	} -	if (p_what == NOTIFICATION_MOUSE_EXIT) { -		mouse_over_spin = false; -		update(); -	} -	if (p_what == NOTIFICATION_FOCUS_ENTER) { -		if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && !value_input_just_closed) { -			_focus_entered(); -		} -		value_input_just_closed = false; +void EditorSpinSlider::_notification(int p_what) { +	switch (p_what) { +		case NOTIFICATION_ENTER_TREE: +		case NOTIFICATION_THEME_CHANGED: +			_update_value_input_stylebox(); +			break; + +		case NOTIFICATION_DRAW: +			_draw_spin_slider(); +			break; + +		case NOTIFICATION_WM_WINDOW_FOCUS_IN: +		case NOTIFICATION_WM_WINDOW_FOCUS_OUT: +		case NOTIFICATION_EXIT_TREE: +			if (grabbing_spinner) { +				grabber->hide(); +				Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); +				grabbing_spinner = false; +				grabbing_spinner_attempt = false; +			} +			break; + +		case NOTIFICATION_MOUSE_ENTER: +			mouse_over_spin = true; +			update(); +			break; +		case NOTIFICATION_MOUSE_EXIT: +			mouse_over_spin = false; +			update(); +			break; +		case NOTIFICATION_FOCUS_ENTER: +			if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && !value_input_just_closed) { +				_focus_entered(); +			} +			value_input_just_closed = false; +			break;  	}  } @@ -567,8 +576,10 @@ void EditorSpinSlider::_ensure_input_popup() {  	if (value_input_popup) {  		return;  	} +  	value_input_popup = memnew(Popup);  	add_child(value_input_popup); +  	value_input = memnew(LineEdit);  	value_input_popup->add_child(value_input);  	value_input_popup->set_wrap_controls(true); @@ -576,6 +587,7 @@ void EditorSpinSlider::_ensure_input_popup() {  	value_input_popup->connect("popup_hide", callable_mp(this, &EditorSpinSlider::_value_input_closed));  	value_input->connect("text_submitted", callable_mp(this, &EditorSpinSlider::_value_input_submitted));  	value_input->connect("focus_exited", callable_mp(this, &EditorSpinSlider::_value_focus_exited)); +  	if (is_inside_tree()) {  		_update_value_input_stylebox();  	} diff --git a/editor/editor_spin_slider.h b/editor/editor_spin_slider.h index 5b99f88505..c09d084e88 100644 --- a/editor/editor_spin_slider.h +++ b/editor/editor_spin_slider.h @@ -81,6 +81,7 @@ class EditorSpinSlider : public Range {  	void _update_value_input_stylebox();  	void _ensure_input_popup(); +	void _draw_spin_slider();  protected:  	void _notification(int p_what); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 2c83fb2df6..7f8229e5e8 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -595,6 +595,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {  	theme->set_stylebox("panel", "PanelContainer", style_menu);  	theme->set_stylebox("MenuPanel", "EditorStyles", style_menu); +	// CanvasItem Editor +	Ref<StyleBoxFlat> style_canvas_editor_info = make_flat_stylebox(Color(0.0, 0.0, 0.0, 0.2)); +	style_canvas_editor_info->set_expand_margin_size_all(4 * EDSCALE); +	theme->set_stylebox("CanvasItemInfoOverlay", "EditorStyles", style_canvas_editor_info); +  	// Script Editor  	theme->set_stylebox("ScriptEditorPanel", "EditorStyles", make_empty_stylebox(default_margin_size, 0, default_margin_size, default_margin_size));  	theme->set_stylebox("ScriptEditor", "EditorStyles", make_empty_stylebox(0, 0, 0, 0)); @@ -1499,15 +1504,14 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {  }  Ref<Theme> create_custom_theme(const Ref<Theme> p_theme) { -	Ref<Theme> theme; - -	const String custom_theme = EditorSettings::get_singleton()->get("interface/theme/custom_theme"); -	if (custom_theme != "") { -		theme = ResourceLoader::load(custom_theme); -	} +	Ref<Theme> theme = create_editor_theme(p_theme); -	if (!theme.is_valid()) { -		theme = create_editor_theme(p_theme); +	const String custom_theme_path = EditorSettings::get_singleton()->get("interface/theme/custom_theme"); +	if (custom_theme_path != "") { +		Ref<Theme> custom_theme = ResourceLoader::load(custom_theme_path); +		if (custom_theme.is_valid()) { +			theme->merge_with(custom_theme); +		}  	}  	return theme; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 76c056ed33..477e066e87 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -3909,6 +3909,11 @@ void CanvasItemEditor::_notification(int p_what) {  		anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE);  		anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + +		info_overlay->get_theme()->set_stylebox("normal", "Label", get_theme_stylebox(SNAME("CanvasItemInfoOverlay"), SNAME("EditorStyles"))); +		warning_child_of_container->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); +		warning_child_of_container->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts"))); +		warning_child_of_container->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")));  	}  	if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { @@ -5280,21 +5285,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {  	info_overlay->add_theme_constant_override("separation", 10);  	viewport_scrollable->add_child(info_overlay); +	// Make sure all labels inside of the container are styled the same.  	Theme *info_overlay_theme = memnew(Theme); -	info_overlay_theme->copy_default_theme();  	info_overlay->set_theme(info_overlay_theme); -	StyleBoxFlat *info_overlay_label_stylebox = memnew(StyleBoxFlat); -	info_overlay_label_stylebox->set_bg_color(Color(0.0, 0.0, 0.0, 0.2)); -	info_overlay_label_stylebox->set_expand_margin_size_all(4); -	info_overlay_theme->set_stylebox("normal", "Label", info_overlay_label_stylebox); -  	warning_child_of_container = memnew(Label);  	warning_child_of_container->hide();  	warning_child_of_container->set_text(TTR("Warning: Children of a container get their position and size determined only by their parent.")); -	warning_child_of_container->add_theme_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); -	warning_child_of_container->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("main"), SNAME("EditorFonts"))); -	warning_child_of_container->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts")));  	add_control_to_info_overlay(warning_child_of_container);  	h_scroll = memnew(HScrollBar); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index d29c1efabb..868b834993 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -532,7 +532,7 @@ private:  	bool grid_enable[3]; //should be always visible if true  	bool grid_enabled;  	bool grid_init_draw = false; -	Camera3D::Projection grid_camera_last_update_perspective; +	Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE;  	Vector3 grid_camera_last_update_position = Vector3();  	Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3]; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index d3f07ab39a..7f2908eada 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1630,6 +1630,13 @@ void ScriptTextEditor::_color_changed(const Color &p_color) {  	code_editor->get_text_editor()->update();  } +void ScriptTextEditor::_prepare_edit_menu() { +	const CodeEdit *tx = code_editor->get_text_editor(); +	PopupMenu *popup = edit_menu->get_popup(); +	popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); +	popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); +} +  void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos) {  	context_menu->clear();  	context_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); @@ -1669,6 +1676,10 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p  		}  	} +	const CodeEdit *tx = code_editor->get_text_editor(); +	context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); +	context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); +  	context_menu->set_position(get_global_transform().xform(p_pos));  	context_menu->set_size(Vector2(1, 1));  	context_menu->popup(); @@ -1754,6 +1765,7 @@ void ScriptTextEditor::_enable_code_editor() {  	search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));  	edit_hb->add_child(edit_menu); +	edit_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_prepare_edit_menu));  	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO);  	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO);  	edit_menu->get_popup()->add_separator(); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index e4a13951e4..1ca6f56ea1 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -178,6 +178,7 @@ protected:  	void _make_context_menu(bool p_selection, bool p_color, bool p_foldable, bool p_open_docs, bool p_goto_definition, Vector2 p_pos);  	void _text_edit_gui_input(const Ref<InputEvent> &ev);  	void _color_changed(const Color &p_color); +	void _prepare_edit_menu();  	void _goto_line(int p_line) { goto_line(p_line); }  	void _lookup_symbol(const String &p_symbol, int p_row, int p_column); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 481eb84081..32bcc1a4e6 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -471,6 +471,13 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) {  	}  } +void TextEditor::_prepare_edit_menu() { +	const CodeEdit *tx = code_editor->get_text_editor(); +	PopupMenu *popup = edit_menu->get_popup(); +	popup->set_item_disabled(popup->get_item_index(EDIT_UNDO), !tx->has_undo()); +	popup->set_item_disabled(popup->get_item_index(EDIT_REDO), !tx->has_redo()); +} +  void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position) {  	context_menu->clear();  	if (p_selection) { @@ -497,6 +504,10 @@ void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is  		context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE);  	} +	const CodeEdit *tx = code_editor->get_text_editor(); +	context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); +	context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); +  	context_menu->set_position(get_global_transform().xform(p_position));  	context_menu->set_size(Vector2(1, 1));  	context_menu->popup(); @@ -542,6 +553,7 @@ TextEditor::TextEditor() {  	edit_hb->add_child(edit_menu);  	edit_menu->set_text(TTR("Edit"));  	edit_menu->set_switch_on_hover(true); +	edit_menu->connect("about_to_popup", callable_mp(this, &TextEditor::_prepare_edit_menu));  	edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option));  	edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 86a4910ac0..839e1c5f7a 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -92,6 +92,7 @@ protected:  	void _edit_option(int p_op);  	void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position);  	void _text_edit_gui_input(const Ref<InputEvent> &ev); +	void _prepare_edit_menu();  	Map<String, Ref<EditorSyntaxHighlighter>> highlighters;  	void _change_syntax_highlighter(int p_idx); diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index 83b0203f32..e7ba80677d 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -1206,11 +1206,10 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope  	}  	tree->connect("cell_selected", callable_mp(this, &SceneTreeEditor::_selected_changed)); -	tree->connect("item_edited", callable_mp(this, &SceneTreeEditor::_renamed), varray(), CONNECT_DEFERRED); +	tree->connect("item_edited", callable_mp(this, &SceneTreeEditor::_renamed));  	tree->connect("multi_selected", callable_mp(this, &SceneTreeEditor::_cell_multi_selected));  	tree->connect("button_pressed", callable_mp(this, &SceneTreeEditor::_cell_button_pressed));  	tree->connect("nothing_selected", callable_mp(this, &SceneTreeEditor::_deselect_items)); -	//tree->connect("item_edited", this,"_renamed",Vector<Variant>(),true);  	error = memnew(AcceptDialog);  	add_child(error); diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 1a844bf241..07f50d14dc 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -200,7 +200,9 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro  	if (singleton->full_gdscript_cache.has(p_path)) {  		return singleton->full_gdscript_cache[p_path];  	} +  	Ref<GDScript> script = get_shallow_script(p_path); +	ERR_FAIL_COND_V(script.is_null(), Ref<GDScript>());  	r_error = script->load_source_code(p_path); diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp index b9004c4989..e03375e8d9 100644 --- a/platform/android/android_input_handler.cpp +++ b/platform/android/android_input_handler.cpp @@ -377,7 +377,7 @@ MouseButton AndroidInputHandler::_android_button_mask_to_godot_button_mask(int a  	if (android_button_mask & AMOTION_EVENT_BUTTON_BACK) {  		godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON1;  	} -	if (android_button_mask & AMOTION_EVENT_BUTTON_SECONDARY) { +	if (android_button_mask & AMOTION_EVENT_BUTTON_FORWARD) {  		godot_button_mask |= MOUSE_BUTTON_MASK_XBUTTON2;  	} diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 17ee173855..8ed2d4e60a 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -717,6 +717,10 @@ Error EditorExportPlatformAndroid::copy_gradle_so(void *p_userdata, const Shared  	return OK;  } +bool EditorExportPlatformAndroid::_has_storage_permission(const Vector<String> &p_permissions) { +	return p_permissions.find("android.permission.READ_EXTERNAL_STORAGE") != -1 || p_permissions.find("android.permission.WRITE_EXTERNAL_STORAGE") != -1; +} +  void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {  	const char **aperms = android_perms;  	while (*aperms) { @@ -763,12 +767,17 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres  	Vector<String> perms;  	_get_permissions(p_preset, p_give_internet, perms);  	for (int i = 0; i < perms.size(); i++) { -		manifest_text += vformat("    <uses-permission android:name=\"%s\" />\n", perms.get(i)); +		String permission = perms.get(i); +		if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") { +			manifest_text += vformat("    <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission); +		} else { +			manifest_text += vformat("    <uses-permission android:name=\"%s\" />\n", permission); +		}  	}  	manifest_text += _get_xr_features_tag(p_preset);  	manifest_text += _get_instrumentation_tag(p_preset); -	manifest_text += _get_application_tag(p_preset); +	manifest_text += _get_application_tag(p_preset, _has_storage_permission(perms));  	manifest_text += "</manifest>\n";  	String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")); @@ -824,6 +833,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p  	Vector<String> perms;  	// Write permissions into the perms variable.  	_get_permissions(p_preset, p_give_internet, perms); +	bool has_storage_permission = _has_storage_permission(perms);  	while (ofs < (uint32_t)p_manifest.size()) {  		uint32_t chunk = decode_uint32(&p_manifest[ofs]); @@ -913,6 +923,10 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p  						}  					} +					if (tname == "application" && attrname == "requestLegacyExternalStorage") { +						encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]); +					} +  					if (tname == "application" && attrname == "allowBackup") {  						encode_uint32(backup_allowed, &p_manifest.write[iofs + 16]);  					} diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 909428c2fe..b061ee4e04 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -134,6 +134,8 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {  	static Error copy_gradle_so(void *p_userdata, const SharedObject &p_so); +	bool _has_storage_permission(const Vector<String> &p_permissions); +  	void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions);  	void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug); diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp index 76512226bf..b9e28a7937 100644 --- a/platform/android/export/gradle_export_util.cpp +++ b/platform/android/export/gradle_export_util.cpp @@ -235,18 +235,20 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {  	return manifest_activity_text;  } -String _get_application_tag(const Ref<EditorExportPreset> &p_preset) { +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission) {  	String manifest_application_text = vformat(  			"    <application android:label=\"@string/godot_project_name_string\"\n"  			"        android:allowBackup=\"%s\"\n"  			"        android:icon=\"@mipmap/icon\"\n"  			"        android:isGame=\"%s\"\n"  			"        android:hasFragileUserData=\"%s\"\n" -			"        tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData\"\n" +			"        android:requestLegacyExternalStorage=\"%s\"\n" +			"        tools:replace=\"android:allowBackup,android:isGame,android:hasFragileUserData,android:requestLegacyExternalStorage\"\n"  			"        tools:ignore=\"GoogleAppIndexingWarning\">\n\n",  			bool_to_string(p_preset->get("user_data_backup/allow")),  			bool_to_string(p_preset->get("package/classify_as_game")), -			bool_to_string(p_preset->get("package/retain_data_on_uninstall"))); +			bool_to_string(p_preset->get("package/retain_data_on_uninstall")), +			bool_to_string(p_has_storage_permission));  	manifest_application_text += _get_activity_tag(p_preset);  	manifest_application_text += "    </application>\n"; diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h index 44e9a1727d..8a93c25d79 100644 --- a/platform/android/export/gradle_export_util.h +++ b/platform/android/export/gradle_export_util.h @@ -81,6 +81,6 @@ String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset);  String _get_activity_tag(const Ref<EditorExportPreset> &p_preset); -String _get_application_tag(const Ref<EditorExportPreset> &p_preset); +String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_storage_permission);  #endif //GODOT_GRADLE_EXPORT_UTIL_H diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml index 467a0dc3c0..00e01884cf 100644 --- a/platform/android/java/app/AndroidManifest.xml +++ b/platform/android/java/app/AndroidManifest.xml @@ -22,6 +22,7 @@          android:icon="@mipmap/icon"          android:isGame="true"          android:hasFragileUserData="false" +        android:requestLegacyExternalStorage="false"          tools:ignore="GoogleAppIndexingWarning" >          <!-- Records the version of the Godot editor used for building --> diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 81fc87b7ef..fad64c675f 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,8 +1,8 @@  ext.versions = [ -    androidGradlePlugin: '4.2.1', -    compileSdk         : 29, -    minSdk             : 18, -    targetSdk          : 29, +    androidGradlePlugin: '4.2.2', +    compileSdk         : 30, +    minSdk             : 19, +    targetSdk          : 30,      buildTools         : '30.0.3',      supportCoreUtils   : '1.0.0',      kotlinVersion      : '1.5.10', diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index 76751a886c..317175858b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -49,7 +49,6 @@ import android.content.ClipData;  import android.content.ClipboardManager;  import android.content.ComponentName;  import android.content.Context; -import android.content.DialogInterface;  import android.content.Intent;  import android.content.SharedPreferences;  import android.content.SharedPreferences.Editor; @@ -68,15 +67,12 @@ import android.os.Environment;  import android.os.Messenger;  import android.os.VibrationEffect;  import android.os.Vibrator; -import android.provider.Settings.Secure;  import android.view.Display; -import android.view.KeyEvent;  import android.view.LayoutInflater;  import android.view.Surface;  import android.view.View;  import android.view.ViewGroup;  import android.view.ViewGroup.LayoutParams; -import android.view.ViewTreeObserver;  import android.view.Window;  import android.view.WindowManager;  import android.widget.Button; @@ -471,7 +467,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC  		final Activity activity = getActivity();  		io = new GodotIO(activity); -		io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);  		GodotLib.io = io;  		netUtils = new GodotNetUtils(activity);  		mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index 66882e8e72..d85d88ec6c 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -30,15 +30,19 @@  package org.godotengine.godot; -import org.godotengine.godot.input.*; +import org.godotengine.godot.input.GodotEditText;  import android.app.Activity; -import android.content.*; +import android.content.ActivityNotFoundException; +import android.content.Intent;  import android.content.pm.ActivityInfo;  import android.content.res.AssetManager;  import android.graphics.Point;  import android.net.Uri; -import android.os.*; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; +import android.text.TextUtils;  import android.util.DisplayMetrics;  import android.util.Log;  import android.util.SparseArray; @@ -47,14 +51,16 @@ import android.view.DisplayCutout;  import android.view.WindowInsets;  import java.io.IOException; -import java.io.InputStream;  import java.util.Locale;  // Wrapper for native library  public class GodotIO { -	AssetManager am; -	final Activity activity; +	private static final String TAG = GodotIO.class.getSimpleName(); + +	private final AssetManager am; +	private final Activity activity; +	private final String uniqueId;  	GodotEditText edit;  	final int SCREEN_LANDSCAPE = 0; @@ -66,167 +72,6 @@ public class GodotIO {  	final int SCREEN_SENSOR = 6;  	///////////////////////// -	/// FILES -	///////////////////////// - -	public int last_file_id = 1; - -	static class AssetData { -		public boolean eof = false; -		public String path; -		public InputStream is; -		public int len; -		public int pos; -	} - -	SparseArray<AssetData> streams; - -	public int file_open(String path, boolean write) { -		//System.out.printf("file_open: Attempt to Open %s\n",path); - -		//Log.v("MyApp", "TRYING TO OPEN FILE: " + path); -		if (write) -			return -1; - -		AssetData ad = new AssetData(); - -		try { -			ad.is = am.open(path); - -		} catch (Exception e) { -			//System.out.printf("Exception on file_open: %s\n",path); -			return -1; -		} - -		try { -			ad.len = ad.is.available(); -		} catch (Exception e) { -			System.out.printf("Exception availabling on file_open: %s\n", path); -			return -1; -		} - -		ad.path = path; -		ad.pos = 0; -		++last_file_id; -		streams.put(last_file_id, ad); - -		return last_file_id; -	} -	public int file_get_size(int id) { -		if (streams.get(id) == null) { -			System.out.printf("file_get_size: Invalid file id: %d\n", id); -			return -1; -		} - -		return streams.get(id).len; -	} -	public void file_seek(int id, int bytes) { -		if (streams.get(id) == null) { -			System.out.printf("file_get_size: Invalid file id: %d\n", id); -			return; -		} -		//seek sucks -		AssetData ad = streams.get(id); -		if (bytes > ad.len) -			bytes = ad.len; -		if (bytes < 0) -			bytes = 0; - -		try { -			if (bytes > (int)ad.pos) { -				int todo = bytes - (int)ad.pos; -				while (todo > 0) { -					todo -= ad.is.skip(todo); -				} -				ad.pos = bytes; -			} else if (bytes < (int)ad.pos) { -				ad.is = am.open(ad.path); - -				ad.pos = bytes; -				int todo = bytes; -				while (todo > 0) { -					todo -= ad.is.skip(todo); -				} -			} - -			ad.eof = false; -		} catch (IOException e) { -			System.out.printf("Exception on file_seek: %s\n", e); -			return; -		} -	} - -	public int file_tell(int id) { -		if (streams.get(id) == null) { -			System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id); -			return 0; -		} - -		AssetData ad = streams.get(id); -		return ad.pos; -	} -	public boolean file_eof(int id) { -		if (streams.get(id) == null) { -			System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id); -			return false; -		} - -		AssetData ad = streams.get(id); -		return ad.eof; -	} - -	public byte[] file_read(int id, int bytes) { -		if (streams.get(id) == null) { -			System.out.printf("file_read: Can't read invalid file id: %d\n", id); -			return new byte[0]; -		} - -		AssetData ad = streams.get(id); - -		if (ad.pos + bytes > ad.len) { -			bytes = ad.len - ad.pos; -			ad.eof = true; -		} - -		if (bytes == 0) { -			return new byte[0]; -		} - -		byte[] buf1 = new byte[bytes]; -		int r = 0; -		try { -			r = ad.is.read(buf1); -		} catch (IOException e) { -			System.out.printf("Exception on file_read: %s\n", e); -			return new byte[bytes]; -		} - -		if (r == 0) { -			return new byte[0]; -		} - -		ad.pos += r; - -		if (r < bytes) { -			byte[] buf2 = new byte[r]; -			for (int i = 0; i < r; i++) -				buf2[i] = buf1[i]; -			return buf2; -		} else { -			return buf1; -		} -	} - -	public void file_close(int id) { -		if (streams.get(id) == null) { -			System.out.printf("file_close: Can't close invalid file id: %d\n", id); -			return; -		} - -		streams.remove(id); -	} - -	/////////////////////////  	/// DIRECTORIES  	///////////////////////// @@ -236,9 +81,9 @@ public class GodotIO {  		public String path;  	} -	public int last_dir_id = 1; +	private int last_dir_id = 1; -	SparseArray<AssetDir> dirs; +	private final SparseArray<AssetDir> dirs;  	public int dir_open(String path) {  		AssetDir ad = new AssetDir(); @@ -257,7 +102,6 @@ public class GodotIO {  			return -1;  		} -		//System.out.printf("Opened dir: %s\n",path);  		++last_dir_id;  		dirs.put(last_dir_id, ad); @@ -320,9 +164,14 @@ public class GodotIO {  	GodotIO(Activity p_activity) {  		am = p_activity.getAssets();  		activity = p_activity; -		//streams = new HashMap<Integer, AssetData>(); -		streams = new SparseArray<>();  		dirs = new SparseArray<>(); +		String androidId = Settings.Secure.getString(activity.getContentResolver(), +				Settings.Secure.ANDROID_ID); +		if (androidId == null) { +			androidId = ""; +		} + +		uniqueId = androidId;  	}  	///////////////////////// @@ -331,7 +180,6 @@ public class GodotIO {  	public int openURI(String p_uri) {  		try { -			Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);  			String path = p_uri;  			String type = "";  			if (path.startsWith("/")) { @@ -357,12 +205,12 @@ public class GodotIO {  		}  	} -	public String getDataDir() { -		return activity.getFilesDir().getAbsolutePath(); +	public String getCacheDir() { +		return activity.getCacheDir().getAbsolutePath();  	} -	public String getExternalDataDir() { -		return activity.getExternalFilesDir(null).getAbsolutePath(); +	public String getDataDir() { +		return activity.getFilesDir().getAbsolutePath();  	}  	public String getLocale() { @@ -456,51 +304,58 @@ public class GodotIO {  	public static final int SYSTEM_DIR_PICTURES = 6;  	public static final int SYSTEM_DIR_RINGTONES = 7; -	public String getSystemDir(int idx) { -		String what = ""; +	public String getSystemDir(int idx, boolean shared_storage) { +		String what;  		switch (idx) { -			case SYSTEM_DIR_DESKTOP: { -				//what=Environment.DIRECTORY_DOCUMENTS; -				what = Environment.DIRECTORY_DOWNLOADS; +			case SYSTEM_DIR_DESKTOP: +			default: { +				what = null; // This leads to the app specific external root directory.  			} break; +  			case SYSTEM_DIR_DCIM: {  				what = Environment.DIRECTORY_DCIM; -  			} break; +  			case SYSTEM_DIR_DOCUMENTS: { -				what = Environment.DIRECTORY_DOWNLOADS; -				//what=Environment.DIRECTORY_DOCUMENTS; +				what = Environment.DIRECTORY_DOCUMENTS;  			} break; +  			case SYSTEM_DIR_DOWNLOADS: {  				what = Environment.DIRECTORY_DOWNLOADS; -  			} break; +  			case SYSTEM_DIR_MOVIES: {  				what = Environment.DIRECTORY_MOVIES; -  			} break; +  			case SYSTEM_DIR_MUSIC: {  				what = Environment.DIRECTORY_MUSIC;  			} break; +  			case SYSTEM_DIR_PICTURES: {  				what = Environment.DIRECTORY_PICTURES;  			} break; +  			case SYSTEM_DIR_RINGTONES: {  				what = Environment.DIRECTORY_RINGTONES; -  			} break;  		} -		if (what.equals("")) -			return ""; -		return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath(); +		if (shared_storage) { +			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { +				Log.w(TAG, "Shared storage access is limited on Android 10 and higher."); +			} +			if (TextUtils.isEmpty(what)) { +				return Environment.getExternalStorageDirectory().getAbsolutePath(); +			} else { +				return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath(); +			} +		} else { +			return activity.getExternalFilesDir(what).getAbsolutePath(); +		}  	} -	protected static final String PREFS_FILE = "device_id.xml"; -	protected static final String PREFS_DEVICE_ID = "device_id"; - -	public static String unique_id = "";  	public String getUniqueID() { -		return unique_id; +		return uniqueId;  	}  } diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp index 5e99135498..5cd2c382d2 100644 --- a/platform/android/java_godot_io_wrapper.cpp +++ b/platform/android/java_godot_io_wrapper.cpp @@ -48,8 +48,8 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc  		}  		_open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I"); +		_get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;");  		_get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;"); -		_get_external_data_dir = p_env->GetMethodID(cls, "getExternalDataDir", "()Ljava/lang/String;");  		_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");  		_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");  		_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I"); @@ -59,7 +59,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc  		_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");  		_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");  		_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I"); -		_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;"); +		_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");  	}  } @@ -82,22 +82,22 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {  	}  } -String GodotIOJavaWrapper::get_user_data_dir() { -	if (_get_data_dir) { +String GodotIOJavaWrapper::get_cache_dir() { +	if (_get_cache_dir) {  		JNIEnv *env = get_jni_env();  		ERR_FAIL_COND_V(env == nullptr, String()); -		jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir); +		jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir);  		return jstring_to_string(s, env);  	} else {  		return String();  	}  } -String GodotIOJavaWrapper::get_external_data_dir() { -	if (_get_external_data_dir) { +String GodotIOJavaWrapper::get_user_data_dir() { +	if (_get_data_dir) {  		JNIEnv *env = get_jni_env();  		ERR_FAIL_COND_V(env == nullptr, String()); -		jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_external_data_dir); +		jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_data_dir);  		return jstring_to_string(s, env);  	} else {  		return String(); @@ -200,11 +200,11 @@ int GodotIOJavaWrapper::get_screen_orientation() {  	}  } -String GodotIOJavaWrapper::get_system_dir(int p_dir) { +String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {  	if (_get_system_dir) {  		JNIEnv *env = get_jni_env();  		ERR_FAIL_COND_V(env == nullptr, String(".")); -		jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir); +		jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage);  		return jstring_to_string(s, env);  	} else {  		return String("."); diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h index e4c0a4b2c7..8f6d7f813f 100644 --- a/platform/android/java_godot_io_wrapper.h +++ b/platform/android/java_godot_io_wrapper.h @@ -46,8 +46,8 @@ private:  	jclass cls;  	jmethodID _open_URI = 0; +	jmethodID _get_cache_dir = 0;  	jmethodID _get_data_dir = 0; -	jmethodID _get_external_data_dir = 0;  	jmethodID _get_locale = 0;  	jmethodID _get_model = 0;  	jmethodID _get_screen_DPI = 0; @@ -66,8 +66,8 @@ public:  	jobject get_instance();  	Error open_uri(const String &p_uri); +	String get_cache_dir();  	String get_user_data_dir(); -	String get_external_data_dir();  	String get_locale();  	String get_model();  	int get_screen_dpi(); @@ -80,7 +80,7 @@ public:  	void set_vk_height(int p_height);  	void set_screen_orientation(int p_orient);  	int get_screen_orientation(); -	String get_system_dir(int p_dir); +	String get_system_dir(int p_dir, bool p_shared_storage);  };  #endif /* !JAVA_GODOT_IO_WRAPPER_H */ diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index c2e12442b3..21fb31d991 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -213,6 +213,10 @@ String OS_Android::get_model_name() const {  	return OS_Unix::get_model_name();  } +String OS_Android::get_data_path() const { +	return get_user_data_dir(); +} +  String OS_Android::get_user_data_dir() const {  	if (data_dir_cache != String())  		return data_dir_cache; @@ -225,11 +229,11 @@ String OS_Android::get_user_data_dir() const {  	return ".";  } -String OS_Android::get_external_data_dir() const { -	String data_dir = godot_io_java->get_external_data_dir(); -	if (data_dir != "") { -		data_dir = _remove_symlink(data_dir); -		return data_dir; +String OS_Android::get_cache_path() const { +	String cache_dir = godot_io_java->get_cache_dir(); +	if (cache_dir != "") { +		cache_dir = _remove_symlink(cache_dir); +		return cache_dir;  	}  	return ".";  } @@ -242,8 +246,8 @@ String OS_Android::get_unique_id() const {  	return OS::get_unique_id();  } -String OS_Android::get_system_dir(SystemDir p_dir) const { -	return godot_io_java->get_system_dir(p_dir); +String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const { +	return godot_io_java->get_system_dir(p_dir, p_shared_storage);  }  void OS_Android::set_display_size(const Size2i &p_size) { diff --git a/platform/android/os_android.h b/platform/android/os_android.h index a5b995a775..c938297821 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -110,14 +110,15 @@ public:  	virtual Error shell_open(String p_uri) override;  	virtual String get_user_data_dir() const override; -	virtual String get_external_data_dir() const override; +	virtual String get_data_path() const override; +	virtual String get_cache_path() const override;  	virtual String get_resource_dir() const override;  	virtual String get_locale() const override;  	virtual String get_model_name() const override;  	virtual String get_unique_id() const override; -	virtual String get_system_dir(SystemDir p_dir) const override; +	virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;  	void vibrate_handheld(int p_duration_ms) override; diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 08630be8b0..2c9801f512 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -273,7 +273,7 @@ String OS_LinuxBSD::get_cache_path() const {  	}  } -String OS_LinuxBSD::get_system_dir(SystemDir p_dir) const { +String OS_LinuxBSD::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {  	String xdgparam;  	switch (p_dir) { diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index 1e06587322..35c80e3f9b 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -84,7 +84,7 @@ public:  	virtual String get_data_path() const override;  	virtual String get_cache_path() const override; -	virtual String get_system_dir(SystemDir p_dir) const override; +	virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;  	virtual Error shell_open(String p_uri) override; diff --git a/platform/osx/os_osx.h b/platform/osx/os_osx.h index 37d30add78..df41ccd892 100644 --- a/platform/osx/os_osx.h +++ b/platform/osx/os_osx.h @@ -84,7 +84,7 @@ public:  	virtual String get_bundle_resource_dir() const override;  	virtual String get_godot_dir_name() const override; -	virtual String get_system_dir(SystemDir p_dir) const override; +	virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;  	Error shell_open(String p_uri) override; diff --git a/platform/osx/os_osx.mm b/platform/osx/os_osx.mm index c458a0264a..c6e35fee83 100644 --- a/platform/osx/os_osx.mm +++ b/platform/osx/os_osx.mm @@ -395,7 +395,7 @@ String OS_OSX::get_godot_dir_name() const {  	return String(VERSION_SHORT_NAME).capitalize();  } -String OS_OSX::get_system_dir(SystemDir p_dir) const { +String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {  	NSSearchPathDirectory id;  	bool found = true; diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 2c8afaf7de..2a0a509d43 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -681,7 +681,7 @@ String OS_Windows::get_godot_dir_name() const {  	return String(VERSION_SHORT_NAME).capitalize();  } -String OS_Windows::get_system_dir(SystemDir p_dir) const { +String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {  	KNOWNFOLDERID id;  	switch (p_dir) { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index ea0c263b78..c4a2eda8f4 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -150,7 +150,7 @@ public:  	virtual String get_cache_path() const override;  	virtual String get_godot_dir_name() const override; -	virtual String get_system_dir(SystemDir p_dir) const override; +	virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;  	virtual String get_user_data_dir() const override;  	virtual String get_unique_id() const override; diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp index 00da94c3b0..dd1a4671d9 100644 --- a/scene/2d/physics_body_2d.cpp +++ b/scene/2d/physics_body_2d.cpp @@ -1104,14 +1104,12 @@ bool CharacterBody2D::move_and_slide() {  	for (int iteration = 0; iteration < max_slides; ++iteration) {  		PhysicsServer2D::MotionResult result; -		bool found_collision = false;  		Vector2 prev_position = get_global_transform().elements[2];  		bool collided = move_and_collide(motion, result, margin, false, !sliding_enabled);  		if (collided) { -			found_collision = true;  			motion_results.push_back(result);  			_set_collision_direction(result); @@ -1136,7 +1134,7 @@ bool CharacterBody2D::move_and_slide() {  			// Move on floor only checks.  			if (floor_block_on_wall && on_wall && motion_slide_up.dot(result.collision_normal) <= 0) {  				// Avoid to move forward on a wall if floor_block_on_wall is true. -				if (was_on_floor && !is_on_floor_only() && !vel_dir_facing_up) { +				if (was_on_floor && !on_floor && !vel_dir_facing_up) {  					// If the movement is large the body can be prevented from reaching the walls.  					if (result.travel.length() <= margin) {  						// Cancels the motion. @@ -1155,7 +1153,7 @@ bool CharacterBody2D::move_and_slide() {  					break;  				}  				// Prevents the body from being able to climb a slope when it moves forward against the wall. -				else if (!is_on_floor_only()) { +				else if (!on_floor) {  					motion = up_direction * up_direction.dot(result.remainder);  					motion = motion.slide(result.collision_normal);  				} else { @@ -1166,9 +1164,7 @@ bool CharacterBody2D::move_and_slide() {  			else if (floor_constant_speed && is_on_floor_only() && can_apply_constant_speed && was_on_floor && motion.dot(result.collision_normal) < 0) {  				can_apply_constant_speed = false;  				Vector2 motion_slide_norm = result.remainder.slide(result.collision_normal).normalized(); -				if (!motion_slide_norm.is_equal_approx(Vector2())) { -					motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length()); -				} +				motion = motion_slide_norm * (motion_slide_up.length() - result.travel.slide(up_direction).length() - last_travel.slide(up_direction).length());  			}  			// Regular sliding, the last part of the test handle the case when you don't want to slide on the ceiling.  			else if ((sliding_enabled || !on_floor) && (!on_ceiling || slide_on_ceiling || !vel_dir_facing_up)) { @@ -1209,17 +1205,15 @@ bool CharacterBody2D::move_and_slide() {  			set_global_transform(gt);  			Vector2 motion_slide_norm = motion.slide(prev_floor_normal).normalized(); -			if (!motion_slide_norm.is_equal_approx(Vector2())) { -				motion = motion_slide_norm * (motion_slide_up.length()); -				found_collision = true; -			} +			motion = motion_slide_norm * (motion_slide_up.length()); +			collided = true;  		}  		can_apply_constant_speed = !can_apply_constant_speed && !sliding_enabled;  		sliding_enabled = true;  		first_slide = false; -		if (!found_collision || motion.is_equal_approx(Vector2())) { +		if (!collided || motion.is_equal_approx(Vector2())) {  			break;  		}  	} diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp index 610974ff90..092efc55d7 100644 --- a/scene/3d/physics_body_3d.cpp +++ b/scene/3d/physics_body_3d.cpp @@ -1176,11 +1176,6 @@ bool CharacterBody3D::move_and_slide() {  		}  	} -	if (!on_floor && !on_wall) { -		// Add last platform velocity when just left a moving platform. -		linear_velocity += current_floor_velocity; -	} -  	if (was_on_floor && snap != Vector3()) {  		// Apply snap.  		Transform3D gt = get_global_transform(); @@ -1213,6 +1208,11 @@ bool CharacterBody3D::move_and_slide() {  		}  	} +	if (!on_floor && !on_wall) { +		// Add last platform velocity when just left a moving platform. +		linear_velocity += current_floor_velocity; +	} +  	return motion_results.size() > 0;  } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 2b3be1d5c2..11e08b231e 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -946,6 +946,17 @@ void LineEdit::paste_text() {  	}  } +bool LineEdit::has_undo() const { +	if (undo_stack_pos == nullptr) { +		return undo_stack.size() > 1; +	} +	return undo_stack_pos != undo_stack.front(); +} + +bool LineEdit::has_redo() const { +	return undo_stack_pos != nullptr && undo_stack_pos != undo_stack.back(); +} +  void LineEdit::undo() {  	if (!editable) {  		return; @@ -2277,6 +2288,11 @@ void LineEdit::_ensure_menu() {  	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);  	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);  	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + +	if (editable) { +		menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); +		menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); +	}  }  LineEdit::LineEdit() { diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h index c5c92d60aa..0e9c032e88 100644 --- a/scene/gui/line_edit.h +++ b/scene/gui/line_edit.h @@ -285,6 +285,8 @@ public:  	void copy_text();  	void cut_text();  	void paste_text(); +	bool has_undo() const; +	bool has_redo() const;  	void undo();  	void redo(); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 12f0c9e89a..87f06445ac 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -2963,6 +2963,18 @@ void TextEdit::end_complex_operation() {  	undo_stack.back()->get().chain_backward = true;  } +bool TextEdit::has_undo() const { +	if (undo_stack_pos == nullptr) { +		int pending = current_op.type == TextOperation::TYPE_NONE ? 0 : 1; +		return undo_stack.size() + pending > 0; +	} +	return undo_stack_pos != undo_stack.front(); +} + +bool TextEdit::has_redo() const { +	return undo_stack_pos != nullptr; +} +  void TextEdit::undo() {  	if (!editable) {  		return; @@ -4482,6 +4494,8 @@ void TextEdit::_bind_methods() {  	ClassDB::bind_method(D_METHOD("begin_complex_operation"), &TextEdit::begin_complex_operation);  	ClassDB::bind_method(D_METHOD("end_complex_operation"), &TextEdit::end_complex_operation); +	ClassDB::bind_method(D_METHOD("has_undo"), &TextEdit::has_undo); +	ClassDB::bind_method(D_METHOD("has_redo"), &TextEdit::has_redo);  	ClassDB::bind_method(D_METHOD("undo"), &TextEdit::undo);  	ClassDB::bind_method(D_METHOD("redo"), &TextEdit::redo);  	ClassDB::bind_method(D_METHOD("clear_undo_history"), &TextEdit::clear_undo_history); @@ -5070,6 +5084,11 @@ void TextEdit::_generate_context_menu() {  	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_AUTO), text_direction == TEXT_DIRECTION_AUTO);  	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_LTR), text_direction == TEXT_DIRECTION_LTR);  	menu_dir->set_item_checked(menu_dir->get_item_index(MENU_DIR_RTL), text_direction == TEXT_DIRECTION_RTL); + +	if (editable) { +		menu->set_item_disabled(menu->get_item_index(MENU_UNDO), !has_undo()); +		menu->set_item_disabled(menu->get_item_index(MENU_REDO), !has_redo()); +	}  }  int TextEdit::_get_menu_action_accelerator(const String &p_action) { diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index da322a7bcd..69468978ab 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -659,6 +659,8 @@ public:  	void begin_complex_operation();  	void end_complex_operation(); +	bool has_undo() const; +	bool has_redo() const;  	void undo();  	void redo();  	void clear_undo_history(); diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index e4a731c7f7..e49d883ba4 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -1350,40 +1350,36 @@ void Theme::clear() {  	_emit_theme_changed();  } -void Theme::copy_default_theme() { -	Ref<Theme> default_theme2 = get_default(); -	copy_theme(default_theme2); -} - -void Theme::copy_theme(const Ref<Theme> &p_other) { +void Theme::merge_with(const Ref<Theme> &p_other) {  	if (p_other.is_null()) { -		clear();  		return;  	}  	_freeze_change_propagation(); -	// These items need reconnecting, so add them normally. +	// Colors.  	{  		const StringName *K = nullptr; -		while ((K = p_other->icon_map.next(K))) { +		while ((K = p_other->color_map.next(K))) {  			const StringName *L = nullptr; -			while ((L = p_other->icon_map[*K].next(L))) { -				set_icon(*L, *K, p_other->icon_map[*K][*L]); +			while ((L = p_other->color_map[*K].next(L))) { +				set_color(*L, *K, p_other->color_map[*K][*L]);  			}  		}  	} +	// Constants.  	{  		const StringName *K = nullptr; -		while ((K = p_other->style_map.next(K))) { +		while ((K = p_other->constant_map.next(K))) {  			const StringName *L = nullptr; -			while ((L = p_other->style_map[*K].next(L))) { -				set_stylebox(*L, *K, p_other->style_map[*K][*L]); +			while ((L = p_other->constant_map[*K].next(L))) { +				set_constant(*L, *K, p_other->constant_map[*K][*L]);  			}  		}  	} +	// Fonts.  	{  		const StringName *K = nullptr;  		while ((K = p_other->font_map.next(K))) { @@ -1394,13 +1390,46 @@ void Theme::copy_theme(const Ref<Theme> &p_other) {  		}  	} -	// These items can be simply copied. -	font_size_map = p_other->font_size_map; -	color_map = p_other->color_map; -	constant_map = p_other->constant_map; +	// Font sizes. +	{ +		const StringName *K = nullptr; +		while ((K = p_other->font_size_map.next(K))) { +			const StringName *L = nullptr; +			while ((L = p_other->font_size_map[*K].next(L))) { +				set_font_size(*L, *K, p_other->font_size_map[*K][*L]); +			} +		} +	} -	variation_map = p_other->variation_map; -	variation_base_map = p_other->variation_base_map; +	// Icons. +	{ +		const StringName *K = nullptr; +		while ((K = p_other->icon_map.next(K))) { +			const StringName *L = nullptr; +			while ((L = p_other->icon_map[*K].next(L))) { +				set_icon(*L, *K, p_other->icon_map[*K][*L]); +			} +		} +	} + +	// Styleboxes. +	{ +		const StringName *K = nullptr; +		while ((K = p_other->style_map.next(K))) { +			const StringName *L = nullptr; +			while ((L = p_other->style_map[*K].next(L))) { +				set_stylebox(*L, *K, p_other->style_map[*K][*L]); +			} +		} +	} + +	// Type variations. +	{ +		const StringName *K = nullptr; +		while ((K = p_other->variation_map.next(K))) { +			set_type_variation(*K, p_other->variation_map[*K]); +		} +	}  	_unfreeze_and_propagate_changes();  } @@ -1534,8 +1563,6 @@ void Theme::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_constant_list", "theme_type"), &Theme::_get_constant_list);  	ClassDB::bind_method(D_METHOD("get_constant_type_list"), &Theme::_get_constant_type_list); -	ClassDB::bind_method(D_METHOD("clear"), &Theme::clear); -  	ClassDB::bind_method(D_METHOD("set_default_font", "font"), &Theme::set_default_theme_font);  	ClassDB::bind_method(D_METHOD("get_default_font"), &Theme::get_default_theme_font); @@ -1558,8 +1585,8 @@ void Theme::_bind_methods() {  	ClassDB::bind_method(D_METHOD("get_type_list"), &Theme::_get_type_list); -	ClassDB::bind_method("copy_default_theme", &Theme::copy_default_theme); -	ClassDB::bind_method(D_METHOD("copy_theme", "other"), &Theme::copy_theme); +	ClassDB::bind_method(D_METHOD("merge_with", "other"), &Theme::merge_with); +	ClassDB::bind_method(D_METHOD("clear"), &Theme::clear);  	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "default_font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_default_font", "get_default_font");  	ADD_PROPERTY(PropertyInfo(Variant::INT, "default_font_size"), "set_default_font_size", "get_default_font_size"); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index 8a8fc28be1..15f21b91b8 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -210,8 +210,7 @@ public:  	void get_type_list(List<StringName> *p_list) const;  	void get_type_dependencies(const StringName &p_base_type, const StringName &p_type_variant, List<StringName> *p_list); -	void copy_default_theme(); -	void copy_theme(const Ref<Theme> &p_other); +	void merge_with(const Ref<Theme> &p_other);  	void clear();  	Theme(); diff --git a/servers/rendering/renderer_rd/renderer_storage_rd.h b/servers/rendering/renderer_rd/renderer_storage_rd.h index c048be5dc8..b7adef6c60 100644 --- a/servers/rendering/renderer_rd/renderer_storage_rd.h +++ b/servers/rendering/renderer_rd/renderer_storage_rd.h @@ -668,14 +668,14 @@ private:  		};  		uint32_t emitting; -		double system_phase; -		double prev_system_phase; +		float system_phase; +		float prev_system_phase;  		uint32_t cycle;  		real_t explosiveness;  		real_t randomness; -		double time; -		double delta; +		float time; +		float delta;  		uint32_t frame;  		uint32_t pad0; @@ -812,7 +812,7 @@ private:  	struct ParticlesShader {  		struct PushConstant { -			double lifetime; +			float lifetime;  			uint32_t clear;  			uint32_t total_particles;  			uint32_t trail_size;  |