summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/config/project_settings.cpp1
-rw-r--r--doc/classes/ProjectSettings.xml4
-rw-r--r--doc/classes/Window.xml9
-rw-r--r--editor/plugin_config_dialog.cpp114
-rw-r--r--editor/plugin_config_dialog.h16
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp268
-rw-r--r--editor/plugins/node_3d_editor_plugin.h11
-rw-r--r--main/main.cpp7
-rw-r--r--platform/windows/detect.py3
-rw-r--r--scene/main/window.cpp46
-rw-r--r--scene/main/window.h10
-rw-r--r--scene/resources/environment.cpp2
12 files changed, 291 insertions, 200 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 8ea92f6692..1bfb745662 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1349,6 +1349,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/mode", PROPERTY_HINT_ENUM, "disabled,canvas_items,viewport"), "disabled");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/aspect", PROPERTY_HINT_ENUM, "ignore,keep,keep_width,keep_height,expand"), "keep");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "display/window/stretch/scale", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), 1.0);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional");
GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384);
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 52419882b0..3eb1959a44 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -813,6 +813,10 @@
[b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art esthetic.
</member>
<member name="display/window/stretch/scale" type="float" setter="" getter="" default="1.0">
+ The scale factor multiplier to use for 2D elements. This multiplies the final scale factor determined by [member display/window/stretch/mode]. If using the [b]Disabled[/b] stretch mode, this scale factor is applied as-is. This can be adjusted to make the UI easier to read on certain displays.
+ </member>
+ <member name="display/window/stretch/scale_mode" type="String" setter="" getter="" default="&quot;fractional&quot;">
+ The policy to use to determine the final scale factor for 2D elements. This affects how [member display/window/stretch/scale] is applied, in addition to the automatic scale factor determined by [member display/window/stretch/mode].
</member>
<member name="display/window/subwindows/embed_subwindows" type="bool" setter="" getter="" default="true">
If [code]true[/code] subwindows are embedded in the main window.
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 82498b9ba4..92cd11d720 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -569,6 +569,9 @@
<member name="content_scale_size" type="Vector2i" setter="set_content_scale_size" getter="get_content_scale_size" default="Vector2i(0, 0)">
Base size of the content (i.e. nodes that are drawn inside the window). If non-zero, [Window]'s content will be scaled when the window is resized to a different size.
</member>
+ <member name="content_scale_stretch" type="int" setter="set_content_scale_stretch" getter="get_content_scale_stretch" enum="Window.ContentScaleStretch" default="0">
+ The policy to use to determine the final scale factor for 2D elements. This affects how [member content_scale_factor] is applied, in addition to the automatic scale factor determined by [member content_scale_size].
+ </member>
<member name="current_screen" type="int" setter="set_current_screen" getter="get_current_screen">
The screen the window is currently on.
</member>
@@ -840,6 +843,12 @@
<constant name="CONTENT_SCALE_ASPECT_EXPAND" value="4" enum="ContentScaleAspect">
The content's aspect will be preserved. If the target size has different aspect from the base one, the content will stay in the top-left corner and add an extra visible area in the stretched space.
</constant>
+ <constant name="CONTENT_SCALE_STRETCH_FRACTIONAL" value="0" enum="ContentScaleStretch">
+ The content will be stretched according to a fractional factor. This fills all the space available in the window, but allows "pixel wobble" to occur due to uneven pixel scaling.
+ </constant>
+ <constant name="CONTENT_SCALE_STRETCH_INTEGER" value="1" enum="ContentScaleStretch">
+ The content will be stretched only according to an integer factor, preserving sharp pixels. This may leave a black background visible on the window's edges depending on the window size.
+ </constant>
<constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection">
Automatic layout direction, determined from the parent window layout direction.
</constant>
diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp
index 97398b8b69..7d7b156bd0 100644
--- a/editor/plugin_config_dialog.cpp
+++ b/editor/plugin_config_dialog.cpp
@@ -35,6 +35,7 @@
#include "editor/editor_node.h"
#include "editor/editor_plugin.h"
#include "editor/editor_scale.h"
+#include "editor/gui/editor_validation_panel.h"
#include "editor/project_settings_editor.h"
#include "scene/gui/grid_container.h"
@@ -96,52 +97,28 @@ void PluginConfigDialog::_on_canceled() {
_clear_fields();
}
-void PluginConfigDialog::_on_language_changed(const int) {
- _on_required_text_changed(String());
-}
-
-void PluginConfigDialog::_on_required_text_changed(const String &) {
+void PluginConfigDialog::_on_required_text_changed() {
int lang_idx = script_option_edit->get_selected();
String ext = ScriptServer::get_language(lang_idx)->get_extension();
- Ref<Texture2D> valid_icon = get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons"));
- Ref<Texture2D> invalid_icon = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons"));
-
- // Set variables to assume all is valid
- bool is_valid = true;
- name_validation->set_texture(valid_icon);
- subfolder_validation->set_texture(valid_icon);
- script_validation->set_texture(valid_icon);
- name_validation->set_tooltip_text("");
- subfolder_validation->set_tooltip_text("");
- script_validation->set_tooltip_text("");
-
- // Change valid status to invalid depending on conditions.
- Vector<String> errors;
if (name_edit->get_text().is_empty()) {
- is_valid = false;
- name_validation->set_texture(invalid_icon);
- name_validation->set_tooltip_text(TTR("Plugin name cannot be blank."));
+ validation_panel->set_message(MSG_ID_PLUGIN, TTR("Plugin name cannot be blank."), EditorValidationPanel::MSG_ERROR);
}
if ((!script_edit->get_text().get_extension().is_empty() && script_edit->get_text().get_extension() != ext) || script_edit->get_text().ends_with(".")) {
- is_valid = false;
- script_validation->set_texture(invalid_icon);
- script_validation->set_tooltip_text(vformat(TTR("Script extension must match chosen language extension (.%s)."), ext));
+ validation_panel->set_message(MSG_ID_SCRIPT, vformat(TTR("Script extension must match chosen language extension (.%s)."), ext), EditorValidationPanel::MSG_ERROR);
}
- if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) {
- is_valid = false;
- subfolder_validation->set_texture(invalid_icon);
- subfolder_validation->set_tooltip_text(TTR("Subfolder name is not a valid folder name."));
- } else {
- String path = "res://addons/" + _get_subfolder();
- if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode.
- is_valid = false;
- subfolder_validation->set_texture(invalid_icon);
- subfolder_validation->set_tooltip_text(TTR("Subfolder cannot be one which already exists."));
+ if (subfolder_edit->is_visible()) {
+ if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) {
+ validation_panel->set_message(MSG_ID_SUBFOLDER, TTR("Subfolder name is not a valid folder name."), EditorValidationPanel::MSG_ERROR);
+ } else {
+ String path = "res://addons/" + _get_subfolder();
+ if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode.
+ validation_panel->set_message(MSG_ID_SUBFOLDER, TTR("Subfolder cannot be one which already exists."), EditorValidationPanel::MSG_ERROR);
+ }
}
+ } else {
+ validation_panel->set_message(MSG_ID_SUBFOLDER, "", EditorValidationPanel::MSG_OK);
}
-
- get_ok_button()->set_disabled(!is_valid);
}
String PluginConfigDialog::_get_subfolder() {
@@ -182,23 +159,20 @@ void PluginConfigDialog::config(const String &p_config_path) {
_edit_mode = true;
active_edit->hide();
- Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 2))->hide();
+ Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 1))->hide();
subfolder_edit->hide();
- subfolder_validation->hide();
- Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 2))->hide();
+ Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 1))->hide();
set_title(TTR("Edit a Plugin"));
} else {
_clear_fields();
_edit_mode = false;
active_edit->show();
- Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 2))->show();
+ Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 1))->show();
subfolder_edit->show();
- subfolder_validation->show();
- Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 2))->show();
+ Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 1))->show();
set_title(TTR("Create a Plugin"));
}
- // Simulate text changing so the errors populate.
- _on_required_text_changed("");
+ validation_panel->update();
get_ok_button()->set_disabled(!_edit_mode);
set_ok_button_text(_edit_mode ? TTR("Update") : TTR("Create"));
@@ -218,7 +192,7 @@ PluginConfigDialog::PluginConfigDialog() {
add_child(vbox);
GridContainer *grid = memnew(GridContainer);
- grid->set_columns(3);
+ grid->set_columns(2);
grid->set_v_size_flags(Control::SIZE_EXPAND_FILL);
vbox->add_child(grid);
@@ -228,12 +202,7 @@ PluginConfigDialog::PluginConfigDialog() {
name_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(name_lb);
- name_validation = memnew(TextureRect);
- name_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
- grid->add_child(name_validation);
-
name_edit = memnew(LineEdit);
- name_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed));
name_edit->set_placeholder("MyPlugin");
name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(name_edit);
@@ -244,14 +213,9 @@ PluginConfigDialog::PluginConfigDialog() {
subfolder_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(subfolder_lb);
- subfolder_validation = memnew(TextureRect);
- subfolder_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
- grid->add_child(subfolder_validation);
-
subfolder_edit = memnew(LineEdit);
subfolder_edit->set_placeholder("\"my_plugin\" -> res://addons/my_plugin");
subfolder_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- subfolder_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed));
grid->add_child(subfolder_edit);
// Description
@@ -260,9 +224,6 @@ PluginConfigDialog::PluginConfigDialog() {
desc_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(desc_lb);
- Control *desc_spacer = memnew(Control);
- grid->add_child(desc_spacer);
-
desc_edit = memnew(TextEdit);
desc_edit->set_custom_minimum_size(Size2(400, 80) * EDSCALE);
desc_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY);
@@ -276,9 +237,6 @@ PluginConfigDialog::PluginConfigDialog() {
author_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(author_lb);
- Control *author_spacer = memnew(Control);
- grid->add_child(author_spacer);
-
author_edit = memnew(LineEdit);
author_edit->set_placeholder("Godette");
author_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -290,9 +248,6 @@ PluginConfigDialog::PluginConfigDialog() {
version_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(version_lb);
- Control *version_spacer = memnew(Control);
- grid->add_child(version_spacer);
-
version_edit = memnew(LineEdit);
version_edit->set_placeholder("1.0");
version_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -304,9 +259,6 @@ PluginConfigDialog::PluginConfigDialog() {
script_option_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(script_option_lb);
- Control *script_opt_spacer = memnew(Control);
- grid->add_child(script_opt_spacer);
-
script_option_edit = memnew(OptionButton);
int default_lang = 0;
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -318,7 +270,6 @@ PluginConfigDialog::PluginConfigDialog() {
}
script_option_edit->select(default_lang);
grid->add_child(script_option_edit);
- script_option_edit->connect("item_selected", callable_mp(this, &PluginConfigDialog::_on_language_changed));
// Plugin Script Name
Label *script_lb = memnew(Label);
@@ -326,12 +277,7 @@ PluginConfigDialog::PluginConfigDialog() {
script_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(script_lb);
- script_validation = memnew(TextureRect);
- script_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
- grid->add_child(script_validation);
-
script_edit = memnew(LineEdit);
- script_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed));
script_edit->set_placeholder("\"plugin.gd\" -> res://addons/my_plugin/plugin.gd");
script_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_child(script_edit);
@@ -343,12 +289,26 @@ PluginConfigDialog::PluginConfigDialog() {
active_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
grid->add_child(active_lb);
- Control *active_spacer = memnew(Control);
- grid->add_child(active_spacer);
-
active_edit = memnew(CheckBox);
active_edit->set_pressed(true);
grid->add_child(active_edit);
+
+ Control *spacing = memnew(Control);
+ vbox->add_child(spacing);
+ spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE));
+
+ validation_panel = memnew(EditorValidationPanel);
+ vbox->add_child(validation_panel);
+ validation_panel->add_line(MSG_ID_PLUGIN, TTR("Plugin name is valid."));
+ validation_panel->add_line(MSG_ID_SCRIPT, TTR("Script extension is valid."));
+ validation_panel->add_line(MSG_ID_SUBFOLDER, TTR("Subfolder name is valid."));
+ validation_panel->set_update_callback(callable_mp(this, &PluginConfigDialog::_on_required_text_changed));
+ validation_panel->set_accept_button(get_ok_button());
+
+ script_option_edit->connect("item_selected", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
+ name_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
+ subfolder_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
+ script_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1));
}
PluginConfigDialog::~PluginConfigDialog() {
diff --git a/editor/plugin_config_dialog.h b/editor/plugin_config_dialog.h
index 50b901a39e..1221d347a7 100644
--- a/editor/plugin_config_dialog.h
+++ b/editor/plugin_config_dialog.h
@@ -35,12 +35,21 @@
#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
+#include "scene/gui/panel_container.h"
#include "scene/gui/text_edit.h"
#include "scene/gui/texture_rect.h"
+class EditorValidationPanel;
+
class PluginConfigDialog : public ConfirmationDialog {
GDCLASS(PluginConfigDialog, ConfirmationDialog);
+ enum {
+ MSG_ID_PLUGIN,
+ MSG_ID_SUBFOLDER,
+ MSG_ID_SCRIPT,
+ };
+
LineEdit *name_edit = nullptr;
LineEdit *subfolder_edit = nullptr;
TextEdit *desc_edit = nullptr;
@@ -50,17 +59,14 @@ class PluginConfigDialog : public ConfirmationDialog {
LineEdit *script_edit = nullptr;
CheckBox *active_edit = nullptr;
- TextureRect *name_validation = nullptr;
- TextureRect *subfolder_validation = nullptr;
- TextureRect *script_validation = nullptr;
+ EditorValidationPanel *validation_panel = nullptr;
bool _edit_mode = false;
void _clear_fields();
void _on_confirmed();
void _on_canceled();
- void _on_language_changed(const int p_language);
- void _on_required_text_changed(const String &p_text);
+ void _on_required_text_changed();
String _get_subfolder();
static String _to_absolute_plugin_path(const String &p_plugin_name);
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index d05db7aa63..65563bd1a3 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1675,6 +1675,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) {
cancel_transform();
+ break;
}
if (b->is_pressed()) {
@@ -2007,7 +2008,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_edit.mode = TRANSFORM_TRANSLATE;
}
- if (_edit.mode == TRANSFORM_NONE) {
+ if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {
return;
}
@@ -2145,6 +2146,43 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
return;
}
+ if (_edit.instant) {
+ // In a Blender-style transform, numbers set the magnitude of the transform.
+ // E.g. pressing g4.5x means "translate 4.5 units along the X axis".
+ // Use the Unicode value because we care about the text, not the actual keycode.
+ // This ensures numbers work consistently across different keyboard language layouts.
+ bool processed = true;
+ Key key = k->get_physical_keycode();
+ char32_t unicode = k->get_unicode();
+ if (unicode >= '0' && unicode <= '9') {
+ uint32_t value = uint32_t(unicode - Key::KEY_0);
+ if (_edit.numeric_next_decimal < 0) {
+ _edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--);
+ } else {
+ _edit.numeric_input = _edit.numeric_input * 10 + value;
+ }
+ update_transform_numeric();
+ } else if (unicode == '-') {
+ _edit.numeric_negate = !_edit.numeric_negate;
+ update_transform_numeric();
+ } else if (unicode == '.') {
+ if (_edit.numeric_next_decimal == 0) {
+ _edit.numeric_next_decimal = -1;
+ }
+ } else if (key == Key::ENTER || key == Key::KP_ENTER || key == Key::SPACE) {
+ commit_transform();
+ } else {
+ processed = false;
+ }
+
+ if (processed) {
+ // Ignore mouse inputs once we receive a numeric input.
+ set_process_input(false);
+ accept_event();
+ return;
+ }
+ }
+
if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) {
const Key code = k->get_physical_keycode();
if (code >= Key::KEY_0 && code <= Key::KEY_9) {
@@ -2165,26 +2203,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
} else {
// We're actively transforming, handle keys specially
TransformPlane new_plane = TRANSFORM_VIEW;
- String new_message;
if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) {
new_plane = TRANSFORM_X_AXIS;
- new_message = TTR("X-Axis Transform.");
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) {
new_plane = TRANSFORM_Y_AXIS;
- new_message = TTR("Y-Axis Transform.");
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) {
new_plane = TRANSFORM_Z_AXIS;
- new_message = TTR("Z-Axis Transform.");
} else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense
if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) {
new_plane = TRANSFORM_YZ;
- new_message = TTR("YZ-Plane Transform.");
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) {
new_plane = TRANSFORM_XZ;
- new_message = TTR("XZ-Plane Transform.");
} else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) {
new_plane = TRANSFORM_XY;
- new_message = TTR("XY-Plane Transform.");
}
}
@@ -2201,8 +2232,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
_edit.plane = TRANSFORM_VIEW;
spatial_editor->set_local_coords_enabled(false);
}
- update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));
- set_message(new_message, 2);
+ if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) {
+ update_transform_numeric();
+ } else {
+ update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT));
+ }
accept_event();
return;
}
@@ -4575,6 +4609,43 @@ void Node3DEditorViewport::commit_transform() {
set_message("");
}
+void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) {
+ bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW);
+ List<Node *> &selection = editor_selection->get_selected_node_list();
+ for (Node *E : selection) {
+ Node3D *sp = Object::cast_to<Node3D>(E);
+ if (!sp) {
+ continue;
+ }
+
+ Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
+ if (!se) {
+ continue;
+ }
+
+ if (sp->has_meta("_edit_lock_")) {
+ continue;
+ }
+
+ if (se->gizmo.is_valid()) {
+ for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
+ Transform3D xform = GE.value;
+ Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo.
+ if (!local_coords) {
+ new_xform = se->original.affine_inverse() * new_xform;
+ }
+ se->gizmo->set_subgizmo_transform(GE.key, new_xform);
+ }
+ } else {
+ Transform3D new_xform = _compute_transform(_edit.mode, se->original, se->original_local, p_motion, p_snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);
+ _transform_gizmo_apply(se->sp, new_xform, local_coords);
+ }
+ }
+
+ spatial_editor->update_transform_gizmo();
+ surface->queue_redraw();
+}
+
// Update the current transform operation in response to an input.
void Node3DEditorViewport::update_transform(bool p_shift) {
Vector3 ray_pos = _get_ray_pos(_edit.mouse_pos);
@@ -4670,43 +4741,11 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
if (local_coords) {
+ // TODO: needed?
motion = _edit.original.basis.inverse().xform(motion);
}
- List<Node *> &selection = editor_selection->get_selected_node_list();
- for (Node *E : selection) {
- Node3D *sp = Object::cast_to<Node3D>(E);
- if (!sp) {
- continue;
- }
-
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
-
- if (sp->has_meta("_edit_lock_")) {
- continue;
- }
-
- if (se->gizmo.is_valid()) {
- for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
- Transform3D xform = GE.value;
- Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo.
- if (!local_coords) {
- new_xform = se->original.affine_inverse() * new_xform;
- }
- se->gizmo->set_subgizmo_transform(GE.key, new_xform);
- }
- } else {
- Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);
- _transform_gizmo_apply(se->sp, new_xform, local_coords);
- }
- }
-
- spatial_editor->update_transform_gizmo();
- surface->queue_redraw();
-
+ apply_transform(motion, snap);
} break;
case TRANSFORM_TRANSLATE: {
@@ -4776,38 +4815,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);
}
- List<Node *> &selection = editor_selection->get_selected_node_list();
- for (Node *E : selection) {
- Node3D *sp = Object::cast_to<Node3D>(E);
- if (!sp) {
- continue;
- }
-
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
-
- if (sp->has_meta("_edit_lock_")) {
- continue;
- }
-
- if (se->gizmo.is_valid()) {
- for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
- Transform3D xform = GE.value;
- Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo.
- new_xform = se->original.affine_inverse() * new_xform;
- se->gizmo->set_subgizmo_transform(GE.key, new_xform);
- }
- } else {
- Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS);
- _transform_gizmo_apply(se->sp, new_xform, false);
- }
- }
-
- spatial_editor->update_transform_gizmo();
- surface->queue_redraw();
-
+ apply_transform(motion, snap);
} break;
case TRANSFORM_ROTATE: {
@@ -4876,53 +4884,85 @@ void Node3DEditorViewport::update_transform(bool p_shift) {
bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW
- List<Node *> &selection = editor_selection->get_selected_node_list();
- for (Node *E : selection) {
- Node3D *sp = Object::cast_to<Node3D>(E);
- if (!sp) {
- continue;
- }
-
- Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp);
- if (!se) {
- continue;
- }
-
- if (sp->has_meta("_edit_lock_")) {
- continue;
- }
-
- Vector3 compute_axis = local_coords ? local_axis : global_axis;
- if (se->gizmo.is_valid()) {
- for (KeyValue<int, Transform3D> &GE : se->subgizmos) {
- Transform3D xform = GE.value;
-
- Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords, true); // Force orthogonal with subgizmo.
- if (!local_coords) {
- new_xform = se->original.affine_inverse() * new_xform;
- }
- se->gizmo->set_subgizmo_transform(GE.key, new_xform);
- }
- } else {
- Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS);
- _transform_gizmo_apply(se->sp, new_xform, local_coords);
- }
- }
-
- spatial_editor->update_transform_gizmo();
- surface->queue_redraw();
-
+ Vector3 compute_axis = local_coords ? local_axis : global_axis;
+ apply_transform(compute_axis, angle);
} break;
default: {
}
}
}
-// Perform cleanup after a transform operation is committed or canceled.
+void Node3DEditorViewport::update_transform_numeric() {
+ Vector3 motion;
+ switch (_edit.plane) {
+ case TRANSFORM_VIEW: {
+ switch (_edit.mode) {
+ case TRANSFORM_TRANSLATE:
+ motion = Vector3(1, 0, 0);
+ break;
+ case TRANSFORM_ROTATE:
+ motion = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized();
+ break;
+ case TRANSFORM_SCALE:
+ motion = Vector3(1, 1, 1);
+ break;
+ case TRANSFORM_NONE:
+ ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");
+ }
+ break;
+ }
+ case TRANSFORM_X_AXIS:
+ motion = Vector3(1, 0, 0);
+ break;
+ case TRANSFORM_Y_AXIS:
+ motion = Vector3(0, 1, 0);
+ break;
+ case TRANSFORM_Z_AXIS:
+ motion = Vector3(0, 0, 1);
+ break;
+ case TRANSFORM_XY:
+ motion = Vector3(1, 1, 0);
+ break;
+ case TRANSFORM_XZ:
+ motion = Vector3(1, 0, 1);
+ break;
+ case TRANSFORM_YZ:
+ motion = Vector3(0, 1, 1);
+ break;
+ }
+
+ double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1);
+ double extra = 0.0;
+ switch (_edit.mode) {
+ case TRANSFORM_TRANSLATE:
+ motion *= value;
+ set_message(vformat(TTR("Translating %s."), motion));
+ break;
+ case TRANSFORM_ROTATE:
+ extra = Math::deg_to_rad(value);
+ set_message(vformat(TTR("Rotating %f degrees."), value));
+ break;
+ case TRANSFORM_SCALE:
+ // To halve the size of an object in Blender, you scale it by 0.5.
+ // Doing the same in Godot is considered scaling it by -0.5.
+ motion *= (value - 1.0);
+ set_message(vformat(TTR("Scaling %s."), motion));
+ break;
+ case TRANSFORM_NONE:
+ ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric.");
+ }
+
+ apply_transform(motion, extra);
+}
+
+// Perform cleanup after a transform operation is committed or cancelled.
void Node3DEditorViewport::finish_transform() {
- spatial_editor->set_local_coords_enabled(_edit.original_local);
_edit.mode = TRANSFORM_NONE;
_edit.instant = false;
+ _edit.numeric_input = 0;
+ _edit.numeric_next_decimal = 0;
+ _edit.numeric_negate = false;
+ spatial_editor->set_local_coords_enabled(_edit.original_local);
spatial_editor->update_transform_gizmo();
surface->queue_redraw();
set_process_input(false);
diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h
index 79674bdd64..e58e224ff4 100644
--- a/editor/plugins/node_3d_editor_plugin.h
+++ b/editor/plugins/node_3d_editor_plugin.h
@@ -349,6 +349,15 @@ private:
Variant gizmo_initial_value;
bool original_local;
bool instant;
+
+ // Numeric blender-style transforms (e.g. 'g5x').
+ // numeric_input tracks the current input value, e.g. 1.23.
+ // numeric_negate indicates whether '-' has been pressed to negate the value
+ // while numeric_next_decimal is 0, numbers are input before the decimal point
+ // after pressing '.', numeric next decimal changes to -1, and decrements after each press.
+ double numeric_input = 0.0;
+ bool numeric_negate = false;
+ int numeric_next_decimal = 0;
} _edit;
struct Cursor {
@@ -445,7 +454,9 @@ private:
void begin_transform(TransformMode p_mode, bool instant);
void commit_transform();
+ void apply_transform(Vector3 p_motion, double p_snap);
void update_transform(bool p_shift);
+ void update_transform_numeric();
void finish_transform();
void register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical = false);
diff --git a/main/main.cpp b/main/main.cpp
index 4bb5e7bf13..220afda5de 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -3122,6 +3122,7 @@ bool Main::start() {
Size2i stretch_size = Size2i(GLOBAL_GET("display/window/size/viewport_width"),
GLOBAL_GET("display/window/size/viewport_height"));
real_t stretch_scale = GLOBAL_GET("display/window/stretch/scale");
+ String stretch_scale_mode = GLOBAL_GET("display/window/stretch/scale_mode");
Window::ContentScaleMode cs_sm = Window::CONTENT_SCALE_MODE_DISABLED;
if (stretch_mode == "canvas_items") {
@@ -3141,8 +3142,14 @@ bool Main::start() {
cs_aspect = Window::CONTENT_SCALE_ASPECT_EXPAND;
}
+ Window::ContentScaleStretch cs_stretch = Window::CONTENT_SCALE_STRETCH_FRACTIONAL;
+ if (stretch_scale_mode == "integer") {
+ cs_stretch = Window::CONTENT_SCALE_STRETCH_INTEGER;
+ }
+
sml->get_root()->set_content_scale_mode(cs_sm);
sml->get_root()->set_content_scale_aspect(cs_aspect);
+ sml->get_root()->set_content_scale_stretch(cs_stretch);
sml->get_root()->set_content_scale_size(stretch_size);
sml->get_root()->set_content_scale_factor(stretch_scale);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index bec1fd2cb6..9548939695 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -355,6 +355,9 @@ def configure_msvc(env, vcvars_msvc_config):
else:
env.AppendUnique(CCFLAGS=["/MD"])
+ # MSVC incremental linking is broken and _increases_ link time (GH-77968).
+ env.Append(LINKFLAGS=["/INCREMENTAL:NO"])
+
if env["arch"] == "x86_32":
env["x86_libtheora_opt_vc"] = True
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 875b53203a..0b95406b94 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1001,6 +1001,17 @@ void Window::_update_viewport_size() {
float font_oversampling = 1.0;
window_transform = Transform2D();
+ if (content_scale_stretch == Window::CONTENT_SCALE_STRETCH_INTEGER) {
+ // We always want to make sure that the content scale factor is a whole
+ // number, else there will be pixel wobble no matter what.
+ content_scale_factor = Math::floor(content_scale_factor);
+
+ // A content scale factor of zero is pretty useless.
+ if (content_scale_factor < 1) {
+ content_scale_factor = 1;
+ }
+ }
+
if (content_scale_mode == CONTENT_SCALE_MODE_DISABLED || content_scale_size.x == 0 || content_scale_size.y == 0) {
font_oversampling = content_scale_factor;
final_size = size;
@@ -1054,13 +1065,26 @@ void Window::_update_viewport_size() {
screen_size = screen_size.floor();
viewport_size = viewport_size.floor();
+ if (content_scale_stretch == Window::CONTENT_SCALE_STRETCH_INTEGER) {
+ Size2i screen_scale = (screen_size / viewport_size).floor();
+ int scale_factor = MIN(screen_scale.x, screen_scale.y);
+
+ if (scale_factor < 1) {
+ scale_factor = 1;
+ }
+
+ screen_size = viewport_size * scale_factor;
+ }
+
Size2 margin;
Size2 offset;
- if (content_scale_aspect != CONTENT_SCALE_ASPECT_EXPAND && screen_size.x < video_mode.x) {
+ if (screen_size.x < video_mode.x) {
margin.x = Math::round((video_mode.x - screen_size.x) / 2.0);
offset.x = Math::round(margin.x * viewport_size.y / screen_size.y);
- } else if (content_scale_aspect != CONTENT_SCALE_ASPECT_EXPAND && screen_size.y < video_mode.y) {
+ }
+
+ if (screen_size.y < video_mode.y) {
margin.y = Math::round((video_mode.y - screen_size.y) / 2.0);
offset.y = Math::round(margin.y * viewport_size.x / screen_size.x);
}
@@ -1337,6 +1361,15 @@ Window::ContentScaleAspect Window::get_content_scale_aspect() const {
return content_scale_aspect;
}
+void Window::set_content_scale_stretch(ContentScaleStretch p_stretch) {
+ content_scale_stretch = p_stretch;
+ _update_viewport_size();
+}
+
+Window::ContentScaleStretch Window::get_content_scale_stretch() const {
+ return content_scale_stretch;
+}
+
void Window::set_content_scale_factor(real_t p_factor) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(p_factor <= 0);
@@ -2593,6 +2626,9 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_content_scale_aspect", "aspect"), &Window::set_content_scale_aspect);
ClassDB::bind_method(D_METHOD("get_content_scale_aspect"), &Window::get_content_scale_aspect);
+ ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch);
+ ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch);
+
ClassDB::bind_method(D_METHOD("set_content_scale_factor", "factor"), &Window::set_content_scale_factor);
ClassDB::bind_method(D_METHOD("get_content_scale_factor"), &Window::get_content_scale_factor);
@@ -2711,7 +2747,8 @@ void Window::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_mode", PROPERTY_HINT_ENUM, "Disabled,Canvas Items,Viewport"), "set_content_scale_mode", "get_content_scale_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_aspect", PROPERTY_HINT_ENUM, "Ignore,Keep,Keep Width,Keep Height,Expand"), "set_content_scale_aspect", "get_content_scale_aspect");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor"), "set_content_scale_factor", "get_content_scale_factor");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_stretch", PROPERTY_HINT_ENUM, "Fractional,Integer"), "set_content_scale_stretch", "get_content_scale_stretch");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), "set_content_scale_factor", "get_content_scale_factor");
ADD_GROUP("Localization", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating");
@@ -2763,6 +2800,9 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(CONTENT_SCALE_ASPECT_KEEP_HEIGHT);
BIND_ENUM_CONSTANT(CONTENT_SCALE_ASPECT_EXPAND);
+ BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_FRACTIONAL);
+ BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_INTEGER);
+
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE);
BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR);
diff --git a/scene/main/window.h b/scene/main/window.h
index 18ddd89662..d5b8cd4ead 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -78,6 +78,11 @@ public:
CONTENT_SCALE_ASPECT_EXPAND,
};
+ enum ContentScaleStretch {
+ CONTENT_SCALE_STRETCH_FRACTIONAL,
+ CONTENT_SCALE_STRETCH_INTEGER,
+ };
+
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
LAYOUT_DIRECTION_LOCALE,
@@ -135,6 +140,7 @@ private:
Size2i content_scale_size;
ContentScaleMode content_scale_mode = CONTENT_SCALE_MODE_DISABLED;
ContentScaleAspect content_scale_aspect = CONTENT_SCALE_ASPECT_IGNORE;
+ ContentScaleStretch content_scale_stretch = CONTENT_SCALE_STRETCH_FRACTIONAL;
real_t content_scale_factor = 1.0;
void _make_window();
@@ -299,6 +305,9 @@ public:
void set_content_scale_aspect(ContentScaleAspect p_aspect);
ContentScaleAspect get_content_scale_aspect() const;
+ void set_content_scale_stretch(ContentScaleStretch p_stretch);
+ ContentScaleStretch get_content_scale_stretch() const;
+
void set_content_scale_factor(real_t p_factor);
real_t get_content_scale_factor() const;
@@ -420,6 +429,7 @@ VARIANT_ENUM_CAST(Window::Mode);
VARIANT_ENUM_CAST(Window::Flags);
VARIANT_ENUM_CAST(Window::ContentScaleMode);
VARIANT_ENUM_CAST(Window::ContentScaleAspect);
+VARIANT_ENUM_CAST(Window::ContentScaleStretch);
VARIANT_ENUM_CAST(Window::LayoutDirection);
VARIANT_ENUM_CAST(Window::WindowInitialPosition);
diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp
index e48f744c72..abe1561310 100644
--- a/scene/resources/environment.cpp
+++ b/scene/resources/environment.cpp
@@ -1446,7 +1446,7 @@ void Environment::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_emission_energy", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_emission_energy", "get_volumetric_fog_emission_energy");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_gi_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_gi_inject", "get_volumetric_fog_gi_inject");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_anisotropy", PROPERTY_HINT_RANGE, "-0.9,0.9,0.01"), "set_volumetric_fog_anisotropy", "get_volumetric_fog_anisotropy");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_length", PROPERTY_HINT_RANGE, "0,1024,0.01,or_greater"), "set_volumetric_fog_length", "get_volumetric_fog_length");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:m"), "set_volumetric_fog_length", "get_volumetric_fog_length");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_detail_spread", PROPERTY_HINT_EXP_EASING, "positive_only"), "set_volumetric_fog_detail_spread", "get_volumetric_fog_detail_spread");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_ambient_inject", PROPERTY_HINT_RANGE, "0.0,16,0.01,exp"), "set_volumetric_fog_ambient_inject", "get_volumetric_fog_ambient_inject");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volumetric_fog_sky_affect", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_volumetric_fog_sky_affect", "get_volumetric_fog_sky_affect");