summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/math/math_funcs.h10
-rw-r--r--doc/classes/Environment.xml2
-rw-r--r--drivers/gles3/shaders/tonemap.glsl16
-rw-r--r--editor/code_editor.cpp14
-rw-r--r--editor/icons/icon_auto_key.svg57
-rw-r--r--editor/icons/icon_ruler.svg5
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp17
-rw-r--r--editor/plugins/animation_player_editor_plugin.h1
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp5
-rw-r--r--editor/plugins/asset_library_editor_plugin.h1
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp134
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h8
-rw-r--r--editor/plugins/curve_editor_plugin.cpp16
-rw-r--r--modules/gdscript/gdscript_editor.cpp10
-rw-r--r--modules/gdscript/gdscript_functions.cpp36
-rw-r--r--modules/gdscript/gdscript_functions.h1
-rw-r--r--modules/gdscript/gdscript_parser.cpp83
-rw-r--r--modules/jsonrpc/jsonrpc.cpp18
-rw-r--r--modules/jsonrpc/jsonrpc.h10
-rw-r--r--modules/mono/glue/Managed/Files/Mathf.cs5
-rw-r--r--modules/mono/glue/Managed/Files/MathfEx.cs7
-rw-r--r--scene/2d/tile_map.cpp1
-rw-r--r--scene/gui/gradient_edit.cpp10
-rw-r--r--scene/gui/text_edit.cpp12
-rw-r--r--scene/main/scene_tree.cpp33
-rw-r--r--scene/main/scene_tree.h1
26 files changed, 410 insertions, 103 deletions
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index af845ca01e..9078abea68 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -300,6 +300,11 @@ public:
}
static _ALWAYS_INLINE_ bool is_equal_approx(real_t a, real_t b) {
+ // Check for exact equality first, required to handle "infinity" values.
+ if (a == b) {
+ return true;
+ }
+ // Then check for approximate equality.
real_t tolerance = CMP_EPSILON * abs(a);
if (tolerance < CMP_EPSILON) {
tolerance = CMP_EPSILON;
@@ -308,6 +313,11 @@ public:
}
static _ALWAYS_INLINE_ bool is_equal_approx(real_t a, real_t b, real_t tolerance) {
+ // Check for exact equality first, required to handle "infinity" values.
+ if (a == b) {
+ return true;
+ }
+ // Then check for approximate equality.
return abs(a - b) < tolerance;
}
diff --git a/doc/classes/Environment.xml b/doc/classes/Environment.xml
index fcbd8a2193..9a943aba51 100644
--- a/doc/classes/Environment.xml
+++ b/doc/classes/Environment.xml
@@ -306,7 +306,7 @@
Replace glow blending mode. Replaces all pixels' color by the glow value.
</constant>
<constant name="TONE_MAPPER_LINEAR" value="0" enum="ToneMapper">
- Linear tonemapper operator. Reads the linear data and performs an exposure adjustment.
+ Linear tonemapper operator. Reads the linear data and passes it on unmodified.
</constant>
<constant name="TONE_MAPPER_REINHARDT" value="1" enum="ToneMapper">
Reinhardt tonemapper operator. Performs a variation on rendered pixels' colors by this formula: [code]color = color / (1 + color)[/code].
diff --git a/drivers/gles3/shaders/tonemap.glsl b/drivers/gles3/shaders/tonemap.glsl
index 626968bc05..f1fe1742eb 100644
--- a/drivers/gles3/shaders/tonemap.glsl
+++ b/drivers/gles3/shaders/tonemap.glsl
@@ -164,7 +164,8 @@ vec3 linear_to_srgb(vec3 color) { // convert linear rgb to srgb, assumes clamped
return mix((vec3(1.0f) + a) * pow(color.rgb, vec3(1.0f / 2.4f)) - a, 12.92f * color.rgb, lessThan(color.rgb, vec3(0.0031308f)));
}
-vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always outputs clamped [0;1] color
+// inputs are LINEAR, If Linear tonemapping is selected no transform is performed else outputs are clamped [0, 1] color
+vec3 apply_tonemapping(vec3 color, float white) {
#ifdef USE_REINHARD_TONEMAPPER
return tonemap_reinhard(color, white);
#endif
@@ -177,7 +178,7 @@ vec3 apply_tonemapping(vec3 color, float white) { // inputs are LINEAR, always o
return tonemap_aces(color, white);
#endif
- return clamp(color, vec3(0.0f), vec3(1.0f)); // no other selected -> linear
+ return color; // no other selected -> linear: no color transform applied
}
vec3 gather_glow(sampler2D tex, vec2 uv) { // sample all selected glow levels
@@ -220,10 +221,14 @@ vec3 apply_glow(vec3 color, vec3 glow) { // apply glow using the selected blendi
#endif
#ifdef USE_GLOW_SCREEN
+ //need color clamping
+ color = clamp(color, vec3(0.0f), vec3(1.0f));
color = max((color + glow) - (color * glow), vec3(0.0));
#endif
#ifdef USE_GLOW_SOFTLIGHT
+ //need color clamping
+ color = clamp(color, vec3(0.0f), vec3(1.0));
glow = glow * vec3(0.5f) + vec3(0.5f);
color.r = (glow.r <= 0.5f) ? (color.r - (1.0f - 2.0f * glow.r) * color.r * (1.0f - color.r)) : (((glow.r > 0.5f) && (color.r <= 0.25f)) ? (color.r + (2.0f * glow.r - 1.0f) * (4.0f * color.r * (4.0f * color.r + 1.0f) * (color.r - 1.0f) + 7.0f * color.r)) : (color.r + (2.0f * glow.r - 1.0f) * (sqrt(color.r) - color.r)));
@@ -265,14 +270,16 @@ void main() {
color *= exposure;
- // Early Tonemap & SRGB Conversion
+ // Early Tonemap & SRGB Conversion; note that Linear tonemapping does not clamp to [0, 1]; some operations below expect a [0, 1] range and will clamp
color = apply_tonemapping(color, white);
#ifdef KEEP_3D_LINEAR
// leave color as is (-> don't convert to SRGB)
#else
- color = linear_to_srgb(color); // regular linear -> SRGB conversion
+ //need color clamping
+ color = clamp(color, vec3(0.0f), vec3(1.0f));
+ color = linear_to_srgb(color); // regular linear -> SRGB conversion (needs clamped values)
#endif
// Glow
@@ -282,6 +289,7 @@ void main() {
// high dynamic range -> SRGB
glow = apply_tonemapping(glow, white);
+ glow = clamp(glow, vec3(0.0f), vec3(1.0f));
glow = linear_to_srgb(glow);
color = apply_glow(color, glow);
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 606b619cac..b6cd69c3cd 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -366,14 +366,12 @@ bool FindReplaceBar::search_prev() {
if (text_edit->is_selection_active())
col--; // Skip currently selected word.
- if (line == result_line && col == result_col) {
- col -= text.length();
- if (col < 0) {
- line -= 1;
- if (line < 0)
- line = text_edit->get_line_count() - 1;
- col = text_edit->get_line(line).length();
- }
+ col -= text.length();
+ if (col < 0) {
+ line -= 1;
+ if (line < 0)
+ line = text_edit->get_line_count() - 1;
+ col = text_edit->get_line(line).length();
}
return _search(flags, line, col);
diff --git a/editor/icons/icon_auto_key.svg b/editor/icons/icon_auto_key.svg
index cbafe1ac38..3d5569397f 100644
--- a/editor/icons/icon_auto_key.svg
+++ b/editor/icons/icon_auto_key.svg
@@ -1,56 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="16"
- height="16"
- version="1.1"
- viewBox="0 0 16 16"
- id="svg6"
- sodipodi:docname="icon_auto_key.svg"
- inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
- <metadata
- id="metadata12">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <defs
- id="defs10" />
- <sodipodi:namedview
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1854"
- inkscape:window-height="1016"
- id="namedview8"
- showgrid="false"
- inkscape:zoom="10.429825"
- inkscape:cx="10.199345"
- inkscape:cy="-4.0344119"
- inkscape:window-x="66"
- inkscape:window-y="27"
- inkscape:window-maximized="1"
- inkscape:current-layer="svg6" />
- <path
- style="fill:#e0e0e0;fill-opacity:1;stroke-width:0.0333107"
- d="M 3.5469681,13.426786 C 2.7965829,13.263778 2.2774312,12.503915 2.4037297,11.753472 c 0.1081234,-0.642451 0.6006808,-1.135008 1.2431317,-1.243131 0.9667125,-0.162696 1.8555225,0.726112 1.6928259,1.692826 -0.103766,0.616558 -0.5592173,1.098057 -1.1588427,1.225117 -0.2719576,0.05763 -0.3626872,0.05741 -0.6338765,-0.0014 z m 8.0861339,-0.08275 c -0.746862,-0.13829 -1.23937,-0.720718 -1.23937,-1.465649 0,-0.527377 0.244831,-0.978806 0.679757,-1.253362 0.471386,-0.297574 1.114188,-0.297574 1.585574,0 0.682727,0.430986 0.892336,1.362194 0.460575,2.046149 -0.307786,0.487563 -0.940521,0.773963 -1.486536,0.672862 z M 0.60726032,9.8305658 V 7.7161233 L 1.1770842,7.7070075 1.7469079,7.6978939 3.1889882,5.1995916 4.6310686,2.7012893 h 3.1726318 3.1726316 l 1.442755,2.4983023 1.442755,2.4983023 0.651097,0.00903 0.651096,0.00903 v 2.1145264 2.1145257 h -0.566282 -0.566281 v -0.161225 c 0,-0.234927 -0.113135,-0.639704 -0.255664,-0.914727 -0.16895,-0.326004 -0.574198,-0.731251 -0.900202,-0.9002019 -0.656732,-0.3403483 -1.428549,-0.3403483 -2.085281,0 -0.326004,0.1689519 -0.731252,0.5741989 -0.9002019,0.9002029 -0.1425297,0.275023 -0.2556639,0.6798 -0.2556639,0.914727 v 0.161225 H 7.8570969 6.0797346 L 6.0617736,11.686851 C 6.006289,10.889347 5.447548,10.170679 4.6603773,9.884336 4.4466221,9.8065798 4.3737631,9.797427 3.9716406,9.7978134 3.5871254,9.7981885 3.4905638,9.809405 3.3054265,9.8752358 2.5067319,10.159236 1.9362359,10.884501 1.8813215,11.68568 l -0.017772,0.259329 H 1.2354063 0.60726287 Z M 12.399247,7.7466889 c 0,-0.037287 -0.02623,-0.1073444 -0.0583,-0.1556843 -0.03206,-0.04834 -0.561225,-0.958444 -1.17592,-2.0224529 L 10.047407,3.6339894 7.6977565,3.6254406 C 5.4917229,3.6174174 5.3450379,3.6204563 5.2979001,3.6754094 5.1898818,3.8013046 2.9723198,7.6840061 2.9723198,7.7472381 c 0,0.067139 0.00758,0.067247 4.7134636,0.067247 h 4.7134636 z"
- id="path6243"
- inkscape:connector-curvature="0" />
-</svg>
+<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0"><path d="m5 3-3 5h-1v4h1.0507812a2.5 2.5 0 0 1 2.4492188-2 2.5 2.5 0 0 1 2.4453125 2h2.1054687a2.5 2.5 0 0 1 2.4492188-2 2.5 2.5 0 0 1 2.445312 2h1.054688v-4h-1l-4-5zm1 1h3l3 4h-8z" stroke-width=".033311"/><circle cx="4.5" cy="12.5" r="1.5"/><circle cx="11.5" cy="12.5" r="1.5"/></g></svg> \ No newline at end of file
diff --git a/editor/icons/icon_ruler.svg b/editor/icons/icon_ruler.svg
new file mode 100644
index 0000000000..a16eda000b
--- /dev/null
+++ b/editor/icons/icon_ruler.svg
@@ -0,0 +1,5 @@
+<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg">
+<g transform="translate(0 -292.77)">
+<path transform="matrix(.26458 0 0 .26458 0 292.77)" d="m1 1v7.5 6.5h14l-14-14zm3 7 4 4h-4v-4z" fill="#e0e0e0"/>
+</g>
+</svg>
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index f42716c827..173079b6de 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -477,6 +477,19 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
_animation_selected(idx);
}
+double AnimationPlayerEditor::_get_editor_step() const {
+
+ // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
+ if (track_editor->is_snap_enabled()) {
+ const String current = player->get_assigned_animation();
+ const Ref<Animation> anim = player->get_animation(current);
+ // Use more precise snapping when holding Shift
+ return Input::get_singleton()->is_key_pressed(KEY_SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
+ }
+
+ return 0.0;
+}
+
void AnimationPlayerEditor::_animation_name_edited() {
player->stop();
@@ -1017,7 +1030,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) {
float pos = CLAMP(anim->get_length() * (p_value / frame->get_max()), 0, anim->get_length());
if (track_editor->is_snap_enabled()) {
- pos = Math::stepify(pos, anim->get_step());
+ pos = Math::stepify(pos, _get_editor_step());
}
if (player->is_valid() && !p_set) {
@@ -1068,7 +1081,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag)
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
updating = true;
- frame->set_value(Math::stepify(p_pos, track_editor->is_snap_enabled() ? anim->get_step() : 0));
+ frame->set_value(Math::stepify(p_pos, _get_editor_step()));
updating = false;
_seek_value_changed(p_pos, !p_drag);
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 2ab2df68e6..4ad30675ec 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -161,6 +161,7 @@ class AnimationPlayerEditor : public VBoxContainer {
} onion;
void _select_anim_by_name(const String &p_anim);
+ double _get_editor_step() const;
void _play_pressed();
void _play_from_pressed();
void _play_bw_pressed();
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index 894e5c7298..60b5f017d2 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -586,7 +586,7 @@ void EditorAssetLibrary::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- if (is_visible()) {
+ if (is_visible() && initial_loading) {
_repository_changed(0); // Update when shown for the first time.
}
} break;
@@ -1133,6 +1133,8 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const
} break;
case REQUESTING_SEARCH: {
+ initial_loading = false;
+
// The loading text only needs to be displayed before the first page is loaded
library_loading->hide();
@@ -1328,6 +1330,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) {
requesting = REQUESTING_NONE;
templates_only = p_templates_only;
+ initial_loading = true;
VBoxContainer *library_main = memnew(VBoxContainer);
diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h
index b17a6dfe54..6a3158889e 100644
--- a/editor/plugins/asset_library_editor_plugin.h
+++ b/editor/plugins/asset_library_editor_plugin.h
@@ -206,6 +206,7 @@ class EditorAssetLibrary : public PanelContainer {
HTTPRequest *request;
bool templates_only;
+ bool initial_loading;
enum Support {
SUPPORT_OFFICIAL,
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 2daee70474..e4cd71fec0 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2246,6 +2246,40 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
return false;
}
+bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) {
+
+ if (tool != TOOL_RULER)
+ return false;
+
+ Ref<InputEventMouseButton> b = p_event;
+ Ref<InputEventMouseMotion> m = p_event;
+
+ Point2 previous_origin = ruler_tool_origin;
+ if (!ruler_tool_active)
+ ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom;
+
+ if (b.is_valid() && b->get_button_index() == BUTTON_LEFT) {
+ if (b->is_pressed()) {
+ ruler_tool_active = true;
+ } else {
+ ruler_tool_active = false;
+ }
+
+ viewport->update();
+ return true;
+ }
+
+ bool is_snap_active = snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+
+ if (m.is_valid() && (ruler_tool_active || (is_snap_active && previous_origin != ruler_tool_origin))) {
+
+ viewport->update();
+ return true;
+ }
+
+ return false;
+}
+
bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseMotion> m = p_event;
@@ -2323,6 +2357,8 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
//printf("Anchors\n");
} else if ((accepted = _gui_input_select(p_event))) {
//printf("Selection\n");
+ } else if ((accepted = _gui_input_ruler_tool(p_event))) {
+ //printf("Measure\n");
} else {
//printf("Not accepted\n");
}
@@ -2350,6 +2386,9 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
case TOOL_PAN:
c = CURSOR_DRAG;
break;
+ case TOOL_RULER:
+ c = CURSOR_CROSS;
+ break;
default:
break;
}
@@ -2626,6 +2665,83 @@ void CanvasItemEditor::_draw_grid() {
}
}
+void CanvasItemEditor::_draw_ruler_tool() {
+
+ if (tool != TOOL_RULER)
+ return;
+
+ bool is_snap_active = snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL);
+
+ if (ruler_tool_active) {
+ Color ruler_primary_color = get_color("accent_color", "Editor");
+ Color ruler_secondary_color = ruler_primary_color;
+ ruler_secondary_color.a = 0.5;
+
+ Point2 begin = ruler_tool_origin - view_offset * zoom;
+ Point2 end = snap_point(viewport->get_local_mouse_position() / zoom + view_offset) * zoom - view_offset * zoom;
+ Point2 corner = Point2(begin.x, end.y);
+ Vector2 length_vector = (begin - end).abs() / zoom;
+
+ bool draw_secondary_lines = (begin.y != corner.y && end.x != corner.x);
+
+ viewport->draw_line(begin, end, ruler_primary_color, Math::round(EDSCALE * 3), true);
+ if (draw_secondary_lines) {
+ viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE));
+ viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE));
+ }
+
+ Ref<Font> font = get_font("bold", "EditorFonts");
+ Color font_color = get_color("font_color", "Editor");
+ Color font_secondary_color = font_color;
+ font_secondary_color.a = 0.5;
+ float text_height = font->get_height();
+ const float text_width = 76;
+
+ Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2);
+ text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
+ text_pos.y = CLAMP(text_pos.y, text_height * 1.5, viewport->get_rect().size.y - text_height * 1.5);
+ viewport->draw_string(font, text_pos, vformat("%.2f px", length_vector.length()), font_color);
+
+ if (draw_secondary_lines) {
+
+ Point2 text_pos2 = text_pos;
+ text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);
+ viewport->draw_string(font, text_pos2, vformat("%.2f px", length_vector.y), font_secondary_color);
+
+ text_pos2 = text_pos;
+ text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2);
+ viewport->draw_string(font, text_pos2, vformat("%.2f px", length_vector.x), font_secondary_color);
+ }
+
+ if (is_snap_active) {
+
+ text_pos = (begin + end) / 2 + Vector2(-text_width / 2, text_height / 2);
+ text_pos.x = CLAMP(text_pos.x, text_width / 2, viewport->get_rect().size.x - text_width * 1.5);
+ text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2);
+
+ if (draw_secondary_lines) {
+ viewport->draw_string(font, text_pos, vformat("%.2f units", (length_vector / grid_step).length()), font_color);
+
+ Point2 text_pos2 = text_pos;
+ text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2);
+ viewport->draw_string(font, text_pos2, vformat("%d units", (int)(length_vector.y / grid_step.y)), font_secondary_color);
+
+ text_pos2 = text_pos;
+ text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y + text_height / 2) : MAX(text_pos.y + text_height * 2, end.y + text_height / 2);
+ viewport->draw_string(font, text_pos2, vformat("%d units", (int)(length_vector.x / grid_step.x)), font_secondary_color);
+ } else {
+ viewport->draw_string(font, text_pos, vformat("%d units", roundf((length_vector / grid_step).length())), font_color);
+ }
+ }
+ } else {
+
+ if (is_snap_active) {
+ Ref<Texture> position_icon = get_icon("EditorPosition", "EditorIcons");
+ viewport->draw_texture(get_icon("EditorPosition", "EditorIcons"), ruler_tool_origin - view_offset * zoom - position_icon->get_size() / 2);
+ }
+ }
+}
+
void CanvasItemEditor::_draw_control_anchors(Control *control) {
Transform2D xform = transform * control->get_global_transform_with_canvas();
RID ci = viewport->get_canvas_item();
@@ -3358,6 +3474,7 @@ void CanvasItemEditor::_draw_viewport() {
info_overlay->set_margin(MARGIN_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10);
_draw_grid();
+ _draw_ruler_tool();
_draw_selection();
_draw_axis();
if (editor->get_edited_scene()) {
@@ -3546,6 +3663,7 @@ void CanvasItemEditor::_notification(int p_what) {
snap_config_menu->set_icon(get_icon("GuiMiniTabMenu", "EditorIcons"));
skeleton_menu->set_icon(get_icon("Bone", "EditorIcons"));
pan_button->set_icon(get_icon("ToolPan", "EditorIcons"));
+ ruler_button->set_icon(get_icon("Ruler", "EditorIcons"));
pivot_button->set_icon(get_icon("EditPivot", "EditorIcons"));
select_handle = get_icon("EditorHandle", "EditorIcons");
anchor_handle = get_icon("EditorControlAnchor", "EditorIcons");
@@ -3940,13 +4058,13 @@ void CanvasItemEditor::_button_toggle_snap(bool p_status) {
void CanvasItemEditor::_button_tool_select(int p_index) {
- ToolButton *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button };
+ ToolButton *tb[TOOL_MAX] = { select_button, list_select_button, move_button, scale_button, rotate_button, pivot_button, pan_button, ruler_button };
for (int i = 0; i < TOOL_MAX; i++) {
tb[i]->set_pressed(i == p_index);
}
- viewport->update();
tool = (Tool)p_index;
+ viewport->update();
}
void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) {
@@ -4926,6 +5044,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
panning = false;
pan_pressed = false;
+ ruler_tool_active = false;
+ ruler_tool_origin = Point2();
+
bone_last_frame = 0;
bone_list_dirty = false;
@@ -5079,6 +5200,13 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
pan_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_PAN));
pan_button->set_tooltip(TTR("Pan Mode"));
+ ruler_button = memnew(ToolButton);
+ hb->add_child(ruler_button);
+ ruler_button->set_toggle_mode(true);
+ ruler_button->connect("pressed", this, "_button_tool_select", make_binds(TOOL_RULER));
+ ruler_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTR("Ruler Mode"), KEY_R));
+ ruler_button->set_tooltip(TTR("Ruler Mode"));
+
hb->add_child(memnew(VSeparator));
snap_button = memnew(ToolButton);
@@ -5171,7 +5299,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) {
p->set_hide_on_checkable_item_selection(false);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Show Grid"), KEY_G), SHOW_GRID);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS);
- p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers"), KEY_R), SHOW_RULERS);
+ p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers"), KEY_MASK_CMD | KEY_R), SHOW_RULERS);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTR("Show Origin")), SHOW_ORIGIN);
p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTR("Show Viewport")), SHOW_VIEWPORT);
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index ac7d612292..4e030c63da 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -80,6 +80,7 @@ public:
TOOL_ROTATE,
TOOL_EDIT_PIVOT,
TOOL_PAN,
+ TOOL_RULER,
TOOL_MAX
};
@@ -276,6 +277,9 @@ private:
bool panning;
bool pan_pressed;
+ bool ruler_tool_active;
+ Point2 ruler_tool_origin;
+
MenuOption last_option;
struct _SelectResult {
@@ -341,6 +345,8 @@ private:
ToolButton *pivot_button;
ToolButton *pan_button;
+ ToolButton *ruler_button;
+
ToolButton *snap_button;
MenuButton *snap_config_menu;
PopupMenu *smartsnap_config_popup;
@@ -457,6 +463,7 @@ private:
void _draw_guides();
void _draw_focus();
void _draw_grid();
+ void _draw_ruler_tool();
void _draw_control_anchors(Control *control);
void _draw_control_helpers(Control *control);
void _draw_selection();
@@ -476,6 +483,7 @@ private:
bool _gui_input_resize(const Ref<InputEvent> &p_event);
bool _gui_input_rotate(const Ref<InputEvent> &p_event);
bool _gui_input_select(const Ref<InputEvent> &p_event);
+ bool _gui_input_ruler_tool(const Ref<InputEvent> &p_event);
bool _gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted);
bool _gui_input_rulers_and_guides(const Ref<InputEvent> &p_event);
bool _gui_input_hover(const Ref<InputEvent> &p_event);
diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp
index 5d3cef4c34..c2b6031e60 100644
--- a/editor/plugins/curve_editor_plugin.cpp
+++ b/editor/plugins/curve_editor_plugin.cpp
@@ -167,10 +167,20 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
_has_undo_data = true;
}
+ const float curve_amplitude = curve.get_max_value() - curve.get_min_value();
+ // Snap to "round" coordinates when holding Ctrl.
+ // Be more precise when holding Shift as well.
+ float snap_threshold;
+ if (mm.get_control()) {
+ snap_threshold = mm.get_shift() ? 0.025 : 0.1;
+ } else {
+ snap_threshold = 0.0;
+ }
+
if (_selected_tangent == TANGENT_NONE) {
// Drag point
- Vector2 point_pos = get_world_pos(mpos);
+ Vector2 point_pos = get_world_pos(mpos).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude));
int i = curve.set_point_offset(_selected_point, point_pos.x);
// The index may change if the point is dragged across another one
@@ -188,8 +198,8 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) {
} else {
// Drag tangent
- Vector2 point_pos = curve.get_point_position(_selected_point);
- Vector2 control_pos = get_world_pos(mpos);
+ const Vector2 point_pos = curve.get_point_position(_selected_point);
+ const Vector2 control_pos = get_world_pos(mpos).snapped(Vector2(snap_threshold, snap_threshold * curve_amplitude));
Vector2 dir = (control_pos - point_pos).normalized();
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 7c01e85ff7..925dbda620 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -2826,6 +2826,16 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path
ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS);
options.insert(option.display, option);
}
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CLASS);
+ options.insert(option.display, option);
+ }
}
List<StringName> native_classes;
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index ad8bf5b2c0..97790e00bb 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -106,6 +106,7 @@ const char *GDScriptFunctions::get_func_name(Function p_func) {
"typeof",
"type_exists",
"char",
+ "ord",
"str",
"print",
"printt",
@@ -665,6 +666,33 @@ void GDScriptFunctions::call(Function p_func, const Variant **p_args, int p_arg_
CharType result[2] = { *p_args[0], 0 };
r_ret = String(result);
} break;
+ case TEXT_ORD: {
+
+ VALIDATE_ARG_COUNT(1);
+
+ if (p_args[0]->get_type() != Variant::STRING) {
+
+ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::STRING;
+ r_ret = Variant();
+ return;
+ }
+
+ String str = p_args[0]->operator String();
+
+ if (str.length() != 1) {
+
+ r_error.error = Variant::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.argument = 0;
+ r_error.expected = Variant::STRING;
+ r_ret = RTR("Expected a string of length 1 (a character).");
+ return;
+ }
+
+ r_ret = str.get(0);
+
+ } break;
case TEXT_STR: {
if (p_arg_count < 1) {
r_error.error = Variant::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS;
@@ -1507,6 +1535,7 @@ bool GDScriptFunctions::is_deterministic(Function p_func) {
case TYPE_OF:
case TYPE_EXISTS:
case TEXT_CHAR:
+ case TEXT_ORD:
case TEXT_STR:
case COLOR8:
case LEN:
@@ -1849,6 +1878,13 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
+ case TEXT_ORD: {
+
+ MethodInfo mi("ord", PropertyInfo(Variant::STRING, "char"));
+ mi.return_val.type = Variant::INT;
+ return mi;
+
+ } break;
case TEXT_STR: {
MethodInfo mi("str");
diff --git a/modules/gdscript/gdscript_functions.h b/modules/gdscript/gdscript_functions.h
index 8f7ba76d2c..9ea5dd46cf 100644
--- a/modules/gdscript/gdscript_functions.h
+++ b/modules/gdscript/gdscript_functions.h
@@ -97,6 +97,7 @@ public:
TYPE_OF,
TYPE_EXISTS,
TEXT_CHAR,
+ TEXT_ORD,
TEXT_STR,
TEXT_PRINT,
TEXT_PRINT_TABBED,
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 99bfdae7ec..e96bf0238a 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -252,6 +252,16 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
}
}
+ // Check that the next token is not TK_CURSOR and if it is, the offset should be incremented.
+ int next_valid_offset = 1;
+ if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_CURSOR) {
+ next_valid_offset++;
+ // There is a chunk of the identifier that also needs to be ignored (not always there!)
+ if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_IDENTIFIER) {
+ next_valid_offset++;
+ }
+ }
+
if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_OPEN) {
//subexpression ()
tokenizer->advance();
@@ -668,7 +678,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s
expr = cn;
}
- } else if (tokenizer->get_token(1) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
+ } else if (tokenizer->get_token(next_valid_offset) == GDScriptTokenizer::TK_PARENTHESIS_OPEN && tokenizer->is_token_literal()) {
// We check with is_token_literal, as this allows us to use match/sync/etc. as a name
//function or constructor
@@ -5245,6 +5255,31 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive
return;
}
p = NULL;
+ } else {
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == base) {
+ String singleton_path = ProjectSettings::get_singleton()->get(s);
+ if (singleton_path.begins_with("*")) {
+ singleton_path = singleton_path.right(1);
+ }
+ if (!singleton_path.begins_with("res://")) {
+ singleton_path = "res://" + singleton_path;
+ }
+ base_script = ResourceLoader::load(singleton_path);
+ if (!base_script.is_valid()) {
+ _set_error("Class '" + base + "' could not be fully loaded (script error or cyclic inheritance).", p_class->line);
+ return;
+ }
+ p = NULL;
+ }
+ }
}
while (p) {
@@ -5589,9 +5624,49 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,
}
name_part++;
continue;
- } else {
- p = current_class;
}
+ List<PropertyInfo> props;
+ ProjectSettings::get_singleton()->get_property_list(&props);
+ String singleton_path;
+ for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+ String s = E->get().name;
+ if (!s.begins_with("autoload/")) {
+ continue;
+ }
+ String name = s.get_slice("/", 1);
+ if (name == id) {
+ singleton_path = ProjectSettings::get_singleton()->get(s);
+ if (singleton_path.begins_with("*")) {
+ singleton_path = singleton_path.right(1);
+ }
+ if (!singleton_path.begins_with("res://")) {
+ singleton_path = "res://" + singleton_path;
+ }
+ break;
+ }
+ }
+ if (!singleton_path.empty()) {
+ Ref<Script> script = ResourceLoader::load(singleton_path);
+ Ref<GDScript> gds = script;
+ if (gds.is_valid()) {
+ if (!gds->is_valid()) {
+ _set_error("Class '" + id + "' could not be fully loaded (script error or cyclic inheritance).", p_line);
+ return DataType();
+ }
+ result.kind = DataType::GDSCRIPT;
+ result.script_type = gds;
+ } else if (script.is_valid()) {
+ result.kind = DataType::SCRIPT;
+ result.script_type = script;
+ } else {
+ _set_error("Couldn't fully load singleton script '" + id + "' (possible cyclic reference or parse error).", p_line);
+ return DataType();
+ }
+ name_part++;
+ continue;
+ }
+
+ p = current_class;
} else if (base_type.kind == DataType::CLASS) {
p = base_type.class_type;
}
@@ -6510,7 +6585,7 @@ GDScriptParser::DataType GDScriptParser::_reduce_node_type(Node *p_node) {
return DataType();
}
}
- if (check_types && !node_type.has_type) {
+ if (check_types && !node_type.has_type && base_type.kind == DataType::BUILTIN) {
// Can infer indexing type for some variant types
DataType result;
result.has_type = true;
diff --git a/modules/jsonrpc/jsonrpc.cpp b/modules/jsonrpc/jsonrpc.cpp
index b18b48d1b0..e1bba60f2f 100644
--- a/modules/jsonrpc/jsonrpc.cpp
+++ b/modules/jsonrpc/jsonrpc.cpp
@@ -47,11 +47,11 @@ void JSONRPC::_bind_methods() {
ClassDB::bind_method(D_METHOD("make_notification", "method", "params"), &JSONRPC::make_notification);
ClassDB::bind_method(D_METHOD("make_response_error", "code", "message", "id"), &JSONRPC::make_response_error, DEFVAL(Variant()));
- BIND_ENUM_CONSTANT(ParseError)
- BIND_ENUM_CONSTANT(InvalidRequest)
- BIND_ENUM_CONSTANT(MethodNotFound)
- BIND_ENUM_CONSTANT(InvalidParams)
- BIND_ENUM_CONSTANT(InternalError)
+ BIND_ENUM_CONSTANT(PARSE_ERROR)
+ BIND_ENUM_CONSTANT(INVALID_REQUEST)
+ BIND_ENUM_CONSTANT(METHOD_NOT_FOUND)
+ BIND_ENUM_CONSTANT(INVALID_PARAMS)
+ BIND_ENUM_CONSTANT(INTERNAL_ERROR)
}
Dictionary JSONRPC::make_response_error(int p_code, const String &p_message, const Variant &p_id) const {
@@ -120,7 +120,7 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
}
if (object == NULL || !object->has_method(method)) {
- ret = make_response_error(JSONRPC::MethodNotFound, "Method not found", id);
+ ret = make_response_error(JSONRPC::METHOD_NOT_FOUND, "Method not found", id);
} else {
Variant call_ret = object->callv(method, args);
if (id.get_type() != Variant::NIL) {
@@ -138,10 +138,10 @@ Variant JSONRPC::process_action(const Variant &p_action, bool p_process_arr_elem
}
ret = arr_ret;
} else {
- ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request");
+ ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request");
}
} else {
- ret = make_response_error(JSONRPC::InvalidRequest, "Invalid Request");
+ ret = make_response_error(JSONRPC::INVALID_REQUEST, "Invalid Request");
}
return ret;
}
@@ -155,7 +155,7 @@ String JSONRPC::process_string(const String &p_input) {
String err_message;
int err_line;
if (OK != JSON::parse(p_input, input, err_message, err_line)) {
- ret = make_response_error(JSONRPC::ParseError, "Parse error");
+ ret = make_response_error(JSONRPC::PARSE_ERROR, "Parse error");
} else {
ret = process_action(input, true);
}
diff --git a/modules/jsonrpc/jsonrpc.h b/modules/jsonrpc/jsonrpc.h
index bcb34ecc65..91897d0b55 100644
--- a/modules/jsonrpc/jsonrpc.h
+++ b/modules/jsonrpc/jsonrpc.h
@@ -47,11 +47,11 @@ public:
~JSONRPC();
enum ErrorCode {
- ParseError = -32700,
- InvalidRequest = -32600,
- MethodNotFound = -32601,
- InvalidParams = -32602,
- InternalError = -32603,
+ PARSE_ERROR = -32700,
+ INVALID_REQUEST = -32600,
+ METHOD_NOT_FOUND = -32601,
+ INVALID_PARAMS = -32602,
+ INTERNAL_ERROR = -32603,
};
Dictionary make_response_error(int p_code, const String &p_message, const Variant &p_id = Variant()) const;
diff --git a/modules/mono/glue/Managed/Files/Mathf.cs b/modules/mono/glue/Managed/Files/Mathf.cs
index 15adf0a13b..ce34cd6a99 100644
--- a/modules/mono/glue/Managed/Files/Mathf.cs
+++ b/modules/mono/glue/Managed/Files/Mathf.cs
@@ -158,6 +158,11 @@ namespace Godot
public static bool IsEqualApprox(real_t a, real_t b)
{
+ // Check for exact equality first, required to handle "infinity" values.
+ if (a == b) {
+ return true;
+ }
+ // Then check for approximate equality.
real_t tolerance = Epsilon * Abs(a);
if (tolerance < Epsilon) {
tolerance = Epsilon;
diff --git a/modules/mono/glue/Managed/Files/MathfEx.cs b/modules/mono/glue/Managed/Files/MathfEx.cs
index b96f01bc2e..6cffc7f01d 100644
--- a/modules/mono/glue/Managed/Files/MathfEx.cs
+++ b/modules/mono/glue/Managed/Files/MathfEx.cs
@@ -48,7 +48,12 @@ namespace Godot
public static bool IsEqualApprox(real_t a, real_t b, real_t tolerance)
{
+ // Check for exact equality first, required to handle "infinity" values.
+ if (a == b) {
+ return true;
+ }
+ // Then check for approximate equality.
return Abs(a - b) < tolerance;
}
}
-} \ No newline at end of file
+}
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index 2cd05b5c50..15423f8c5e 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -1944,6 +1944,7 @@ TileMap::TileMap() {
quadrant_order_dirty = false;
quadrant_size = 16;
cell_size = Size2(64, 64);
+ custom_transform = Transform2D(64, 0, 0, 64, 0, 0);
collision_layer = 1;
collision_mask = 1;
friction = 1;
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index 75f5f79873..09ef6f26bf 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -241,9 +241,13 @@ void GradientEdit::_gui_input(const Ref<InputEvent> &p_event) {
float newofs = CLAMP(x / float(total_w), 0, 1);
- //Snap to nearest point if holding shift
- if (mm->get_shift()) {
- float snap_threshold = 0.03;
+ // Snap to "round" coordinates if holding Ctrl.
+ // Be more precise if holding Shift as well
+ if (mm->get_control()) {
+ newofs = Math::stepify(newofs, mm->get_shift() ? 0.025 : 0.1);
+ } else if (mm->get_shift()) {
+ // Snap to nearest point if holding just Shift
+ const float snap_threshold = 0.03;
float smallest_ofs = snap_threshold;
bool found = false;
int nearest_point = 0;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index ab5ed3166c..0464cc1ac8 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -611,7 +611,6 @@ void TextEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
-
_update_caches();
if (cursor_changed_dirty)
MessageQueue::get_singleton()->push_call(this, "_cursor_changed_emit");
@@ -620,12 +619,16 @@ void TextEdit::_notification(int p_what) {
_update_wrap_at();
} break;
case NOTIFICATION_RESIZED: {
-
_update_scrollbars();
_update_wrap_at();
} break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ if (is_visible()) {
+ call_deferred("_update_scrollbars");
+ call_deferred("_update_wrap_at");
+ }
+ } break;
case NOTIFICATION_THEME_CHANGED: {
-
_update_caches();
_update_wrap_at();
syntax_highlighting_cache.clear();
@@ -5362,6 +5365,9 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l
break;
}
pos_from = last_pos - p_key.length();
+ if (pos_from < 0) {
+ break;
+ }
}
} else {
while ((last_pos = (p_search_flags & SEARCH_MATCH_CASE) ? text_line.find(p_key, pos_from) : text_line.findn(p_key, pos_from)) != -1) {
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 617a703855..6c89016ea3 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -33,6 +33,7 @@
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
#include "core/message_queue.h"
+#include "core/os/dir_access.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/print_string.h"
@@ -1953,6 +1954,38 @@ bool SceneTree::is_using_font_oversampling() const {
return use_font_oversampling;
}
+void SceneTree::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+
+ if (p_function == "change_scene") {
+ DirAccessRef dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ List<String> directories;
+ directories.push_back(dir_access->get_current_dir());
+
+ while (!directories.empty()) {
+ dir_access->change_dir(directories.back()->get());
+ directories.pop_back();
+
+ dir_access->list_dir_begin();
+ String filename = dir_access->get_next();
+
+ while (filename != "") {
+ if (filename == "." || filename == "..") {
+ filename = dir_access->get_next();
+ continue;
+ }
+
+ if (dir_access->dir_exists(filename)) {
+ directories.push_back(dir_access->get_current_dir().plus_file(filename));
+ } else if (filename.ends_with(".tscn") || filename.ends_with(".scn")) {
+ r_options->push_back("\"" + dir_access->get_current_dir().plus_file(filename) + "\"");
+ }
+
+ filename = dir_access->get_next();
+ }
+ }
+ }
+}
+
SceneTree::SceneTree() {
if (singleton == NULL) singleton = this;
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 42a87545a6..d387886d61 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -408,6 +408,7 @@ public:
void drop_files(const Vector<String> &p_files, int p_from_screen = 0);
void global_menu_action(const Variant &p_id, const Variant &p_meta);
+ void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
//network API