summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--editor/animation_track_editor.cpp2
-rw-r--r--editor/editor_audio_buses.cpp4
-rw-r--r--editor/editor_command_palette.cpp10
-rw-r--r--editor/editor_command_palette.h1
-rw-r--r--editor/editor_node.cpp4
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/gui/editor_spin_slider.cpp2
-rw-r--r--editor/plugins/abstract_polygon_2d_editor.cpp2
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp4
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp12
-rw-r--r--editor/plugins/curve_editor_plugin.cpp4
-rw-r--r--editor/plugins/gradient_editor.cpp2
-rw-r--r--editor/plugins/gradient_texture_2d_editor_plugin.cpp4
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp5
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp3
-rw-r--r--editor/plugins/polygon_3d_editor_plugin.cpp4
-rw-r--r--editor/plugins/script_text_editor.cpp4
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp5
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp5
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp4
-rw-r--r--editor/project_manager.cpp4
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp43
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp6
-rw-r--r--modules/gdscript/gdscript_editor.cpp1
-rw-r--r--modules/gdscript/gdscript_parser.cpp223
-rw-r--r--modules/gdscript/gdscript_parser.h5
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp280
-rw-r--r--modules/gdscript/gdscript_tokenizer.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out11
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd22
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out7
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/r_strings.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/r_strings.out22
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd144
-rw-r--r--modules/lightmapper_rd/lightmapper_rd.cpp16
-rw-r--r--modules/regex/doc_classes/RegEx.xml2
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/graph_edit.cpp8
-rw-r--r--scene/gui/line_edit.cpp8
-rw-r--r--scene/gui/text_edit.cpp8
-rw-r--r--scene/gui/tree.cpp2
-rw-r--r--servers/audio/effects/audio_stream_generator.cpp11
52 files changed, 642 insertions, 325 deletions
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 920d94081e..5b87dc4f46 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -4302,7 +4302,7 @@ bool AnimationTrackEditor::is_selection_active() const {
}
bool AnimationTrackEditor::is_snap_enabled() const {
- return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CTRL);
+ return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
}
void AnimationTrackEditor::_update_tracks() {
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 6116ad8e4c..109a10750f 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -329,7 +329,7 @@ void EditorAudioBus::_volume_changed(float p_normalized) {
const float p_db = this->_normalized_volume_to_scaled_db(p_normalized);
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
// Snap the value when holding Ctrl for easier editing.
// To do so, it needs to be converted back to normalized volume (as the slider uses that unit).
slider->set_value(_scaled_db_to_normalized_volume(Math::round(p_db)));
@@ -389,7 +389,7 @@ float EditorAudioBus::_scaled_db_to_normalized_volume(float db) {
void EditorAudioBus::_show_value(float slider_value) {
float db;
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
// Display the correct (snapped) value when holding Ctrl
db = Math::round(_normalized_volume_to_scaled_db(slider_value));
} else {
diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp
index 168fe5a7ac..18a251a306 100644
--- a/editor/editor_command_palette.cpp
+++ b/editor/editor_command_palette.cpp
@@ -357,3 +357,13 @@ Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name
EditorCommandPalette::get_singleton()->add_shortcut_command(p_command_name, p_path, shortcut);
return shortcut;
}
+
+Ref<Shortcut> ED_SHORTCUT_ARRAY_AND_COMMAND(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, String p_command_name) {
+ if (p_command_name.is_empty()) {
+ p_command_name = p_name;
+ }
+
+ Ref<Shortcut> shortcut = ED_SHORTCUT_ARRAY(p_path, p_name, p_keycodes);
+ EditorCommandPalette::get_singleton()->add_shortcut_command(p_command_name, p_path, shortcut);
+ return shortcut;
+}
diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h
index 7eb9ff7404..b34c4ddf97 100644
--- a/editor/editor_command_palette.h
+++ b/editor/editor_command_palette.h
@@ -105,5 +105,6 @@ public:
};
Ref<Shortcut> ED_SHORTCUT_AND_COMMAND(const String &p_path, const String &p_name, Key p_keycode = Key::NONE, String p_command = "");
+Ref<Shortcut> ED_SHORTCUT_ARRAY_AND_COMMAND(const String &p_path, const String &p_name, const PackedInt32Array &p_keycodes, String p_command = "");
#endif // EDITOR_COMMAND_PALETTE_H
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 5341d73fe4..223e50f557 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -7226,8 +7226,8 @@ EditorNode::EditorNode() {
file_menu->add_separator();
- file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN);
- ED_SHORTCUT_OVERRIDE("editor/quick_open", "macos", KeyModifierMask::META + KeyModifierMask::CTRL + Key::O);
+ file_menu->add_shortcut(ED_SHORTCUT_ARRAY_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), { int32_t(KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), int32_t(KeyModifierMask::CMD_OR_CTRL + Key::P) }), FILE_QUICK_OPEN);
+ ED_SHORTCUT_OVERRIDE_ARRAY("editor/quick_open", "macos", { int32_t(KeyModifierMask::META + KeyModifierMask::CTRL + Key::O), int32_t(KeyModifierMask::CMD_OR_CTRL + Key::P) });
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE);
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT);
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 1a518f7d03..338376c724 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2682,7 +2682,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data,
}
}
if (!to_move.is_empty()) {
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
_move_operation_confirm(to_dir, true);
} else {
_move_operation_confirm(to_dir);
diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp
index fa94a107f0..c59822ba55 100644
--- a/editor/gui/editor_spin_slider.cpp
+++ b/editor/gui/editor_spin_slider.cpp
@@ -212,7 +212,7 @@ void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
}
}
- if (k->is_ctrl_pressed()) {
+ if (k->is_command_or_control_pressed()) {
step *= 100.0;
} else if (k->is_shift_pressed()) {
step *= 10.0;
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp
index fd15735e65..fd5ebc423e 100644
--- a/editor/plugins/abstract_polygon_2d_editor.cpp
+++ b/editor/plugins/abstract_polygon_2d_editor.cpp
@@ -285,7 +285,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event)
if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
- if (mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
+ if (mb->is_meta_pressed() || mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) {
return false;
}
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index 638838bcca..a16d689e3d 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -158,7 +158,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
}
// Select node or push a field inside
- if (mb.is_valid() && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ if (mb.is_valid() && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
selected_transition_from = StringName();
selected_transition_to = StringName();
selected_transition_index = -1;
@@ -337,7 +337,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv
ABS(box_selecting_from.x - box_selecting_to.x),
ABS(box_selecting_from.y - box_selecting_to.y));
- if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) {
+ if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
previous_selected = selected_nodes;
} else {
selected_nodes.clear();
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 91f403bc49..f62eaee96e 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -354,7 +354,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
snap_target[0] = SNAP_TARGET_NONE;
snap_target[1] = SNAP_TARGET_NONE;
- bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CTRL);
+ bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
// Smart snap using the canvas position
Vector2 output = p_target;
@@ -480,7 +480,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig
}
real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const {
- if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) && snap_rotation_step != 0) {
+ if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) && snap_rotation_step != 0) {
if (snap_relative) {
return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step);
} else {
@@ -505,7 +505,7 @@ void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) {
viewport->queue_redraw();
}
- if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {
+ if (k->is_pressed() && !k->is_command_or_control_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) {
if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) {
// Multiply the grid size
grid_step_multiplier = MIN(grid_step_multiplier + 1, 12);
@@ -1924,7 +1924,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;
bool uniform = m->is_shift_pressed();
- bool is_ctrl = m->is_ctrl_pressed();
+ bool is_ctrl = m->is_command_or_control_pressed();
Point2 drag_from_local = simple_xform.xform(drag_from);
Point2 drag_to_local = simple_xform.xform(drag_to);
@@ -5609,7 +5609,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons
Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res));
if (texture != nullptr || scene != nullptr) {
bool root_node_selected = EditorNode::get_singleton()->get_editor_selection()->is_selected(EditorNode::get_singleton()->get_edited_scene());
- String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + TTR("Hold Ctrl when dropping to add as child of selected node.");
+ String desc = TTR("Drag and drop to add as child of current scene's root node.") + "\n" + vformat(TTR("Hold %s when dropping to add as child of selected node."), keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL));
if (!root_node_selected) {
desc += "\n" + TTR("Hold Shift when dropping to add as sibling of selected node.");
}
@@ -5913,7 +5913,7 @@ bool CanvasItemEditorViewport::_only_packed_scenes_selected() const {
void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) {
bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT);
- bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL);
+ bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
selected_files.clear();
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index 1d3ffc0e3a..37f0ca449d 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -225,7 +225,7 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
} else if (grabbing == GRAB_NONE) {
// Adding a new point. Insert a temporary point for the user to adjust, so it's not in the undo/redo.
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value()));
- if (snap_enabled || mb->is_ctrl_pressed()) {
+ if (snap_enabled || mb->is_command_or_control_pressed()) {
new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count);
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value();
}
@@ -276,7 +276,7 @@ void CurveEdit::gui_input(const Ref<InputEvent> &p_event) {
// Drag point.
Vector2 new_pos = get_world_pos(mpos).clamp(Vector2(0.0, curve->get_min_value()), Vector2(1.0, curve->get_max_value()));
- if (snap_enabled || mm->is_ctrl_pressed()) {
+ if (snap_enabled || mm->is_command_or_control_pressed()) {
new_pos.x = Math::snapped(new_pos.x, 1.0 / snap_count);
new_pos.y = Math::snapped(new_pos.y - curve->get_min_value(), curve->get_range() / snap_count) + curve->get_min_value();
}
diff --git a/editor/plugins/gradient_editor.cpp b/editor/plugins/gradient_editor.cpp
index 63dede4850..bcc7d2a004 100644
--- a/editor/plugins/gradient_editor.cpp
+++ b/editor/plugins/gradient_editor.cpp
@@ -312,7 +312,7 @@ void GradientEditor::gui_input(const Ref<InputEvent> &p_event) {
// Snap to "round" coordinates if holding Ctrl.
// Be more precise if holding Shift as well.
- if (mm->is_ctrl_pressed()) {
+ if (mm->is_command_or_control_pressed()) {
newofs = Math::snapped(newofs, mm->is_shift_pressed() ? 0.025 : 0.1);
} else if (mm->is_shift_pressed()) {
// Snap to nearest point if holding just Shift.
diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
index 494d97c45c..5952185cc0 100644
--- a/editor/plugins/gradient_texture_2d_editor_plugin.cpp
+++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp
@@ -113,7 +113,7 @@ void GradientTexture2DEdit::gui_input(const Ref<InputEvent> &p_event) {
}
Vector2 new_pos = (mpos / size).clamp(Vector2(0, 0), Vector2(1, 1));
- if (snap_enabled || mm->is_ctrl_pressed()) {
+ if (snap_enabled || mm->is_command_or_control_pressed()) {
new_pos = new_pos.snapped(Vector2(1.0 / snap_count, 1.0 / snap_count));
}
@@ -201,7 +201,7 @@ void GradientTexture2DEdit::_draw() {
draw_texture_rect(texture, Rect2(Point2(), size));
// Draw grid snap lines.
- if (snap_enabled || (Input::get_singleton()->is_key_pressed(Key::CTRL) && grabbed != HANDLE_NONE)) {
+ if (snap_enabled || (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL) && grabbed != HANDLE_NONE)) {
const Color line_color = Color(0.5, 0.5, 0.5, 0.5);
for (int idx = 0; idx < snap_count + 1; idx++) {
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index c05a6d1392..2215c371d0 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1977,7 +1977,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) {
nav_mode = NAVIGATION_ORBIT;
} else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) {
nav_mode = NAVIGATION_PAN;
- } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_ctrl_pressed()) {
+ } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_command_or_control_pressed()) {
nav_mode = NAVIGATION_ZOOM;
} else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed()) {
nav_mode = NAVIGATION_ORBIT;
@@ -5244,7 +5244,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
preview_material_label_desc = memnew(Label);
preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE);
- preview_material_label_desc->set_text(TTR("Drag and drop to override the material of any geometry node.\nHold Ctrl when dropping to override a specific surface."));
+ Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
+ preview_material_label_desc->set_text(vformat(TTR("Drag and drop to override the material of any geometry node.\nHold %s when dropping to override a specific surface."), find_keycode_name(key)));
preview_material_label_desc->add_theme_color_override("font_color", Color(0.8, 0.8, 0.8, 1));
preview_material_label_desc->add_theme_constant_override("line_spacing", 0);
preview_material_label_desc->hide();
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 40e8482a0b..e1b402475a 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -455,7 +455,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p
set_handle_clicked(false);
}
- if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) {
+ if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_command_or_control_pressed()))) {
//click into curve, break it down
Vector<Vector3> v3a = c->tessellate();
int rc = v3a.size();
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index 5ed9f4946d..e700d28afb 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -1304,7 +1304,8 @@ Polygon2DEditor::Polygon2DEditor() {
uv_button[UV_MODE_CREATE]->set_tooltip_text(TTR("Create Polygon"));
uv_button[UV_MODE_CREATE_INTERNAL]->set_tooltip_text(TTR("Create Internal Vertex"));
uv_button[UV_MODE_REMOVE_INTERNAL]->set_tooltip_text(TTR("Remove Internal Vertex"));
- uv_button[UV_MODE_EDIT_POINT]->set_tooltip_text(TTR("Move Points") + "\n" + TTR("Ctrl: Rotate") + "\n" + TTR("Shift: Move All") + "\n" + TTR("Shift+Ctrl: Scale"));
+ Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
+ uv_button[UV_MODE_EDIT_POINT]->set_tooltip_text(TTR("Move Points") + "\n" + find_keycode_name(key) + TTR(": Rotate") + "\n" + TTR("Shift: Move All") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Shift: Scale"));
uv_button[UV_MODE_MOVE]->set_tooltip_text(TTR("Move Polygon"));
uv_button[UV_MODE_ROTATE]->set_tooltip_text(TTR("Rotate Polygon"));
uv_button[UV_MODE_SCALE]->set_tooltip_text(TTR("Scale Polygon"));
diff --git a/editor/plugins/polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp
index 41672822b2..fa5413787c 100644
--- a/editor/plugins/polygon_3d_editor_plugin.cpp
+++ b/editor/plugins/polygon_3d_editor_plugin.cpp
@@ -184,7 +184,7 @@ EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_ca
case MODE_EDIT: {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
- if (mb->is_ctrl_pressed()) {
+ if (mb->is_command_or_control_pressed()) {
if (poly.size() < 3) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Edit Poly"));
@@ -329,7 +329,7 @@ EditorPlugin::AfterGUIInput Polygon3DEditor::forward_3d_gui_input(Camera3D *p_ca
Vector2 cpoint(spoint.x, spoint.y);
- if (snap_ignore && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (snap_ignore && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
snap_ignore = false;
}
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 5aaa3365b3..ca80c2e087 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1747,7 +1747,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
Array files = d["files"];
String text_to_drop;
- bool preload = Input::get_singleton()->is_key_pressed(Key::CTRL);
+ bool preload = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
for (int i = 0; i < files.size(); i++) {
if (i > 0) {
text_to_drop += ", ";
@@ -1787,7 +1787,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
Array nodes = d["nodes"];
String text_to_drop;
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
bool use_type = EDITOR_GET("text_editor/completion/add_type_hints");
for (int i = 0; i < nodes.size(); i++) {
NodePath np = nodes[i];
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 3134c0b951..1844358069 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -188,7 +188,7 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
frames_toggled_by_mouse_hover.insert(this_idx);
- if (mb->is_ctrl_pressed()) {
+ if (mb->is_command_or_control_pressed()) {
frames_selected.erase(this_idx);
} else if (!frames_selected.has(this_idx)) {
frames_selected.insert(this_idx, selected_count);
@@ -255,6 +255,7 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
// Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer
// to allow performing this action anywhere, even if the cursor isn't
// hovering the texture in the workspace.
+ // keep CTRL and not CMD_OR_CTRL as CTRL is expected even on MacOS.
if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) {
_sheet_zoom_on_position(scale_ratio, mb->get_position());
// Don't scroll up after zooming in.
@@ -1485,7 +1486,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
if (String(d["type"]) == "files") {
Vector<String> files = d["files"];
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
_prepare_sprite_sheet(files[0]);
} else {
_file_load_request(files, at_pos);
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index d5011380d3..b3ed98c58d 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -2217,7 +2217,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
paint_tool_button->set_toggle_mode(true);
paint_tool_button->set_button_group(tool_buttons_group);
paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", TTR("Paint"), Key::D));
- paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle."));
+ paint_tool_button->set_tooltip_text(TTR("Shift: Draw line.") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Shift: Draw rectangle."));
paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar));
tilemap_tiles_tools_buttons->add_child(paint_tool_button);
viewport_shortcut_buttons.push_back(paint_tool_button);
@@ -2263,7 +2263,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() {
picker_button->set_flat(true);
picker_button->set_toggle_mode(true);
picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", TTR("Picker"), Key::P));
- picker_button->set_tooltip_text(TTR("Alternatively hold Ctrl with other tools to pick tile."));
+ Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
+ picker_button->set_tooltip_text(vformat(TTR("Alternatively hold %s with other tools to pick tile."), find_keycode_name(key)));
picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport));
tools_settings->add_child(picker_button);
viewport_shortcut_buttons.push_back(picker_button);
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 1485ee9115..4037655e2c 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -1202,7 +1202,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) {
if (tools_settings_erase_button->is_pressed()) {
// Erasing
- if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) {
+ if (mb->is_command_or_control_pressed() || mb->is_shift_pressed()) {
// Remove tiles using rect.
// Setup the dragging info.
@@ -1241,7 +1241,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven
// Create a tile.
tile_set_atlas_source->create_tile(coords);
}
- } else if (mb->is_ctrl_pressed()) {
+ } else if (mb->is_command_or_control_pressed()) {
// Create tiles using rect.
drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT;
drag_start_mouse_pos = mouse_local_pos;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index feb3d7fa14..e7fe9a353c 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1863,7 +1863,7 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
CRASH_COND(anchor_index == -1);
_select_project_range(anchor_index, clicked_index);
- } else if (mb->is_ctrl_pressed()) {
+ } else if (mb->is_command_or_control_pressed()) {
_toggle_project(clicked_index);
} else {
@@ -1875,7 +1875,7 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
// Do not allow opening a project more than once using a single project manager instance.
// Opening the same project in several editor instances at once can lead to various issues.
- if (!mb->is_ctrl_pressed() && mb->is_double_click() && !project_opening_initiated) {
+ if (!mb->is_command_or_control_pressed() && mb->is_double_click() && !project_opening_initiated) {
emit_signal(SNAME(SIGNAL_PROJECT_ASK_OPEN));
}
}
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 966cba6348..c91f1599ed 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -2818,7 +2818,7 @@ void SceneTreeDock::_script_dropped(String p_file, NodePath p_to) {
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
Object *obj = ClassDB::instantiate(scr->get_instance_base_type());
ERR_FAIL_NULL(obj);
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index e488d6e266..1be690d894 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -52,6 +52,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool in_keyword = false;
bool in_word = false;
bool in_number = false;
+ bool in_raw_string = false;
bool in_node_path = false;
bool in_node_ref = false;
bool in_annotation = false;
@@ -234,15 +235,33 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (str[from] == '\\') {
- Dictionary escape_char_highlighter_info;
- escape_char_highlighter_info["color"] = symbol_color;
- color_map[from] = escape_char_highlighter_info;
+ if (!in_raw_string) {
+ Dictionary escape_char_highlighter_info;
+ escape_char_highlighter_info["color"] = symbol_color;
+ color_map[from] = escape_char_highlighter_info;
+ }
from++;
- Dictionary region_continue_highlighter_info;
- region_continue_highlighter_info["color"] = region_color;
- color_map[from + 1] = region_continue_highlighter_info;
+ if (!in_raw_string) {
+ int esc_len = 0;
+ if (str[from] == 'u') {
+ esc_len = 4;
+ } else if (str[from] == 'U') {
+ esc_len = 6;
+ }
+ for (int k = 0; k < esc_len && from < line_length - 1; k++) {
+ if (!is_hex_digit(str[from + 1])) {
+ break;
+ }
+ from++;
+ }
+
+ Dictionary region_continue_highlighter_info;
+ region_continue_highlighter_info["color"] = region_color;
+ color_map[from + 1] = region_continue_highlighter_info;
+ }
+
continue;
}
@@ -489,6 +508,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_member_variable = false;
}
+ if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) {
+ in_raw_string = true;
+ } else if (in_raw_string && in_region == -1) {
+ in_raw_string = false;
+ }
+
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
@@ -520,7 +545,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_annotation = false;
}
- if (in_node_ref) {
+ if (in_raw_string) {
+ color = string_color;
+ } else if (in_node_ref) {
next_type = NODE_REF;
color = node_ref_color;
} else if (in_annotation) {
@@ -692,7 +719,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
/* Strings */
- const Color string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
+ string_color = EDITOR_GET("text_editor/theme/highlighting/string_color");
List<String> strings;
gdscript->get_string_delimiters(&strings);
for (const String &string : strings) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index fe3b63d713..090857f397 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -78,6 +78,7 @@ private:
Color built_in_type_color;
Color number_color;
Color member_color;
+ Color string_color;
Color node_path_color;
Color node_ref_color;
Color annotation_color;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 077b30a7fe..04c86d60a8 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -2609,7 +2609,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
}
// Check if assigned value is an array literal, so we can make it a typed array too if appropriate.
- if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.has_container_element_type()) {
+ if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type()) {
update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type());
}
@@ -3213,7 +3213,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
// If the function requires typed arrays we must make literals be typed.
for (const KeyValue<int, GDScriptParser::ArrayNode *> &E : arrays) {
int index = E.key;
- if (index < par_types.size() && par_types[index].has_container_element_type()) {
+ if (index < par_types.size() && par_types[index].is_hard_type() && par_types[index].has_container_element_type()) {
update_array_literal_element_type(E.value, par_types[index].get_container_element_type());
}
}
@@ -4099,7 +4099,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
GDScriptParser::DataType base_type = p_subscript->base->get_datatype();
bool valid = false;
// If the base is a metatype, use the analyzer instead.
- if (p_subscript->base->is_constant && !base_type.is_meta_type) {
+ if (p_subscript->base->is_constant && !base_type.is_meta_type && base_type.kind != GDScriptParser::DataType::CLASS) {
// Just try to get it.
Variant value = p_subscript->base->reduced_value.get_named(p_subscript->attribute->name, valid);
if (valid) {
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index aec8f56516..00d3df8fd0 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -59,6 +59,7 @@ void GDScriptLanguage::get_string_delimiters(List<String> *p_delimiters) const {
p_delimiters->push_back("' '");
p_delimiters->push_back("\"\"\" \"\"\"");
p_delimiters->push_back("''' '''");
+ // NOTE: StringName, NodePath and r-strings are not listed here.
}
bool GDScriptLanguage::is_using_templates() {
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 52c1a5b141..1202e7e235 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -383,8 +383,10 @@ GDScriptTokenizer::Token GDScriptParser::advance() {
push_error(current.literal);
current = tokenizer.scan();
}
- for (Node *n : nodes_in_progress) {
- update_extents(n);
+ if (previous.type != GDScriptTokenizer::Token::DEDENT) { // `DEDENT` belongs to the next non-empty line.
+ for (Node *n : nodes_in_progress) {
+ update_extents(n);
+ }
}
return previous;
}
@@ -579,13 +581,14 @@ void GDScriptParser::parse_program() {
complete_extents(head);
#ifdef TOOLS_ENABLED
- for (const KeyValue<int, GDScriptTokenizer::CommentData> &E : tokenizer.get_comments()) {
- if (E.value.new_line && E.value.comment.begins_with("##")) {
- class_doc_line = MIN(class_doc_line, E.key);
+ const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
+ int line = MIN(max_script_doc_line, head->end_line);
+ while (line > 0) {
+ if (comments.has(line) && comments[line].new_line && comments[line].comment.begins_with("##")) {
+ head->doc_data = parse_class_doc_comment(line);
+ break;
}
- }
- if (has_comment(class_doc_line, true)) {
- head->doc_data = parse_class_doc_comment(class_doc_line, false);
+ line--;
}
#endif // TOOLS_ENABLED
@@ -747,10 +750,6 @@ template <class T>
void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(bool), AnnotationInfo::TargetKind p_target, const String &p_member_kind, bool p_is_static) {
advance();
-#ifdef TOOLS_ENABLED
- int doc_comment_line = previous.start_line - 1;
-#endif // TOOLS_ENABLED
-
// Consume annotations.
List<AnnotationNode *> annotations;
while (!annotation_stack.is_empty()) {
@@ -762,11 +761,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
push_error(vformat(R"(Annotation "%s" cannot be applied to a %s.)", last_annotation->name, p_member_kind));
clear_unused_annotations();
}
-#ifdef TOOLS_ENABLED
- if (last_annotation->start_line == doc_comment_line) {
- doc_comment_line--;
- }
-#endif // TOOLS_ENABLED
}
T *member = (this->*p_parse_function)(p_is_static);
@@ -774,28 +768,40 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)(b
return;
}
+#ifdef TOOLS_ENABLED
+ int doc_comment_line = member->start_line - 1;
+#endif // TOOLS_ENABLED
+
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
+#ifdef TOOLS_ENABLED
+ if (annotation->start_line <= doc_comment_line) {
+ doc_comment_line = annotation->start_line - 1;
+ }
+#endif // TOOLS_ENABLED
}
#ifdef TOOLS_ENABLED
- // Consume doc comments.
- class_doc_line = MIN(class_doc_line, doc_comment_line - 1);
-
- // Check whether current line has a doc comment
- if (has_comment(previous.start_line, true)) {
- if constexpr (std::is_same_v<T, ClassNode>) {
- member->doc_data = parse_class_doc_comment(previous.start_line, true, true);
- } else {
- member->doc_data = parse_doc_comment(previous.start_line, true);
+ if constexpr (std::is_same_v<T, ClassNode>) {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_class_doc_comment(member->start_line, true);
+ } else if (has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment. Don't check `min_member_doc_line` because a class ends parsing after its members.
+ // This may not work correctly for cases like `var a; class B`, but it doesn't matter in practice.
+ member->doc_data = parse_class_doc_comment(doc_comment_line);
}
- } else if (has_comment(doc_comment_line, true)) {
- if constexpr (std::is_same_v<T, ClassNode>) {
- member->doc_data = parse_class_doc_comment(doc_comment_line, true);
- } else {
+ } else {
+ if (has_comment(member->start_line, true)) {
+ // Inline doc comment.
+ member->doc_data = parse_doc_comment(member->start_line, true);
+ } else if (doc_comment_line >= min_member_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
member->doc_data = parse_doc_comment(doc_comment_line);
}
}
+
+ min_member_doc_line = member->end_line + 1; // Prevent multiple members from using the same doc comment.
#endif // TOOLS_ENABLED
if (member->identifier != nullptr) {
@@ -1263,6 +1269,9 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
push_multiline(true);
consume(GDScriptTokenizer::Token::BRACE_OPEN, vformat(R"(Expected "{" after %s.)", named ? "enum name" : R"("enum")"));
+#ifdef TOOLS_ENABLED
+ int min_enum_value_doc_line = previous.end_line + 1;
+#endif
HashMap<StringName, int> elements;
@@ -1325,43 +1334,35 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) {
}
} while (match(GDScriptTokenizer::Token::COMMA));
- pop_multiline();
- consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
-
#ifdef TOOLS_ENABLED
// Enum values documentation.
for (int i = 0; i < enum_node->values.size(); i++) {
- int doc_comment_line = enum_node->values[i].line;
- bool single_line = false;
-
- if (has_comment(doc_comment_line, true)) {
- single_line = true;
- } else if (has_comment(doc_comment_line - 1, true)) {
- doc_comment_line--;
- } else {
- continue;
- }
-
- if (i == enum_node->values.size() - 1) {
- // If close bracket is same line as last value.
- if (doc_comment_line == previous.start_line) {
- break;
- }
- } else {
- // If two values are same line.
- if (doc_comment_line == enum_node->values[i + 1].line) {
- continue;
+ int enum_value_line = enum_node->values[i].line;
+ int doc_comment_line = enum_value_line - 1;
+
+ MemberDocData doc_data;
+ if (has_comment(enum_value_line, true)) {
+ // Inline doc comment.
+ if (i == enum_node->values.size() - 1 || enum_node->values[i + 1].line > enum_value_line) {
+ doc_data = parse_doc_comment(enum_value_line, true);
}
+ } else if (doc_comment_line >= min_enum_value_doc_line && has_comment(doc_comment_line, true) && tokenizer.get_comments()[doc_comment_line].new_line) {
+ // Normal doc comment.
+ doc_data = parse_doc_comment(doc_comment_line);
}
if (named) {
- enum_node->values.write[i].doc_data = parse_doc_comment(doc_comment_line, single_line);
+ enum_node->values.write[i].doc_data = doc_data;
} else {
- current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, parse_doc_comment(doc_comment_line, single_line));
+ current_class->set_enum_value_doc_data(enum_node->values[i].identifier->name, doc_data);
}
+
+ min_enum_value_doc_line = enum_value_line + 1; // Prevent multiple enum values from using the same doc comment.
}
#endif // TOOLS_ENABLED
+ pop_multiline();
+ consume(GDScriptTokenizer::Token::BRACE_CLOSE, R"(Expected closing "}" for enum.)");
complete_extents(enum_node);
end_statement("enum");
@@ -3454,31 +3455,21 @@ bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) {
}
GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool p_single_line) {
- MemberDocData result;
+ ERR_FAIL_COND_V(!has_comment(p_line, true), MemberDocData());
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), result);
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- result.description = comments[p_line].comment.trim_prefix("##").strip_edges();
- return result;
- }
- return result;
- }
-
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
+ line--;
}
- line--;
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
String space_prefix;
- if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ {
int i = 2;
for (; i < comments[line].comment.length(); i++) {
if (comments[line].comment[i] != ' ') {
@@ -3488,11 +3479,10 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ MemberDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
line++;
@@ -3513,35 +3503,22 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool
return result;
}
-GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line) {
- ClassDocData result;
+GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, bool p_single_line) {
+ ERR_FAIL_COND_V(!has_comment(p_line, true), ClassDocData());
const HashMap<int, GDScriptTokenizer::CommentData> &comments = tokenizer.get_comments();
- ERR_FAIL_COND_V(!comments.has(p_line), result);
-
- if (p_single_line) {
- if (comments[p_line].comment.begins_with("##")) {
- result.brief = comments[p_line].comment.trim_prefix("##").strip_edges();
- return result;
- }
- return result;
- }
-
int line = p_line;
- DocLineState state = DOC_LINE_NORMAL;
- bool is_in_brief = true;
- if (p_inner_class) {
- while (comments.has(line - 1)) {
- if (!comments[line - 1].new_line || !comments[line - 1].comment.begins_with("##")) {
- break;
- }
+ if (!p_single_line) {
+ while (comments.has(line - 1) && comments[line - 1].new_line && comments[line - 1].comment.begins_with("##")) {
line--;
}
}
+ max_script_doc_line = MIN(max_script_doc_line, line - 1);
+
String space_prefix;
- if (comments.has(line) && comments[line].comment.begins_with("##")) {
+ {
int i = 2;
for (; i < comments[line].comment.length(); i++) {
if (comments[line].comment[i] != ' ') {
@@ -3551,11 +3528,11 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
space_prefix = String(" ").repeat(i - 2);
}
- while (comments.has(line)) {
- if (!comments[line].new_line || !comments[line].comment.begins_with("##")) {
- break;
- }
+ DocLineState state = DOC_LINE_NORMAL;
+ bool is_in_brief = true;
+ ClassDocData result;
+ while (line <= p_line) {
String doc_line = comments[line].comment.trim_prefix("##");
line++;
@@ -3630,14 +3607,6 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line,
}
}
- if (current_class->members.size() > 0) {
- const ClassNode::Member &m = current_class->members[0];
- int first_member_line = m.get_line();
- if (first_member_line == line) {
- result = ClassDocData(); // Clear result.
- }
- }
-
return result;
}
#endif // TOOLS_ENABLED
@@ -4095,25 +4064,29 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
}
} break;
case GDScriptParser::DataType::ENUM: {
- variable->export_info.type = Variant::INT;
- variable->export_info.hint = PROPERTY_HINT_ENUM;
-
- String enum_hint_string;
- bool first = true;
- for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
- if (!first) {
- enum_hint_string += ",";
- } else {
- first = false;
+ if (export_type.is_meta_type) {
+ variable->export_info.type = Variant::DICTIONARY;
+ } else {
+ variable->export_info.type = Variant::INT;
+ variable->export_info.hint = PROPERTY_HINT_ENUM;
+
+ String enum_hint_string;
+ bool first = true;
+ for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) {
+ if (!first) {
+ enum_hint_string += ",";
+ } else {
+ first = false;
+ }
+ enum_hint_string += E.key.operator String().capitalize().xml_escape();
+ enum_hint_string += ":";
+ enum_hint_string += String::num_int64(E.value).xml_escape();
}
- enum_hint_string += E.key.operator String().capitalize().xml_escape();
- enum_hint_string += ":";
- enum_hint_string += String::num_int64(E.value).xml_escape();
- }
- variable->export_info.hint_string = enum_hint_string;
- variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
- variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ variable->export_info.hint_string = enum_hint_string;
+ variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM;
+ variable->export_info.class_name = String(export_type.native_type).replace("::", ".");
+ }
} break;
default:
push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", variable);
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 27d4d0fb47..988524d058 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1517,10 +1517,11 @@ private:
TypeNode *parse_type(bool p_allow_void = false);
#ifdef TOOLS_ENABLED
- int class_doc_line = 0x7FFFFFFF;
+ int max_script_doc_line = INT_MAX;
+ int min_member_doc_line = 1;
bool has_comment(int p_line, bool p_must_be_doc = false);
MemberDocData parse_doc_comment(int p_line, bool p_single_line = false);
- ClassDocData parse_class_doc_comment(int p_line, bool p_inner_class, bool p_single_line = false);
+ ClassDocData parse_class_doc_comment(int p_line, bool p_single_line = false);
#endif // TOOLS_ENABLED
public:
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 42b983ef45..07f2b8b406 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -857,10 +857,14 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
STRING_NODEPATH,
};
+ bool is_raw = false;
bool is_multiline = false;
StringType type = STRING_REGULAR;
- if (_peek(-1) == '&') {
+ if (_peek(-1) == 'r') {
+ is_raw = true;
+ _advance();
+ } else if (_peek(-1) == '&') {
type = STRING_NAME;
_advance();
} else if (_peek(-1) == '^') {
@@ -890,7 +894,12 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
char32_t ch = _peek();
if (ch == 0x200E || ch == 0x200F || (ch >= 0x202A && ch <= 0x202E) || (ch >= 0x2066 && ch <= 0x2069)) {
- Token error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
+ Token error;
+ if (is_raw) {
+ error = make_error("Invisible text direction control character present in the string, use regular string literal instead of r-string.");
+ } else {
+ error = make_error("Invisible text direction control character present in the string, escape it (\"\\u" + String::num_int64(ch, 16) + "\") to avoid confusion.");
+ }
error.start_column = column;
error.leftmost_column = error.start_column;
error.end_column = column + 1;
@@ -905,144 +914,164 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
return make_error("Unterminated string.");
}
- // Grab escape character.
- char32_t code = _peek();
- _advance();
- if (_is_at_end()) {
- return make_error("Unterminated string.");
- }
+ if (is_raw) {
+ if (_peek() == quote_char) {
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+ result += '\\';
+ result += quote_char;
+ } else if (_peek() == '\\') { // For `\\\"`.
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+ result += '\\';
+ result += '\\';
+ } else {
+ result += '\\';
+ }
+ } else {
+ // Grab escape character.
+ char32_t code = _peek();
+ _advance();
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
- char32_t escaped = 0;
- bool valid_escape = true;
+ char32_t escaped = 0;
+ bool valid_escape = true;
- switch (code) {
- case 'a':
- escaped = '\a';
- break;
- case 'b':
- escaped = '\b';
- break;
- case 'f':
- escaped = '\f';
- break;
- case 'n':
- escaped = '\n';
- break;
- case 'r':
- escaped = '\r';
- break;
- case 't':
- escaped = '\t';
- break;
- case 'v':
- escaped = '\v';
- break;
- case '\'':
- escaped = '\'';
- break;
- case '\"':
- escaped = '\"';
- break;
- case '\\':
- escaped = '\\';
- break;
- case 'U':
- case 'u': {
- // Hexadecimal sequence.
- int hex_len = (code == 'U') ? 6 : 4;
- for (int j = 0; j < hex_len; j++) {
- if (_is_at_end()) {
- return make_error("Unterminated string.");
+ switch (code) {
+ case 'a':
+ escaped = '\a';
+ break;
+ case 'b':
+ escaped = '\b';
+ break;
+ case 'f':
+ escaped = '\f';
+ break;
+ case 'n':
+ escaped = '\n';
+ break;
+ case 'r':
+ escaped = '\r';
+ break;
+ case 't':
+ escaped = '\t';
+ break;
+ case 'v':
+ escaped = '\v';
+ break;
+ case '\'':
+ escaped = '\'';
+ break;
+ case '\"':
+ escaped = '\"';
+ break;
+ case '\\':
+ escaped = '\\';
+ break;
+ case 'U':
+ case 'u': {
+ // Hexadecimal sequence.
+ int hex_len = (code == 'U') ? 6 : 4;
+ for (int j = 0; j < hex_len; j++) {
+ if (_is_at_end()) {
+ return make_error("Unterminated string.");
+ }
+
+ char32_t digit = _peek();
+ char32_t value = 0;
+ if (is_digit(digit)) {
+ value = digit - '0';
+ } else if (digit >= 'a' && digit <= 'f') {
+ value = digit - 'a';
+ value += 10;
+ } else if (digit >= 'A' && digit <= 'F') {
+ value = digit - 'A';
+ value += 10;
+ } else {
+ // Make error, but keep parsing the string.
+ Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
+ error.start_column = column;
+ error.leftmost_column = error.start_column;
+ error.end_column = column + 1;
+ error.rightmost_column = error.end_column;
+ push_error(error);
+ valid_escape = false;
+ break;
+ }
+
+ escaped <<= 4;
+ escaped |= value;
+
+ _advance();
}
-
- char32_t digit = _peek();
- char32_t value = 0;
- if (is_digit(digit)) {
- value = digit - '0';
- } else if (digit >= 'a' && digit <= 'f') {
- value = digit - 'a';
- value += 10;
- } else if (digit >= 'A' && digit <= 'F') {
- value = digit - 'A';
- value += 10;
- } else {
- // Make error, but keep parsing the string.
- Token error = make_error("Invalid hexadecimal digit in unicode escape sequence.");
- error.start_column = column;
- error.leftmost_column = error.start_column;
- error.end_column = column + 1;
- error.rightmost_column = error.end_column;
- push_error(error);
- valid_escape = false;
+ } break;
+ case '\r':
+ if (_peek() != '\n') {
+ // Carriage return without newline in string. (???)
+ // Just add it to the string and keep going.
+ result += ch;
+ _advance();
break;
}
-
- escaped <<= 4;
- escaped |= value;
-
- _advance();
- }
- } break;
- case '\r':
- if (_peek() != '\n') {
- // Carriage return without newline in string. (???)
- // Just add it to the string and keep going.
- result += ch;
- _advance();
+ [[fallthrough]];
+ case '\n':
+ // Escaping newline.
+ newline(false);
+ valid_escape = false; // Don't add to the string.
break;
- }
- [[fallthrough]];
- case '\n':
- // Escaping newline.
- newline(false);
- valid_escape = false; // Don't add to the string.
- break;
- default:
- Token error = make_error("Invalid escape in string.");
- error.start_column = column - 2;
- error.leftmost_column = error.start_column;
- push_error(error);
- valid_escape = false;
- break;
- }
- // Parse UTF-16 pair.
- if (valid_escape) {
- if ((escaped & 0xfffffc00) == 0xd800) {
- if (prev == 0) {
- prev = escaped;
- prev_pos = column - 2;
- continue;
- } else {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
+ default:
+ Token error = make_error("Invalid escape in string.");
error.start_column = column - 2;
error.leftmost_column = error.start_column;
push_error(error);
valid_escape = false;
- prev = 0;
+ break;
+ }
+ // Parse UTF-16 pair.
+ if (valid_escape) {
+ if ((escaped & 0xfffffc00) == 0xd800) {
+ if (prev == 0) {
+ prev = escaped;
+ prev_pos = column - 2;
+ continue;
+ } else {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ prev = 0;
+ }
+ } else if ((escaped & 0xfffffc00) == 0xdc00) {
+ if (prev == 0) {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate.");
+ error.start_column = column - 2;
+ error.leftmost_column = error.start_column;
+ push_error(error);
+ valid_escape = false;
+ } else {
+ escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
+ prev = 0;
+ }
}
- } else if ((escaped & 0xfffffc00) == 0xdc00) {
- if (prev == 0) {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired trail surrogate");
- error.start_column = column - 2;
+ if (prev != 0) {
+ Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate.");
+ error.start_column = prev_pos;
error.leftmost_column = error.start_column;
push_error(error);
- valid_escape = false;
- } else {
- escaped = (prev << 10UL) + escaped - ((0xd800 << 10UL) + 0xdc00 - 0x10000);
prev = 0;
}
}
- if (prev != 0) {
- Token error = make_error("Invalid UTF-16 sequence in string, unpaired lead surrogate");
- error.start_column = prev_pos;
- error.leftmost_column = error.start_column;
- push_error(error);
- prev = 0;
- }
- }
- if (valid_escape) {
- result += escaped;
+ if (valid_escape) {
+ result += escaped;
+ }
}
} else if (ch == quote_char) {
if (prev != 0) {
@@ -1216,7 +1245,7 @@ void GDScriptTokenizer::check_indent() {
if (line_continuation || multiline_mode) {
// We cleared up all the whitespace at the beginning of the line.
- // But if this is a continuation or multiline mode and we don't want any indentation change.
+ // If this is a line continuation or we're in multiline mode then we don't want any indentation changes.
return;
}
@@ -1416,6 +1445,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (is_digit(c)) {
return number();
+ } else if (c == 'r' && (_peek() == '"' || _peek() == '\'')) {
+ // Raw string literals.
+ return string();
} else if (is_unicode_identifier_start(c)) {
return potential_identifier();
}
diff --git a/modules/gdscript/gdscript_tokenizer.h b/modules/gdscript/gdscript_tokenizer.h
index 068393cee9..f916407b18 100644
--- a/modules/gdscript/gdscript_tokenizer.h
+++ b/modules/gdscript/gdscript_tokenizer.h
@@ -187,6 +187,8 @@ public:
#ifdef TOOLS_ENABLED
struct CommentData {
String comment;
+ // true: Comment starts at beginning of line or after indentation.
+ // false: Inline comment (starts after some code).
bool new_line = false;
CommentData() {}
CommentData(const String &p_comment, bool p_new_line) {
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
new file mode 100644
index 0000000000..dafd2ec0c8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd
@@ -0,0 +1,17 @@
+class_name TestExportEnumAsDictionary
+
+enum MyEnum {A, B, C}
+
+const Utils = preload("../../utils.notest.gd")
+
+@export var x1 = MyEnum
+@export var x2 = MyEnum.A
+@export var x3 := MyEnum
+@export var x4 := MyEnum.A
+@export var x5: MyEnum
+
+func test():
+ for property in get_property_list():
+ if property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE:
+ print(Utils.get_property_signature(property))
+ print(" ", Utils.get_property_additional_info(property))
diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
new file mode 100644
index 0000000000..f1a13f1045
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.out
@@ -0,0 +1,11 @@
+GDTEST_OK
+@export var x1: Dictionary
+ hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+@export var x2: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
+@export var x3: Dictionary
+ hint=NONE hint_string="" usage=DEFAULT|SCRIPT_VARIABLE
+@export var x4: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
+@export var x5: TestExportEnumAsDictionary.MyEnum
+ hint=ENUM hint_string="A:0,B:1,C:2" usage=DEFAULT|SCRIPT_VARIABLE|CLASS_IS_ENUM
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd
new file mode 100644
index 0000000000..e1a1f07e47
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.gd
@@ -0,0 +1,22 @@
+var _typed_array: Array[int]
+
+func weak_param_func(weak_param = _typed_array):
+ weak_param = [11] # Don't treat the literal as typed!
+ return weak_param
+
+func hard_param_func(hard_param := _typed_array):
+ hard_param = [12]
+ return hard_param
+
+func test():
+ var weak_var = _typed_array
+ print(weak_var.is_typed())
+ weak_var = [21] # Don't treat the literal as typed!
+ print(weak_var.is_typed())
+ print(weak_param_func().is_typed())
+
+ var hard_var := _typed_array
+ print(hard_var.is_typed())
+ hard_var = [22]
+ print(hard_var.is_typed())
+ print(hard_param_func().is_typed())
diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out
new file mode 100644
index 0000000000..34b18dbe7c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_dont_make_literal_typed_with_weak_type.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+false
+false
+true
+true
+true
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd
new file mode 100644
index 0000000000..e5eecbb819
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.gd
@@ -0,0 +1,2 @@
+func test():
+ print(r"\")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out
new file mode 100644
index 0000000000..c8e843b0d7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_1.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unterminated string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd
new file mode 100644
index 0000000000..9168b69f86
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.gd
@@ -0,0 +1,2 @@
+func test():
+ print(r"\\"")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out
new file mode 100644
index 0000000000..c8e843b0d7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_2.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Unterminated string.
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd
new file mode 100644
index 0000000000..37dc910e5f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.gd
@@ -0,0 +1,3 @@
+func test():
+ # v
+ print(r"['"]*")
diff --git a/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out
new file mode 100644
index 0000000000..dcb5c2f289
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/errors/bad_r_string_3.out
@@ -0,0 +1,2 @@
+GDTEST_PARSER_ERROR
+Closing "]" doesn't have an opening counterpart.
diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.gd b/modules/gdscript/tests/scripts/parser/features/r_strings.gd
new file mode 100644
index 0000000000..6f546f28be
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/r_strings.gd
@@ -0,0 +1,22 @@
+func test():
+ print(r"test ' \' \" \\ \n \t \u2023 test")
+ print(r"\n\\[\t ]*(\w+)")
+ print(r"")
+ print(r"\"")
+ print(r"\\\"")
+ print(r"\\")
+ print(r"\" \\\" \\\\\"")
+ print(r"\ \\ \\\ \\\\ \\\\\ \\")
+ print(r'"')
+ print(r'"(?:\\.|[^"])*"')
+ print(r"""""")
+ print(r"""test \t "test"="" " \" \\\" \ \\ \\\ test""")
+ print(r'''r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""''')
+ print(r"\t
+ \t")
+ print(r"\t \
+ \t")
+ print(r"""\t
+ \t""")
+ print(r"""\t \
+ \t""")
diff --git a/modules/gdscript/tests/scripts/parser/features/r_strings.out b/modules/gdscript/tests/scripts/parser/features/r_strings.out
new file mode 100644
index 0000000000..114ef0a6c3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/r_strings.out
@@ -0,0 +1,22 @@
+GDTEST_OK
+test ' \' \" \\ \n \t \u2023 test
+\n\\[\t ]*(\w+)
+
+\"
+\\\"
+\\
+\" \\\" \\\\\"
+\ \\ \\\ \\\\ \\\\\ \\
+"
+"(?:\\.|[^"])*"
+
+test \t "test"="" " \" \\\" \ \\ \\\ test
+r"""test \t "test"="" " \" \\\" \ \\ \\\ test"""
+\t
+ \t
+\t \
+ \t
+\t
+ \t
+\t \
+ \t
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index 50444e62a1..fb20817117 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -19,6 +19,7 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
return property.class_name
return variant_get_type_name(property.type)
+
static func get_property_signature(property: Dictionary, is_static: bool = false) -> String:
var result: String = ""
if not (property.usage & PROPERTY_USAGE_SCRIPT_VARIABLE):
@@ -30,6 +31,15 @@ static func get_property_signature(property: Dictionary, is_static: bool = false
result += "var " + property.name + ": " + get_type(property)
return result
+
+static func get_property_additional_info(property: Dictionary) -> String:
+ return 'hint=%s hint_string="%s" usage=%s' % [
+ get_property_hint_name(property.hint).trim_prefix("PROPERTY_HINT_"),
+ str(property.hint_string).c_escape(),
+ get_property_usage_string(property.usage).replace("PROPERTY_USAGE_", ""),
+ ]
+
+
static func get_method_signature(method: Dictionary, is_signal: bool = false) -> String:
var result: String = ""
if method.flags & METHOD_FLAG_STATIC:
@@ -55,6 +65,7 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) ->
result += " -> " + get_type(method.return, true)
return result
+
static func variant_get_type_name(type: Variant.Type) -> String:
match type:
TYPE_NIL:
@@ -135,3 +146,136 @@ static func variant_get_type_name(type: Variant.Type) -> String:
return "PackedColorArray"
push_error("Argument `type` is invalid. Use `TYPE_*` constants.")
return "<invalid type>"
+
+
+static func get_property_hint_name(hint: PropertyHint) -> String:
+ match hint:
+ PROPERTY_HINT_NONE:
+ return "PROPERTY_HINT_NONE"
+ PROPERTY_HINT_RANGE:
+ return "PROPERTY_HINT_RANGE"
+ PROPERTY_HINT_ENUM:
+ return "PROPERTY_HINT_ENUM"
+ PROPERTY_HINT_ENUM_SUGGESTION:
+ return "PROPERTY_HINT_ENUM_SUGGESTION"
+ PROPERTY_HINT_EXP_EASING:
+ return "PROPERTY_HINT_EXP_EASING"
+ PROPERTY_HINT_LINK:
+ return "PROPERTY_HINT_LINK"
+ PROPERTY_HINT_FLAGS:
+ return "PROPERTY_HINT_FLAGS"
+ PROPERTY_HINT_LAYERS_2D_RENDER:
+ return "PROPERTY_HINT_LAYERS_2D_RENDER"
+ PROPERTY_HINT_LAYERS_2D_PHYSICS:
+ return "PROPERTY_HINT_LAYERS_2D_PHYSICS"
+ PROPERTY_HINT_LAYERS_2D_NAVIGATION:
+ return "PROPERTY_HINT_LAYERS_2D_NAVIGATION"
+ PROPERTY_HINT_LAYERS_3D_RENDER:
+ return "PROPERTY_HINT_LAYERS_3D_RENDER"
+ PROPERTY_HINT_LAYERS_3D_PHYSICS:
+ return "PROPERTY_HINT_LAYERS_3D_PHYSICS"
+ PROPERTY_HINT_LAYERS_3D_NAVIGATION:
+ return "PROPERTY_HINT_LAYERS_3D_NAVIGATION"
+ PROPERTY_HINT_LAYERS_AVOIDANCE:
+ return "PROPERTY_HINT_LAYERS_AVOIDANCE"
+ PROPERTY_HINT_FILE:
+ return "PROPERTY_HINT_FILE"
+ PROPERTY_HINT_DIR:
+ return "PROPERTY_HINT_DIR"
+ PROPERTY_HINT_GLOBAL_FILE:
+ return "PROPERTY_HINT_GLOBAL_FILE"
+ PROPERTY_HINT_GLOBAL_DIR:
+ return "PROPERTY_HINT_GLOBAL_DIR"
+ PROPERTY_HINT_RESOURCE_TYPE:
+ return "PROPERTY_HINT_RESOURCE_TYPE"
+ PROPERTY_HINT_MULTILINE_TEXT:
+ return "PROPERTY_HINT_MULTILINE_TEXT"
+ PROPERTY_HINT_EXPRESSION:
+ return "PROPERTY_HINT_EXPRESSION"
+ PROPERTY_HINT_PLACEHOLDER_TEXT:
+ return "PROPERTY_HINT_PLACEHOLDER_TEXT"
+ PROPERTY_HINT_COLOR_NO_ALPHA:
+ return "PROPERTY_HINT_COLOR_NO_ALPHA"
+ PROPERTY_HINT_OBJECT_ID:
+ return "PROPERTY_HINT_OBJECT_ID"
+ PROPERTY_HINT_TYPE_STRING:
+ return "PROPERTY_HINT_TYPE_STRING"
+ PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE:
+ return "PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE"
+ PROPERTY_HINT_OBJECT_TOO_BIG:
+ return "PROPERTY_HINT_OBJECT_TOO_BIG"
+ PROPERTY_HINT_NODE_PATH_VALID_TYPES:
+ return "PROPERTY_HINT_NODE_PATH_VALID_TYPES"
+ PROPERTY_HINT_SAVE_FILE:
+ return "PROPERTY_HINT_SAVE_FILE"
+ PROPERTY_HINT_GLOBAL_SAVE_FILE:
+ return "PROPERTY_HINT_GLOBAL_SAVE_FILE"
+ PROPERTY_HINT_INT_IS_OBJECTID:
+ return "PROPERTY_HINT_INT_IS_OBJECTID"
+ PROPERTY_HINT_INT_IS_POINTER:
+ return "PROPERTY_HINT_INT_IS_POINTER"
+ PROPERTY_HINT_ARRAY_TYPE:
+ return "PROPERTY_HINT_ARRAY_TYPE"
+ PROPERTY_HINT_LOCALE_ID:
+ return "PROPERTY_HINT_LOCALE_ID"
+ PROPERTY_HINT_LOCALIZABLE_STRING:
+ return "PROPERTY_HINT_LOCALIZABLE_STRING"
+ PROPERTY_HINT_NODE_TYPE:
+ return "PROPERTY_HINT_NODE_TYPE"
+ PROPERTY_HINT_HIDE_QUATERNION_EDIT:
+ return "PROPERTY_HINT_HIDE_QUATERNION_EDIT"
+ PROPERTY_HINT_PASSWORD:
+ return "PROPERTY_HINT_PASSWORD"
+ push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.")
+ return "<invalid hint>"
+
+
+static func get_property_usage_string(usage: int) -> String:
+ if usage == PROPERTY_USAGE_NONE:
+ return "PROPERTY_USAGE_NONE"
+
+ const FLAGS: Array[Array] = [
+ [PROPERTY_USAGE_DEFAULT, "PROPERTY_USAGE_DEFAULT"],
+ [PROPERTY_USAGE_STORAGE, "PROPERTY_USAGE_STORAGE"],
+ [PROPERTY_USAGE_EDITOR, "PROPERTY_USAGE_EDITOR"],
+ [PROPERTY_USAGE_INTERNAL, "PROPERTY_USAGE_INTERNAL"],
+ [PROPERTY_USAGE_CHECKABLE, "PROPERTY_USAGE_CHECKABLE"],
+ [PROPERTY_USAGE_CHECKED, "PROPERTY_USAGE_CHECKED"],
+ [PROPERTY_USAGE_GROUP, "PROPERTY_USAGE_GROUP"],
+ [PROPERTY_USAGE_CATEGORY, "PROPERTY_USAGE_CATEGORY"],
+ [PROPERTY_USAGE_SUBGROUP, "PROPERTY_USAGE_SUBGROUP"],
+ [PROPERTY_USAGE_CLASS_IS_BITFIELD, "PROPERTY_USAGE_CLASS_IS_BITFIELD"],
+ [PROPERTY_USAGE_NO_INSTANCE_STATE, "PROPERTY_USAGE_NO_INSTANCE_STATE"],
+ [PROPERTY_USAGE_RESTART_IF_CHANGED, "PROPERTY_USAGE_RESTART_IF_CHANGED"],
+ [PROPERTY_USAGE_SCRIPT_VARIABLE, "PROPERTY_USAGE_SCRIPT_VARIABLE"],
+ [PROPERTY_USAGE_STORE_IF_NULL, "PROPERTY_USAGE_STORE_IF_NULL"],
+ [PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED"],
+ [PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE, "PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE"],
+ [PROPERTY_USAGE_CLASS_IS_ENUM, "PROPERTY_USAGE_CLASS_IS_ENUM"],
+ [PROPERTY_USAGE_NIL_IS_VARIANT, "PROPERTY_USAGE_NIL_IS_VARIANT"],
+ [PROPERTY_USAGE_ARRAY, "PROPERTY_USAGE_ARRAY"],
+ [PROPERTY_USAGE_ALWAYS_DUPLICATE, "PROPERTY_USAGE_ALWAYS_DUPLICATE"],
+ [PROPERTY_USAGE_NEVER_DUPLICATE, "PROPERTY_USAGE_NEVER_DUPLICATE"],
+ [PROPERTY_USAGE_HIGH_END_GFX, "PROPERTY_USAGE_HIGH_END_GFX"],
+ [PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT, "PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT"],
+ [PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT, "PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT"],
+ [PROPERTY_USAGE_KEYING_INCREMENTS, "PROPERTY_USAGE_KEYING_INCREMENTS"],
+ [PROPERTY_USAGE_DEFERRED_SET_RESOURCE, "PROPERTY_USAGE_DEFERRED_SET_RESOURCE"],
+ [PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT, "PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT"],
+ [PROPERTY_USAGE_EDITOR_BASIC_SETTING, "PROPERTY_USAGE_EDITOR_BASIC_SETTING"],
+ [PROPERTY_USAGE_READ_ONLY, "PROPERTY_USAGE_READ_ONLY"],
+ [PROPERTY_USAGE_SECRET, "PROPERTY_USAGE_SECRET"],
+ ]
+
+ var result: String = ""
+
+ for flag in FLAGS:
+ if usage & flag[0]:
+ result += flag[1] + "|"
+ usage &= ~flag[0]
+
+ if usage != PROPERTY_USAGE_NONE:
+ push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.")
+ return "<invalid usage flags>"
+
+ return result.left(-1)
diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp
index 748e8ae50c..4ed730b3af 100644
--- a/modules/lightmapper_rd/lightmapper_rd.cpp
+++ b/modules/lightmapper_rd/lightmapper_rd.cpp
@@ -589,8 +589,12 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int
raster_push_constant.grid_size[0] = grid_size;
raster_push_constant.grid_size[1] = grid_size;
raster_push_constant.grid_size[2] = grid_size;
- raster_push_constant.uv_offset[0] = 0;
- raster_push_constant.uv_offset[1] = 0;
+
+ // Half pixel offset is required so the rasterizer doesn't output face edges directly aligned into pixels.
+ // This fixes artifacts where the pixel would be traced from the edge of a face, causing half the rays to
+ // be outside of the boundaries of the geometry. See <https://github.com/godotengine/godot/issues/69126>.
+ raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x);
+ raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y);
RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_READ, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors);
//draw opaque
@@ -1579,8 +1583,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
{
seams_push_constant.base_index = seam_offset;
rd->draw_list_bind_render_pipeline(draw_list, blendseams_line_raster_pipeline);
- seams_push_constant.uv_offset[0] = uv_offsets[0].x / float(atlas_size.width);
- seams_push_constant.uv_offset[1] = uv_offsets[0].y / float(atlas_size.height);
+ seams_push_constant.uv_offset[0] = (uv_offsets[0].x - 0.5f) / float(atlas_size.width);
+ seams_push_constant.uv_offset[1] = (uv_offsets[0].y - 0.5f) / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
@@ -1603,8 +1607,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
for (int j = 1; j < uv_offset_count; j++) {
seams_push_constant.base_index = seam_offset;
- seams_push_constant.uv_offset[0] = uv_offsets[j].x / float(atlas_size.width);
- seams_push_constant.uv_offset[1] = uv_offsets[j].y / float(atlas_size.height);
+ seams_push_constant.uv_offset[0] = (uv_offsets[j].x - 0.5f) / float(atlas_size.width);
+ seams_push_constant.uv_offset[1] = (uv_offsets[j].y - 0.5f) / float(atlas_size.height);
seams_push_constant.blend = uv_offsets[0].z;
rd->draw_list_set_push_constant(draw_list, &seams_push_constant, sizeof(RasterSeamsPushConstant));
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index 5770e7155e..ab74fce3a9 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -10,7 +10,7 @@
var regex = RegEx.new()
regex.compile("\\w-(\\d+)")
[/codeblock]
- The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code].
+ The search pattern must be escaped first for GDScript before it is escaped for the expression. For example, [code]compile("\\d+")[/code] would be read by RegEx as [code]\d+[/code]. Similarly, [code]compile("\"(?:\\\\.|[^\"])*\"")[/code] would be read as [code]"(?:\\.|[^"])*"[/code]. In GDScript, you can also use raw string literals (r-strings). For example, [code]compile(r'"(?:\\.|[^"])*"')[/code] would be read the same.
Using [method search], you can find the pattern within the given text. If a pattern is found, [RegExMatch] is returned and you can retrieve details of the results using methods such as [method RegExMatch.get_string] and [method RegExMatch.get_start].
[codeblock]
var regex = RegEx.new()
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 6ef8eacfd0..d35d35d36d 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -433,7 +433,7 @@ void CodeEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
// Allow unicode handling if:
// No modifiers are pressed (except Shift and CapsLock)
- bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
/* AUTO-COMPLETE */
if (code_completion_enabled && k->is_action("ui_text_completion_query", true)) {
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 6b5e3486ac..e37a3671f3 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -1155,7 +1155,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Snapping can be toggled temporarily by holding down Ctrl.
// This is done here as to not toggle the grid when holding down Ctrl.
- if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
}
@@ -1214,7 +1214,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
}
if (mb->get_button_index() == MouseButton::LEFT && !mb->is_pressed() && dragging) {
- if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
// Deselect current node.
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i));
@@ -1281,7 +1281,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
dragging = true;
drag_accum = Vector2();
just_selected = !graph_element->is_selected();
- if (!graph_element->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!graph_element->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
for (int i = 0; i < get_child_count(); i++) {
GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i));
if (!child_element) {
@@ -1314,7 +1314,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
// Left-clicked on empty space, start box select.
box_selecting = true;
box_selecting_from = mb->get_position();
- if (mb->is_ctrl_pressed()) {
+ if (mb->is_command_or_control_pressed()) {
box_selection_mode_additive = true;
prev_selected.clear();
for (int i = get_child_count() - 1; i >= 0; i--) {
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 8746d4079a..100e0c4548 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -614,7 +614,7 @@ void LineEdit::gui_input(const Ref<InputEvent> &p_event) {
// Allow unicode handling if:
// * No Modifiers are pressed (except shift)
- bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
if (allow_unicode_handling && editable && k->get_unicode() >= 32) {
// Handle Unicode if no modifiers are active.
@@ -679,13 +679,13 @@ void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
set_caret_at_pixel_pos(p_point.x);
int caret_column_tmp = caret_column;
bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end;
- if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
is_inside_sel = selection.enabled && caret_column > selection.begin && caret_column < selection.end;
}
if (selection.drag_attempt) {
selection.drag_attempt = false;
if (!is_inside_sel) {
- if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
if (caret_column_tmp > selection.end) {
caret_column_tmp = caret_column_tmp - (selection.end - selection.begin);
}
@@ -1139,7 +1139,7 @@ void LineEdit::_notification(int p_what) {
if (is_drag_successful()) {
if (selection.drag_attempt) {
selection.drag_attempt = false;
- if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
selection_delete();
} else if (deselect_on_focus_loss_enabled) {
deselect();
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 98bedd4493..4a013be1dc 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1598,7 +1598,7 @@ void TextEdit::_notification(int p_what) {
if (is_drag_successful()) {
if (selection_drag_attempt) {
selection_drag_attempt = false;
- if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (is_editable() && !Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
delete_selection();
} else if (deselect_on_focus_loss_enabled) {
deselect();
@@ -2049,7 +2049,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
// Allow unicode handling if:
// * No modifiers are pressed (except Shift and CapsLock)
- bool allow_unicode_handling = !(k->is_command_or_control_pressed() || k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
+ bool allow_unicode_handling = !(k->is_ctrl_pressed() || k->is_alt_pressed() || k->is_meta_pressed());
// Check and handle all built-in shortcuts.
@@ -3077,13 +3077,13 @@ void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) {
int caret_column_tmp = pos.x;
if (selection_drag_attempt) {
selection_drag_attempt = false;
- if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CTRL))) {
+ if (!is_mouse_over_selection(!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL))) {
// Set caret back at selection for undo / redo.
set_caret_line(get_selection_to_line(), false, false);
set_caret_column(get_selection_to_column());
begin_complex_operation();
- if (!Input::get_singleton()->is_key_pressed(Key::CTRL)) {
+ if (!Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
if (caret_row_tmp > get_selection_to_line()) {
caret_row_tmp = caret_row_tmp - (get_selection_to_line() - get_selection_from_line());
} else if (caret_row_tmp == get_selection_to_line() && caret_column_tmp >= get_selection_to_column()) {
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 4ed52bd465..e2b16cdd66 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -3815,7 +3815,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) {
}
if (mb->get_button_index() == MouseButton::LEFT) {
- if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_ctrl_pressed() && !mb->is_command_or_control_pressed()) {
+ if (get_item_at_position(mb->get_position()) == nullptr && !mb->is_shift_pressed() && !mb->is_command_or_control_pressed()) {
emit_signal(SNAME("nothing_selected"));
}
}
diff --git a/servers/audio/effects/audio_stream_generator.cpp b/servers/audio/effects/audio_stream_generator.cpp
index 5cfe51465d..f4727e72ec 100644
--- a/servers/audio/effects/audio_stream_generator.cpp
+++ b/servers/audio/effects/audio_stream_generator.cpp
@@ -143,6 +143,10 @@ void AudioStreamGeneratorPlayback::clear_buffer() {
}
int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_frames) {
+ if (!active) {
+ return 0;
+ }
+
int read_amount = buffer.data_left();
if (p_frames < read_amount) {
read_amount = p_frames;
@@ -151,16 +155,15 @@ int AudioStreamGeneratorPlayback::_mix_internal(AudioFrame *p_buffer, int p_fram
buffer.read(p_buffer, read_amount);
if (read_amount < p_frames) {
- //skipped, not ideal
+ // Fill with zeros as fallback in case of buffer underrun.
for (int i = read_amount; i < p_frames; i++) {
p_buffer[i] = AudioFrame(0, 0);
}
-
skips++;
}
mixed += p_frames / generator->get_mix_rate();
- return read_amount < p_frames ? read_amount : p_frames;
+ return p_frames;
}
float AudioStreamGeneratorPlayback::get_stream_sampling_rate() {
@@ -181,7 +184,7 @@ void AudioStreamGeneratorPlayback::stop() {
}
bool AudioStreamGeneratorPlayback::is_playing() const {
- return active; //always playing, can't be stopped
+ return active;
}
int AudioStreamGeneratorPlayback::get_loop_count() const {