Repository: godotengine/godot (language: cpp)

Issue:
Viewport size doesn't match what's defined in the project settings when running a project with floating game window
### Tested versions

- Reproducible in: 4.4.dev7

### System information

Godot v4.4.dev7 - Windows 10.0.22631 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4090 (NVIDIA; 32.0.15.6636) - 13th Gen Intel(R) Core(TM) i9-13900K (32 Threads)

### Issue description

The viewport size doesn't match what's defined in the project settings when running a project with floating game window. This can be misleading, as what you see when running the project no longer matches the editor by default (with the `disabled` and `canvas_items` stretch modes).

For example, if you run a project that uses a viewport size of 1920x1080, the actual window size will be set to that, so the viewport size will be smaller than that (typically around 1900x1020 for 1920x1080). This is because the UI elements that are part of the game window take up space away from the game viewport.

A way to fix this is to resize the window on launch to take the controls into account. In other words, the viewport should remain 1920x1080 and the window will be a bit larger than that as a result. (If there isn't enough space available on screen, the window should be maximized instead to make the viewport size as close as possible to the "real" one).

### Steps to reproduce

- Run a project in the editor with the game in a separate window.
- Notice how the displayed viewport resolution in the top-right corner doesn't match the size defined in the project settings.

### Minimal reproduction project (MRP)

https://github.com/godotengine/godot-demo-projects/tree/master/3d/platformer, but any project should do.


Discussion / hints:
Effectively, when embedded and floating, the floating window size does not follow the project settings. I believe this could be fixed relatively easily. However, one of the current features is that when you resize the floating window, the size and position are saved and reused for the next play session. Personally, I find this feature useful because it’s easier to resize and position the game window using the floating window rather than manually updating the values in the project settings (which might include size, position, and screen settings, if I remember correctly).

Do you think Godot should stop preserving the size and position of the floating window and always use the project settings? Or perhaps a new toggle could be added to indicate whether you want to use the project settings or not?

Fix patch:
diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp
index 5251f1e558d5..84367971ce5d 100644
--- a/editor/editor_run.cpp
+++ b/editor/editor_run.cpp
@@ -104,99 +104,16 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie, const V
 		}
 	}
 
-	int screen = EDITOR_GET("run/window_placement/screen");
-	if (screen == -5) {
-		// Same as editor
-		screen = DisplayServer::get_singleton()->window_get_current_screen();
-	} else if (screen == -4) {
-		// Previous monitor (wrap to the other end if needed)
-		screen = Math::wrapi(
-				DisplayServer::get_singleton()->window_get_current_screen() - 1,
-				0,
-				DisplayServer::get_singleton()->get_screen_count());
-	} else if (screen == -3) {
-		// Next monitor (wrap to the other end if needed)
-		screen = Math::wrapi(
-				DisplayServer::get_singleton()->window_get_current_screen() + 1,
-				0,
-				DisplayServer::get_singleton()->get_screen_count());
+	WindowPlacement window_placement = get_window_placement();
+	if (window_placement.position != Point2i(INT_MAX, INT_MAX)) {
+		args.push_back("--position");
+		args.push_back(itos(window_placement.position.x) + "," + itos(window_placement.position.y));
 	}
 
-	Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(screen);
-
-	int window_placement = EDITOR_GET("run/window_placement/rect");
-	if (screen_rect != Rect2()) {
-		Size2 window_size;
-		window_size.x = GLOBAL_GET("display/window/size/viewport_width");
-		window_size.y = GLOBAL_GET("display/window/size/viewport_height");
-
-		Size2 desired_size;
-		desired_size.x = GLOBAL_GET("display/window/size/window_width_override");
-		desired_size.y = GLOBAL_GET("display/window/size/window_height_override");
-		if (desired_size.x > 0 && desired_size.y > 0) {
-			window_size = desired_size;
-		}
-
-		if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) {
-			bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi");
-			int display_scale = 1;
-
-			if (OS::get_singleton()->is_hidpi_allowed()) {
-				if (hidpi_proj) {
-					display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale.
-				} else {
-					display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down.
-				}
-			} else {
-				if (hidpi_proj) {
-					display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up.
-				} else {
-					display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale.
-				}
-			}
-			screen_rect.position /= display_scale;
-			screen_rect.size /= display_scale;
-		}
-
-		switch (window_placement) {
-			case 0: { // top left
-				args.push_back("--position");
-				args.push_back(itos(screen_rect.position.x) + "," + itos(screen_rect.position.y));
-			} break;
-			case 1: { // centered
-				Vector2 pos = (screen_rect.position) + ((screen_rect.size - window_size) / 2).floor();
-				args.push_back("--position");
-				args.push_back(itos(pos.x) + "," + itos(pos.y));
-			} break;
-			case 2: { // custom pos
-				Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position");
-				pos += screen_rect.position;
-				args.push_back("--position");
-				args.push_back(itos(pos.x) + "," + itos(pos.y));
-			} break;
-			case 3: { // force maximized
-				Vector2 pos = screen_rect.position + screen_rect.size / 2;
-				args.push_back("--position");
-				args.push_back(itos(pos.x) + "," + itos(pos.y));
-				args.push_back("--maximized");
-			} break;
-			case 4: { // force fullscreen
-				Vector2 pos = screen_rect.position + screen_rect.size / 2;
-				args.push_back("--position");
-				args.push_back(itos(pos.x) + "," + itos(pos.y));
-				args.push_back("--fullscreen");
-			} break;
-		}
-	} else {
-		// Unable to get screen info, skip setting position.
-		switch (window_placement) {
-			case 3: { // force maximized
-				args.push_back("--maximized");
-			} break;
-			case 4: { // force fullscreen
-				args.push_back("--fullscreen");
-			} break;
-		}
+	if (window_placement.force_maximized) {
+		args.push_back("--maximized");
+	} else if (window_placement.force_fullscreen) {
+		args.push_back("--fullscreen");
 	}
 
 	List<String> breakpoints;
@@ -297,6 +214,98 @@ OS::ProcessID EditorRun::get_current_process() const {
 	return pids.front()->get();
 }
 
+EditorRun::WindowPlacement EditorRun::get_window_placement() {
+	WindowPlacement placement = WindowPlacement();
+	placement.screen = EDITOR_GET("run/window_placement/screen");
+	if (placement.screen == -5) {
+		// Same as editor
+		placement.screen = DisplayServer::get_singleton()->window_get_current_screen();
+	} else if (placement.screen == -4) {
+		// Previous monitor (wrap to the other end if needed)
+		placement.screen = Math::wrapi(
+				DisplayServer::get_singleton()->window_get_current_screen() - 1,
+				0,
+				DisplayServer::get_singleton()->get_screen_count());
+	} else if (placement.screen == -3) {
+		// Next monitor (wrap to the other end if needed)
+		placement.screen = Math::wrapi(
+				DisplayServer::get_singleton()->window_get_current_screen() + 1,
+				0,
+				DisplayServer::get_singleton()->get_screen_count());
+	} else if (placement.screen == -2) {
+		// Primary screen
+		placement.screen = DisplayServer::get_singleton()->get_primary_screen();
+	}
+
+	placement.size.x = GLOBAL_GET("display/window/size/viewport_width");
+	placement.size.y = GLOBAL_GET("display/window/size/viewport_height");
+
+	Size2 desired_size;
+	desired_size.x = GLOBAL_GET("display/window/size/window_width_override");
+	desired_size.y = GLOBAL_GET("display/window/size/window_height_override");
+	if (desired_size.x > 0 && desired_size.y > 0) {
+		placement.size = desired_size;
+	}
+
+	Rect2 screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(placement.screen);
+
+	int window_placement = EDITOR_GET("run/window_placement/rect");
+	if (screen_rect != Rect2()) {
+		if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_HIDPI)) {
+			bool hidpi_proj = GLOBAL_GET("display/window/dpi/allow_hidpi");
+			int display_scale = 1;
+
+			if (OS::get_singleton()->is_hidpi_allowed()) {
+				if (hidpi_proj) {
+					display_scale = 1; // Both editor and project runs in hiDPI mode, do not scale.
+				} else {
+					display_scale = DisplayServer::get_singleton()->screen_get_max_scale(); // Editor is in hiDPI mode, project is not, scale down.
+				}
+			} else {
+				if (hidpi_proj) {
+					display_scale = (1.f / DisplayServer::get_singleton()->screen_get_max_scale()); // Editor is not in hiDPI mode, project is, scale up.
+				} else {
+					display_scale = 1; // Both editor and project runs in lowDPI mode, do not scale.
+				}
+			}
+			screen_rect.position /= display_scale;
+			screen_rect.size /= display_scale;
+		}
+
+		switch (window_placement) {
+			case 0: { // top left
+				placement.position = screen_rect.position;
+			} break;
+			case 1: { // centered
+				placement.position = (screen_rect.position) + ((screen_rect.size - placement.size) / 2).floor();
+			} break;
+			case 2: { // custom pos
+				Vector2 pos = EDITOR_GET("run/window_placement/rect_custom_position");
+				pos += screen_rect.position;
+				placement.position = pos;
+			} break;
+			case 3: { // force maximized
+				placement.force_maximized = true;
+			} break;
+			case 4: { // force fullscreen
+				placement.force_fullscreen = true;
+			} break;
+		}
+	} else {
+		// Unable to get screen info, skip setting position.
+		switch (window_placement) {
+			case 3: { // force maximized
+				placement.force_maximized = true;
+			} break;
+			case 4: { // force fullscreen
+				placement.force_fullscreen = true;
+			} break;
+		}
+	}
+
+	return placement;
+}
+
 EditorRun::EditorRun() {
 	status = STATUS_STOP;
 	running_scene = "";
diff --git a/editor/editor_run.h b/editor/editor_run.h
index 94fb977e931b..5e342944130c 100644
--- a/editor/editor_run.h
+++ b/editor/editor_run.h
@@ -45,6 +45,14 @@ class EditorRun {
 
 	List<OS::ProcessID> pids;
 
+	struct WindowPlacement {
+		int screen = 0;
+		Point2i position = Point2i(INT_MAX, INT_MAX);
+		Size2i size;
+		bool force_maximized = false;
+		bool force_fullscreen = false;
+	};
+
 private:
 	Status status;
 	String running_scene;
@@ -64,6 +72,8 @@ class EditorRun {
 	int get_child_process_count() const { return pids.size(); }
 	OS::ProcessID get_current_process() const;
 
+	static WindowPlacement get_window_placement();
+
 	EditorRun();
 };
 
diff --git a/editor/icons/FixedSize.svg b/editor/icons/FixedSize.svg
new file mode 100644
index 000000000000..1ca5752c23ca
--- /dev/null
+++ b/editor/icons/FixedSize.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m11.032 6.8435a3.032 2.8914 0 0 0-6.064 0v1.1565h-0.6064v4.0479h7.2768v-4.0479h-0.6064zm-4.8512 0a1.8192 1.7348 0 0 1 3.6384 0v1.1565h-3.6384zm1.2128 2.3131h1.2128v1.7348h-1.2128z"/><g fill="none" stroke="#e0e0e0" stroke-linejoin="round" stroke-miterlimit="0"><path d="m2.1058 5.8844v-2.7749h2.9127m5.8565 0.0498h2.7749v2.9127" transform="matrix(1.1315 0 0 1.1398 -.914 -1.888)"/><path d="m13.757 11.466v2.7749h-2.9127m-5.856-0.0499h-2.7749v-2.9127" transform="matrix(1.1315 0 0 1.1398 -1.035 -1.888)"/></g></svg>
\ No newline at end of file
diff --git a/editor/icons/KeepAspect.svg b/editor/icons/KeepAspect.svg
index 1cc831563fcb..f178a2aa879b 100644
--- a/editor/icons/KeepAspect.svg
+++ b/editor/icons/KeepAspect.svg
@@ -1,1 +1,1 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" stroke="#e0e0e0" stroke-linejoin="round" stroke-miterlimit="0"><path d="M2.1058 5.8844v-2.7749h2.9127M10.875 3.1593h2.7749v2.9127" transform="translate(.122 -.675)"/><path d="M13.757 11.466v2.7749h-2.9127M4.9883 14.191h-2.7749v-2.9127" transform="translate(.015 -.675)"/></g></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="none" stroke="#e0e0e0" stroke-linejoin="round" stroke-miterlimit="0"><path d="m10.875 3.1593h2.7749v2.9127" transform="matrix(1.1315 0 0 1.1398 -.914 -1.888)"/><path d="m4.9883 14.191h-2.7749v-2.9127" transform="matrix(1.1315 0 0 1.1398 -1.035 -1.888)"/></g><path fill="#e0e0e0" d="m11.509 10.223-1.1866 1.1424 2.0978 2.0196-1.5046 1.4485h4.1955v-4.0392l-1.5046 1.4485-2.0978-2.0196zm-10.592-9.0557v4.0897l1.5233-1.4666 2.124 2.0448 1.2014-1.1566-2.124-2.0448 1.5233-1.4666h-4.2479z"/></svg>
\ No newline at end of file
diff --git a/editor/icons/Stretch.svg b/editor/icons/Stretch.svg
new file mode 100644
index 000000000000..f8cb75fe864a
--- /dev/null
+++ b/editor/icons/Stretch.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m11.505 10.226-1.1835 1.144 2.0923 2.0225-1.5006 1.4505h4.1844v-4.0448l-1.5006 1.4505-2.0923-2.0225zm-6.9268-0.057673-2.1184 2.0477-1.5193-1.4686v4.0954h4.2367l-1.5193-1.4686 2.1184-2.0477-1.1982-1.1582zm6.3009-9.0107 1.513 1.4625-2.1097 2.0393 1.1933 1.1535 2.1097-2.0393 1.513 1.4625v-4.0786zm-9.9385-1.96e-5v4.0954l1.5193-1.4686 2.1184 2.0477 1.1982-1.1583-2.1184-2.0477 1.5193-1.4686h-4.2367z"/></svg>
\ No newline at end of file
diff --git a/editor/plugins/embedded_process.cpp b/editor/plugins/embedded_process.cpp
index 06c207ca7942..10c7188b5c53 100644
--- a/editor/plugins/embedded_process.cpp
+++ b/editor/plugins/embedded_process.cpp
@@ -50,7 +50,7 @@ void EmbeddedProcess::_notification(int p_what) {
 			Rect2i new_global_rect = get_global_rect();
 			if (last_global_rect != new_global_rect) {
 				last_global_rect = new_global_rect;
-				_queue_update_embedded_process();
+				queue_update_embedded_process();
 			}
 
 		} break;
@@ -60,7 +60,7 @@ void EmbeddedProcess::_notification(int p_what) {
 		case NOTIFICATION_RESIZED:
 		case NOTIFICATION_VISIBILITY_CHANGED:
 		case NOTIFICATION_WM_POSITION_CHANGED: {
-			_queue_update_embedded_process();
+			queue_update_embedded_process();
 		} break;
 		case NOTIFICATION_THEME_CHANGED: {
 			focus_style_box = get_theme_stylebox(SNAME("FocusViewport"), EditorStringName(EditorStyles));
@@ -77,7 +77,7 @@ void EmbeddedProcess::_notification(int p_what) {
 			}
 		} break;
 		case NOTIFICATION_FOCUS_ENTER: {
-			_queue_update_embedded_process();
+			queue_update_embedded_process();
 		} break;
 		case NOTIFICATION_APPLICATION_FOCUS_IN: {
 			application_has_focus = true;
@@ -88,7 +88,7 @@ void EmbeddedProcess::_notification(int p_what) {
 				// or if the current window is a different popup or secondary window.
 				if (embedding_completed && current_process_id != focused_process_id && window && window->has_focus()) {
 					grab_focus();
-					_queue_update_embedded_process();
+					queue_update_embedded_process();
 				}
 			}
 		} break;
@@ -102,27 +102,33 @@ void EmbeddedProcess::_notification(int p_what) {
 void EmbeddedProcess::set_window_size(const Size2i p_window_size) {
 	if (window_size != p_window_size) {
 		window_size = p_window_size;
-		_queue_update_embedded_process();
+		queue_update_embedded_process();
 	}
 }
 
 void EmbeddedProcess::set_keep_aspect(bool p_keep_aspect) {
 	if (keep_aspect != p_keep_aspect) {
 		keep_aspect = p_keep_aspect;
-		_queue_update_embedded_process();
+		queue_update_embedded_process();
 	}
 }
 
 Rect2i EmbeddedProcess::_get_global_embedded_window_rect() {
 	Rect2i control_rect = get_global_rect();
-	control_rect = Rect2i(control_rect.position, control_rect.size.maxi(1));
-	if (keep_aspect) {
-		Rect2i desired_rect = control_rect;
-		float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
-		desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
+	control_rect = Rect2i(control_rect.position + margin_top_left, (control_rect.size - get_margins_size()).maxi(1));
+	if (window_size != Size2i()) {
+		Rect2i desired_rect = Rect2i();
+		if (!keep_aspect && control_rect.size.x >= window_size.x && control_rect.size.y >= window_size.y) {
+			// Fixed at the desired size.
+			desired_rect.size = window_size;
+		} else {
+			float ratio = MIN((float)control_rect.size.x / window_size.x, (float)control_rect.size.y / window_size.y);
+			desired_rect.size = Size2i(window_size.x * ratio, window_size.y * ratio).maxi(1);
+		}
 		desired_rect.position = Size2i(control_rect.position.x + ((control_rect.size.x - desired_rect.size.x) / 2), control_rect.position.y + ((control_rect.size.y - desired_rect.size.y) / 2));
 		return desired_rect;
 	} else {
+		// Stretch, use all the control area.
 		return control_rect;
 	}
 }
@@ -133,8 +139,28 @@ Rect2i EmbeddedProcess::get_screen_embedded_window_rect() {
 		rect.position += window->get_position();
 	}
 
-	// Removing margins to make space for the focus border style.
-	return Rect2i(rect.position.x + margin_top_left.x, rect.position.y + margin_top_left.y, MAX(rect.size.x - (margin_top_left.x + margin_bottom_right.x), 1), MAX(rect.size.y - (margin_top_left.y + margin_bottom_right.y), 1));
+	return rect;
+}
+
+int EmbeddedProcess::get_margin_size(Side p_side) const {
+	ERR_FAIL_INDEX_V((int)p_side, 4, 0);
+
+	switch (p_side) {
+		case SIDE_LEFT:
+			return margin_top_left.x;
+		case SIDE_RIGHT:
+			return margin_bottom_right.x;
+		case SIDE_TOP:
+			return margin_top_left.y;
+		case SIDE_BOTTOM:
+			return margin_bottom_right.y;
+	}
+
+	return 0;
+}
+
+Size2 EmbeddedProcess::get_margins_size() {
+	return margin_top_left + margin_bottom_right;
 }
 
 bool EmbeddedProcess::is_embedding_in_progress() {
@@ -215,7 +241,7 @@ bool EmbeddedProcess::_is_embedded_process_updatable() {
 	return window && current_process_id != 0 && embedding_completed;
 }
 
-void EmbeddedProcess::_queue_update_embedded_process() {
+void EmbeddedProcess::queue_update_embedded_process() {
 	if (updated_embedded_process_queued || !_is_embedded_process_updatable()) {
 		return;
 	}
@@ -287,7 +313,7 @@ void EmbeddedProcess::_check_mouse_over() {
 	// When we already have the focus and the user moves the mouse over the embedded process,
 	// we just need to refocus the process.
 	if (focused) {
-		_queue_update_embedded_process();
+		queue_update_embedded_process();
 	} else {
 		grab_focus();
 		queue_redraw();
diff --git a/editor/plugins/embedded_process.h b/editor/plugins/embedded_process.h
index 444cac1786b9..14ad44fdaedd 100644
--- a/editor/plugins/embedded_process.h
+++ b/editor/plugins/embedded_process.h
@@ -59,7 +59,6 @@ class EmbeddedProcess : public Control {
 	Rect2i last_global_rect;
 
 	void _try_embed_process();
-	void _queue_update_embedded_process();
 	void _update_embedded_process();
 	void _timer_embedding_timeout();
 	void _draw();
@@ -78,8 +77,11 @@ class EmbeddedProcess : public Control {
 
 	void set_window_size(const Size2i p_window_size);
 	void set_keep_aspect(bool p_keep_aspect);
+	void queue_update_embedded_process();
 
 	Rect2i get_screen_embedded_window_rect();
+	int get_margin_size(Side p_side) const;
+	Size2 get_margins_size();
 	bool is_embedding_in_progress();
 	bool is_embedding_completed();
 	int get_embedded_pid() const;
diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp
index b1f7a8498a3a..1c1b39ffbaab 100644
--- a/editor/plugins/game_view_plugin.cpp
+++ b/editor/plugins/game_view_plugin.cpp
@@ -41,6 +41,7 @@
 #include "editor/editor_main_screen.h"
 #include "editor/editor_node.h"
 #include "editor/editor_settings.h"
+#include "editor/editor_string_names.h"
 #include "editor/gui/editor_run_bar.h"
 #include "editor/plugins/embedded_process.h"
 #include "editor/themes/editor_scale.h"
@@ -222,18 +223,49 @@ void GameView::_instance_starting(int p_idx, List<String> &r_arguments) {
 	if (!is_feature_enabled) {
 		return;
 	}
-	if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled()) {
+	if (p_idx == 0 && embed_on_play && make_floating_on_play && !window_wrapper->get_window_enabled() && EditorNode::get_singleton()->is_multi_window_enabled() && _get_embed_available() == EMBED_AVAILABLE) {
 		// Set the Floating Window default title. Always considered in DEBUG mode, same as in Window::set_title.
 		String appname = GLOBAL_GET("application/config/name");
 		appname = vformat("%s (DEBUG)", TranslationServer::get_singleton()->translate(appname));
 		window_wrapper->set_window_title(appname);
 
-		window_wrapper->restore_window_from_saved_position(floating_window_rect, floating_window_screen, floating_window_screen_rect);
+		_show_update_window_wrapper();
 	}
 
 	_update_arguments_for_instance(p_idx, r_arguments);
 }
 
+void GameView::_show_update_window_wrapper() {
+	EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
+	Point2 position = floating_window_rect.position;
+	Size2i size = floating_window_rect.size;
+	int screen = floating_window_screen;
+
+	Size2 wrapped_margins_size = window_wrapper->get_margins_size();
+	Point2 offset_embedded_process = embedded_process->get_global_position() - get_global_position();
+	offset_embedded_process.x += embedded_process->get_margin_size(SIDE_LEFT);
+	offset_embedded_process.y += embedded_process->get_margin_size(SIDE_TOP);
+
+	// Obtain the size around the embedded process control. Usually, the difference between the game view's get_size
+	// and the embedded control should work. However, when the control is hidden and has never been displayed,
+	// the size of the embedded control is not calculated.
+	Size2 old_min_size = embedded_process->get_custom_minimum_size();
+	embedded_process->set_custom_minimum_size(Size2i());
+	Size2 min_size = get_minimum_size();
+	embedded_process->set_custom_minimum_size(old_min_size);
+
+	Point2 size_diff_embedded_process = Point2(0, min_size.y) + embedded_process->get_margins_size();
+
+	if (placement.position != Point2i(INT_MAX, INT_MAX)) {
+		position = placement.position - offset_embedded_process;
+		screen = placement.screen;
+	}
+	if (placement.size != Size2i()) {
+		size = placement.size + size_diff_embedded_process + wrapped_margins_size;
+	}
+	window_wrapper->restore_window_from_saved_position(Rect2(position, size), screen, Rect2i());
+}
+
 void GameView::_play_pressed() {
 	if (!is_feature_enabled) {
 		return;
@@ -248,7 +280,7 @@ void GameView::_play_pressed() {
 		screen_index_before_start = EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index();
 	}
 
-	if (embed_on_play) {
+	if (embed_on_play && _get_embed_available() == EMBED_AVAILABLE) {
 		// It's important to disable the low power mode when unfocused because otherwise
 		// the button in the editor are not responsive and if the user moves the mouse quickly,
 		// the mouse clicks are not registered.
@@ -306,9 +338,18 @@ void GameView::_embedded_process_focused() {
 	}
 }
 
-void GameView::_project_settings_changed() {
+void GameView::_editor_or_project_settings_changed() {
 	// Update the window size and aspect ratio.
 	_update_embed_window_size();
+
+	if (window_wrapper->get_window_enabled()) {
+		_show_update_window_wrapper();
+		if (embedded_process->is_embedding_completed()) {
+			embedded_process->queue_update_embedded_process();
+		}
+	}
+
+	_update_ui();
 }
 
 void GameView::_update_debugger_buttons() {
@@ -368,27 +409,82 @@ void GameView::_embed_options_menu_menu_id_pressed(int p_id) {
 		} break;
 	}
 	_update_embed_menu_options();
+	_update_ui();
 }
 
-void GameView::_keep_aspect_button_pressed() {
-	embedded_process->set_keep_aspect(keep_aspect_button->is_pressed());
+void GameView::_size_mode_button_pressed(int size_mode) {
+	embed_size_mode = (EmbedSizeMode)size_mode;
+
+	_update_embed_menu_options();
+	_update_embed_window_size();
+}
+
+GameView::EmbedAvailability GameView::_get_embed_available() {
+	if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
+		return EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED;
+	}
+
+	EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
+	if (placement.force_fullscreen) {
+		return EMBED_NOT_AVAILABLE_FULLSCREEN;
+	}
+	if (placement.force_maximized) {
+		return EMBED_NOT_AVAILABLE_MAXIMIZED;
+	}
+
+	DisplayServer::WindowMode window_mode = (DisplayServer::WindowMode)(GLOBAL_GET("display/window/size/mode").operator int());
+	if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED) {
+		return EMBED_NOT_AVAILABLE_MINIMIZED;
+	}
+	if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_MAXIMIZED) {
+		return EMBED_NOT_AVAILABLE_MAXIMIZED;
+	}
+	if (window_mode == DisplayServer::WindowMode::WINDOW_MODE_FULLSCREEN || window_mode == DisplayServer::WindowMode::WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
+		return EMBED_NOT_AVAILABLE_FULLSCREEN;
+	}
+
+	return EMBED_AVAILABLE;
 }
 
 void GameView::_update_ui() {
 	bool show_game_size = false;
-	if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
-		state_label->set_text(TTR("Game embedding not available on your OS."));
-	} else if (embedded_process->is_embedding_completed()) {
-		state_label->set_text("");
-		show_game_size = true;
-	} else if (embedded_process->is_embedding_in_progress()) {
-		state_label->set_text(TTR("Game starting..."));
-	} else if (EditorRunBar::get_singleton()->is_playing()) {
-		state_label->set_text(TTR("Game running not embedded."));
-	} else if (embed_on_play) {
-		state_label->set_text(TTR("Press play to start the game."));
+	EmbedAvailability available = _get_embed_available();
+
+	switch (available) {
+		case EMBED_AVAILABLE:
+			if (embedded_process->is_embedding_completed()) {
+				state_label->set_text("");
+				show_game_size = true;
+			} else if (embedded_process->is_embedding_in_progress()) {
+				state_label->set_text(TTR("Game starting..."));
+			} else if (EditorRunBar::get_singleton()->is_playing()) {
+				state_label->set_text(TTR("Game running not embedded."));
+			} else if (embed_on_play) {
+				state_label->set_text(TTR("Press play to start the game."));
+			} else {
+				state_label->set_text(TTR("Embedding is disabled."));
+			}
+			break;
+		case EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED:
+			state_label->set_text(TTR("Game embedding not available on your OS."));
+			break;
+		case EMBED_NOT_AVAILABLE_MINIMIZED:
+			state_label->set_text(TTR("Game embedding not available when the game starts minimized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact."));
+			break;
+		case EMBED_NOT_AVAILABLE_MAXIMIZED:
+			state_label->set_text(TTR("Game embedding not available when the game starts maximized.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact."));
+			break;
+		case EMBED_NOT_AVAILABLE_FULLSCREEN:
+			state_label->set_text(TTR("Game embedding not available when the game starts in fullscreen.\nConsider overriding the window mode project setting with the editor feature tag to Windowed to use game embedding while leaving the exported project intact."));
+			break;
+	}
+
+	if (available == EMBED_AVAILABLE) {
+		if (state_label->has_theme_color_override(SceneStringName(font_color))) {
+			state_label->remove_theme_color_override(SceneStringName(font_color));
+		}
 	} else {
-		state_label->set_text(TTR("Embedding is disabled."));
+		state_label->add_theme_color_override(SceneStringName(font_color), state_label->get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
 	}
 
 	game_size_label->set_visible(show_game_size);
@@ -401,20 +497,22 @@ void GameView::_update_embed_menu_options() {
 
 	// When embed is Off or in single window mode, Make floating is not available.
 	menu->set_item_disabled(menu->get_item_index(EMBED_MAKE_FLOATING_ON_PLAY), !embed_on_play || !EditorNode::get_singleton()->is_multi_window_enabled());
+
+	fixed_size_button->set_pressed(embed_size_mode == SIZE_MODE_FIXED);
+	keep_aspect_button->set_pressed(embed_size_mode == SIZE_MODE_KEEP_ASPECT);
+	stretch_button->set_pressed(embed_size_mode == SIZE_MODE_STRETCH);
 }
 
 void GameView::_update_embed_window_size() {
-	Size2 window_size;
-	window_size.x = GLOBAL_GET("display/window/size/viewport_width");
-	window_size.y = GLOBAL_GET("display/window/size/viewport_height");
-
-	Size2 desired_size;
-	desired_size.x = GLOBAL_GET("display/window/size/window_width_override");
-	desired_size.y = GLOBAL_GET("display/window/size/window_height_override");
-	if (desired_size.x > 0 && desired_size.y > 0) {
-		window_size = desired_size;
+	if (embed_size_mode == SIZE_MODE_FIXED || embed_size_mode == SIZE_MODE_KEEP_ASPECT) {
+		//The embedded process control will need the desired window size.
+		EditorRun::WindowPlacement placement = EditorRun::get_window_placement();
+		embedded_process->set_window_size(placement.size);
+	} else {
+		//Stretch... No need for the window size.
+		embedded_process->set_window_size(Size2i());
 	}
-	embedded_process->set_window_size(window_size);
+	embedded_process->set_keep_aspect(embed_size_mode == SIZE_MODE_KEEP_ASPECT);
 }
 
 void GameView::_hide_selection_toggled(bool p_pressed) {
@@ -475,7 +573,9 @@ void GameView::_notification(int p_what) {
 			select_mode_button[RuntimeNodeSelect::SELECT_MODE_LIST]->set_button_icon(get_editor_theme_icon(SNAME("ListSelect")));
 
 			hide_selection->set_button_icon(get_editor_theme_icon(hide_selection->is_pressed() ? SNAME("GuiVisibilityHidden") : SNAME("GuiVisibilityVisible")));
+			fixed_size_button->set_button_icon(get_editor_theme_icon(SNAME("FixedSize")));
 			keep_aspect_button->set_button_icon(get_editor_theme_icon(SNAME("KeepAspect")));
+			stretch_button->set_button_icon(get_editor_theme_icon(SNAME("Stretch")));
 			embed_options_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
 
 			camera_override_button->set_button_icon(get_editor_theme_icon(SNAME("Camera")));
@@ -487,7 +587,7 @@ void GameView::_notification(int p_what) {
 				// Embedding available.
 				embed_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_on_play", true);
 				make_floating_on_play = EditorSettings::get_singleton()->get_project_metadata("game_view", "make_floating_on_play", true);
-				keep_aspect_button->set_pressed(EditorSettings::get_singleton()->get_project_metadata("game_view", "keep_aspect", true));
+				embed_size_mode = (EmbedSizeMode)(int)EditorSettings::get_singleton()->get_project_metadata("game_view", "embed_size_mode", SIZE_MODE_FIXED);
 				_update_embed_menu_options();
 
 				EditorRunBar::get_singleton()->connect("play_pressed", callable_mp(this, &GameView::_play_pressed));
@@ -495,15 +595,15 @@ void GameView::_notification(int p_what) {
 				EditorRun::instance_starting_callback = _instance_starting_static;
 
 				// Listen for project settings changes to update the window size and aspect ratio.
-				ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_project_settings_changed));
-
-				embedded_process->set_keep_aspect(keep_aspect_button->is_pressed());
+				ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed));
+				EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &GameView::_editor_or_project_settings_changed));
 			} else {
 				// Embedding not available.
 				embedding_separator->hide();
 				embed_options_menu->hide();
+				fixed_size_button->hide();
 				keep_aspect_button->hide();
-				keep_aspect_button->hide();
+				stretch_button->hide();
 			}
 
 			_update_ui();
@@ -558,7 +658,6 @@ Dictionary GameView::get_state() const {
 void GameView::set_window_layout(Ref<ConfigFile> p_layout) {
 	floating_window_rect = p_layout->get_value("GameView", "floating_window_rect", Rect2i());
 	floating_window_screen = p_layout->get_value("GameView", "floating_window_screen", -1);
-	floating_window_screen_rect = p_layout->get_value("GameView", "floating_window_screen_rect", Rect2i());
 }
 
 void GameView::get_window_layout(Ref<ConfigFile> p_layout) {
@@ -568,14 +667,12 @@ void GameView::get_window_layout(Ref<ConfigFile> p_layout) {
 
 	p_layout->set_value("GameView", "floating_window_rect", floating_window_rect);
 	p_layout->set_value("GameView", "floating_window_screen", floating_window_screen);
-	p_layout->set_value("GameView", "floating_window_screen_rect", floating_window_screen_rect);
 }
 
 void GameView::_update_floating_window_settings() {
 	if (window_wrapper->get_window_enabled()) {
 		floating_window_rect = window_wrapper->get_window_rect();
 		floating_window_screen = window_wrapper->get_window_screen();
-		floating_window_screen_rect = DisplayServer::get_singleton()->screen_get_usable_rect(floating_window_screen);
 	}
 }
 
@@ -610,7 +707,7 @@ void GameView::_remote_window_title_changed(String title) {
 }
 
 void GameView::_update_arguments_for_instance(int p_idx, List<String> &r_arguments) {
-	if (p_idx != 0 || !embed_on_play || !DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_EMBEDDING)) {
+	if (p_idx != 0 || !embed_on_play || _get_embed_available() != EMBED_AVAILABLE) {
 		return;
 	}
 
@@ -778,12 +875,26 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
 	embedding_separator = memnew(VSeparator);
 	main_menu_hbox->add_child(embedding_separator);
 
+	fixed_size_button = memnew(Button);
+	main_menu_hbox->add_child(fixed_size_button);
+	fixed_size_button->set_toggle_mode(true);
+	fixed_size_button->set_theme_type_variation("FlatButton");
+	fixed_size_button->set_tooltip_text(TTR("Embedded game size is based on project settings.\nThe 'Keep Aspect' mode is used when the Game Workspace is smaller than the desired size."));
+	fixed_size_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_FIXED));
+
 	keep_aspect_button = memnew(Button);
 	main_menu_hbox->add_child(keep_aspect_button);
 	keep_aspect_button->set_toggle_mode(true);
 	keep_aspect_button->set_theme_type_variation("FlatButton");
 	keep_aspect_button->set_tooltip_text(TTR("Keep the aspect ratio of the embedded game."));
-	keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_keep_aspect_button_pressed));
+	keep_aspect_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_KEEP_ASPECT));
+
+	stretch_button = memnew(Button);
+	main_menu_hbox->add_child(stretch_button);
+	stretch_button->set_toggle_mode(true);
+	stretch_button->set_theme_type_variation("FlatButton");
+	stretch_button->set_tooltip_text(TTR("Embedded game size stretches to fit the Game Workspace."));
+	stretch_button->connect(SceneStringName(pressed), callable_mp(this, &GameView::_size_mode_button_pressed).bind(SIZE_MODE_STRETCH));
 
 	embed_options_menu = memnew(MenuButton);
 	main_menu_hbox->add_child(embed_options_menu);
@@ -817,8 +928,14 @@ GameView::GameView(Ref<GameViewDebugger> p_debugger, WindowWrapper *p_wrapper) {
 	embedded_process->connect("embedded_process_focused", callable_mp(this, &GameView::_embedded_process_focused));
 	embedded_process->set_custom_minimum_size(Size2i(100, 100));
 
+	MarginContainer *state_container = memnew(MarginContainer);
+	state_container->add_theme_constant_override("margin_left", 8 * EDSCALE);
+	state_container->add_theme_constant_override("margin_right", 8 * EDSCALE);
+	state_container->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
+	panel->add_child(state_container);
+
 	state_label = memnew(Label());
-	panel->add_child(state_label);
+	state_container->add_child(state_label);
 	state_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
 	state_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
 	state_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD);
diff --git a/editor/plugins/game_view_plugin.h b/editor/plugins/game_view_plugin.h
index 959da73dcde8..7d34b050049c 100644
--- a/editor/plugins/game_view_plugin.h
+++ b/editor/plugins/game_view_plugin.h
@@ -94,6 +94,20 @@ class GameView : public VBoxContainer {
 		EMBED_MAKE_FLOATING_ON_PLAY,
 	};
 
+	enum EmbedSizeMode {
+		SIZE_MODE_FIXED,
+		SIZE_MODE_KEEP_ASPECT,
+		SIZE_MODE_STRETCH,
+	};
+
+	enum EmbedAvailability {
+		EMBED_AVAILABLE,
+		EMBED_NOT_AVAILABLE_FEATURE_NOT_SUPPORTED,
+		EMBED_NOT_AVAILABLE_MINIMIZED,
+		EMBED_NOT_AVAILABLE_MAXIMIZED,
+		EMBED_NOT_AVAILABLE_FULLSCREEN,
+	};
+
 	inline static GameView *singleton = nullptr;
 
 	Ref<GameViewDebugger> debugger;
@@ -106,10 +120,10 @@ class GameView : public VBoxContainer {
 
 	bool embed_on_play = true;
 	bool make_floating_on_play = true;
+	EmbedSizeMode embed_size_mode = SIZE_MODE_FIXED;
 
 	Rect2i floating_window_rect;
 	int floating_window_screen = -1;
-	Rect2i floating_window_screen_rect;
 
 	Button *suspend_button = nullptr;
 	Button *next_frame_button = nullptr;
@@ -123,10 +137,11 @@ class GameView : public VBoxContainer {
 	MenuButton *camera_override_menu = nullptr;
 
 	VSeparator *embedding_separator = nullptr;
+	Button *fixed_size_button = nullptr;
 	Button *keep_aspect_button = nullptr;
+	Button *stretch_button = nullptr;
 	MenuButton *embed_options_menu = nullptr;
 	Label *game_size_label = nullptr;
-
 	Panel *panel = nullptr;
 	EmbeddedProcess *embedded_process = nullptr;
 	Label *state_label = nullptr;
@@ -140,7 +155,7 @@ class GameView : public VBoxContainer {
 	void _node_type_pressed(int p_option);
 	void _select_mode_pressed(int p_option);
 	void _embed_options_menu_menu_id_pressed(int p_id);
-	void _keep_aspect_button_pressed();
+	void _size_mode_button_pressed(int size_mode);
 
 	void _play_pressed();
 	static void _instance_starting_static(int p_idx, List<String> &r_arguments);
@@ -150,12 +165,14 @@ class GameView : public VBoxContainer {
 	void _embedding_failed();
 	void _embedded_process_updated();
 	void _embedded_process_focused();
-	void _project_settings_changed();
+	void _editor_or_project_settings_changed();
 
+	EmbedAvailability _get_embed_available();
 	void _update_ui();
 	void _update_embed_menu_options();
 	void _update_embed_window_size();
 	void _update_arguments_for_instance(int p_idx, List<String> &r_arguments);
+	void _show_update_window_wrapper();
 
 	void _hide_selection_toggled(bool p_pressed);
 
diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp
index 37a3efc91e90..14cc10de1aa6 100644
--- a/editor/window_wrapper.cpp
+++ b/editor/window_wrapper.cpp
@@ -324,6 +324,14 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) {
 	}
 }
 
+Size2 WindowWrapper::get_margins_size() {
+	if (!margins) {
+		return Size2();
+	}
+
+	return Size2(margins->get_margin_size(SIDE_LEFT) + margins->get_margin_size(SIDE_RIGHT), margins->get_margin_size(SIDE_TOP) + margins->get_margin_size(SIDE_RIGHT));
+}
+
 void WindowWrapper::grab_window_focus() {
 	if (get_window_enabled() && is_visible()) {
 		window->grab_focus();
diff --git a/editor/window_wrapper.h b/editor/window_wrapper.h
index 3f4e9385861c..92dffabf2c84 100644
--- a/editor/window_wrapper.h
+++ b/editor/window_wrapper.h
@@ -82,6 +82,7 @@ class WindowWrapper : public MarginContainer {
 
 	void set_window_title(const String &p_title);
 	void set_margins_enabled(bool p_enabled);
+	Size2 get_margins_size();
 	void grab_window_focus();
 
 	WindowWrapper();


Test patch: