mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 13:41:03 +00:00 
			
		
		
		
	Merge pull request #107038 from bruvzg/emb_scr
Add support for taking embedded window screenshots.
This commit is contained in:
		
						commit
						042ad3a62f
					
				
					 10 changed files with 185 additions and 14 deletions
				
			
		|  | @ -96,6 +96,7 @@ | |||
| #include "editor/editor_property_name_processor.h" | ||||
| #include "editor/editor_resource_picker.h" | ||||
| #include "editor/editor_resource_preview.h" | ||||
| #include "editor/editor_run.h" | ||||
| #include "editor/editor_script.h" | ||||
| #include "editor/editor_settings.h" | ||||
| #include "editor/editor_settings_dialog.h" | ||||
|  | @ -3419,14 +3420,39 @@ void EditorNode::_request_screenshot() { | |||
| 
 | ||||
| void EditorNode::_screenshot(bool p_use_utc) { | ||||
| 	String name = "editor_screenshot_" + Time::get_singleton()->get_datetime_string_from_system(p_use_utc).remove_char(':') + ".png"; | ||||
| 	NodePath path = String("user://") + name; | ||||
| 	String path = String("user://") + name; | ||||
| 
 | ||||
| 	if (!EditorRun::request_screenshot(callable_mp(this, &EditorNode::_save_screenshot_with_embedded_process).bind(path))) { | ||||
| 		_save_screenshot(path); | ||||
| 	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) { | ||||
| 		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(path), true); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void EditorNode::_save_screenshot(NodePath p_path) { | ||||
| void EditorNode::_save_screenshot_with_embedded_process(int64_t p_w, int64_t p_h, const String &p_emb_path, const Rect2i &p_rect, const String &p_path) { | ||||
| 	Control *main_screen_control = editor_main_screen->get_control(); | ||||
| 	ERR_FAIL_NULL_MSG(main_screen_control, "Cannot get the editor main screen control."); | ||||
| 	Viewport *viewport = main_screen_control->get_viewport(); | ||||
| 	ERR_FAIL_NULL_MSG(viewport, "Cannot get a viewport from the editor main screen."); | ||||
| 	Ref<ViewportTexture> texture = viewport->get_texture(); | ||||
| 	ERR_FAIL_COND_MSG(texture.is_null(), "Cannot get a viewport texture from the editor main screen."); | ||||
| 	Ref<Image> img = texture->get_image(); | ||||
| 	ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen."); | ||||
| 	img->convert(Image::FORMAT_RGBA8); | ||||
| 	ERR_FAIL_COND(p_emb_path.is_empty()); | ||||
| 	Ref<Image> overlay = Image::load_from_file(p_emb_path); | ||||
| 	DirAccess::remove_absolute(p_emb_path); | ||||
| 	ERR_FAIL_COND_MSG(overlay.is_null(), "Cannot get an image from a embedded process."); | ||||
| 	overlay->convert(Image::FORMAT_RGBA8); | ||||
| 	overlay->resize(p_rect.size.x, p_rect.size.y); | ||||
| 	img->blend_rect(overlay, Rect2i(0, 0, p_w, p_h), p_rect.position); | ||||
| 	Error error = img->save_png(p_path); | ||||
| 	ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'."); | ||||
| 
 | ||||
| 	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) { | ||||
| 		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(p_path), true); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void EditorNode::_save_screenshot(const String &p_path) { | ||||
| 	Control *main_screen_control = editor_main_screen->get_control(); | ||||
| 	ERR_FAIL_NULL_MSG(main_screen_control, "Cannot get the editor main screen control."); | ||||
| 	Viewport *viewport = main_screen_control->get_viewport(); | ||||
|  | @ -3437,6 +3463,10 @@ void EditorNode::_save_screenshot(NodePath p_path) { | |||
| 	ERR_FAIL_COND_MSG(img.is_null(), "Cannot get an image from a viewport texture of the editor main screen."); | ||||
| 	Error error = img->save_png(p_path); | ||||
| 	ERR_FAIL_COND_MSG(error != OK, "Cannot save screenshot to file '" + p_path + "'."); | ||||
| 
 | ||||
| 	if (EDITOR_GET("interface/editor/automatically_open_screenshots")) { | ||||
| 		OS::get_singleton()->shell_show_in_file_manager(ProjectSettings::get_singleton()->globalize_path(p_path), true); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void EditorNode::_check_system_theme_changed() { | ||||
|  |  | |||
|  | @ -548,7 +548,8 @@ private: | |||
| 
 | ||||
| 	void _request_screenshot(); | ||||
| 	void _screenshot(bool p_use_utc = false); | ||||
| 	void _save_screenshot(NodePath p_path); | ||||
| 	void _save_screenshot(const String &p_path); | ||||
| 	void _save_screenshot_with_embedded_process(int64_t p_w, int64_t p_h, const String &p_emb_path, const Rect2i &p_rect, const String &p_path); | ||||
| 
 | ||||
| 	void _check_system_theme_changed(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -189,6 +189,14 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie, const V | |||
| 	return OK; | ||||
| } | ||||
| 
 | ||||
| bool EditorRun::request_screenshot(const Callable &p_callback) { | ||||
| 	if (instance_rq_screenshot_callback) { | ||||
| 		return instance_rq_screenshot_callback(p_callback); | ||||
| 	} else { | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| bool EditorRun::has_child_process(OS::ProcessID p_pid) const { | ||||
| 	for (const OS::ProcessID &E : pids) { | ||||
| 		if (E == p_pid) { | ||||
|  |  | |||
|  | @ -33,6 +33,7 @@ | |||
| #include "core/os/os.h" | ||||
| 
 | ||||
| typedef void (*EditorRunInstanceStarting)(int p_index, List<String> &r_arguments); | ||||
| typedef bool (*EditorRunInstanceRequestScreenshot)(const Callable &p_callback); | ||||
| 
 | ||||
| class EditorRun { | ||||
| public: | ||||
|  | @ -58,6 +59,7 @@ private: | |||
| 
 | ||||
| public: | ||||
| 	inline static EditorRunInstanceStarting instance_starting_callback = nullptr; | ||||
| 	inline static EditorRunInstanceRequestScreenshot instance_rq_screenshot_callback = nullptr; | ||||
| 
 | ||||
| 	Status get_status() const; | ||||
| 	String get_running_scene() const; | ||||
|  | @ -71,6 +73,8 @@ public: | |||
| 	int get_child_process_count() const { return pids.size(); } | ||||
| 	OS::ProcessID get_current_process() const; | ||||
| 
 | ||||
| 	static bool request_screenshot(const Callable &p_callback); | ||||
| 
 | ||||
| 	static WindowPlacement get_window_placement(); | ||||
| 
 | ||||
| 	EditorRun(); | ||||
|  |  | |||
|  | @ -32,6 +32,7 @@ | |||
| 
 | ||||
| #include "core/config/project_settings.h" | ||||
| #include "core/debugger/debugger_marshalls.h" | ||||
| #include "core/debugger/engine_debugger.h" | ||||
| #include "core/string/translation_server.h" | ||||
| #include "editor/debugger/editor_debugger_node.h" | ||||
| #include "editor/debugger/script_editor_debugger.h" | ||||
|  | @ -225,6 +226,61 @@ void GameViewDebugger::_bind_methods() { | |||
| 	ADD_SIGNAL(MethodInfo("session_stopped")); | ||||
| } | ||||
| 
 | ||||
| bool GameViewDebugger::add_screenshot_callback(const Callable &p_callaback, const Rect2i &p_rect) { | ||||
| 	bool found = false; | ||||
| 	for (Ref<EditorDebuggerSession> &I : sessions) { | ||||
| 		if (I->is_active()) { | ||||
| 			ScreenshotCB sd; | ||||
| 			sd.cb = p_callaback; | ||||
| 			sd.rect = p_rect; | ||||
| 			screenshot_callbacks[scr_rq_id] = sd; | ||||
| 
 | ||||
| 			Array arr; | ||||
| 			arr.append(scr_rq_id); | ||||
| 			I->send_message("scene:rq_screenshot", arr); | ||||
| 			scr_rq_id++; | ||||
| 			found = true; | ||||
| 		} | ||||
| 	} | ||||
| 	return found; | ||||
| } | ||||
| 
 | ||||
| bool GameViewDebugger::_msg_get_screenshot(const Array &p_args) { | ||||
| 	ERR_FAIL_COND_V_MSG(p_args.size() != 4, false, "get_screenshot: invalid number of arguments"); | ||||
| 
 | ||||
| 	int64_t id = p_args[0]; | ||||
| 	int64_t w = p_args[1]; | ||||
| 	int64_t h = p_args[2]; | ||||
| 	const String &path = p_args[3]; | ||||
| 
 | ||||
| 	if (screenshot_callbacks.has(id)) { | ||||
| 		if (screenshot_callbacks[id].cb.is_valid()) { | ||||
| 			screenshot_callbacks[id].cb.call(w, h, path, screenshot_callbacks[id].rect); | ||||
| 		} | ||||
| 		screenshot_callbacks.erase(id); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool GameViewDebugger::capture(const String &p_message, const Array &p_data, int p_session) { | ||||
| 	Ref<EditorDebuggerSession> session = get_session(p_session); | ||||
| 	ERR_FAIL_COND_V(session.is_null(), true); | ||||
| 
 | ||||
| 	if (p_message == "game_view:get_screenshot") { | ||||
| 		return _msg_get_screenshot(p_data); | ||||
| 	} else { | ||||
| 		// Any other messages with this prefix should be ignored.
 | ||||
| 		WARN_PRINT("GameViewDebugger unknown message: " + p_message); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| bool GameViewDebugger::has_capture(const String &p_capture) const { | ||||
| 	return p_capture == "game_view"; | ||||
| } | ||||
| 
 | ||||
| GameViewDebugger::GameViewDebugger() { | ||||
| 	EditorFeatureProfileManager::get_singleton()->connect("current_feature_profile_changed", callable_mp(this, &GameViewDebugger::_feature_profile_changed)); | ||||
| 
 | ||||
|  | @ -287,6 +343,23 @@ void GameView::_instance_starting(int p_idx, List<String> &r_arguments) { | |||
| 	_update_arguments_for_instance(p_idx, r_arguments); | ||||
| } | ||||
| 
 | ||||
| bool GameView::_instance_rq_screenshot_static(const Callable &p_callback) { | ||||
| 	ERR_FAIL_NULL_V(singleton, false); | ||||
| 	return singleton->_instance_rq_screenshot(p_callback); | ||||
| } | ||||
| 
 | ||||
| bool GameView::_instance_rq_screenshot(const Callable &p_callback) { | ||||
| 	if (debugger.is_null() || window_wrapper->get_window_enabled() || !embedded_process || !embedded_process->is_embedding_completed()) { | ||||
| 		return false; | ||||
| 	} | ||||
| 	Rect2 r = embedded_process->get_adjusted_embedded_window_rect(embedded_process->get_rect()); | ||||
| 	r.position += embedded_process->get_global_position(); | ||||
| #ifndef MACOS_ENABLED | ||||
| 	r.position -= embedded_process->get_window()->get_position(); | ||||
| #endif | ||||
| 	return debugger->add_screenshot_callback(p_callback, r); | ||||
| } | ||||
| 
 | ||||
| void GameView::_show_update_window_wrapper() { | ||||
| 	EditorRun::WindowPlacement placement = EditorRun::get_window_placement(); | ||||
| 	Point2 position = floating_window_rect.position; | ||||
|  | @ -756,6 +829,7 @@ void GameView::_notification(int p_what) { | |||
| 				EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed)); | ||||
| 				EditorRunBar::get_singleton()->connect("stop_pressed", callable_mp(this, &GameView::_stop_pressed)); | ||||
| 				EditorRun::instance_starting_callback = _instance_starting_static; | ||||
| 				EditorRun::instance_rq_screenshot_callback = _instance_rq_screenshot_static; | ||||
| 
 | ||||
| 				// Listen for project settings changes to update the window size and aspect ratio.
 | ||||
| 				ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed)); | ||||
|  |  | |||
|  | @ -60,10 +60,25 @@ private: | |||
| 
 | ||||
| 	void _feature_profile_changed(); | ||||
| 
 | ||||
| 	struct ScreenshotCB { | ||||
| 		Callable cb; | ||||
| 		Rect2i rect; | ||||
| 	}; | ||||
| 
 | ||||
| 	int64_t scr_rq_id = 0; | ||||
| 	HashMap<uint64_t, ScreenshotCB> screenshot_callbacks; | ||||
| 
 | ||||
| 	bool _msg_get_screenshot(const Array &p_args); | ||||
| 
 | ||||
| protected: | ||||
| 	static void _bind_methods(); | ||||
| 
 | ||||
| public: | ||||
| 	virtual bool capture(const String &p_message, const Array &p_data, int p_session) override; | ||||
| 	virtual bool has_capture(const String &p_capture) const override; | ||||
| 
 | ||||
| 	bool add_screenshot_callback(const Callable &p_callaback, const Rect2i &p_rect); | ||||
| 
 | ||||
| 	void set_suspend(bool p_enabled); | ||||
| 	void next_frame(); | ||||
| 
 | ||||
|  | @ -173,6 +188,8 @@ class GameView : public VBoxContainer { | |||
| 	void _play_pressed(); | ||||
| 	static void _instance_starting_static(int p_idx, List<String> &r_arguments); | ||||
| 	void _instance_starting(int p_idx, List<String> &r_arguments); | ||||
| 	static bool _instance_rq_screenshot_static(const Callable &p_callback); | ||||
| 	bool _instance_rq_screenshot(const Callable &p_callback); | ||||
| 	void _stop_pressed(); | ||||
| 	void _embedding_completed(); | ||||
| 	void _embedding_failed(); | ||||
|  |  | |||
|  | @ -60,7 +60,6 @@ class GameViewDebuggerMacOS : public GameViewDebugger { | |||
| 
 | ||||
| public: | ||||
| 	virtual bool capture(const String &p_message, const Array &p_data, int p_session) override; | ||||
| 	virtual bool has_capture(const String &p_capture) const override; | ||||
| 
 | ||||
| 	GameViewDebuggerMacOS(EmbeddedProcessMacOS *p_embedded_process); | ||||
| }; | ||||
|  |  | |||
|  | @ -109,10 +109,6 @@ void GameViewDebuggerMacOS::_init_capture_message_handlers() { | |||
| 	parse_message_handlers["game_view:joy_stop"] = &GameViewDebuggerMacOS::_msg_joy_stop; | ||||
| } | ||||
| 
 | ||||
| bool GameViewDebuggerMacOS::has_capture(const String &p_capture) const { | ||||
| 	return p_capture == "game_view"; | ||||
| } | ||||
| 
 | ||||
| bool GameViewDebuggerMacOS::capture(const String &p_message, const Array &p_data, int p_session) { | ||||
| 	Ref<EditorDebuggerSession> session = get_session(p_session); | ||||
| 	ERR_FAIL_COND_V(session.is_null(), true); | ||||
|  | @ -121,9 +117,7 @@ bool GameViewDebuggerMacOS::capture(const String &p_message, const Array &p_data | |||
| 	if (fn_ptr) { | ||||
| 		return (this->**fn_ptr)(p_data); | ||||
| 	} else { | ||||
| 		// Any other messages with this prefix should be ignored. | ||||
| 		WARN_PRINT("GameViewDebuggerMacOS unknown message: " + p_message); | ||||
| 		return ERR_SKIP; | ||||
| 		return GameViewDebugger::capture(p_message, p_data, p_session); | ||||
| 	} | ||||
| 
 | ||||
| 	return true; | ||||
|  |  | |||
|  | @ -32,9 +32,11 @@ | |||
| 
 | ||||
| #include "core/debugger/debugger_marshalls.h" | ||||
| #include "core/debugger/engine_debugger.h" | ||||
| #include "core/io/dir_access.h" | ||||
| #include "core/io/marshalls.h" | ||||
| #include "core/math/math_fieldwise.h" | ||||
| #include "core/object/script_language.h" | ||||
| #include "core/os/time.h" | ||||
| #include "core/templates/local_vector.h" | ||||
| #include "scene/gui/popup_menu.h" | ||||
| #include "scene/main/canvas_layer.h" | ||||
|  | @ -423,6 +425,46 @@ Error SceneDebugger::_msg_runtime_node_select_reset_camera_3d(const Array &p_arg | |||
| 
 | ||||
| // endregion
 | ||||
| 
 | ||||
| // region Embedded process screenshot.
 | ||||
| 
 | ||||
| Error SceneDebugger::_msg_rq_screenshot(const Array &p_args) { | ||||
| 	ERR_FAIL_COND_V(p_args.is_empty(), ERR_INVALID_DATA); | ||||
| 
 | ||||
| 	Viewport *viewport = SceneTree::get_singleton()->get_root(); | ||||
| 	ERR_FAIL_NULL_V_MSG(viewport, ERR_UNCONFIGURED, "Cannot get a viewport from the main screen."); | ||||
| 	Ref<ViewportTexture> texture = viewport->get_texture(); | ||||
| 	ERR_FAIL_COND_V_MSG(texture.is_null(), ERR_UNCONFIGURED, "Cannot get a viewport texture from the main screen."); | ||||
| 	Ref<Image> img = texture->get_image(); | ||||
| 	ERR_FAIL_COND_V_MSG(img.is_null(), ERR_UNCONFIGURED, "Cannot get an image from a viewport texture of the main screen."); | ||||
| 	img->clear_mipmaps(); | ||||
| 
 | ||||
| 	const String TEMP_DIR = OS::get_singleton()->get_temp_path(); | ||||
| 	uint32_t suffix_i = 0; | ||||
| 	String path; | ||||
| 	while (true) { | ||||
| 		String datetime = Time::get_singleton()->get_datetime_string_from_system().remove_chars("-T:"); | ||||
| 		datetime += itos(Time::get_singleton()->get_ticks_usec()); | ||||
| 		String suffix = datetime + (suffix_i > 0 ? itos(suffix_i) : ""); | ||||
| 		path = TEMP_DIR.path_join("scr-" + suffix + ".png"); | ||||
| 		if (!DirAccess::exists(path)) { | ||||
| 			break; | ||||
| 		} | ||||
| 		suffix_i += 1; | ||||
| 	} | ||||
| 	img->save_png(path); | ||||
| 
 | ||||
| 	Array arr; | ||||
| 	arr.append(p_args[0]); | ||||
| 	arr.append(img->get_width()); | ||||
| 	arr.append(img->get_height()); | ||||
| 	arr.append(path); | ||||
| 	EngineDebugger::get_singleton()->send_message("game_view:get_screenshot", arr); | ||||
| 
 | ||||
| 	return OK; | ||||
| } | ||||
| 
 | ||||
| // endregion
 | ||||
| 
 | ||||
| HashMap<String, SceneDebugger::ParseMessageFunc> SceneDebugger::message_handlers; | ||||
| 
 | ||||
| Error SceneDebugger::parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured) { | ||||
|  | @ -490,6 +532,7 @@ void SceneDebugger::_init_message_handlers() { | |||
| #ifndef _3D_DISABLED | ||||
| 	message_handlers["runtime_node_select_reset_camera_3d"] = _msg_runtime_node_select_reset_camera_3d; | ||||
| #endif | ||||
| 	message_handlers["rq_screenshot"] = _msg_rq_screenshot; | ||||
| } | ||||
| 
 | ||||
| void SceneDebugger::_save_node(ObjectID id, const String &p_path) { | ||||
|  |  | |||
|  | @ -119,6 +119,7 @@ private: | |||
| #ifndef _3D_DISABLED | ||||
| 	static Error _msg_runtime_node_select_reset_camera_3d(const Array &p_args); | ||||
| #endif | ||||
| 	static Error _msg_rq_screenshot(const Array &p_args); | ||||
| 
 | ||||
| public: | ||||
| 	static Error parse_message(void *p_user, const String &p_msg, const Array &p_args, bool &r_captured); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Rémi Verschelde
						Rémi Verschelde