Merge pull request #101807 from Hilderin/fix-embedded-game-size

Fix Embedded Game Size
This commit is contained in:
Thaddeus Crews 2025-01-22 09:19:29 -06:00
commit fef1bc664a
No known key found for this signature in database
GPG key ID: 62181B86FE9E5D84
11 changed files with 343 additions and 151 deletions

View file

@ -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 = "";

View file

@ -45,6 +45,14 @@ public:
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 @@ public:
int get_child_process_count() const { return pids.size(); }
OS::ProcessID get_current_process() const;
static WindowPlacement get_window_placement();
EditorRun();
};

View file

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 600 B

View file

@ -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>
<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>

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 563 B

1
editor/icons/Stretch.svg Normal file
View file

@ -0,0 +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>

After

Width:  |  Height:  |  Size: 492 B

View file

@ -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();

View file

@ -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 @@ public:
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;

View file

@ -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;
}
@ -784,12 +881,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);
@ -823,8 +934,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);

View file

@ -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);

View file

@ -325,6 +325,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();

View file

@ -85,6 +85,7 @@ public:
void set_window_title(const String &p_title);
void set_margins_enabled(bool p_enabled);
Size2 get_margins_size();
void grab_window_focus();
void set_override_close_request(bool p_enabled);