summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/linux_builds.yml6
-rw-r--r--core/os/keyboard.h2
-rw-r--r--core/variant/variant.cpp40
-rw-r--r--core/variant/variant.h1
-rw-r--r--core/variant/variant_utility.cpp7
-rw-r--r--doc/classes/@GlobalScope.xml25
-rw-r--r--doc/classes/Dictionary.xml2
-rw-r--r--doc/classes/MultiplayerAPI.xml4
-rw-r--r--doc/classes/Node.xml2
-rw-r--r--doc/classes/RichTextLabel.xml8
-rw-r--r--editor/animation_bezier_editor.cpp25
-rw-r--r--editor/animation_bezier_editor.h5
-rw-r--r--editor/animation_track_editor.cpp46
-rw-r--r--editor/animation_track_editor.h10
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp91
-rw-r--r--editor/plugins/animation_state_machine_editor.h22
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp56
-rw-r--r--editor/plugins/canvas_item_editor_plugin.h6
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.cpp16
-rw-r--r--editor/plugins/polygon_2d_editor_plugin.h5
-rw-r--r--editor/plugins/script_text_editor.cpp6
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp16
-rw-r--r--editor/plugins/texture_region_editor_plugin.h5
-rw-r--r--editor/plugins/tiles/tile_atlas_view.cpp12
-rw-r--r--editor/plugins/tiles/tile_atlas_view.h5
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp27
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.cpp55
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.h4
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp41
-rw-r--r--editor/plugins/tiles/tile_set_editor.h2
-rw-r--r--editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp25
-rw-r--r--editor/plugins/tiles/tile_set_scenes_collection_source_editor.h2
-rw-r--r--editor/plugins/tiles/tiles_editor_plugin.cpp2
-rw-r--r--editor/project_converter_3_to_4.cpp40
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml2
-rw-r--r--modules/gdscript/gdscript.cpp100
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp79
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_editor.cpp3
-rw-r--r--modules/gdscript/gdscript_parser.cpp67
-rw-r--r--modules/gdscript/gdscript_parser.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out1
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_variable.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/await_on_void.gd2
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/gdscript.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd10
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd2
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml2
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp7
-rw-r--r--platform/windows/display_server_windows.cpp10
-rw-r--r--scene/animation/animation_node_state_machine.cpp30
-rw-r--r--scene/animation/animation_node_state_machine.h9
-rw-r--r--scene/gui/base_button.cpp6
-rw-r--r--scene/gui/button.cpp6
-rw-r--r--scene/gui/graph_edit.cpp28
-rw-r--r--scene/gui/graph_edit.h5
-rw-r--r--scene/gui/rich_text_label.cpp4
-rw-r--r--scene/gui/view_panner.cpp84
-rw-r--r--scene/gui/view_panner.h16
-rw-r--r--tests/core/variant/test_variant.h190
74 files changed, 875 insertions, 474 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
index ce4f757a19..bfda7c72f8 100644
--- a/.github/workflows/linux_builds.yml
+++ b/.github/workflows/linux_builds.yml
@@ -82,10 +82,8 @@ jobs:
sudo cp -f misc/ci/sources.list /etc/apt/sources.list
sudo apt-get update
# The actual dependencies
- sudo apt-get install build-essential pkg-config libx11-dev libxcursor-dev \
- libxinerama-dev libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev \
- libdbus-1-dev libudev-dev libxi-dev libxrandr-dev yasm xvfb wget unzip \
- llvm libspeechd-dev speech-dispatcher fontconfig libfontconfig-dev libxkbcommon-dev
+ sudo apt-get install build-essential pkg-config libgl1-mesa-dev libglu-dev \
+ xvfb wget unzip llvm
- name: Setup Godot build cache
uses: ./.github/actions/godot-cache
diff --git a/core/os/keyboard.h b/core/os/keyboard.h
index 6315356510..84017e89a6 100644
--- a/core/os/keyboard.h
+++ b/core/os/keyboard.h
@@ -124,8 +124,6 @@ enum class Key {
KP_7 = SPECIAL | 0x8D,
KP_8 = SPECIAL | 0x8E,
KP_9 = SPECIAL | 0x8F,
- SUPER_L = SPECIAL | 0x40,
- SUPER_R = SPECIAL | 0x41,
MENU = SPECIAL | 0x42,
HYPER = SPECIAL | 0x43,
HELP = SPECIAL | 0x45,
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index ca42738b05..672b030806 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -3492,6 +3492,46 @@ bool Variant::hash_compare(const Variant &p_variant, int recursion_count) const
}
}
+bool Variant::identity_compare(const Variant &p_variant) const {
+ if (type != p_variant.type) {
+ return false;
+ }
+
+ switch (type) {
+ case OBJECT: {
+ return _get_obj().obj == p_variant._get_obj().obj;
+ } break;
+
+ case DICTIONARY: {
+ const Dictionary &l = *(reinterpret_cast<const Dictionary *>(_data._mem));
+ const Dictionary &r = *(reinterpret_cast<const Dictionary *>(p_variant._data._mem));
+ return l.id() == r.id();
+ } break;
+
+ case ARRAY: {
+ const Array &l = *(reinterpret_cast<const Array *>(_data._mem));
+ const Array &r = *(reinterpret_cast<const Array *>(p_variant._data._mem));
+ return l.id() == r.id();
+ } break;
+
+ case PACKED_BYTE_ARRAY:
+ case PACKED_INT32_ARRAY:
+ case PACKED_INT64_ARRAY:
+ case PACKED_FLOAT32_ARRAY:
+ case PACKED_FLOAT64_ARRAY:
+ case PACKED_STRING_ARRAY:
+ case PACKED_VECTOR2_ARRAY:
+ case PACKED_VECTOR3_ARRAY:
+ case PACKED_COLOR_ARRAY: {
+ return _data.packed_array == p_variant._data.packed_array;
+ } break;
+
+ default: {
+ return hash_compare(p_variant);
+ }
+ }
+}
+
bool StringLikeVariantComparator::compare(const Variant &p_lhs, const Variant &p_rhs) {
if (p_lhs.hash_compare(p_rhs)) {
return true;
diff --git a/core/variant/variant.h b/core/variant/variant.h
index b9294de77d..b2f31a6d57 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -748,6 +748,7 @@ public:
uint32_t recursive_hash(int recursion_count) const;
bool hash_compare(const Variant &p_variant, int recursion_count = 0) const;
+ bool identity_compare(const Variant &p_variant) const;
bool booleanize() const;
String stringify(int recursion_count = 0) const;
String to_json_string() const;
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index fe7150bca9..042ebe368a 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -1007,9 +1007,14 @@ struct VariantUtilityFunctions {
static inline uint64_t rid_allocate_id() {
return RID_AllocBase::_gen_id();
}
+
static inline RID rid_from_int64(uint64_t p_base) {
return RID::from_uint64(p_base);
}
+
+ static inline bool is_same(const Variant &p_a, const Variant &p_b) {
+ return p_a.identity_compare(p_b);
+ }
};
#ifdef DEBUG_METHODS_ENABLED
@@ -1601,6 +1606,8 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(rid_allocate_id, Vector<String>(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDR(rid_from_int64, sarray("base"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+
+ FUNCBINDR(is_same, sarray("a", "b"), Variant::UTILITY_FUNC_TYPE_GENERAL);
}
void Variant::_unregister_variant_utility_functions() {
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 4fdb7d82c5..ecb6a83c8a 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -525,6 +525,31 @@
Returns [code]true[/code] if [param x] is a NaN ("Not a Number" or invalid) value.
</description>
</method>
+ <method name="is_same">
+ <return type="bool" />
+ <param index="0" name="a" type="Variant" />
+ <param index="1" name="b" type="Variant" />
+ <description>
+ Returns [code]true[/code], for value types, if [param a] and [param b] share the same value. Returns [code]true[/code], for reference types, if the references of [param a] and [param b] are the same.
+ [codeblock]
+ # Vector2 is a value type
+ var vec2_a = Vector2(0, 0)
+ var vec2_b = Vector2(0, 0)
+ var vec2_c = Vector2(1, 1)
+ is_same(vec2_a, vec2_a) # true
+ is_same(vec2_a, vec2_b) # true
+ is_same(vec2_a, vec2_c) # false
+
+ # Array is a reference type
+ var arr_a = []
+ var arr_b = []
+ is_same(arr_a, arr_a) # true
+ is_same(arr_a, arr_b) # false
+ [/codeblock]
+ These are [Variant] value types: [code]null[/code], [bool], [int], [float], [String], [StringName], [Vector2], [Vector2i], [Vector3], [Vector3i], [Vector4], [Vector4i], [Rect2], [Rect2i], [Transform2D], [Transform3D], [Plane], [Quaternion], [AABB], [Basis], [Projection], [Color], [NodePath], [RID], [Callable] and [Signal].
+ These are [Variant] reference types: [Object], [Dictionary], [Array], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], [PackedFloat32Array], [PackedFloat64Array], [PackedStringArray], [PackedVector2Array], [PackedVector3Array] and [PackedColorArray].
+ </description>
+ </method>
<method name="is_zero_approx">
<return type="bool" />
<param index="0" name="x" type="float" />
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index 5f99ba82b8..03e5b5d1d8 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -42,7 +42,7 @@
You can access a dictionary's value by referencing its corresponding key. In the above example, [code]points_dict["White"][/code] will return [code]50[/code]. You can also write [code]points_dict.White[/code], which is equivalent. However, you'll have to use the bracket syntax if the key you're accessing the dictionary with isn't a fixed string (such as a number or variable).
[codeblocks]
[gdscript]
- @export(String, "White", "Yellow", "Orange") var my_color
+ @export_enum("White", "Yellow", "Orange") var my_color: String
var points_dict = {"White": 50, "Yellow": 75, "Orange": 100}
func _ready():
# We can't use dot syntax here as `my_color` is a variable.
diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml
index 3ce6ce41b4..020ce4f468 100644
--- a/doc/classes/MultiplayerAPI.xml
+++ b/doc/classes/MultiplayerAPI.xml
@@ -138,10 +138,10 @@
Used with [method Node.rpc_config] to disable a method or property for all RPC calls, making it unavailable. Default for all methods.
</constant>
<constant name="RPC_MODE_ANY_PEER" value="1" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc(any)[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
+ Used with [method Node.rpc_config] to set a method to be callable remotely by any peer. Analogous to the [code]@rpc("any")[/code] annotation. Calls are accepted from all remote peers, no matter if they are node's authority or not.
</constant>
<constant name="RPC_MODE_AUTHORITY" value="2" enum="RPCMode">
- Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc(authority)[/code] annotation. See [method Node.set_multiplayer_authority].
+ Used with [method Node.rpc_config] to set a method to be callable remotely only by the current multiplayer authority (which is the server by default). Analogous to the [code]@rpc("authority")[/code] annotation. See [method Node.set_multiplayer_authority].
</constant>
</constants>
</class>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 7b27f16d82..7c40c189c0 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -666,7 +666,7 @@
channel = 0,
}
[/codeblock]
- See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc(any)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs).
+ See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding annotation ([code]@rpc("any")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs).
</description>
</method>
<method name="rpc_id" qualifiers="vararg">
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 8c0b16c76f..1ecc8a1d4e 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -251,6 +251,14 @@
Adds a [code][color][/code] tag to the tag stack.
</description>
</method>
+ <method name="push_customfx">
+ <return type="void" />
+ <param index="0" name="effect" type="RichTextEffect" />
+ <param index="1" name="env" type="Dictionary" />
+ <description>
+ Adds a custom effect tag to the tag stack. The effect does not need to be in [member custom_effects]. The environment is directly passed to the effect.
+ </description>
+ </method>
<method name="push_dropcap">
<return type="void" />
<param index="0" name="string" type="String" />
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp
index 639f5e6de5..8defa04ada 100644
--- a/editor/animation_bezier_editor.cpp
+++ b/editor/animation_bezier_editor.cpp
@@ -1489,32 +1489,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
}
}
-void AnimationBezierTrackEdit::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- _pan_callback(-p_scroll_vec * 32);
-}
-
-void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec) {
+void AnimationBezierTrackEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
v_scroll += p_scroll_vec.y * v_zoom;
v_scroll = CLAMP(v_scroll, -100000, 100000);
timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());
queue_redraw();
}
-void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
+void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
const float v_zoom_orig = v_zoom;
- if (p_alt) {
+ Ref<InputEventWithModifiers> iewm = p_event;
+ if (iewm.is_valid() && iewm->is_alt_pressed()) {
// Alternate zoom (doesn't affect timeline).
- if (p_scroll_vec.y > 0) {
- v_zoom = MIN(v_zoom * 1.2, 100000);
- } else {
- v_zoom = MAX(v_zoom / 1.2, 0.000001);
- }
+ v_zoom = CLAMP(v_zoom * p_zoom_factor, 0.000001, 100000);
} else {
- if (p_scroll_vec.y > 0) {
- timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / 1.05);
- } else {
- timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() * 1.05);
- }
+ timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / p_zoom_factor);
}
v_scroll = v_scroll + (p_origin.y - get_size().y / 2.0) * (v_zoom - v_zoom_orig);
queue_redraw();
@@ -1681,7 +1670,7 @@ void AnimationBezierTrackEdit::_bind_methods() {
AnimationBezierTrackEdit::AnimationBezierTrackEdit() {
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_scroll_callback), callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback));
play_position = memnew(Control);
play_position->set_mouse_filter(MOUSE_FILTER_PASS);
diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h
index e6d6424ef2..dbc231ccac 100644
--- a/editor/animation_bezier_editor.h
+++ b/editor/animation_bezier_editor.h
@@ -174,9 +174,8 @@ class AnimationBezierTrackEdit : public Control {
SelectionSet selection;
Ref<ViewPanner> panner;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
void _draw_line_clipped(const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, int p_clip_left, int p_clip_right);
void _draw_track(int p_track, const Color &p_color);
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 857a9a664a..338a22c070 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -1704,25 +1704,13 @@ Control::CursorShape AnimationTimelineEdit::get_cursor_shape(const Point2 &p_pos
}
}
-void AnimationTimelineEdit::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- // Timeline has no vertical scroll, so we change it to horizontal.
- p_scroll_vec.x += p_scroll_vec.y;
- _pan_callback(-p_scroll_vec * 32);
-}
-
-void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec) {
+void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
set_value(get_value() - p_scroll_vec.x / get_zoom_scale());
}
-void AnimationTimelineEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- double new_zoom_value;
+void AnimationTimelineEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
double current_zoom_value = get_zoom()->get_value();
- if (current_zoom_value <= 0.1) {
- new_zoom_value = MAX(0.01, current_zoom_value - 0.01 * SIGN(p_scroll_vec.y));
- } else {
- new_zoom_value = p_scroll_vec.y > 0 ? MAX(0.01, current_zoom_value / 1.05) : current_zoom_value * 1.05;
- }
- get_zoom()->set_value(new_zoom_value);
+ get_zoom()->set_value(MAX(0.01, current_zoom_value * p_zoom_factor));
}
void AnimationTimelineEdit::set_use_fps(bool p_use_fps) {
@@ -1798,7 +1786,8 @@ AnimationTimelineEdit::AnimationTimelineEdit() {
len_hb->hide();
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_scroll_callback), callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback));
+ panner->set_pan_axis(ViewPanner::PAN_AXIS_HORIZONTAL);
set_layout_direction(Control::LAYOUT_DIRECTION_LTR);
}
@@ -5358,32 +5347,23 @@ void AnimationTrackEditor::_toggle_bezier_edit() {
}
}
-void AnimationTrackEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- if (p_alt) {
+void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
+ Ref<InputEventWithModifiers> iewm = p_event;
+ if (iewm.is_valid() && iewm->is_alt_pressed()) {
if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) {
goto_prev_step(true);
} else {
goto_next_step(true);
}
} else {
- _pan_callback(-p_scroll_vec * 32);
+ timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());
+ scroll->set_v_scroll(scroll->get_v_scroll() - p_scroll_vec.y);
}
}
-void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec) {
- timeline->set_value(timeline->get_value() - p_scroll_vec.x / timeline->get_zoom_scale());
- scroll->set_v_scroll(scroll->get_v_scroll() - p_scroll_vec.y);
-}
-
-void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- double new_zoom_value;
+void AnimationTrackEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
double current_zoom_value = timeline->get_zoom()->get_value();
- if (current_zoom_value <= 0.1) {
- new_zoom_value = MAX(0.01, current_zoom_value - 0.01 * SIGN(p_scroll_vec.y));
- } else {
- new_zoom_value = p_scroll_vec.y > 0 ? MAX(0.01, current_zoom_value / 1.05) : current_zoom_value * 1.05;
- }
- timeline->get_zoom()->set_value(new_zoom_value);
+ timeline->get_zoom()->set_value(MAX(0.01, current_zoom_value * p_zoom_factor));
}
void AnimationTrackEditor::_cancel_bezier_edit() {
@@ -6398,7 +6378,7 @@ AnimationTrackEditor::AnimationTrackEditor() {
timeline->connect("length_changed", callable_mp(this, &AnimationTrackEditor::_update_length));
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_scroll_callback), callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback));
scroll = memnew(ScrollContainer);
timeline_vbox->add_child(scroll);
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index ef06445011..8506d9b80d 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -159,9 +159,8 @@ class AnimationTimelineEdit : public Range {
bool use_fps = false;
Ref<ViewPanner> panner;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
bool dragging_timeline = false;
bool dragging_hsize = false;
@@ -460,9 +459,8 @@ class AnimationTrackEditor : public VBoxContainer {
PropertyInfo _find_hint_for_track(int p_idx, NodePath &r_base_path, Variant *r_current_val = nullptr);
Ref<ViewPanner> panner;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
void _timeline_value_changed(double);
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index c0972d201e..7ede0bd68c 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -1127,7 +1127,7 @@ void AnimationNodeStateMachineEditor::_add_transition(const bool p_nested_action
connecting = false;
}
-void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance, bool p_multi_transitions) {
+void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions) {
Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label"));
Color icon_color(1, 1, 1);
Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor"));
@@ -1153,11 +1153,15 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co
if (p_travel) {
linecolor = accent;
- linecolor.set_hsv(1.0, linecolor.get_s(), linecolor.get_v());
}
state_machine_draw->draw_line(p_from, p_to, linecolor, 2);
+ if (p_fade_ratio > 0.0) {
+ Color fade_linecolor = accent;
+ fade_linecolor.set_hsv(1.0, fade_linecolor.get_s(), fade_linecolor.get_v());
+ state_machine_draw->draw_line(p_from, p_from.lerp(p_to, p_fade_ratio), fade_linecolor, 2);
+ }
Ref<Texture2D> icon = icons[p_mode + (p_auto_advance ? 3 : 0)];
Transform2D xf;
@@ -1327,7 +1331,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
}
}
- _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(switch_mode->get_selected()), true, false, false, false, false);
+ _connection_draw(from, to, AnimationNodeStateMachineTransition::SwitchMode(switch_mode->get_selected()), true, false, false, 0.0, false, false);
}
Ref<Texture2D> tr_reference_icon = get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons"));
@@ -1357,6 +1361,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
tl.mode = tr->get_switch_mode();
tl.width = tr_bidi_offset;
tl.travel = false;
+ tl.fade_ratio = 0.0;
tl.hidden = false;
if (state_machine->has_local_transition(local_to, local_from)) { //offset if same exists
@@ -1378,6 +1383,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
if (blend_from == local_from && current == local_to) {
tl.travel = true;
+ tl.fade_ratio = MIN(1.0, fading_pos / fading_time);
}
if (travel_path.size()) {
@@ -1418,7 +1424,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
for (int i = 0; i < transition_lines.size(); i++) {
TransitionLine tl = transition_lines[i];
if (!tl.hidden) {
- _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.auto_advance, !tl.multi_transitions.is_empty());
+ _connection_draw(tl.from, tl.to, tl.mode, !tl.disabled, tl.selected, tl.travel, tl.fade_ratio, tl.auto_advance, !tl.multi_transitions.is_empty());
}
}
@@ -1508,25 +1514,24 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() {
state_machine_play_pos->queue_redraw();
}
-void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
+void AnimationNodeStateMachineEditor::_state_machine_pos_draw_individual(String p_name, float p_ratio) {
AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
if (!tree) {
return;
}
Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
-
if (!playback.is_valid() || !playback->is_playing()) {
return;
}
- if (playback->get_current_node() == state_machine->start_node || playback->get_current_node() == state_machine->end_node) {
+ if (p_name == state_machine->start_node || p_name == state_machine->end_node || p_name.is_empty()) {
return;
}
int idx = -1;
for (int i = 0; i < node_rects.size(); i++) {
- if (node_rects[i].node_name == playback->get_current_node()) {
+ if (node_rects[i].node_name == p_name) {
idx = i;
break;
}
@@ -1550,10 +1555,7 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
}
to.y = from.y;
- float len = MAX(0.0001, current_length);
-
- float pos = CLAMP(play_pos, 0, len);
- float c = pos / len;
+ float c = p_ratio;
Color fg = get_theme_color(SNAME("font_color"), SNAME("Label"));
Color bg = fg;
bg.a *= 0.3;
@@ -1565,6 +1567,32 @@ void AnimationNodeStateMachineEditor::_state_machine_pos_draw() {
state_machine_play_pos->draw_line(from, to, fg, 2);
}
+void AnimationNodeStateMachineEditor::_state_machine_pos_draw_all() {
+ AnimationTree *tree = AnimationTreeEditor::get_singleton()->get_animation_tree();
+ if (!tree) {
+ return;
+ }
+
+ Ref<AnimationNodeStateMachinePlayback> playback = tree->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback");
+ if (!playback.is_valid() || !playback->is_playing()) {
+ return;
+ }
+
+ {
+ float len = MAX(0.0001, current_length);
+ float pos = CLAMP(current_play_pos, 0, len);
+ float c = pos / len;
+ _state_machine_pos_draw_individual(playback->get_current_node(), c);
+ }
+
+ {
+ float len = MAX(0.0001, fade_from_length);
+ float pos = CLAMP(fade_from_current_play_pos, 0, len);
+ float c = pos / len;
+ _state_machine_pos_draw_individual(playback->get_fading_from_node(), c);
+ }
+}
+
void AnimationNodeStateMachineEditor::_update_graph() {
if (updating) {
return;
@@ -1687,17 +1715,28 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
Vector<StringName> tp;
bool is_playing = false;
StringName current_node;
- StringName blend_from_node;
- play_pos = 0;
+ StringName fading_from_node;
+
+ current_play_pos = 0;
current_length = 0;
+ fade_from_current_play_pos = 0;
+ fade_from_length = 0;
+
+ fading_time = 0;
+ fading_pos = 0;
+
if (playback.is_valid()) {
tp = playback->get_travel_path();
is_playing = playback->is_playing();
current_node = playback->get_current_node();
- blend_from_node = playback->get_fading_from_node();
- play_pos = playback->get_current_play_pos();
+ fading_from_node = playback->get_fading_from_node();
+ current_play_pos = playback->get_current_play_pos();
current_length = playback->get_current_length();
+ fade_from_current_play_pos = playback->get_fade_from_play_pos();
+ fade_from_length = playback->get_fade_from_length();
+ fading_time = playback->get_fading_time();
+ fading_pos = playback->get_fading_pos();
}
{
@@ -1714,12 +1753,19 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
}
//redraw if travel state changed
- if (!same_travel_path || last_active != is_playing || last_current_node != current_node || last_blend_from_node != blend_from_node) {
+ if (!same_travel_path ||
+ last_active != is_playing ||
+ last_current_node != current_node ||
+ last_fading_from_node != fading_from_node ||
+ last_fading_time != fading_time ||
+ last_fading_pos != fading_pos) {
state_machine_draw->queue_redraw();
last_travel_path = tp;
last_current_node = current_node;
last_active = is_playing;
- last_blend_from_node = blend_from_node;
+ last_fading_from_node = fading_from_node;
+ last_fading_time = fading_time;
+ last_fading_pos = fading_pos;
state_machine_play_pos->queue_redraw();
}
@@ -1737,14 +1783,15 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) {
// when current_node is a state machine, use playback of current_node to set play_pos
if (current_node_playback.is_valid()) {
- play_pos = current_node_playback->get_current_play_pos();
+ current_play_pos = current_node_playback->get_current_play_pos();
current_length = current_node_playback->get_current_length();
}
}
}
- if (last_play_pos != play_pos) {
- last_play_pos = play_pos;
+ if (last_play_pos != current_play_pos || fade_from_last_play_pos != fade_from_current_play_pos) {
+ last_play_pos = current_play_pos;
+ fade_from_last_play_pos = fade_from_current_play_pos;
state_machine_play_pos->queue_redraw();
}
} break;
@@ -2048,7 +2095,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() {
state_machine_draw->add_child(state_machine_play_pos);
state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent
state_machine_play_pos->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
- state_machine_play_pos->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_pos_draw));
+ state_machine_play_pos->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_pos_draw_all));
v_scroll = memnew(VScrollBar);
state_machine_draw->add_child(v_scroll);
diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h
index 46fe13ccc1..66338c820e 100644
--- a/editor/plugins/animation_state_machine_editor.h
+++ b/editor/plugins/animation_state_machine_editor.h
@@ -85,9 +85,12 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
static AnimationNodeStateMachineEditor *singleton;
void _state_machine_gui_input(const Ref<InputEvent> &p_event);
- void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, bool p_auto_advance, bool p_multi_transitions);
+ void _connection_draw(const Vector2 &p_from, const Vector2 &p_to, AnimationNodeStateMachineTransition::SwitchMode p_mode, bool p_enabled, bool p_selected, bool p_travel, float p_fade_ratio, bool p_auto_advance, bool p_multi_transitions);
+
void _state_machine_draw();
- void _state_machine_pos_draw();
+
+ void _state_machine_pos_draw_individual(String p_name, float p_ratio);
+ void _state_machine_pos_draw_all();
void _update_graph();
@@ -150,6 +153,7 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
float width = 0;
bool selected;
bool travel;
+ float fade_ratio;
bool hidden;
int transition_index;
Vector<TransitionLine> multi_transitions;
@@ -204,13 +208,23 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin {
void _delete_tree_draw();
bool last_active = false;
- StringName last_blend_from_node;
+ StringName last_fading_from_node;
StringName last_current_node;
Vector<StringName> last_travel_path;
+
+ float fade_from_last_play_pos = 0.0f;
+ float fade_from_current_play_pos = 0.0f;
+ float fade_from_length = 0.0f;
+
float last_play_pos = 0.0f;
- float play_pos = 0.0f;
+ float current_play_pos = 0.0f;
float current_length = 0.0f;
+ float last_fading_time = 0.0f;
+ float last_fading_pos = 0.0f;
+ float fading_time = 0.0f;
+ float fading_pos = 0.0f;
+
float error_time = 0.0f;
String error_text;
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 5d0555a10e..e09636d297 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -1259,57 +1259,25 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo
}
}
- Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
- if (magnify_gesture.is_valid() && !p_already_accepted) {
- // Zoom gesture
- _zoom_on_position(zoom * magnify_gesture->get_factor(), magnify_gesture->get_position());
- return true;
- }
-
- Ref<InputEventPanGesture> pan_gesture = p_event;
- if (pan_gesture.is_valid() && !p_already_accepted) {
- // If ctrl key pressed, then zoom instead of pan.
- if (pan_gesture->is_ctrl_pressed()) {
- const real_t factor = pan_gesture->get_delta().y;
-
- zoom_widget->set_zoom_by_increments(1);
- if (factor != 1.f) {
- zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * factor + 1.f));
- }
- _zoom_on_position(zoom_widget->get_zoom(), pan_gesture->get_position());
-
- return true;
- }
-
- // Pan gesture
- const Vector2 delta = (pan_speed / zoom) * pan_gesture->get_delta();
- view_offset.x += delta.x;
- view_offset.y += delta.y;
- update_viewport();
- return true;
- }
-
return false;
}
-void CanvasItemEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- _pan_callback(-p_scroll_vec * pan_speed);
-}
-
-void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec) {
+void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
view_offset.x -= p_scroll_vec.x / zoom;
view_offset.y -= p_scroll_vec.y / zoom;
update_viewport();
}
-void CanvasItemEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- int scroll_sign = (int)SIGN(p_scroll_vec.y);
- // Inverted so that scrolling up (-1) zooms in, scrolling down (+1) zooms out.
- zoom_widget->set_zoom_by_increments(-scroll_sign, p_alt);
- if (!Math::is_equal_approx(ABS(p_scroll_vec.y), (real_t)1.0)) {
- // Handle high-precision (analog) scrolling.
- zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * ABS(p_scroll_vec.y) + 1.f));
+void CanvasItemEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ // Special behvior for scroll events, as the zoom_by_increment method can smartly end up on powers of two.
+ int increment = p_zoom_factor > 1.0 ? 1 : -1;
+ zoom_widget->set_zoom_by_increments(increment, mb->is_alt_pressed());
+ } else {
+ zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);
}
+
_zoom_on_position(zoom_widget->get_zoom(), p_origin);
}
@@ -3868,7 +3836,7 @@ void CanvasItemEditor::_update_editor_settings() {
context_menu_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("ContextualToolbar"), SNAME("EditorStyles")));
panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning")));
- pan_speed = int(EDITOR_GET("editors/panning/2d_editor_pan_speed"));
+ panner->set_scroll_speed(EDITOR_GET("editors/panning/2d_editor_pan_speed"));
warped_panning = bool(EDITOR_GET("editors/panning/warped_mouse_panning"));
}
@@ -5059,7 +5027,7 @@ CanvasItemEditor::CanvasItemEditor() {
zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom));
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_scroll_callback), callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback));
viewport = memnew(CanvasItemEditorViewport(this));
viewport_scrollable->add_child(viewport);
diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h
index 1e01eac82d..ebe87a56f7 100644
--- a/editor/plugins/canvas_item_editor_plugin.h
+++ b/editor/plugins/canvas_item_editor_plugin.h
@@ -363,10 +363,8 @@ private:
Ref<ViewPanner> panner;
bool warped_panning = true;
- int pan_speed = 20;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
bool _is_node_locked(const Node *p_node) const;
bool _is_node_movable(const Node *p_node, bool p_popup_warning = false);
diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp
index c30f0ec62d..fb35668310 100644
--- a/editor/plugins/polygon_2d_editor_plugin.cpp
+++ b/editor/plugins/polygon_2d_editor_plugin.cpp
@@ -939,21 +939,13 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) {
}
}
-void Polygon2DEditor::_uv_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- _uv_pan_callback(-p_scroll_vec * 32);
-}
-
-void Polygon2DEditor::_uv_pan_callback(Vector2 p_scroll_vec) {
+void Polygon2DEditor::_uv_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
uv_hscroll->set_value(uv_hscroll->get_value() - p_scroll_vec.x);
uv_vscroll->set_value(uv_vscroll->get_value() - p_scroll_vec.y);
}
-void Polygon2DEditor::_uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- if (p_scroll_vec.y < 0) {
- uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * Math::abs(p_scroll_vec.y))));
- } else {
- uv_zoom->set_value(uv_zoom->get_value() * (1 - (0.1 * Math::abs(p_scroll_vec.y))));
- }
+void Polygon2DEditor::_uv_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ uv_zoom->set_value(uv_zoom->get_value() * p_zoom_factor);
}
void Polygon2DEditor::_uv_scroll_changed(real_t) {
@@ -1478,7 +1470,7 @@ Polygon2DEditor::Polygon2DEditor() {
bone_scroll->add_child(bone_scroll_vb);
uv_panner.instantiate();
- uv_panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_uv_scroll_callback), callable_mp(this, &Polygon2DEditor::_uv_pan_callback), callable_mp(this, &Polygon2DEditor::_uv_zoom_callback));
+ uv_panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_uv_pan_callback), callable_mp(this, &Polygon2DEditor::_uv_zoom_callback));
uv_edit_draw->connect("draw", callable_mp(this, &Polygon2DEditor::_uv_draw));
uv_edit_draw->connect("gui_input", callable_mp(this, &Polygon2DEditor::_uv_input));
diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h
index 7246c08bea..2c55a5f631 100644
--- a/editor/plugins/polygon_2d_editor_plugin.h
+++ b/editor/plugins/polygon_2d_editor_plugin.h
@@ -90,9 +90,8 @@ class Polygon2DEditor : public AbstractPolygon2DEditor {
TextureRect *uv_icon_zoom = nullptr;
Ref<ViewPanner> uv_panner;
- void _uv_scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _uv_pan_callback(Vector2 p_scroll_vec);
- void _uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _uv_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _uv_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
VBoxContainer *bone_scroll_main_vb = nullptr;
ScrollContainer *bone_scroll = nullptr;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index b719a2ce30..6b4e7184d9 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -252,12 +252,14 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) {
} else if (p_line.get_type() == Variant::DICTIONARY) {
Dictionary meta = p_line.operator Dictionary();
const int line = meta["line"].operator int64_t() - 1;
+ const String code = meta["code"].operator String();
+ const String quote_style = EDITOR_GET("text_editor/completion/use_single_quotes") ? "'" : "\"";
CodeEdit *text_editor = code_editor->get_text_editor();
String prev_line = line > 0 ? text_editor->get_line(line - 1) : "";
if (prev_line.contains("@warning_ignore")) {
const int closing_bracket_idx = prev_line.find(")");
- const String text_to_insert = ", " + meta["code"].operator String();
+ const String text_to_insert = ", " + code.quote(quote_style);
prev_line = prev_line.insert(closing_bracket_idx, text_to_insert);
text_editor->set_line(line - 1, prev_line);
} else {
@@ -268,7 +270,7 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) {
} else {
annotation_indent = String(" ").repeat(text_editor->get_indent_size() * indent);
}
- text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + meta["code"].operator String() + ")");
+ text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + code.quote(quote_style) + ")");
}
_validate_script();
diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp
index d7559bc18e..76f2bb7bb5 100644
--- a/editor/plugins/texture_region_editor_plugin.cpp
+++ b/editor/plugins/texture_region_editor_plugin.cpp
@@ -620,22 +620,14 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) {
}
}
-void TextureRegionEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- _pan_callback(-p_scroll_vec * 32);
-}
-
-void TextureRegionEditor::_pan_callback(Vector2 p_scroll_vec) {
+void TextureRegionEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
p_scroll_vec /= draw_zoom;
hscroll->set_value(hscroll->get_value() - p_scroll_vec.x);
vscroll->set_value(vscroll->get_value() - p_scroll_vec.y);
}
-void TextureRegionEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- if (p_scroll_vec.y < 0) {
- _zoom_on_position(draw_zoom * ((0.95 + (0.05 * Math::abs(p_scroll_vec.y))) / 0.95), p_origin);
- } else {
- _zoom_on_position(draw_zoom * (1 - (0.05 * Math::abs(p_scroll_vec.y))), p_origin);
- }
+void TextureRegionEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ _zoom_on_position(draw_zoom * p_zoom_factor, p_origin);
}
void TextureRegionEditor::_scroll_changed(float) {
@@ -1169,7 +1161,7 @@ TextureRegionEditor::TextureRegionEditor() {
hb_grid->hide();
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &TextureRegionEditor::_scroll_callback), callable_mp(this, &TextureRegionEditor::_pan_callback), callable_mp(this, &TextureRegionEditor::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &TextureRegionEditor::_pan_callback), callable_mp(this, &TextureRegionEditor::_zoom_callback));
edit_draw = memnew(Panel);
vb->add_child(edit_draw);
diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h
index 0325700d25..ba64a04084 100644
--- a/editor/plugins/texture_region_editor_plugin.h
+++ b/editor/plugins/texture_region_editor_plugin.h
@@ -103,9 +103,8 @@ class TextureRegionEditor : public AcceptDialog {
bool request_center = false;
Ref<ViewPanner> panner;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
void _set_snap_mode(int p_mode);
void _set_snap_off_x(float p_val);
diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp
index 0ac375407c..e430848875 100644
--- a/editor/plugins/tiles/tile_atlas_view.cpp
+++ b/editor/plugins/tiles/tile_atlas_view.cpp
@@ -47,18 +47,14 @@ void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) {
}
}
-void TileAtlasView::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- _pan_callback(-p_scroll_vec * 32);
-}
-
-void TileAtlasView::_pan_callback(Vector2 p_scroll_vec) {
+void TileAtlasView::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
panning += p_scroll_vec;
_update_zoom_and_panning(true);
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
}
-void TileAtlasView::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- zoom_widget->set_zoom_by_increments(-p_scroll_vec.y * 2);
+void TileAtlasView::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ zoom_widget->set_zoom(zoom_widget->get_zoom() * p_zoom_factor);
_update_zoom_and_panning(true);
emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning);
}
@@ -583,7 +579,7 @@ TileAtlasView::TileAtlasView() {
add_child(button_center_view);
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &TileAtlasView::_scroll_callback), callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback));
panner->set_enable_rmb(true);
center_container = memnew(CenterContainer);
diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h
index f719bee704..4a7547f34b 100644
--- a/editor/plugins/tiles/tile_atlas_view.h
+++ b/editor/plugins/tiles/tile_atlas_view.h
@@ -65,9 +65,8 @@ private:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
Ref<ViewPanner> panner;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
HashMap<Vector2, HashMap<int, Rect2i>> alternative_tiles_rect_cache;
void _update_alternative_tiles_rect_cache();
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 6e95ce9945..d79bd8ced3 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -266,7 +266,7 @@ void TileMapEditorTilesPlugin::_patterns_item_list_gui_input(const Ref<InputEven
}
Ref<TileSet> tile_set = tile_map->get_tileset();
- if (!tile_set.is_valid()) {
+ if (!tile_set.is_valid() || EditorNode::get_singleton()->is_resource_read_only(tile_set)) {
return;
}
@@ -1277,13 +1277,15 @@ void TileMapEditorTilesPlugin::_stop_dragging() {
tile_map->set_cell(tile_map_layer, kv.key, kv.value.source_id, kv.value.get_atlas_coords(), kv.value.alternative_tile);
}
- // Creating a pattern in the pattern list.
- select_last_pattern = true;
- int new_pattern_index = tile_set->get_patterns_count();
- undo_redo->create_action(TTR("Add TileSet pattern"));
- undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index);
- undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index);
- undo_redo->commit_action();
+ if (EditorNode::get_singleton()->is_resource_read_only(tile_set)) {
+ // Creating a pattern in the pattern list.
+ select_last_pattern = true;
+ int new_pattern_index = tile_set->get_patterns_count();
+ undo_redo->create_action(TTR("Add TileSet pattern"));
+ undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index);
+ undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index);
+ undo_redo->commit_action();
+ }
} else {
// Get the top-left cell.
Vector2i top_left;
@@ -1989,6 +1991,15 @@ TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const {
void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) {
_stop_dragging(); // Avoids staying in a wrong drag state.
+ // Disable sort button if the tileset is read-only
+ TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id));
+ if (tile_map) {
+ Ref<TileSet> tile_set = tile_map->get_tileset();
+ if (tile_set.is_valid()) {
+ source_sort_button->set_disabled(EditorNode::get_singleton()->is_resource_read_only(tile_set));
+ }
+ }
+
if (tile_map_id != p_tile_map_id) {
tile_map_id = p_tile_map_id;
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
index 32421daa92..a12a647e99 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp
@@ -585,6 +585,7 @@ void TileSetAtlasSourceEditor::_update_atlas_source_inspector() {
// Update visibility.
bool inspector_visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button;
atlas_source_inspector->set_visible(inspector_visible);
+ atlas_source_inspector->set_read_only(read_only);
}
void TileSetAtlasSourceEditor::_update_tile_inspector() {
@@ -599,6 +600,7 @@ void TileSetAtlasSourceEditor::_update_tile_inspector() {
tile_inspector->hide();
tile_inspector_no_tile_selected_label->hide();
}
+ tile_inspector->set_read_only(read_only);
}
void TileSetAtlasSourceEditor::_update_tile_data_editors() {
@@ -970,19 +972,19 @@ void TileSetAtlasSourceEditor::_update_toolbar() {
current_tile_data_editor_toolbar->hide();
}
tools_settings_erase_button->show();
- tool_advanced_menu_buttom->show();
+ tool_advanced_menu_button->show();
} else if (tools_button_group->get_pressed_button() == tool_select_button) {
if (current_tile_data_editor_toolbar) {
current_tile_data_editor_toolbar->hide();
}
tools_settings_erase_button->hide();
- tool_advanced_menu_buttom->hide();
+ tool_advanced_menu_button->hide();
} else if (tools_button_group->get_pressed_button() == tool_paint_button) {
if (current_tile_data_editor_toolbar) {
current_tile_data_editor_toolbar->show();
}
tools_settings_erase_button->hide();
- tool_advanced_menu_buttom->hide();
+ tool_advanced_menu_button->hide();
}
}
@@ -2188,7 +2190,12 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource
ERR_FAIL_COND(p_source_id < 0);
ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source);
- if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id) {
+ bool new_read_only_state = false;
+ if (p_tile_set.is_valid()) {
+ new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set);
+ }
+
+ if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id && new_read_only_state == read_only) {
return;
}
@@ -2205,11 +2212,23 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource
tile_set_atlas_source = p_tile_set_atlas_source;
tile_set_atlas_source_id = p_source_id;
- // Add the listener again.
+ // Read-only is off by default.
+ read_only = new_read_only_state;
+
if (tile_set.is_valid()) {
tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed));
}
+ if (read_only && tools_button_group->get_pressed_button() == tool_paint_button) {
+ tool_paint_button->set_pressed(false);
+ tool_setup_atlas_source_button->set_pressed(true);
+ }
+
+ // Disable buttons in read-only mode.
+ tool_paint_button->set_disabled(read_only);
+ tools_settings_erase_button->set_disabled(read_only);
+ tool_advanced_menu_button->set_disabled(read_only);
+
// Update everything.
_update_source_inspector();
@@ -2344,7 +2363,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {
tools_settings_erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons")));
- tool_advanced_menu_buttom->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
+ tool_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons")));
resize_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons"));
resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons"));
@@ -2352,6 +2371,18 @@ void TileSetAtlasSourceEditor::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
if (tile_set_changed_needs_update) {
+ // Read-only is off by default
+ read_only = false;
+ // Add the listener again and check for read-only status.
+ if (tile_set.is_valid()) {
+ read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set);
+ }
+
+ // Disable buttons in read-only mode.
+ tool_paint_button->set_disabled(read_only);
+ tools_settings_erase_button->set_disabled(read_only);
+ tool_advanced_menu_button->set_disabled(read_only);
+
// Update everything.
_update_source_inspector();
@@ -2516,12 +2547,12 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() {
tools_settings_erase_button->set_shortcut_context(this);
tool_settings->add_child(tools_settings_erase_button);
- tool_advanced_menu_buttom = memnew(MenuButton);
- tool_advanced_menu_buttom->set_flat(true);
- tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
- tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
- tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
- tool_settings->add_child(tool_advanced_menu_buttom);
+ tool_advanced_menu_button = memnew(MenuButton);
+ tool_advanced_menu_button->set_flat(true);
+ tool_advanced_menu_button->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES);
+ tool_advanced_menu_button->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES);
+ tool_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option));
+ tool_settings->add_child(tool_advanced_menu_button);
_update_toolbar();
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h
index bcab1296ad..a4826bc56f 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.h
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h
@@ -113,6 +113,8 @@ public:
};
private:
+ bool read_only = false;
+
Ref<TileSet> tile_set;
TileSetAtlasSource *tile_set_atlas_source = nullptr;
int tile_set_atlas_source_id = TileSet::INVALID_SOURCE;
@@ -209,7 +211,7 @@ private:
HBoxContainer *tool_settings = nullptr;
HBoxContainer *tool_settings_tile_data_toolbar_container = nullptr;
Button *tools_settings_erase_button = nullptr;
- MenuButton *tool_advanced_menu_buttom = nullptr;
+ MenuButton *tool_advanced_menu_button = nullptr;
// Selection.
RBSet<TileSelection> selection;
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index 53c2d4de51..39d17c718e 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -89,6 +89,10 @@ void TileSetEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data,
bool TileSetEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
ERR_FAIL_COND_V(!tile_set.is_valid(), false);
+ if (read_only) {
+ return false;
+ }
+
if (p_from == sources_list) {
Dictionary d = p_data;
@@ -223,7 +227,7 @@ void TileSetEditor::_source_selected(int p_source_index) {
ERR_FAIL_COND(!tile_set.is_valid());
// Update the selected source.
- sources_delete_button->set_disabled(p_source_index < 0);
+ sources_delete_button->set_disabled(p_source_index < 0 || read_only);
if (p_source_index >= 0) {
int source_id = sources_list->get_item_metadata(p_source_index);
@@ -356,8 +360,19 @@ void TileSetEditor::_notification(int p_what) {
if (tile_set.is_valid()) {
tile_set->set_edited(true);
}
+
+ read_only = false;
+ if (tile_set.is_valid()) {
+ read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set);
+ }
+
_update_sources_list();
_update_patterns_list();
+
+ sources_add_button->set_disabled(read_only);
+ sources_advanced_menu_button->set_disabled(read_only);
+ source_sort_button->set_disabled(read_only);
+
tile_set_changed_needs_update = false;
}
} break;
@@ -367,6 +382,10 @@ void TileSetEditor::_notification(int p_what) {
void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(!tile_set.is_valid());
+ if (EditorNode::get_singleton()->is_resource_read_only(tile_set)) {
+ return;
+ }
+
if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) {
Vector<int> selected = patterns_item_list->get_selected_items();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
@@ -667,7 +686,12 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p
}
void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
- if (p_tile_set == tile_set) {
+ bool new_read_only_state = false;
+ if (tile_set.is_valid()) {
+ new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set);
+ }
+
+ if (p_tile_set == tile_set && new_read_only_state == read_only) {
return;
}
@@ -679,8 +703,15 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
// Change the edited object.
tile_set = p_tile_set;
- // Add the listener again.
+ // Read-only status is false by default
+ read_only = new_read_only_state;
+
+ // Add the listener again and check for read-only status.
if (tile_set.is_valid()) {
+ sources_add_button->set_disabled(read_only);
+ sources_advanced_menu_button->set_disabled(read_only);
+ source_sort_button->set_disabled(read_only);
+
tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed));
if (first_edit) {
first_edit = false;
@@ -690,10 +721,6 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) {
}
_update_patterns_list();
}
-
- tile_set_atlas_source_editor->hide();
- tile_set_scenes_collection_source_editor->hide();
- no_source_selected_label->show();
}
TileSetEditor::TileSetEditor() {
diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h
index e3dff11277..d36d3bde41 100644
--- a/editor/plugins/tiles/tile_set_editor.h
+++ b/editor/plugins/tiles/tile_set_editor.h
@@ -45,6 +45,8 @@ class TileSetEditor : public VBoxContainer {
static TileSetEditor *singleton;
private:
+ bool read_only = false;
+
Ref<TileSet> tile_set;
bool tile_set_changed_needs_update = false;
HSplitContainer *split_container = nullptr;
diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp
index 6251cd18f7..cc276597fa 100644
--- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp
+++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp
@@ -284,7 +284,7 @@ void TileSetScenesCollectionSourceEditor::_update_tile_inspector() {
void TileSetScenesCollectionSourceEditor::_update_action_buttons() {
Vector<int> selected_indices = scene_tiles_list->get_selected_items();
- scene_tile_delete_button->set_disabled(selected_indices.size() <= 0);
+ scene_tile_delete_button->set_disabled(selected_indices.size() <= 0 || read_only);
}
void TileSetScenesCollectionSourceEditor::_update_scenes_list() {
@@ -342,6 +342,12 @@ void TileSetScenesCollectionSourceEditor::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
if (tile_set_scenes_collection_source_changed_needs_update) {
+ read_only = false;
+ // Add the listener again and check for read-only status.
+ if (tile_set.is_valid()) {
+ read_only = EditorNode::get_singleton()->is_resource_read_only(tile_set);
+ }
+
// Update everything.
_update_source_inspector();
_update_scenes_list();
@@ -365,7 +371,12 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS
ERR_FAIL_COND(p_source_id < 0);
ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source);
- if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) {
+ bool new_read_only_state = false;
+ if (p_tile_set.is_valid()) {
+ new_read_only_state = EditorNode::get_singleton()->is_resource_read_only(p_tile_set);
+ }
+
+ if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id && new_read_only_state == read_only) {
return;
}
@@ -379,6 +390,16 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS
tile_set_scenes_collection_source = p_tile_set_scenes_collection_source;
tile_set_source_id = p_source_id;
+ // Read-only status is false by default
+ read_only = new_read_only_state;
+
+ if (tile_set.is_valid()) {
+ scenes_collection_source_inspector->set_read_only(read_only);
+ tile_inspector->set_read_only(read_only);
+
+ scene_tile_add_button->set_disabled(read_only);
+ }
+
// Add the listener again.
if (tile_set_scenes_collection_source) {
tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed));
diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h
index 0901205a29..2a0e8595c4 100644
--- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h
+++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h
@@ -91,6 +91,8 @@ private:
};
private:
+ bool read_only = false;
+
Ref<TileSet> tile_set;
TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr;
int tile_set_source_id = -1;
diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp
index 19ee0ae98d..77dd0f7793 100644
--- a/editor/plugins/tiles/tiles_editor_plugin.cpp
+++ b/editor/plugins/tiles/tiles_editor_plugin.cpp
@@ -101,7 +101,7 @@ void TilesEditorPlugin::_thread() {
encompassing_rect.expand_to(world_pos);
// Texture.
- Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(tile_map->get_cell_source_id(0, cell));
+ Ref<TileSetAtlasSource> atlas_source = item.tile_set->get_source(tile_map->get_cell_source_id(0, cell));
if (atlas_source.is_valid()) {
Vector2i coords = tile_map->get_cell_atlas_coords(0, cell);
int alternative = tile_map->get_cell_alternative_tile(0, cell);
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index 88fb88de98..5800fcca91 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -2549,14 +2549,14 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n tool", "\n tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\ntool", "\n\n@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(any_peer) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(any_peer, call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(any_peer, call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\nslave func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\npuppet func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n\npuppetsync func", "\n\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_with_regex("\n\nmaster func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
- valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(call_local) func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
+ valid = valid && test_conversion_with_regex("\n\nmastersync func", "\n\nThe master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
valid = valid && test_conversion_gdscript_builtin("var size : Vector2 = Vector2() setget set_function , get_function", "var size : Vector2 = Vector2() : get = get_function, set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
valid = valid && test_conversion_gdscript_builtin("var size : Vector2 = Vector2() setget set_function , ", "var size : Vector2 = Vector2() : set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@@ -4087,13 +4087,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const
line = reg_container.keyword_gdscript_onready.sub(line, "@onready", true);
}
if (line.contains("remote")) {
- line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(any_peer) func", true);
+ line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true);
}
if (line.contains("remote")) {
- line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(any_peer, call_local) func", true);
+ line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true);
}
if (line.contains("sync")) {
- line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(any_peer, call_local) func", true);
+ line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\") func", true);
}
if (line.contains("slave")) {
line = reg_container.keyword_gdscript_slave.sub(line, "@rpc func", true);
@@ -4102,13 +4102,13 @@ void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const
line = reg_container.keyword_gdscript_puppet.sub(line, "@rpc func", true);
}
if (line.contains("puppet")) {
- line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(call_local) func", true);
+ line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true);
}
if (line.contains("master")) {
line = reg_container.keyword_gdscript_master.sub(line, error_message + "@rpc func", true);
}
if (line.contains("master")) {
- line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(call_local) func", true);
+ line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + "@rpc(\"call_local\") func", true);
}
}
}
@@ -4156,25 +4156,25 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
if (line.contains("remote")) {
old = line;
- line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(any_peer) func", true);
+ line = reg_container.keyword_gdscript_remote.sub(line, "@rpc(\"any_peer\") func", true);
if (old != line) {
- found_renames.append(line_formatter(current_line, "remote func", "@rpc(any_peer) func", line));
+ found_renames.append(line_formatter(current_line, "remote func", "@rpc(\"any_peer\") func", line));
}
}
if (line.contains("remote")) {
old = line;
- line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(any_peer, call_local)) func", true);
+ line = reg_container.keyword_gdscript_remotesync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true);
if (old != line) {
- found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(any_peer, call_local)) func", line));
+ found_renames.append(line_formatter(current_line, "remotesync func", "@rpc(\"any_peer\", \"call_local\")) func", line));
}
}
if (line.contains("sync")) {
old = line;
- line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(any_peer, call_local)) func", true);
+ line = reg_container.keyword_gdscript_sync.sub(line, "@rpc(\"any_peer\", \"call_local\")) func", true);
if (old != line) {
- found_renames.append(line_formatter(current_line, "sync func", "@rpc(any_peer, call_local)) func", line));
+ found_renames.append(line_formatter(current_line, "sync func", "@rpc(\"any_peer\", \"call_local\")) func", line));
}
}
@@ -4196,9 +4196,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
if (line.contains("puppet")) {
old = line;
- line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(call_local) func", true);
+ line = reg_container.keyword_gdscript_puppetsync.sub(line, "@rpc(\"call_local\") func", true);
if (old != line) {
- found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(call_local) func", line));
+ found_renames.append(line_formatter(current_line, "puppetsync func", "@rpc(\"call_local\") func", line));
}
}
@@ -4212,9 +4212,9 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
if (line.contains("master")) {
old = line;
- line = reg_container.keyword_gdscript_master.sub(line, "@rpc(call_local) func", true);
+ line = reg_container.keyword_gdscript_master.sub(line, "@rpc(\"call_local\") func", true);
if (old != line) {
- found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(call_local) func", line));
+ found_renames.append(line_formatter(current_line, "mastersync func", "@rpc(\"call_local\") func", line));
}
}
}
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 3fe741a582..5bed1b9da3 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -485,7 +485,7 @@
Export a [NodePath] property with a filter for allowed node types.
See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES].
[codeblock]
- @export_node_path(Button, TouchScreenButton) var some_button
+ @export_node_path("Button", "TouchScreenButton") var some_button
[/codeblock]
</description>
</annotation>
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 6b325d6451..4fc3929bbd 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2448,7 +2448,6 @@ bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
}
String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type, String *r_icon_path) const {
- Vector<uint8_t> sourcef;
Error err;
Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ, &err);
if (err) {
@@ -2459,88 +2458,31 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
GDScriptParser parser;
err = parser.parse(source, p_path, false);
+ if (err) {
+ return String();
+ }
- // TODO: Simplify this code by using the analyzer to get full inheritance.
- if (err == OK) {
- const GDScriptParser::ClassNode *c = parser.get_tree();
- if (r_icon_path) {
- if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
- *r_icon_path = c->icon_path;
- } else if (c->icon_path.is_relative_path()) {
- *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
- }
- }
- if (r_base_type) {
- const GDScriptParser::ClassNode *subclass = c;
- String path = p_path;
- GDScriptParser subparser;
- while (subclass) {
- if (subclass->extends_used) {
- if (!subclass->extends_path.is_empty()) {
- if (subclass->extends.size() == 0) {
- get_global_class_name(subclass->extends_path, r_base_type);
- subclass = nullptr;
- break;
- } else {
- Vector<StringName> extend_classes = subclass->extends;
-
- Ref<FileAccess> subfile = FileAccess::open(subclass->extends_path, FileAccess::READ);
- if (subfile.is_null()) {
- break;
- }
- String subsource = subfile->get_as_utf8_string();
-
- if (subsource.is_empty()) {
- break;
- }
- String subpath = subclass->extends_path;
- if (subpath.is_relative_path()) {
- subpath = path.get_base_dir().path_join(subpath).simplify_path();
- }
-
- if (OK != subparser.parse(subsource, subpath, false)) {
- break;
- }
- path = subpath;
- subclass = subparser.get_tree();
-
- while (extend_classes.size() > 0) {
- bool found = false;
- for (int i = 0; i < subclass->members.size(); i++) {
- if (subclass->members[i].type != GDScriptParser::ClassNode::Member::CLASS) {
- continue;
- }
-
- const GDScriptParser::ClassNode *inner_class = subclass->members[i].m_class;
- if (inner_class->identifier->name == extend_classes[0]) {
- extend_classes.remove_at(0);
- found = true;
- subclass = inner_class;
- break;
- }
- }
- if (!found) {
- subclass = nullptr;
- break;
- }
- }
- }
- } else if (subclass->extends.size() == 1) {
- *r_base_type = subclass->extends[0];
- subclass = nullptr;
- } else {
- break;
- }
- } else {
- *r_base_type = "RefCounted";
- subclass = nullptr;
- }
- }
+ GDScriptAnalyzer analyzer(&parser);
+ err = analyzer.resolve_inheritance();
+ if (err) {
+ return String();
+ }
+
+ const GDScriptParser::ClassNode *c = parser.get_tree();
+
+ if (r_base_type) {
+ *r_base_type = c->get_datatype().native_type;
+ }
+
+ if (r_icon_path) {
+ if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) {
+ *r_icon_path = c->icon_path.simplify_path();
+ } else if (c->icon_path.is_relative_path()) {
+ *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path();
}
- return c->identifier != nullptr ? String(c->identifier->name) : String();
}
- return String();
+ return c->identifier != nullptr ? String(c->identifier->name) : String();
}
GDScriptLanguage::GDScriptLanguage() {
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index e04a962dcb..d7f6126207 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -802,6 +802,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.variable->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.variable);
}
} break;
@@ -812,6 +813,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.constant->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.constant);
}
} break;
@@ -835,6 +837,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.signal->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.signal);
}
} break;
@@ -882,6 +885,7 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class,
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.m_enum->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.m_enum);
}
} break;
@@ -1064,6 +1068,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
// Apply annotations.
for (GDScriptParser::AnnotationNode *&E : member.function->annotations) {
+ resolve_annotation(E);
E->apply(parser, member.function);
}
@@ -1290,7 +1295,55 @@ void GDScriptAnalyzer::resolve_node(GDScriptParser::Node *p_node, bool p_is_root
}
void GDScriptAnalyzer::resolve_annotation(GDScriptParser::AnnotationNode *p_annotation) {
- // TODO: Add second validation function for annotations, so they can use checked types.
+ ERR_FAIL_COND_MSG(!parser->valid_annotations.has(p_annotation->name), vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
+
+ const MethodInfo &annotation_info = parser->valid_annotations[p_annotation->name].info;
+
+ const List<PropertyInfo>::Element *E = annotation_info.arguments.front();
+ for (int i = 0; i < p_annotation->arguments.size(); i++) {
+ GDScriptParser::ExpressionNode *argument = p_annotation->arguments[i];
+ const PropertyInfo &argument_info = E->get();
+
+ if (E->next() != nullptr) {
+ E = E->next();
+ }
+
+ reduce_expression(argument);
+
+ if (!argument->is_constant) {
+ push_error(vformat(R"(Argument %d of annotation "%s" isn't a constant expression.)", i + 1, p_annotation->name), argument);
+ return;
+ }
+
+ Variant value = argument->reduced_value;
+
+ if (value.get_type() != argument_info.type) {
+#ifdef DEBUG_ENABLED
+ if (argument_info.type == Variant::INT && value.get_type() == Variant::FLOAT) {
+ parser->push_warning(argument, GDScriptWarning::NARROWING_CONVERSION);
+ }
+#endif
+
+ if (!Variant::can_convert_strict(value.get_type(), argument_info.type)) {
+ push_error(vformat(R"(Invalid argument for annotation "%s": argument %d should be "%s" but is "%s".)", p_annotation->name, i + 1, Variant::get_type_name(argument_info.type), argument->get_datatype().to_string()), argument);
+ return;
+ }
+
+ Variant converted_to;
+ const Variant *converted_from = &value;
+ Callable::CallError call_error;
+ Variant::construct(argument_info.type, converted_to, &converted_from, 1, call_error);
+
+ if (call_error.error != Callable::CallError::CALL_OK) {
+ push_error(vformat(R"(Cannot convert argument %d of annotation "%s" from "%s" to "%s".)", i + 1, p_annotation->name, Variant::get_type_name(value.get_type()), Variant::get_type_name(argument_info.type)), argument);
+ return;
+ }
+
+ value = converted_to;
+ }
+
+ p_annotation->resolved_arguments.push_back(value);
+ }
}
void GDScriptAnalyzer::resolve_function_signature(GDScriptParser::FunctionNode *p_function, const GDScriptParser::Node *p_source, bool p_is_lambda) {
@@ -1486,8 +1539,10 @@ void GDScriptAnalyzer::decide_suite_type(GDScriptParser::Node *p_suite, GDScript
void GDScriptAnalyzer::resolve_suite(GDScriptParser::SuiteNode *p_suite) {
for (int i = 0; i < p_suite->statements.size(); i++) {
GDScriptParser::Node *stmt = p_suite->statements[i];
- for (GDScriptParser::AnnotationNode *&annotation : stmt->annotations) {
- annotation->apply(parser, stmt);
+ // Apply annotations.
+ for (GDScriptParser::AnnotationNode *&E : stmt->annotations) {
+ resolve_annotation(E);
+ E->apply(parser, stmt);
}
#ifdef DEBUG_ENABLED
@@ -1544,10 +1599,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype();
if (p_assignable->infer_datatype) {
- if (!initializer_type.is_set() || initializer_type.has_no_type()) {
+ if (!initializer_type.is_set() || initializer_type.has_no_type() || !initializer_type.is_hard_type()) {
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value doesn't have a set type.)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
- } else if (initializer_type.is_variant() && !initializer_type.is_hard_type()) {
- push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is Variant. Use explicit "Variant" type if this is intended.)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
} else if (initializer_type.kind == GDScriptParser::DataType::BUILTIN && initializer_type.builtin_type == Variant::NIL && !is_constant) {
push_error(vformat(R"(Cannot infer the type of "%s" %s because the value is "null".)", p_assignable->identifier->name, p_kind), p_assignable->initializer);
}
@@ -2014,7 +2067,7 @@ void GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode *p_expre
reduce_subscript(static_cast<GDScriptParser::SubscriptNode *>(p_expression));
break;
case GDScriptParser::Node::TERNARY_OPERATOR:
- reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression));
+ reduce_ternary_op(static_cast<GDScriptParser::TernaryOpNode *>(p_expression), p_is_root);
break;
case GDScriptParser::Node::UNARY_OPERATOR:
reduce_unary_op(static_cast<GDScriptParser::UnaryOpNode *>(p_expression));
@@ -3534,6 +3587,7 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
}
#endif
result_type.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_subscript);
}
}
if (!valid) {
@@ -3735,10 +3789,10 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri
p_subscript->set_datatype(result_type);
}
-void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op) {
+void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root) {
reduce_expression(p_ternary_op->condition);
- reduce_expression(p_ternary_op->true_expr);
- reduce_expression(p_ternary_op->false_expr);
+ reduce_expression(p_ternary_op->true_expr, p_is_root);
+ reduce_expression(p_ternary_op->false_expr, p_is_root);
GDScriptParser::DataType result;
@@ -4552,6 +4606,11 @@ Ref<GDScriptParserRef> GDScriptAnalyzer::get_parser_for(const String &p_path) {
}
Error GDScriptAnalyzer::resolve_inheritance() {
+ // Apply annotations.
+ for (GDScriptParser::AnnotationNode *&E : parser->head->annotations) {
+ resolve_annotation(E);
+ E->apply(parser, parser->head);
+ }
return resolve_class_inheritance(parser->head, true);
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index b22d47982f..5397be33f0 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -99,7 +99,7 @@ class GDScriptAnalyzer {
void reduce_preload(GDScriptParser::PreloadNode *p_preload);
void reduce_self(GDScriptParser::SelfNode *p_self);
void reduce_subscript(GDScriptParser::SubscriptNode *p_subscript);
- void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op);
+ void reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternary_op, bool p_is_root = false);
void reduce_unary_op(GDScriptParser::UnaryOpNode *p_unary_op);
void const_fold_array(GDScriptParser::ArrayNode *p_array, bool p_is_const);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3fc0924b4c..f88ac581ca 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -782,6 +782,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
}
} else if (p_annotation->name == SNAME("@export_node_path")) {
ScriptLanguage::CodeCompletionOption node("Node", ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+ node.insert_text = node.display.quote(p_quote_style);
r_result.insert(node.display, node);
List<StringName> node_types;
ClassDB::get_inheriters_from_class("Node", &node_types);
@@ -790,11 +791,13 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
continue;
}
ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_CLASS);
+ option.insert_text = option.display.quote(p_quote_style);
r_result.insert(option.display, option);
}
} else if (p_annotation->name == SNAME("@warning_ignore")) {
for (int warning_code = 0; warning_code < GDScriptWarning::WARNING_MAX; warning_code++) {
ScriptLanguage::CodeCompletionOption warning(GDScriptWarning::get_name_from_code((GDScriptWarning::Code)warning_code).to_lower(), ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ warning.insert_text = warning.display.quote(p_quote_style);
r_result.insert(warning.display, warning);
}
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 99b78a8326..1a744d59ba 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -529,7 +529,7 @@ void GDScriptParser::parse_program() {
AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- annotation->apply(this, head);
+ head->annotations.push_back(annotation);
} else {
annotation_stack.push_back(annotation);
// This annotation must appear after script-level annotations
@@ -771,7 +771,6 @@ void GDScriptParser::parse_class_member(T *(GDScriptParser::*p_parse_function)()
return;
}
- // Apply annotations.
for (AnnotationNode *&annotation : annotations) {
member->annotations.push_back(annotation);
}
@@ -848,7 +847,7 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
- annotation->apply(this, head);
+ head->annotations.push_back(annotation);
} else {
annotation_stack.push_back(annotation);
}
@@ -1470,7 +1469,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali
match(GDScriptTokenizer::Token::NEWLINE); // Newline after annotation is optional.
if (valid) {
- valid = validate_annotation_arguments(annotation);
+ valid = validate_annotation_argument_count(annotation);
}
return valid ? annotation : nullptr;
@@ -1701,6 +1700,7 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
case Node::CALL:
case Node::ASSIGNMENT:
case Node::AWAIT:
+ case Node::TERNARY_OPERATOR:
// Fine.
break;
case Node::LAMBDA:
@@ -1716,7 +1716,6 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
}
}
- // Apply annotations to statement.
while (!is_annotation && result != nullptr && !annotation_stack.is_empty()) {
AnnotationNode *last_annotation = annotation_stack.back()->get();
if (last_annotation->applies_to(AnnotationInfo::STATEMENT)) {
@@ -3597,7 +3596,7 @@ bool GDScriptParser::AnnotationNode::applies_to(uint32_t p_target_kinds) const {
return (info->target_kind & p_target_kinds) > 0;
}
-bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation) {
+bool GDScriptParser::validate_annotation_argument_count(AnnotationNode *p_annotation) {
ERR_FAIL_COND_V_MSG(!valid_annotations.has(p_annotation->name), false, vformat(R"(Annotation "%s" not found to validate.)", p_annotation->name));
const MethodInfo &info = valid_annotations[p_annotation->name].info;
@@ -3612,62 +3611,6 @@ bool GDScriptParser::validate_annotation_arguments(AnnotationNode *p_annotation)
return false;
}
- const List<PropertyInfo>::Element *E = info.arguments.front();
- for (int i = 0; i < p_annotation->arguments.size(); i++) {
- ExpressionNode *argument = p_annotation->arguments[i];
- const PropertyInfo &parameter = E->get();
-
- if (E->next() != nullptr) {
- E = E->next();
- }
-
- switch (parameter.type) {
- case Variant::STRING:
- case Variant::STRING_NAME:
- case Variant::NODE_PATH:
- // Allow "quote-less strings", as long as they are recognized as identifiers.
- if (argument->type == Node::IDENTIFIER) {
- IdentifierNode *string = static_cast<IdentifierNode *>(argument);
- Callable::CallError error;
- Vector<Variant> args = varray(string->name);
- const Variant *name = args.ptr();
- Variant r;
- Variant::construct(parameter.type, r, &(name), 1, error);
- p_annotation->resolved_arguments.push_back(r);
- if (error.error != Callable::CallError::CALL_OK) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
- return false;
- }
- break;
- }
- [[fallthrough]];
- default: {
- if (argument->type != Node::LITERAL) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- return false;
- }
-
- Variant value = static_cast<LiteralNode *>(argument)->value;
- if (!Variant::can_convert_strict(value.get_type(), parameter.type)) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- return false;
- }
- Callable::CallError error;
- const Variant *args = &value;
- Variant r;
- Variant::construct(parameter.type, r, &(args), 1, error);
- p_annotation->resolved_arguments.push_back(r);
- if (error.error != Callable::CallError::CALL_OK) {
- push_error(vformat(R"(Expected %s as argument %d of annotation "%s".)", Variant::get_type_name(parameter.type), i + 1, p_annotation->name));
- p_annotation->resolved_arguments.remove_at(p_annotation->resolved_arguments.size() - 1);
- return false;
- }
- break;
- }
- }
- }
-
return true;
}
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index 5da709e8cd..74e12d0b5e 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -1401,7 +1401,7 @@ private:
// Annotations
AnnotationNode *parse_annotation(uint32_t p_valid_targets);
bool register_annotation(const MethodInfo &p_info, uint32_t p_target_kinds, AnnotationAction p_apply, const Vector<Variant> &p_default_arguments = Vector<Variant>(), bool p_is_vararg = false);
- bool validate_annotation_arguments(AnnotationNode *p_annotation);
+ bool validate_annotation_argument_count(AnnotationNode *p_annotation);
void clear_unused_annotations();
bool tool_annotation(const AnnotationNode *p_annotation, Node *p_target);
bool icon_annotation(const AnnotationNode *p_annotation, Node *p_target);
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd
new file mode 100644
index 0000000000..75524c32ae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.gd
@@ -0,0 +1,6 @@
+var num := 1
+
+@export_range(num, 10) var a
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out
new file mode 100644
index 0000000000..b4f0e79237
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/annotation_non_constant_parameter.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Argument 1 of annotation "@export_range" isn't a constant expression.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd
new file mode 100644
index 0000000000..6014ee831c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.gd
@@ -0,0 +1,3 @@
+func test():
+ var untyped = 1
+ var inferred := untyped
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out
new file mode 100644
index 0000000000..b6dc6d0b01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_local_variable.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "inferred" variable because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd
new file mode 100644
index 0000000000..040aa2e82a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.gd
@@ -0,0 +1,5 @@
+var untyped = 1
+var inferred := untyped
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out
new file mode 100644
index 0000000000..b6dc6d0b01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_member_variable.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "inferred" variable because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd
new file mode 100644
index 0000000000..80c676488e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.gd
@@ -0,0 +1,5 @@
+func check(untyped = 1, inferred := untyped):
+ pass
+
+func test():
+ check()
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out
new file mode 100644
index 0000000000..8c9f0c13ae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/inferring_with_weak_type_parameter.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "inferred" parameter because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd
new file mode 100644
index 0000000000..272dce8bbe
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.gd
@@ -0,0 +1,10 @@
+const BEFORE = 1
+
+@export_range(-10, 10) var a = 0
+@export_range(1 + 2, absi(-10) + 1) var b = 5
+@export_range(BEFORE + 1, BEFORE + AFTER + 1) var c = 5
+
+const AFTER = 10
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/annotation_constant_expression_parameters.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd
index 2d2c2bef19..595563541f 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.gd
@@ -1,9 +1,4 @@
func test():
- var one_0 = 0
- one_0 = 1
- var one_1 := one_0
- print(one_1)
-
var two: Variant = 0
two += 2
print(two)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out
index 7536c38490..0ddfa4b75f 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out
+++ b/modules/gdscript/tests/scripts/analyzer/features/assignments_with_untyped.out
@@ -1,5 +1,4 @@
GDTEST_OK
-1
2
3
4
diff --git a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
index 877a4ea221..4c02fd4b0d 100644
--- a/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
+++ b/modules/gdscript/tests/scripts/analyzer/features/warning_ignore_annotation.gd
@@ -1,12 +1,12 @@
-@warning_ignore(unused_private_class_variable)
+@warning_ignore("unused_private_class_variable")
var _unused = 2
-@warning_ignore(unused_variable)
+@warning_ignore("unused_variable")
func test():
print("test")
var unused = 3
- @warning_ignore(redundant_await)
+ @warning_ignore("redundant_await")
print(await regular_func())
print("done")
diff --git a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
index cc78309ae4..a34cc26e67 100644
--- a/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
+++ b/modules/gdscript/tests/scripts/parser/features/arrays_dictionaries_nested_const.gd
@@ -1,6 +1,6 @@
# https://github.com/godotengine/godot/issues/50285
-@warning_ignore(unused_local_constant)
+@warning_ignore("unused_local_constant")
func test():
const CONST_INNER_DICTIONARY = { "key": true }
const CONST_NESTED_DICTIONARY_OLD_WORKAROUND = {
diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
index 1e072728fc..acf9ff2e21 100644
--- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd
+++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd
@@ -5,7 +5,7 @@
@export var color: Color
@export_color_no_alpha var color_no_alpha: Color
-@export_node_path(Sprite2D, Sprite3D, Control, Node) var nodepath := ^"hello"
+@export_node_path("Sprite2D", "Sprite3D", "Control", "Node") var nodepath := ^"hello"
func test():
diff --git a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
index 46b9fbc951..1490a164c9 100644
--- a/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/await_on_void.gd
@@ -2,6 +2,6 @@ func wait() -> void:
pass
func test():
- @warning_ignore(redundant_await)
+ @warning_ignore("redundant_await")
await wait()
print("end")
diff --git a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
index 1d4b400d81..48af734317 100644
--- a/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/does_not_override_temp_values.gd
@@ -7,11 +7,11 @@ func test():
func builtin_method():
var pba := PackedByteArray()
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
pba.resize(1) # Built-in validated.
func builtin_method_static():
var _pba := PackedByteArray()
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
Vector2.from_angle(PI) # Static built-in validated.
diff --git a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd
index f2368643de..e686cffc48 100644
--- a/modules/gdscript/tests/scripts/runtime/features/gdscript.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/gdscript.gd
@@ -11,10 +11,10 @@ class InnerClass:
func _init() -> void:
prints("Inner")
'''
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
gdscr.reload()
var inst = gdscr.new()
- @warning_ignore(unsafe_method_access)
+ @warning_ignore("unsafe_method_access")
inst.test()
diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
index cc34e71b01..2f55059334 100644
--- a/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/standalone-calls-do-not-write-to-nil.gd
@@ -20,26 +20,26 @@ func test_utility(v, f):
assert(not f) # Test unary operator reading from `nil`.
func test_builtin_call(v, f):
- @warning_ignore(unsafe_method_access)
+ @warning_ignore("unsafe_method_access")
v.angle() # Built-in method call.
assert(not f) # Test unary operator reading from `nil`.
func test_builtin_call_validated(v: Vector2, f):
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
v.abs() # Built-in method call validated.
assert(not f) # Test unary operator reading from `nil`.
func test_object_call(v, f):
- @warning_ignore(unsafe_method_access)
+ @warning_ignore("unsafe_method_access")
v.get_reference_count() # Native type method call.
assert(not f) # Test unary operator reading from `nil`.
func test_object_call_method_bind(v: Resource, f):
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
v.duplicate() # Native type method call with MethodBind.
assert(not f) # Test unary operator reading from `nil`.
func test_object_call_ptrcall(v: RefCounted, f):
- @warning_ignore(return_value_discarded)
+ @warning_ignore("return_value_discarded")
v.get_reference_count() # Native type method call with ptrcall.
assert(not f) # Test unary operator reading from `nil`.
diff --git a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd
index af3f3cb941..efa8270526 100644
--- a/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/use_conversion_assign_with_variant_value.gd
@@ -1,7 +1,7 @@
# https://github.com/godotengine/godot/issues/71172
func test():
- @warning_ignore(narrowing_conversion)
+ @warning_ignore("narrowing_conversion")
var foo: int = 0.0
print(typeof(foo) == TYPE_INT)
var dict : Dictionary = {"a":0.0}
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index ba1750386f..0c18acbcb1 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -90,7 +90,7 @@
You can use both methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices.
</description>
<tutorials>
- <link title="How to make a VR game for WebXR with Godot">https://www.snopekgames.com/blog/2020/how-make-vr-game-webxr-godot</link>
+ <link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link>
</tutorials>
<methods>
<method name="get_input_source_target_ray_mode" qualifiers="const">
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index f1159c3981..e4553905cd 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -2933,6 +2933,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
xkeyevent_no_mod.state &= ~ShiftMask;
xkeyevent_no_mod.state &= ~ControlMask;
XLookupString(xkeyevent, str, 255, &keysym_unicode, nullptr);
+ XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);
String keysym;
if (xkb_keysym_to_utf32 && xkb_keysym_to_upper) {
@@ -4859,7 +4860,11 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
{
wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo.screen), win_rect.position.x, win_rect.position.y, win_rect.size.width > 0 ? win_rect.size.width : 1, win_rect.size.height > 0 ? win_rect.size.height : 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes);
- wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, visualInfo.depth, InputOutput, visualInfo.visual, valuemask, &windowAttributes);
+
+ XSetWindowAttributes window_attributes_ime = {};
+ window_attributes_ime.event_mask = KeyPressMask | KeyReleaseMask | StructureNotifyMask | ExposureMask;
+
+ wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime);
// Enable receiving notification when the window is initialized (MapNotify)
// so the focus can be set at the right time.
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 610fc92265..a7e3451297 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -1671,9 +1671,11 @@ void DisplayServerWindows::window_set_ime_active(const bool p_active, WindowID p
if (p_active) {
wd.ime_active = true;
ImmAssociateContext(wd.hWnd, wd.im_himc);
+ CreateCaret(wd.hWnd, NULL, 1, 1);
window_set_ime_position(wd.im_position, p_window);
} else {
ImmAssociateContext(wd.hWnd, (HIMC)0);
+ DestroyCaret();
wd.ime_active = false;
}
}
@@ -3469,15 +3471,21 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
case WM_IME_COMPOSITION: {
CANDIDATEFORM cf;
cf.dwIndex = 0;
- cf.dwStyle = CFS_EXCLUDE;
+
+ cf.dwStyle = CFS_CANDIDATEPOS;
cf.ptCurrentPos.x = windows[window_id].im_position.x;
cf.ptCurrentPos.y = windows[window_id].im_position.y;
+ ImmSetCandidateWindow(windows[window_id].im_himc, &cf);
+
+ cf.dwStyle = CFS_EXCLUDE;
cf.rcArea.left = windows[window_id].im_position.x;
cf.rcArea.right = windows[window_id].im_position.x;
cf.rcArea.top = windows[window_id].im_position.y;
cf.rcArea.bottom = windows[window_id].im_position.y;
ImmSetCandidateWindow(windows[window_id].im_himc, &cf);
+
if (windows[window_id].ime_active) {
+ SetCaretPos(windows[window_id].im_position.x, windows[window_id].im_position.y);
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_IME_UPDATE);
}
} break;
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index f8c09b4bde..7fb831b3b2 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -228,6 +228,22 @@ float AnimationNodeStateMachinePlayback::get_current_length() const {
return len_current;
}
+float AnimationNodeStateMachinePlayback::get_fade_from_play_pos() const {
+ return pos_fade_from;
+}
+
+float AnimationNodeStateMachinePlayback::get_fade_from_length() const {
+ return len_fade_from;
+}
+
+float AnimationNodeStateMachinePlayback::get_fading_time() const {
+ return fading_time;
+}
+
+float AnimationNodeStateMachinePlayback::get_fading_pos() const {
+ return fading_pos;
+}
+
bool AnimationNodeStateMachinePlayback::_travel(AnimationNodeStateMachine *p_state_machine, const StringName &p_travel) {
ERR_FAIL_COND_V(!playing, false);
ERR_FAIL_COND_V(!p_state_machine->states.has(p_travel), false);
@@ -466,7 +482,17 @@ double AnimationNodeStateMachinePlayback::_process(AnimationNodeStateMachine *p_
if (fading_from != StringName()) {
double fade_blend_inv = 1.0 - fade_blend;
- p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend_inv) ? CMP_EPSILON : fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+ float fading_from_rem = 0.0;
+ fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(fade_blend_inv) ? CMP_EPSILON : fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+ //guess playback position
+ if (fading_from_rem > len_fade_from) { // weird but ok
+ len_fade_from = fading_from_rem;
+ }
+
+ { //advance and loop check
+ float next_pos = len_fade_from - fading_from_rem;
+ pos_fade_from = next_pos; //looped
+ }
if (fade_blend >= 1.0) {
fading_from = StringName();
}
@@ -633,6 +659,8 @@ double AnimationNodeStateMachinePlayback::_process(AnimationNodeStateMachine *p_
}
current = next;
+ pos_fade_from = pos_current;
+ len_fade_from = len_current;
if (reset_request) {
len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, CMP_EPSILON, AnimationNode::FILTER_IGNORE, true); // Process next node's first key in here.
diff --git a/scene/animation/animation_node_state_machine.h b/scene/animation/animation_node_state_machine.h
index 1b4e010a06..cf4d850aa6 100644
--- a/scene/animation/animation_node_state_machine.h
+++ b/scene/animation/animation_node_state_machine.h
@@ -118,6 +118,9 @@ class AnimationNodeStateMachinePlayback : public Resource {
StringName next;
};
+ double len_fade_from = 0.0;
+ double pos_fade_from = 0.0;
+
double len_current = 0.0;
double pos_current = 0.0;
bool end_loop = false;
@@ -164,6 +167,12 @@ public:
float get_current_play_pos() const;
float get_current_length() const;
+ float get_fade_from_play_pos() const;
+ float get_fade_from_length() const;
+
+ float get_fading_time() const;
+ float get_fading_pos() const;
+
AnimationNodeStateMachinePlayback();
};
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 472299b135..c26a00221a 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -479,14 +479,16 @@ void BaseButton::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "button_pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group");
+
+ ADD_GROUP("Shortcut", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback");
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled");
BIND_ENUM_CONSTANT(DRAW_NORMAL);
BIND_ENUM_CONSTANT(DRAW_PRESSED);
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
index 1e07a53642..2a8b1cd8c4 100644
--- a/scene/gui/button.cpp
+++ b/scene/gui/button.cpp
@@ -575,9 +575,13 @@ void Button::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text");
+
+ ADD_GROUP("Text Behavior", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_alignment", "get_text_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text");
+
+ ADD_GROUP("Icon Behavior", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_alignment", "get_icon_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon");
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index 6c495ab2c9..af52f6664a 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -1380,34 +1380,15 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) {
accept_event();
}
}
-
- Ref<InputEventMagnifyGesture> magnify_gesture = p_ev;
- if (magnify_gesture.is_valid()) {
- set_zoom_custom(zoom * magnify_gesture->get_factor(), magnify_gesture->get_position());
- }
-
- Ref<InputEventPanGesture> pan_gesture = p_ev;
- if (pan_gesture.is_valid()) {
- h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * pan_gesture->get_delta().x / 8);
- v_scroll->set_value(v_scroll->get_value() + v_scroll->get_page() * pan_gesture->get_delta().y / 8);
- }
-}
-
-void GraphEdit::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) {
- if (p_scroll_vec.x != 0) {
- h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * Math::abs(p_scroll_vec.x) / 8) * SIGN(p_scroll_vec.x));
- } else {
- v_scroll->set_value(v_scroll->get_value() + (v_scroll->get_page() * Math::abs(p_scroll_vec.y) / 8) * SIGN(p_scroll_vec.y));
- }
}
-void GraphEdit::_pan_callback(Vector2 p_scroll_vec) {
+void GraphEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) {
h_scroll->set_value(h_scroll->get_value() - p_scroll_vec.x);
v_scroll->set_value(v_scroll->get_value() - p_scroll_vec.y);
}
-void GraphEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) {
- set_zoom_custom(p_scroll_vec.y < 0 ? zoom * zoom_step : zoom / zoom_step, p_origin);
+void GraphEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) {
+ set_zoom_custom(zoom * p_zoom_factor, p_origin);
}
void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity) {
@@ -1502,6 +1483,7 @@ void GraphEdit::set_zoom_step(float p_zoom_step) {
}
zoom_step = p_zoom_step;
+ panner->set_scroll_zoom_factor(zoom_step);
}
float GraphEdit::get_zoom_step() const {
@@ -2421,7 +2403,7 @@ GraphEdit::GraphEdit() {
zoom_max = (1 * Math::pow(zoom_step, 4));
panner.instantiate();
- panner->set_callbacks(callable_mp(this, &GraphEdit::_scroll_callback), callable_mp(this, &GraphEdit::_pan_callback), callable_mp(this, &GraphEdit::_zoom_callback));
+ panner->set_callbacks(callable_mp(this, &GraphEdit::_pan_callback), callable_mp(this, &GraphEdit::_zoom_callback));
top_layer = memnew(GraphEditFilter(this));
add_child(top_layer, false, INTERNAL_MODE_BACK);
diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h
index 030f40e370..dfe6b94906 100644
--- a/scene/gui/graph_edit.h
+++ b/scene/gui/graph_edit.h
@@ -129,9 +129,8 @@ private:
Ref<ViewPanner> panner;
bool warped_panning = true;
- void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
- void _pan_callback(Vector2 p_scroll_vec);
- void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt);
+ void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
+ void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
bool arrange_nodes_button_hidden = false;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index a68a7ddfb8..9baf7c9180 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -3435,6 +3435,8 @@ void RichTextLabel::push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionar
item->custom_effect = p_custom_effect;
item->char_fx_transform->environment = p_environment;
_add_item(item, true);
+
+ set_process_internal(true);
}
void RichTextLabel::set_table_column_expand(int p_column, bool p_expand, int p_ratio) {
@@ -4552,7 +4554,6 @@ void RichTextLabel::append_text(const String &p_bbcode) {
push_customfx(effect, properties);
pos = brk_end + 1;
tag_stack.push_front(identifier);
- set_process_internal(true);
} else {
add_text("["); //ignore
pos = brk_pos + 1;
@@ -5346,6 +5347,7 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("push_cell"), &RichTextLabel::push_cell);
ClassDB::bind_method(D_METHOD("push_fgcolor", "fgcolor"), &RichTextLabel::push_fgcolor);
ClassDB::bind_method(D_METHOD("push_bgcolor", "bgcolor"), &RichTextLabel::push_bgcolor);
+ ClassDB::bind_method(D_METHOD("push_customfx", "effect", "env"), &RichTextLabel::push_customfx);
ClassDB::bind_method(D_METHOD("pop"), &RichTextLabel::pop);
ClassDB::bind_method(D_METHOD("clear"), &RichTextLabel::clear);
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index e8d54e6937..145497fa61 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -43,36 +43,42 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
if (scroll_vec != Vector2() && mb->is_pressed()) {
if (control_scheme == SCROLL_PANS) {
if (mb->is_ctrl_pressed()) {
- scroll_vec.y *= mb->get_factor();
- callback_helper(zoom_callback, varray(scroll_vec, mb->get_position(), mb->is_alt_pressed()));
+ // Compute the zoom factor.
+ float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor();
+ zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0;
+ float zoom = (scroll_vec.x + scroll_vec.y) > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor;
+ callback_helper(zoom_callback, varray(zoom, mb->get_position(), p_event));
return true;
} else {
- Vector2 panning;
- if (mb->is_shift_pressed()) {
- panning.x += mb->get_factor() * scroll_vec.y;
- panning.y += mb->get_factor() * scroll_vec.x;
- } else {
- panning.y += mb->get_factor() * scroll_vec.y;
- panning.x += mb->get_factor() * scroll_vec.x;
+ Vector2 panning = scroll_vec * mb->get_factor();
+ if (pan_axis == PAN_AXIS_HORIZONTAL) {
+ panning = Vector2(panning.x + panning.y, 0);
+ } else if (pan_axis == PAN_AXIS_VERTICAL) {
+ panning = Vector2(0, panning.x + panning.y);
+ } else if (mb->is_shift_pressed()) {
+ panning = Vector2(panning.y, panning.x);
}
- callback_helper(scroll_callback, varray(panning, mb->is_alt_pressed()));
+ callback_helper(pan_callback, varray(-panning * scroll_speed, p_event));
return true;
}
} else {
if (mb->is_ctrl_pressed()) {
- Vector2 panning;
- if (mb->is_shift_pressed()) {
- panning.x += mb->get_factor() * scroll_vec.y;
- panning.y += mb->get_factor() * scroll_vec.x;
- } else {
- panning.y += mb->get_factor() * scroll_vec.y;
- panning.x += mb->get_factor() * scroll_vec.x;
+ Vector2 panning = scroll_vec * mb->get_factor();
+ if (pan_axis == PAN_AXIS_HORIZONTAL) {
+ panning = Vector2(panning.x + panning.y, 0);
+ } else if (pan_axis == PAN_AXIS_VERTICAL) {
+ panning = Vector2(0, panning.x + panning.y);
+ } else if (mb->is_shift_pressed()) {
+ panning = Vector2(panning.y, panning.x);
}
- callback_helper(scroll_callback, varray(panning, mb->is_alt_pressed()));
+ callback_helper(pan_callback, varray(-panning * scroll_speed, p_event));
return true;
} else if (!mb->is_shift_pressed()) {
- scroll_vec.y *= mb->get_factor();
- callback_helper(zoom_callback, varray(scroll_vec, mb->get_position(), mb->is_alt_pressed()));
+ // Compute the zoom factor.
+ float zoom_factor = mb->get_factor() <= 0 ? 1.0 : mb->get_factor();
+ zoom_factor = ((scroll_zoom_factor - 1.0) * zoom_factor) + 1.0;
+ float zoom = (scroll_vec.x + scroll_vec.y) > 0 ? 1.0 / scroll_zoom_factor : scroll_zoom_factor;
+ callback_helper(zoom_callback, varray(zoom, mb->get_position(), p_event));
return true;
}
}
@@ -102,14 +108,31 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
if (mm.is_valid()) {
if (is_dragging) {
if (p_canvas_rect != Rect2()) {
- callback_helper(pan_callback, varray(Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect)));
+ callback_helper(pan_callback, varray(Input::get_singleton()->warp_mouse_motion(mm, p_canvas_rect), p_event));
} else {
- callback_helper(pan_callback, varray(mm->get_relative()));
+ callback_helper(pan_callback, varray(mm->get_relative(), p_event));
}
return true;
}
}
+ Ref<InputEventMagnifyGesture> magnify_gesture = p_event;
+ if (magnify_gesture.is_valid()) {
+ // Zoom gesture
+ callback_helper(zoom_callback, varray(magnify_gesture->get_factor(), magnify_gesture->get_position(), p_event));
+ return true;
+ }
+
+ Ref<InputEventPanGesture> pan_gesture = p_event;
+ if (pan_gesture.is_valid()) {
+ callback_helper(pan_callback, varray(-pan_gesture->get_delta(), p_event));
+ }
+
+ Ref<InputEventScreenDrag> screen_drag = p_event;
+ if (screen_drag.is_valid()) {
+ callback_helper(pan_callback, varray(screen_drag->get_relative(), p_event));
+ }
+
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
if (pan_view_shortcut.is_valid() && pan_view_shortcut->matches_event(k)) {
@@ -140,8 +163,7 @@ void ViewPanner::callback_helper(Callable p_callback, Vector<Variant> p_args) {
p_callback.callp(argptr, p_args.size(), result, ce);
}
-void ViewPanner::set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback) {
- scroll_callback = p_scroll_callback;
+void ViewPanner::set_callbacks(Callable p_pan_callback, Callable p_zoom_callback) {
pan_callback = p_pan_callback;
zoom_callback = p_zoom_callback;
}
@@ -163,6 +185,20 @@ void ViewPanner::set_simple_panning_enabled(bool p_enabled) {
simple_panning_enabled = p_enabled;
}
+void ViewPanner::set_scroll_speed(int p_scroll_speed) {
+ ERR_FAIL_COND(p_scroll_speed <= 0);
+ scroll_speed = p_scroll_speed;
+}
+
+void ViewPanner::set_scroll_zoom_factor(float p_scroll_zoom_factor) {
+ ERR_FAIL_COND(p_scroll_zoom_factor <= 1.0);
+ scroll_zoom_factor = p_scroll_zoom_factor;
+}
+
+void ViewPanner::set_pan_axis(PanAxis p_pan_axis) {
+ pan_axis = p_pan_axis;
+}
+
void ViewPanner::setup(ControlScheme p_scheme, Ref<Shortcut> p_shortcut, bool p_simple_panning) {
set_control_scheme(p_scheme);
set_pan_shortcut(p_shortcut);
diff --git a/scene/gui/view_panner.h b/scene/gui/view_panner.h
index 861574a80c..60d36ca04c 100644
--- a/scene/gui/view_panner.h
+++ b/scene/gui/view_panner.h
@@ -45,7 +45,17 @@ public:
SCROLL_PANS,
};
+ enum PanAxis {
+ PAN_AXIS_BOTH,
+ PAN_AXIS_HORIZONTAL,
+ PAN_AXIS_VERTICAL,
+ };
+
private:
+ int scroll_speed = 32;
+ float scroll_zoom_factor = 1.1;
+ PanAxis pan_axis = PAN_AXIS_BOTH;
+
bool is_dragging = false;
bool pan_key_pressed = false;
bool force_drag = false;
@@ -55,7 +65,6 @@ private:
Ref<Shortcut> pan_view_shortcut;
- Callable scroll_callback;
Callable pan_callback;
Callable zoom_callback;
@@ -63,11 +72,14 @@ private:
ControlScheme control_scheme = SCROLL_ZOOMS;
public:
- void set_callbacks(Callable p_scroll_callback, Callable p_pan_callback, Callable p_zoom_callback);
+ void set_callbacks(Callable p_pan_callback, Callable p_zoom_callback);
void set_control_scheme(ControlScheme p_scheme);
void set_enable_rmb(bool p_enable);
void set_pan_shortcut(Ref<Shortcut> p_shortcut);
void set_simple_panning_enabled(bool p_enabled);
+ void set_scroll_speed(int p_scroll_speed);
+ void set_scroll_zoom_factor(float p_scroll_zoom_factor);
+ void set_pan_axis(PanAxis p_pan_axis);
void setup(ControlScheme p_scheme, Ref<Shortcut> p_shortcut, bool p_simple_panning);
diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h
index 85b53264f2..dfbaa36af8 100644
--- a/tests/core/variant/test_variant.h
+++ b/tests/core/variant/test_variant.h
@@ -868,6 +868,196 @@ TEST_CASE("[Variant] Basic comparison") {
CHECK_NE(Variant(Dictionary()), Variant());
}
+TEST_CASE("[Variant] Identity comparison") {
+ // Value types are compared by value
+ Variant aabb = AABB();
+ CHECK(aabb.identity_compare(aabb));
+ CHECK(aabb.identity_compare(AABB()));
+ CHECK_FALSE(aabb.identity_compare(AABB(Vector3(1, 2, 3), Vector3(1, 2, 3))));
+
+ Variant basis = Basis();
+ CHECK(basis.identity_compare(basis));
+ CHECK(basis.identity_compare(Basis()));
+ CHECK_FALSE(basis.identity_compare(Basis(Quaternion(Vector3(1, 2, 3).normalized(), 45))));
+
+ Variant bool_var = true;
+ CHECK(bool_var.identity_compare(bool_var));
+ CHECK(bool_var.identity_compare(true));
+ CHECK_FALSE(bool_var.identity_compare(false));
+
+ Variant callable = Callable();
+ CHECK(callable.identity_compare(callable));
+ CHECK(callable.identity_compare(Callable()));
+ CHECK_FALSE(callable.identity_compare(Callable(ObjectID(), StringName("lambda"))));
+
+ Variant color = Color();
+ CHECK(color.identity_compare(color));
+ CHECK(color.identity_compare(Color()));
+ CHECK_FALSE(color.identity_compare(Color(255, 0, 255)));
+
+ Variant float_var = 1.0;
+ CHECK(float_var.identity_compare(float_var));
+ CHECK(float_var.identity_compare(1.0));
+ CHECK_FALSE(float_var.identity_compare(2.0));
+
+ Variant int_var = 1;
+ CHECK(int_var.identity_compare(int_var));
+ CHECK(int_var.identity_compare(1));
+ CHECK_FALSE(int_var.identity_compare(2));
+
+ Variant nil = Variant();
+ CHECK(nil.identity_compare(nil));
+ CHECK(nil.identity_compare(Variant()));
+ CHECK_FALSE(nil.identity_compare(true));
+
+ Variant node_path = NodePath("godot");
+ CHECK(node_path.identity_compare(node_path));
+ CHECK(node_path.identity_compare(NodePath("godot")));
+ CHECK_FALSE(node_path.identity_compare(NodePath("waiting")));
+
+ Variant plane = Plane();
+ CHECK(plane.identity_compare(plane));
+ CHECK(plane.identity_compare(Plane()));
+ CHECK_FALSE(plane.identity_compare(Plane(Vector3(1, 2, 3), 42)));
+
+ Variant projection = Projection();
+ CHECK(projection.identity_compare(projection));
+ CHECK(projection.identity_compare(Projection()));
+ CHECK_FALSE(projection.identity_compare(Projection(Transform3D(Basis(Vector3(1, 2, 3).normalized(), 45), Vector3(1, 2, 3)))));
+
+ Variant quaternion = Quaternion();
+ CHECK(quaternion.identity_compare(quaternion));
+ CHECK(quaternion.identity_compare(Quaternion()));
+ CHECK_FALSE(quaternion.identity_compare(Quaternion(Vector3(1, 2, 3).normalized(), 45)));
+
+ Variant rect2 = Rect2();
+ CHECK(rect2.identity_compare(rect2));
+ CHECK(rect2.identity_compare(Rect2()));
+ CHECK_FALSE(rect2.identity_compare(Rect2(Point2(Vector2(1, 2)), Size2(Vector2(1, 2)))));
+
+ Variant rect2i = Rect2i();
+ CHECK(rect2i.identity_compare(rect2i));
+ CHECK(rect2i.identity_compare(Rect2i()));
+ CHECK_FALSE(rect2i.identity_compare(Rect2i(Point2i(Vector2i(1, 2)), Size2i(Vector2i(1, 2)))));
+
+ Variant rid = RID();
+ CHECK(rid.identity_compare(rid));
+ CHECK(rid.identity_compare(RID()));
+ CHECK_FALSE(rid.identity_compare(RID::from_uint64(123)));
+
+ Variant signal = Signal();
+ CHECK(signal.identity_compare(signal));
+ CHECK(signal.identity_compare(Signal()));
+ CHECK_FALSE(signal.identity_compare(Signal(ObjectID(), StringName("lambda"))));
+
+ Variant str = "godot";
+ CHECK(str.identity_compare(str));
+ CHECK(str.identity_compare("godot"));
+ CHECK_FALSE(str.identity_compare("waiting"));
+
+ Variant str_name = StringName("godot");
+ CHECK(str_name.identity_compare(str_name));
+ CHECK(str_name.identity_compare(StringName("godot")));
+ CHECK_FALSE(str_name.identity_compare(StringName("waiting")));
+
+ Variant transform2d = Transform2D();
+ CHECK(transform2d.identity_compare(transform2d));
+ CHECK(transform2d.identity_compare(Transform2D()));
+ CHECK_FALSE(transform2d.identity_compare(Transform2D(45, Vector2(1, 2))));
+
+ Variant transform3d = Transform3D();
+ CHECK(transform3d.identity_compare(transform3d));
+ CHECK(transform3d.identity_compare(Transform3D()));
+ CHECK_FALSE(transform3d.identity_compare(Transform3D(Basis(Quaternion(Vector3(1, 2, 3).normalized(), 45)), Vector3(1, 2, 3))));
+
+ Variant vect2 = Vector2();
+ CHECK(vect2.identity_compare(vect2));
+ CHECK(vect2.identity_compare(Vector2()));
+ CHECK_FALSE(vect2.identity_compare(Vector2(1, 2)));
+
+ Variant vect2i = Vector2i();
+ CHECK(vect2i.identity_compare(vect2i));
+ CHECK(vect2i.identity_compare(Vector2i()));
+ CHECK_FALSE(vect2i.identity_compare(Vector2i(1, 2)));
+
+ Variant vect3 = Vector3();
+ CHECK(vect3.identity_compare(vect3));
+ CHECK(vect3.identity_compare(Vector3()));
+ CHECK_FALSE(vect3.identity_compare(Vector3(1, 2, 3)));
+
+ Variant vect3i = Vector3i();
+ CHECK(vect3i.identity_compare(vect3i));
+ CHECK(vect3i.identity_compare(Vector3i()));
+ CHECK_FALSE(vect3i.identity_compare(Vector3i(1, 2, 3)));
+
+ Variant vect4 = Vector4();
+ CHECK(vect4.identity_compare(vect4));
+ CHECK(vect4.identity_compare(Vector4()));
+ CHECK_FALSE(vect4.identity_compare(Vector4(1, 2, 3, 4)));
+
+ Variant vect4i = Vector4i();
+ CHECK(vect4i.identity_compare(vect4i));
+ CHECK(vect4i.identity_compare(Vector4i()));
+ CHECK_FALSE(vect4i.identity_compare(Vector4i(1, 2, 3, 4)));
+
+ // Reference types are compared by reference
+ Variant array = Array();
+ CHECK(array.identity_compare(array));
+ CHECK_FALSE(array.identity_compare(Array()));
+
+ Variant dictionary = Dictionary();
+ CHECK(dictionary.identity_compare(dictionary));
+ CHECK_FALSE(dictionary.identity_compare(Dictionary()));
+
+ Variant packed_byte_array = PackedByteArray();
+ CHECK(packed_byte_array.identity_compare(packed_byte_array));
+ CHECK_FALSE(packed_byte_array.identity_compare(PackedByteArray()));
+
+ Variant packed_color_array = PackedColorArray();
+ CHECK(packed_color_array.identity_compare(packed_color_array));
+ CHECK_FALSE(packed_color_array.identity_compare(PackedColorArray()));
+
+ Variant packed_float32_array = PackedFloat32Array();
+ CHECK(packed_float32_array.identity_compare(packed_float32_array));
+ CHECK_FALSE(packed_float32_array.identity_compare(PackedFloat32Array()));
+
+ Variant packed_float64_array = PackedFloat64Array();
+ CHECK(packed_float64_array.identity_compare(packed_float64_array));
+ CHECK_FALSE(packed_float64_array.identity_compare(PackedFloat64Array()));
+
+ Variant packed_int32_array = PackedInt32Array();
+ CHECK(packed_int32_array.identity_compare(packed_int32_array));
+ CHECK_FALSE(packed_int32_array.identity_compare(PackedInt32Array()));
+
+ Variant packed_int64_array = PackedInt64Array();
+ CHECK(packed_int64_array.identity_compare(packed_int64_array));
+ CHECK_FALSE(packed_int64_array.identity_compare(PackedInt64Array()));
+
+ Variant packed_string_array = PackedStringArray();
+ CHECK(packed_string_array.identity_compare(packed_string_array));
+ CHECK_FALSE(packed_string_array.identity_compare(PackedStringArray()));
+
+ Variant packed_vector2_array = PackedVector2Array();
+ CHECK(packed_vector2_array.identity_compare(packed_vector2_array));
+ CHECK_FALSE(packed_vector2_array.identity_compare(PackedVector2Array()));
+
+ Variant packed_vector3_array = PackedVector3Array();
+ CHECK(packed_vector3_array.identity_compare(packed_vector3_array));
+ CHECK_FALSE(packed_vector3_array.identity_compare(PackedVector3Array()));
+
+ Object obj_one = Object();
+ Variant obj_one_var = &obj_one;
+ Object obj_two = Object();
+ Variant obj_two_var = &obj_two;
+ CHECK(obj_one_var.identity_compare(obj_one_var));
+ CHECK_FALSE(obj_one_var.identity_compare(obj_two_var));
+
+ Variant obj_null_one_var = Variant((Object *)nullptr);
+ Variant obj_null_two_var = Variant((Object *)nullptr);
+ CHECK(obj_null_one_var.identity_compare(obj_null_one_var));
+ CHECK(obj_null_one_var.identity_compare(obj_null_two_var));
+}
+
TEST_CASE("[Variant] Nested array comparison") {
Array a1 = build_array(1, build_array(2, 3));
Array a2 = build_array(1, build_array(2, 3));