Browse Source

Add inspector plugin for key time edit & Change find key argument

Silc Renew 2 years ago
parent
commit
060fb2d093

+ 11 - 2
doc/classes/Animation.xml

@@ -305,9 +305,9 @@
 			<return type="int" />
 			<param index="0" name="track_idx" type="int" />
 			<param index="1" name="time" type="float" />
-			<param index="2" name="exact" type="bool" default="false" />
+			<param index="2" name="find_mode" type="int" enum="Animation.FindMode" default="0" />
 			<description>
-				Finds the key index by time in a given track. Optionally, only find it if the exact time is given.
+				Finds the key index by time in a given track. Optionally, only find it if the approx/exact time is given.
 			</description>
 		</method>
 		<method name="track_get_interpolation_loop_wrap" qualifiers="const">
@@ -622,5 +622,14 @@
 		<constant name="LOOPED_FLAG_START" value="2" enum="LoopedFlag">
 			This flag indicates that the animation has reached the start of the animation and just after loop processed.
 		</constant>
+		<constant name="FIND_MODE_NEAREST" value="0" enum="FindMode">
+			Finds the nearest time key.
+		</constant>
+		<constant name="FIND_MODE_APPROX" value="1" enum="FindMode">
+			Finds only the key with approximating the time.
+		</constant>
+		<constant name="FIND_MODE_EXACT" value="2" enum="FindMode">
+			Finds only the key with matching the time.
+		</constant>
 	</constants>
 </class>

+ 22 - 0
doc/classes/EditorSpinSlider.xml

@@ -28,4 +28,26 @@
 			The suffix to display after the value (in a faded color). This should generally be a plural word. You may have to use an abbreviation if the suffix is too long to be displayed.
 		</member>
 	</members>
+	<signals>
+		<signal name="grabbed">
+			<description>
+				Emitted when the spinner/slider is grabbed.
+			</description>
+		</signal>
+		<signal name="ungrabbed">
+			<description>
+				Emitted when the spinner/slider is ungrabbed.
+			</description>
+		</signal>
+		<signal name="value_focus_entered">
+			<description>
+				Emitted when the value form gains focus.
+			</description>
+		</signal>
+		<signal name="value_focus_exited">
+			<description>
+				Emitted when the value form loses focus.
+			</description>
+		</signal>
+	</signals>
 </class>

+ 9 - 9
editor/animation_bezier_editor.cpp

@@ -575,7 +575,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
 							ep.point_rect.size = bezier_icon->get_size();
 							if (selection.has(IntPair(i, j))) {
 								draw_texture(selected_icon, ep.point_rect.position);
-								draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
+								draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.0001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
 								draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent);
 							} else {
 								Color track_color = Color(1, 1, 1, 1);
@@ -812,7 +812,7 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int
 		return;
 	}
 
-	int idx = animation->track_find_key(p_track, p_pos, true);
+	int idx = animation->track_find_key(p_track, p_pos, Animation::FIND_MODE_APPROX);
 	ERR_FAIL_COND(idx < 0);
 
 	selection.insert(IntPair(p_track, idx));
@@ -1168,8 +1168,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 			new_point[4] = 0;
 
 			real_t time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
-			while (animation->track_find_key(selected_track, time, true) != -1) {
-				time += 0.001;
+			while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
+				time += 0.0001;
 			}
 
 			Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
@@ -1179,7 +1179,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 			undo_redo->commit_action();
 
 			//then attempt to move
-			int index = animation->track_find_key(selected_track, time, true);
+			int index = animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX);
 			ERR_FAIL_COND(index == -1);
 			_clear_selection();
 			selection.insert(IntPair(selected_track, index));
@@ -1283,7 +1283,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
 					real_t newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
 
-					int idx = animation->track_find_key(E->get().first, newtime, true);
+					int idx = animation->track_find_key(E->get().first, newtime, Animation::FIND_MODE_APPROX);
 					if (idx == -1) {
 						continue;
 					}
@@ -1539,7 +1539,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
 
 				real_t time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value();
 
-				while (animation->track_find_key(selected_track, time, true) != -1) {
+				while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) {
 					time += 0.001;
 				}
 
@@ -1599,7 +1599,7 @@ void AnimationBezierTrackEdit::duplicate_selection() {
 	for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
 		real_t t = animation->track_get_key_time(E->get().first, E->get().second);
 		real_t dst_time = t + (timeline->get_play_position() - top_time);
-		int existing_idx = animation->track_find_key(E->get().first, dst_time, true);
+		int existing_idx = animation->track_find_key(E->get().first, dst_time, Animation::FIND_MODE_APPROX);
 
 		undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second));
 		undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time);
@@ -1623,7 +1623,7 @@ void AnimationBezierTrackEdit::duplicate_selection() {
 		int track = E.first;
 		real_t time = E.second;
 
-		int existing_idx = animation->track_find_key(track, time, true);
+		int existing_idx = animation->track_find_key(track, time, Animation::FIND_MODE_APPROX);
 
 		if (existing_idx == -1) {
 			continue;

File diff suppressed because it is too large
+ 923 - 1097
editor/animation_track_editor.cpp


+ 100 - 2
editor/animation_track_editor.h

@@ -50,9 +50,83 @@
 #include "scene/resources/animation.h"
 #include "scene_tree_editor.h"
 
+class AnimationTrackEditor;
 class AnimationTrackEdit;
 class ViewPanner;
 
+class AnimationTrackKeyEdit : public Object {
+	GDCLASS(AnimationTrackKeyEdit, Object);
+
+public:
+	bool setting = false;
+	bool animation_read_only = false;
+
+	Ref<Animation> animation;
+	int track = -1;
+	float key_ofs = 0;
+	Node *root_path = nullptr;
+
+	PropertyInfo hint;
+	NodePath base;
+	bool use_fps = false;
+
+	bool _hide_script_from_inspector() { return true; }
+	bool _hide_metadata_from_inspector() { return true; }
+	bool _dont_undo_redo() { return true; }
+
+	bool _is_read_only() { return animation_read_only; }
+
+	void notify_change();
+	Node *get_root_path();
+	void set_use_fps(bool p_enable);
+
+protected:
+	static void _bind_methods();
+	void _fix_node_path(Variant &value);
+	void _update_obj(const Ref<Animation> &p_anim);
+	void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to);
+	bool _set(const StringName &p_name, const Variant &p_value);
+	bool _get(const StringName &p_name, Variant &r_ret) const;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
+};
+
+class AnimationMultiTrackKeyEdit : public Object {
+	GDCLASS(AnimationMultiTrackKeyEdit, Object);
+
+public:
+	bool setting = false;
+	bool animation_read_only = false;
+
+	Ref<Animation> animation;
+
+	RBMap<int, List<float>> key_ofs_map;
+	RBMap<int, NodePath> base_map;
+	PropertyInfo hint;
+
+	Node *root_path = nullptr;
+
+	bool use_fps = false;
+
+	bool _hide_script_from_inspector() { return true; }
+	bool _hide_metadata_from_inspector() { return true; }
+	bool _dont_undo_redo() { return true; }
+
+	bool _is_read_only() { return animation_read_only; }
+
+	void notify_change();
+	Node *get_root_path();
+	void set_use_fps(bool p_enable);
+
+protected:
+	static void _bind_methods();
+	void _fix_node_path(Variant &value, NodePath &base);
+	void _update_obj(const Ref<Animation> &p_anim);
+	void _key_ofs_changed(const Ref<Animation> &p_anim, float from, float to);
+	bool _set(const StringName &p_name, const Variant &p_value);
+	bool _get(const StringName &p_name, Variant &r_ret) const;
+	void _get_property_list(List<PropertyInfo> *p_list) const;
+};
+
 class AnimationTimelineEdit : public Range {
 	GDCLASS(AnimationTimelineEdit, Range);
 
@@ -129,8 +203,6 @@ public:
 	AnimationTimelineEdit();
 };
 
-class AnimationTrackEditor;
-
 class AnimationTrackEdit : public Control {
 	GDCLASS(AnimationTrackEdit, Control);
 	friend class AnimationTimelineEdit;
@@ -592,4 +664,30 @@ public:
 	~AnimationTrackEditor();
 };
 
+// AnimationTrackKeyEditEditorPlugin
+
+class AnimationTrackKeyEditEditor : public EditorProperty {
+	GDCLASS(AnimationTrackKeyEditEditor, EditorProperty);
+
+	Ref<Animation> animation;
+	int track = -1;
+	real_t key_ofs = 0.0;
+	bool use_fps = false;
+
+	EditorSpinSlider *spinner = nullptr;
+
+	struct KeyDataCache {
+		real_t time = 0.0;
+		float transition = 0.0;
+		Variant value;
+	} key_data_cache;
+
+	void _time_edit_entered();
+	void _time_edit_exited();
+
+public:
+	AnimationTrackKeyEditEditor(Ref<Animation> p_animation, int p_track, real_t p_key_ofs, bool p_use_fps);
+	~AnimationTrackKeyEditEditor();
+};
+
 #endif // ANIMATION_TRACK_EDITOR_H

+ 6 - 6
editor/animation_track_editor_plugins.cpp

@@ -831,8 +831,8 @@ Rect2 AnimationTrackEditTypeAudio::get_key_rect(int p_index, float p_pixels_sec)
 
 	len -= end_ofs;
 	len -= start_ofs;
-	if (len <= 0.001) {
-		len = 0.001;
+	if (len <= 0.0001) {
+		len = 0.0001;
 	}
 
 	if (get_animation()->track_get_key_count(get_track()) > p_index + 1) {
@@ -887,8 +887,8 @@ void AnimationTrackEditTypeAudio::draw_key(int p_index, float p_pixels_sec, int
 	len -= end_ofs;
 	len -= start_ofs;
 
-	if (len <= 0.001) {
-		len = 0.001;
+	if (len <= 0.0001) {
+		len = 0.0001;
 	}
 
 	int pixel_len = len * p_pixels_sec;
@@ -1014,8 +1014,8 @@ void AnimationTrackEditTypeAudio::drop_data(const Point2 &p_point, const Variant
 
 			ofs = get_editor()->snap_time(ofs);
 
-			while (get_animation()->track_find_key(get_track(), ofs, true) != -1) { //make sure insertion point is valid
-				ofs += 0.001;
+			while (get_animation()->track_find_key(get_track(), ofs, Animation::FIND_MODE_APPROX) != -1) { //make sure insertion point is valid
+				ofs += 0.0001;
 			}
 
 			Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();

+ 1 - 0
editor/editor_node.cpp

@@ -7262,6 +7262,7 @@ EditorNode::EditorNode() {
 	gui_base->add_child(disk_changed);
 
 	add_editor_plugin(memnew(AnimationPlayerEditorPlugin));
+	add_editor_plugin(memnew(AnimationTrackKeyEditEditorPlugin));
 	add_editor_plugin(memnew(CanvasItemEditorPlugin));
 	add_editor_plugin(memnew(Node3DEditorPlugin));
 	add_editor_plugin(memnew(ScriptEditorPlugin));

+ 13 - 14
editor/editor_properties.cpp

@@ -1384,10 +1384,11 @@ void EditorPropertyInteger::update_property() {
 void EditorPropertyInteger::_bind_methods() {
 }
 
-void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix) {
+void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix) {
 	spin->set_min(p_min);
 	spin->set_max(p_max);
 	spin->set_step(p_step);
+	spin->set_hide_slider(p_hide_slider);
 	spin->set_allow_greater(p_allow_greater);
 	spin->set_allow_lesser(p_allow_lesser);
 	spin->set_suffix(p_suffix);
@@ -2242,12 +2243,11 @@ void EditorPropertyVector2i::_notification(int p_what) {
 	}
 }
 
-void EditorPropertyVector2i::setup(int p_min, int p_max, bool p_hide_slider, bool p_link, const String &p_suffix) {
+void EditorPropertyVector2i::setup(int p_min, int p_max, bool p_link, const String &p_suffix) {
 	for (int i = 0; i < 2; i++) {
 		spin[i]->set_min(p_min);
 		spin[i]->set_max(p_max);
 		spin[i]->set_step(1);
-		spin[i]->set_hide_slider(p_hide_slider);
 		spin[i]->set_allow_greater(true);
 		spin[i]->set_allow_lesser(true);
 		spin[i]->set_suffix(p_suffix);
@@ -2352,12 +2352,11 @@ void EditorPropertyRect2i::_notification(int p_what) {
 void EditorPropertyRect2i::_bind_methods() {
 }
 
-void EditorPropertyRect2i::setup(int p_min, int p_max, bool p_hide_slider, const String &p_suffix) {
+void EditorPropertyRect2i::setup(int p_min, int p_max, const String &p_suffix) {
 	for (int i = 0; i < 4; i++) {
 		spin[i]->set_min(p_min);
 		spin[i]->set_max(p_max);
 		spin[i]->set_step(1);
-		spin[i]->set_hide_slider(p_hide_slider);
 		spin[i]->set_allow_greater(true);
 		spin[i]->set_allow_lesser(true);
 		spin[i]->set_suffix(p_suffix);
@@ -2496,12 +2495,12 @@ void EditorPropertyVector3i::_notification(int p_what) {
 void EditorPropertyVector3i::_bind_methods() {
 }
 
-void EditorPropertyVector3i::setup(int p_min, int p_max, bool p_hide_slider, bool p_link, const String &p_suffix) {
+void EditorPropertyVector3i::setup(int p_min, int p_max, bool p_link, const String &p_suffix) {
 	for (int i = 0; i < 3; i++) {
 		spin[i]->set_min(p_min);
 		spin[i]->set_max(p_max);
 		spin[i]->set_step(1);
-		spin[i]->set_hide_slider(p_hide_slider);
+		spin[i]->set_hide_slider(false);
 		spin[i]->set_allow_greater(true);
 		spin[i]->set_allow_lesser(true);
 		spin[i]->set_suffix(p_suffix);
@@ -3004,11 +3003,11 @@ void EditorPropertyVector4i::_notification(int p_what) {
 void EditorPropertyVector4i::_bind_methods() {
 }
 
-void EditorPropertyVector4i::setup(double p_min, double p_max, bool p_hide_slider, const String &p_suffix) {
+void EditorPropertyVector4i::setup(double p_min, double p_max, const String &p_suffix) {
 	for (int i = 0; i < 4; i++) {
 		spin[i]->set_min(p_min);
 		spin[i]->set_max(p_max);
-		spin[i]->set_hide_slider(p_hide_slider);
+		spin[i]->set_step(1);
 		spin[i]->set_allow_greater(true);
 		spin[i]->set_allow_lesser(true);
 		spin[i]->set_suffix(p_suffix);
@@ -4347,7 +4346,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 				EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
 
 				EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1);
-				editor->setup(hint.min, hint.max, hint.step, hint.or_greater, hint.or_less, hint.suffix);
+				editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, hint.or_greater, hint.or_less, hint.suffix);
 
 				return editor;
 			}
@@ -4475,7 +4474,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 		case Variant::VECTOR2I: {
 			EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i(p_wide));
 			EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1);
-			editor->setup(hint.min, hint.max, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix);
+			editor->setup(hint.min, hint.max, p_hint == PROPERTY_HINT_LINK, hint.suffix);
 			return editor;
 
 		} break;
@@ -4488,7 +4487,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 		case Variant::RECT2I: {
 			EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i(p_wide));
 			EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1);
-			editor->setup(hint.min, hint.max, hint.hide_slider, hint.suffix);
+			editor->setup(hint.min, hint.max, hint.suffix);
 
 			return editor;
 		} break;
@@ -4502,7 +4501,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 		case Variant::VECTOR3I: {
 			EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i(p_wide));
 			EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1);
-			editor->setup(hint.min, hint.max, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix);
+			editor->setup(hint.min, hint.max, p_hint == PROPERTY_HINT_LINK, hint.suffix);
 			return editor;
 
 		} break;
@@ -4516,7 +4515,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
 		case Variant::VECTOR4I: {
 			EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i);
 			EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1);
-			editor->setup(hint.min, hint.max, hint.hide_slider, hint.suffix);
+			editor->setup(hint.min, hint.max, hint.suffix);
 			return editor;
 
 		} break;

+ 5 - 5
editor/editor_properties.h

@@ -378,7 +378,7 @@ protected:
 
 public:
 	virtual void update_property() override;
-	void setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix = String());
+	void setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix = String());
 	EditorPropertyInteger();
 };
 
@@ -566,7 +566,7 @@ protected:
 
 public:
 	virtual void update_property() override;
-	void setup(int p_min, int p_max, bool p_hide_slider, bool p_link = false, const String &p_suffix = String());
+	void setup(int p_min, int p_max, bool p_link = false, const String &p_suffix = String());
 	EditorPropertyVector2i(bool p_force_wide = false);
 };
 
@@ -583,7 +583,7 @@ protected:
 
 public:
 	virtual void update_property() override;
-	void setup(int p_min, int p_max, bool p_hide_slider, const String &p_suffix = String());
+	void setup(int p_min, int p_max, const String &p_suffix = String());
 	EditorPropertyRect2i(bool p_force_wide = false);
 };
 
@@ -608,7 +608,7 @@ protected:
 
 public:
 	virtual void update_property() override;
-	void setup(int p_min, int p_max, bool p_hide_slider, bool p_link = false, const String &p_suffix = String());
+	void setup(int p_min, int p_max, bool p_link = false, const String &p_suffix = String());
 	EditorPropertyVector3i(bool p_force_wide = false);
 };
 
@@ -693,7 +693,7 @@ protected:
 
 public:
 	virtual void update_property() override;
-	void setup(double p_min, double p_max, bool p_hide_slider, const String &p_suffix = String());
+	void setup(double p_min, double p_max, const String &p_suffix = String());
 	EditorPropertyVector4i();
 };
 

+ 5 - 5
editor/editor_properties_array_dict.cpp

@@ -919,7 +919,7 @@ void EditorPropertyDictionary::update_property() {
 				} break;
 				case Variant::INT: {
 					EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
-					editor->setup(-100000, 100000, 1, true, true);
+					editor->setup(-100000, 100000, 1, false, true, true);
 					prop = editor;
 
 				} break;
@@ -942,7 +942,7 @@ void EditorPropertyDictionary::update_property() {
 				} break;
 				case Variant::VECTOR2I: {
 					EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i);
-					editor->setup(-100000, 100000, true);
+					editor->setup(-100000, 100000);
 					prop = editor;
 
 				} break;
@@ -954,7 +954,7 @@ void EditorPropertyDictionary::update_property() {
 				} break;
 				case Variant::RECT2I: {
 					EditorPropertyRect2i *editor = memnew(EditorPropertyRect2i);
-					editor->setup(-100000, 100000, true);
+					editor->setup(-100000, 100000);
 					prop = editor;
 
 				} break;
@@ -966,7 +966,7 @@ void EditorPropertyDictionary::update_property() {
 				} break;
 				case Variant::VECTOR3I: {
 					EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i);
-					editor->setup(-100000, 100000, true);
+					editor->setup(-100000, 100000);
 					prop = editor;
 
 				} break;
@@ -978,7 +978,7 @@ void EditorPropertyDictionary::update_property() {
 				} break;
 				case Variant::VECTOR4I: {
 					EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i);
-					editor->setup(-100000, 100000, true);
+					editor->setup(-100000, 100000);
 					prop = editor;
 
 				} break;

+ 77 - 66
editor/editor_spin_slider.cpp

@@ -76,6 +76,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) {
 					pre_grab_value = get_value();
 					grabbing_spinner = false;
 					grabbing_spinner_mouse_pos = get_global_mouse_position();
+					emit_signal("grabbed");
 				}
 			} else {
 				if (grabbing_spinner_attempt) {
@@ -83,6 +84,7 @@ void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) {
 						Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
 						Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos);
 						queue_redraw();
+						emit_signal("ungrabbed");
 					} else {
 						_focus_entered();
 					}
@@ -178,9 +180,11 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
 				grabbing_ratio = get_as_ratio();
 				grabbing_from = grabber->get_transform().xform(mb->get_position()).x;
 			}
+			emit_signal("grabbed");
 		} else {
 			grabbing_grabber = false;
 			mousewheel_over_grabber = false;
+			emit_signal("ungrabbed");
 		}
 	}
 
@@ -299,10 +303,6 @@ void EditorSpinSlider::_draw_spin_slider() {
 
 	Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
 
-	if (get_step() == 1) {
-		number_width -= updown->get_width();
-	}
-
 	String numstr = get_text_value();
 
 	int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
@@ -359,76 +359,79 @@ void EditorSpinSlider::_draw_spin_slider() {
 	}
 	TS->free_rid(num_rid);
 
-	if (get_step() == 1) {
-		Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
-		int updown_vofs = (size.height - updown2->get_height()) / 2;
-		if (rtl) {
-			updown_offset = sb->get_margin(SIDE_LEFT);
-		} else {
-			updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();
-		}
-		Color c(1, 1, 1);
-		if (hover_updown) {
-			c *= Color(1.2, 1.2, 1.2);
-		}
-		draw_texture(updown2, Vector2(updown_offset, updown_vofs), c);
-		if (grabber->is_visible()) {
-			grabber->hide();
-		}
-	} else if (!hide_slider) {
-		const int grabber_w = 4 * EDSCALE;
-		const int width = size.width - sb->get_minimum_size().width - grabber_w;
-		const int ofs = sb->get_offset().x;
-		const int svofs = (size.height + vofs) / 2 - 1;
-		Color c = fc;
-
-		// Draw the horizontal slider's background.
-		c.a = 0.2;
-		draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c);
-
-		// Draw the horizontal slider's filled part on the left.
-		const int gofs = get_as_ratio() * width;
-		c.a = 0.45;
-		draw_rect(Rect2(ofs, svofs + 1, gofs, 2 * EDSCALE), c);
-
-		// Draw the horizontal slider's grabber.
-		c.a = 0.9;
-		const Rect2 grabber_rect = Rect2(ofs + gofs, svofs, grabber_w, 4 * EDSCALE);
-		draw_rect(grabber_rect, c);
-
-		grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.get_center();
-
-		bool display_grabber = (grabbing_grabber || mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible());
-		if (grabber->is_visible() != display_grabber) {
-			if (display_grabber) {
-				grabber->show();
+	if (!hide_slider) {
+		if (get_step() == 1) {
+			number_width -= updown->get_width();
+			Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
+			int updown_vofs = (size.height - updown2->get_height()) / 2;
+			if (rtl) {
+				updown_offset = sb->get_margin(SIDE_LEFT);
 			} else {
+				updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();
+			}
+			Color c(1, 1, 1);
+			if (hover_updown) {
+				c *= Color(1.2, 1.2, 1.2);
+			}
+			draw_texture(updown2, Vector2(updown_offset, updown_vofs), c);
+			if (grabber->is_visible()) {
 				grabber->hide();
 			}
-		}
-
-		if (display_grabber) {
-			Ref<Texture2D> grabber_tex;
-			if (mouse_over_grabber) {
-				grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider"));
-			} else {
-				grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider"));
+		} else {
+			const int grabber_w = 4 * EDSCALE;
+			const int width = size.width - sb->get_minimum_size().width - grabber_w;
+			const int ofs = sb->get_offset().x;
+			const int svofs = (size.height + vofs) / 2 - 1;
+			Color c = fc;
+
+			// Draw the horizontal slider's background.
+			c.a = 0.2;
+			draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c);
+
+			// Draw the horizontal slider's filled part on the left.
+			const int gofs = get_as_ratio() * width;
+			c.a = 0.45;
+			draw_rect(Rect2(ofs, svofs + 1, gofs, 2 * EDSCALE), c);
+
+			// Draw the horizontal slider's grabber.
+			c.a = 0.9;
+			const Rect2 grabber_rect = Rect2(ofs + gofs, svofs, grabber_w, 4 * EDSCALE);
+			draw_rect(grabber_rect, c);
+
+			grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.get_center();
+
+			bool display_grabber = (grabbing_grabber || mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible());
+			if (grabber->is_visible() != display_grabber) {
+				if (display_grabber) {
+					grabber->show();
+				} else {
+					grabber->hide();
+				}
 			}
 
-			if (grabber->get_texture() != grabber_tex) {
-				grabber->set_texture(grabber_tex);
-			}
+			if (display_grabber) {
+				Ref<Texture2D> grabber_tex;
+				if (mouse_over_grabber) {
+					grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider"));
+				} else {
+					grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider"));
+				}
 
-			Vector2 scale = get_global_transform_with_canvas().get_scale();
-			grabber->set_scale(scale);
-			grabber->reset_size();
-			grabber->set_position(get_global_position() + (grabber_rect.get_center() - grabber->get_size() * 0.5) * scale);
+				if (grabber->get_texture() != grabber_tex) {
+					grabber->set_texture(grabber_tex);
+				}
 
-			if (mousewheel_over_grabber) {
-				Input::get_singleton()->warp_mouse(grabber->get_position() + grabber_rect.size);
-			}
+				Vector2 scale = get_global_transform_with_canvas().get_scale();
+				grabber->set_scale(scale);
+				grabber->reset_size();
+				grabber->set_position(get_global_position() + (grabber_rect.get_center() - grabber->get_size() * 0.5) * scale);
+
+				if (mousewheel_over_grabber) {
+					Input::get_singleton()->warp_mouse(grabber->get_position() + grabber_rect.size);
+				}
 
-			grabber_range = width;
+				grabber_range = width;
+			}
 		}
 	}
 }
@@ -584,6 +587,8 @@ void EditorSpinSlider::_value_focus_exited() {
 		//enter, click, esc
 		grab_focus();
 	}
+
+	emit_signal("value_focus_exited");
 }
 
 void EditorSpinSlider::_grabber_mouse_entered() {
@@ -627,6 +632,7 @@ void EditorSpinSlider::_focus_entered() {
 	value_input->call_deferred(SNAME("select_all"));
 	value_input->set_focus_next(find_next_valid_focus()->get_path());
 	value_input->set_focus_previous(find_prev_valid_focus()->get_path());
+	emit_signal("value_focus_entered");
 }
 
 void EditorSpinSlider::_bind_methods() {
@@ -650,6 +656,11 @@ void EditorSpinSlider::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_slider"), "set_hide_slider", "is_hiding_slider");
+
+	ADD_SIGNAL(MethodInfo("grabbed"));
+	ADD_SIGNAL(MethodInfo("ungrabbed"));
+	ADD_SIGNAL(MethodInfo("value_focus_entered"));
+	ADD_SIGNAL(MethodInfo("value_focus_exited"));
 }
 
 void EditorSpinSlider::_ensure_input_popup() {

+ 26 - 3
editor/plugins/animation_player_editor_plugin.cpp

@@ -87,13 +87,13 @@ void AnimationPlayerEditor::_notification(int p_what) {
 				}
 				frame->set_value(player->get_current_animation_position());
 				track_editor->set_anim_pos(player->get_current_animation_position());
-
 			} else if (!player->is_valid()) {
 				// Reset timeline when the player has been stopped externally
 				frame->set_value(0);
 			} else if (last_active) {
 				// Need the last frame after it stopped.
 				frame->set_value(player->get_current_animation_position());
+				track_editor->set_anim_pos(player->get_current_animation_position());
 			}
 
 			last_active = player->is_playing();
@@ -423,7 +423,7 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) {
 	_animation_selected(idx);
 }
 
-double AnimationPlayerEditor::_get_editor_step() const {
+float AnimationPlayerEditor::_get_editor_step() const {
 	// Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled.
 	if (track_editor->is_snap_enabled()) {
 		const String current = player->get_assigned_animation();
@@ -434,7 +434,7 @@ double AnimationPlayerEditor::_get_editor_step() const {
 		return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step();
 	}
 
-	return 0.0;
+	return 0.0f;
 }
 
 void AnimationPlayerEditor::_animation_name_edited() {
@@ -1973,3 +1973,26 @@ AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() {
 
 AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() {
 }
+
+// AnimationTrackKeyEditEditorPlugin
+
+bool EditorInspectorPluginAnimationTrackKeyEdit::can_handle(Object *p_object) {
+	return Object::cast_to<AnimationTrackKeyEdit>(p_object) != nullptr;
+}
+
+void EditorInspectorPluginAnimationTrackKeyEdit::parse_begin(Object *p_object) {
+	AnimationTrackKeyEdit *atk = Object::cast_to<AnimationTrackKeyEdit>(p_object);
+	ERR_FAIL_COND(!atk);
+
+	atk_editor = memnew(AnimationTrackKeyEditEditor(atk->animation, atk->track, atk->key_ofs, atk->use_fps));
+	add_custom_control(atk_editor);
+}
+
+AnimationTrackKeyEditEditorPlugin::AnimationTrackKeyEditEditorPlugin() {
+	atk_plugin = memnew(EditorInspectorPluginAnimationTrackKeyEdit);
+	EditorInspector::add_inspector_plugin(atk_plugin);
+}
+
+bool AnimationTrackKeyEditEditorPlugin::handles(Object *p_object) const {
+	return p_object->is_class("AnimationTrackKeyEdit");
+}

+ 27 - 1
editor/plugins/animation_player_editor_plugin.h

@@ -162,7 +162,7 @@ class AnimationPlayerEditor : public VBoxContainer {
 	} onion;
 
 	void _select_anim_by_name(const String &p_anim);
-	double _get_editor_step() const;
+	float _get_editor_step() const;
 	void _play_pressed();
 	void _play_from_pressed();
 	void _play_bw_pressed();
@@ -272,4 +272,30 @@ public:
 	~AnimationPlayerEditorPlugin();
 };
 
+// AnimationTrackKeyEditEditorPlugin
+
+class EditorInspectorPluginAnimationTrackKeyEdit : public EditorInspectorPlugin {
+	GDCLASS(EditorInspectorPluginAnimationTrackKeyEdit, EditorInspectorPlugin);
+
+	AnimationTrackKeyEditEditor *atk_editor = nullptr;
+
+public:
+	virtual bool can_handle(Object *p_object) override;
+	virtual void parse_begin(Object *p_object) override;
+};
+
+class AnimationTrackKeyEditEditorPlugin : public EditorPlugin {
+	GDCLASS(AnimationTrackKeyEditEditorPlugin, EditorPlugin);
+
+	EditorInspectorPluginAnimationTrackKeyEdit *atk_plugin = nullptr;
+
+public:
+	bool has_main_screen() const override { return false; }
+	virtual bool handles(Object *p_object) const override;
+
+	virtual String get_name() const override { return "AnimationTrackKeyEdit"; }
+
+	AnimationTrackKeyEditEditorPlugin();
+};
+
 #endif // ANIMATION_PLAYER_EDITOR_PLUGIN_H

+ 2 - 2
editor/plugins/font_config_plugin.cpp

@@ -483,7 +483,7 @@ void EditorPropertyOTVariation::update_property() {
 			Vector3i range = supported.get_value_at_index(i);
 
 			EditorPropertyInteger *prop = memnew(EditorPropertyInteger);
-			prop->setup(range.x, range.y, 1, false, false);
+			prop->setup(range.x, range.y, false, 1, false, false);
 			prop->set_object_and_property(object.ptr(), "keys/" + itos(name_tag));
 
 			String name = TS->tag_to_name(name_tag);
@@ -762,7 +762,7 @@ void EditorPropertyOTFeatures::update_property() {
 					} break;
 					case Variant::INT: {
 						EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
-						editor->setup(0, 255, 1, false, false);
+						editor->setup(0, 255, 1, false, false, false);
 						prop = editor;
 					} break;
 					default: {

+ 4 - 4
editor/plugins/tiles/tile_proxies_manager_dialog.cpp

@@ -398,7 +398,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() {
 	source_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
 	source_from_property_editor->set_selectable(false);
 	source_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	source_from_property_editor->setup(-1, 99999, 1, true, false);
+	source_from_property_editor->setup(-1, 99999, 1, false, true, false);
 	vboxcontainer_from->add_child(source_from_property_editor);
 
 	coords_from_property_editor = memnew(EditorPropertyVector2i);
@@ -417,7 +417,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() {
 	alternative_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
 	alternative_from_property_editor->set_selectable(false);
 	alternative_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	alternative_from_property_editor->setup(-1, 99999, 1, true, false);
+	alternative_from_property_editor->setup(-1, 99999, 1, false, true, false);
 	alternative_from_property_editor->hide();
 	vboxcontainer_from->add_child(alternative_from_property_editor);
 
@@ -432,7 +432,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() {
 	source_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
 	source_to_property_editor->set_selectable(false);
 	source_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	source_to_property_editor->setup(-1, 99999, 1, true, false);
+	source_to_property_editor->setup(-1, 99999, 1, false, true, false);
 	vboxcontainer_to->add_child(source_to_property_editor);
 
 	coords_to_property_editor = memnew(EditorPropertyVector2i);
@@ -451,7 +451,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() {
 	alternative_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed));
 	alternative_to_property_editor->set_selectable(false);
 	alternative_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL);
-	alternative_to_property_editor->setup(-1, 99999, 1, true, false);
+	alternative_to_property_editor->setup(-1, 99999, 1, false, true, false);
 	alternative_to_property_editor->hide();
 	vboxcontainer_to->add_child(alternative_to_property_editor);
 

+ 4 - 4
scene/animation/animation_player.cpp

@@ -691,7 +691,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 						}
 					} else {
 						if (p_started) {
-							int first_key = a->track_find_key(i, p_prev_time, true);
+							int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
 							if (first_key >= 0) {
 								indices.push_back(first_key);
 							}
@@ -761,7 +761,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 					}
 				} else {
 					if (p_started) {
-						int first_key = a->track_find_key(i, p_prev_time, true);
+						int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
 						if (first_key >= 0) {
 							indices.push_back(first_key);
 						}
@@ -855,7 +855,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 					//find stuff to play
 					List<int> to_play;
 					if (p_started) {
-						int first_key = a->track_find_key(i, p_prev_time, true);
+						int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
 						if (first_key >= 0) {
 							to_play.push_back(first_key);
 						}
@@ -968,7 +968,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
 					//find stuff to play
 					List<int> to_play;
 					if (p_started) {
-						int first_key = a->track_find_key(i, p_prev_time, true);
+						int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
 						if (first_key >= 0) {
 							to_play.push_back(first_key);
 						}

+ 4 - 4
scene/animation/animation_tree.cpp

@@ -1383,7 +1383,7 @@ void AnimationTree::_process_graph(double p_delta) {
 							}
 						} else {
 							if (seeked) {
-								int idx = a->track_find_key(i, time, !is_external_seeking);
+								int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
 								if (idx < 0) {
 									continue;
 								}
@@ -1406,7 +1406,7 @@ void AnimationTree::_process_graph(double p_delta) {
 						TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
 
 						if (seeked) {
-							int idx = a->track_find_key(i, time, !is_external_seeking);
+							int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
 							if (idx < 0) {
 								continue;
 							}
@@ -1440,7 +1440,7 @@ void AnimationTree::_process_graph(double p_delta) {
 
 						if (seeked) {
 							//find whatever should be playing
-							int idx = a->track_find_key(i, time, !is_external_seeking);
+							int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
 							if (idx < 0) {
 								continue;
 							}
@@ -1553,7 +1553,7 @@ void AnimationTree::_process_graph(double p_delta) {
 
 						if (seeked) {
 							//seek
-							int idx = a->track_find_key(i, time, !is_external_seeking);
+							int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
 							if (idx < 0) {
 								continue;
 							}

+ 27 - 23
scene/resources/animation.cpp

@@ -1319,7 +1319,7 @@ Error Animation::blend_shape_track_interpolate(int p_track, double p_time, float
 }
 
 void Animation::track_remove_key_at_time(int p_track, double p_time) {
-	int idx = track_find_key(p_track, p_time, true);
+	int idx = track_find_key(p_track, p_time, FIND_MODE_APPROX);
 	ERR_FAIL_COND(idx < 0);
 	track_remove_key(p_track, idx);
 }
@@ -1400,7 +1400,7 @@ void Animation::track_remove_key(int p_track, int p_idx) {
 	emit_changed();
 }
 
-int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
+int Animation::track_find_key(int p_track, double p_time, FindMode p_find_mode) const {
 	ERR_FAIL_INDEX_V(p_track, tracks.size(), -1);
 	Track *t = tracks[p_track];
 
@@ -1416,7 +1416,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 				uint32_t key_index;
 				bool fetch_compressed_success = _fetch_compressed<3>(tt->compressed_track, p_time, key, time, key_next, time_next, &key_index);
 				ERR_FAIL_COND_V(!fetch_compressed_success, -1);
-				if (p_exact && time != p_time) {
+				if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(time, p_time)) || (p_find_mode == FIND_MODE_EXACT && time != p_time)) {
 					return -1;
 				}
 				return key_index;
@@ -1426,7 +1426,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= tt->positions.size()) {
 				return -1;
 			}
-			if (tt->positions[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(tt->positions[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && tt->positions[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1443,7 +1443,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 				uint32_t key_index;
 				bool fetch_compressed_success = _fetch_compressed<3>(rt->compressed_track, p_time, key, time, key_next, time_next, &key_index);
 				ERR_FAIL_COND_V(!fetch_compressed_success, -1);
-				if (p_exact && time != p_time) {
+				if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(time, p_time)) || (p_find_mode == FIND_MODE_EXACT && time != p_time)) {
 					return -1;
 				}
 				return key_index;
@@ -1453,7 +1453,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= rt->rotations.size()) {
 				return -1;
 			}
-			if (rt->rotations[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(rt->rotations[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && rt->rotations[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1470,7 +1470,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 				uint32_t key_index;
 				bool fetch_compressed_success = _fetch_compressed<3>(st->compressed_track, p_time, key, time, key_next, time_next, &key_index);
 				ERR_FAIL_COND_V(!fetch_compressed_success, -1);
-				if (p_exact && time != p_time) {
+				if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(time, p_time)) || (p_find_mode == FIND_MODE_EXACT && time != p_time)) {
 					return -1;
 				}
 				return key_index;
@@ -1480,7 +1480,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= st->scales.size()) {
 				return -1;
 			}
-			if (st->scales[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(st->scales[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && st->scales[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1497,7 +1497,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 				uint32_t key_index;
 				bool fetch_compressed_success = _fetch_compressed<1>(bst->compressed_track, p_time, key, time, key_next, time_next, &key_index);
 				ERR_FAIL_COND_V(!fetch_compressed_success, -1);
-				if (p_exact && time != p_time) {
+				if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(time, p_time)) || (p_find_mode == FIND_MODE_EXACT && time != p_time)) {
 					return -1;
 				}
 				return key_index;
@@ -1507,7 +1507,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= bst->blend_shapes.size()) {
 				return -1;
 			}
-			if (bst->blend_shapes[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(bst->blend_shapes[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && bst->blend_shapes[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1519,7 +1519,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= vt->values.size()) {
 				return -1;
 			}
-			if (vt->values[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(vt->values[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && vt->values[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1531,7 +1531,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= mt->methods.size()) {
 				return -1;
 			}
-			if (mt->methods[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(mt->methods[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && mt->methods[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1543,7 +1543,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= bt->values.size()) {
 				return -1;
 			}
-			if (bt->values[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(bt->values[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && bt->values[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1555,7 +1555,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= at->values.size()) {
 				return -1;
 			}
-			if (at->values[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(at->values[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && at->values[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -1567,7 +1567,7 @@ int Animation::track_find_key(int p_track, double p_time, bool p_exact) const {
 			if (k < 0 || k >= at->values.size()) {
 				return -1;
 			}
-			if (at->values[k].time != p_time && p_exact) {
+			if ((p_find_mode == FIND_MODE_APPROX && !Math::is_equal_approx(at->values[k].time, p_time)) || (p_find_mode == FIND_MODE_EXACT && at->values[k].time != p_time)) {
 				return -1;
 			}
 			return k;
@@ -2944,12 +2944,12 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 			// Not from_time > to_time but most recent of looping...
 			if (p_looped_flag != Animation::LOOPED_FLAG_NONE) {
 				if (!is_backward && Math::is_equal_approx(from_time, 0)) {
-					int edge = track_find_key(p_track, 0, true);
+					int edge = track_find_key(p_track, 0, FIND_MODE_EXACT);
 					if (edge >= 0) {
 						p_indices->push_back(edge);
 					}
 				} else if (is_backward && Math::is_equal_approx(to_time, length)) {
-					int edge = track_find_key(p_track, length, true);
+					int edge = track_find_key(p_track, length, FIND_MODE_EXACT);
 					if (edge >= 0) {
 						p_indices->push_back(edge);
 					}
@@ -2971,7 +2971,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 						const PositionTrack *tt = static_cast<const PositionTrack *>(t);
 						if (tt->compressed_track >= 0) {
 							_get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, from_time, p_indices);
-							_get_compressed_key_indices_in_range<3>(tt->compressed_track, CMP_EPSILON, to_time, p_indices);
+							_get_compressed_key_indices_in_range<3>(tt->compressed_track, 0, to_time, p_indices);
 						} else {
 							_track_get_key_indices_in_range(tt->positions, 0, from_time, p_indices, true);
 							_track_get_key_indices_in_range(tt->positions, 0, to_time, p_indices, false);
@@ -2981,7 +2981,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 						const RotationTrack *rt = static_cast<const RotationTrack *>(t);
 						if (rt->compressed_track >= 0) {
 							_get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, from_time, p_indices);
-							_get_compressed_key_indices_in_range<3>(rt->compressed_track, CMP_EPSILON, to_time, p_indices);
+							_get_compressed_key_indices_in_range<3>(rt->compressed_track, 0, to_time, p_indices);
 						} else {
 							_track_get_key_indices_in_range(rt->rotations, 0, from_time, p_indices, true);
 							_track_get_key_indices_in_range(rt->rotations, 0, to_time, p_indices, false);
@@ -3072,7 +3072,7 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 						const BlendShapeTrack *bst = static_cast<const BlendShapeTrack *>(t);
 						if (bst->compressed_track >= 0) {
 							_get_compressed_key_indices_in_range<1>(bst->compressed_track, from_time, length, p_indices);
-							_get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length - CMP_EPSILON, p_indices);
+							_get_compressed_key_indices_in_range<1>(bst->compressed_track, to_time, length, p_indices);
 						} else {
 							_track_get_key_indices_in_range(bst->blend_shapes, from_time, length, p_indices, false);
 							_track_get_key_indices_in_range(bst->blend_shapes, to_time, length, p_indices, true);
@@ -3109,9 +3109,9 @@ void Animation::track_get_key_indices_in_range(int p_track, double p_time, doubl
 
 			// The edge will be pingponged in the next frame and processed there, so let's ignore it now...
 			if (!is_backward && Math::is_equal_approx(to_time, length)) {
-				to_time = length - CMP_EPSILON;
+				to_time -= CMP_EPSILON;
 			} else if (is_backward && Math::is_equal_approx(from_time, 0)) {
-				from_time = CMP_EPSILON;
+				from_time += CMP_EPSILON;
 			}
 		} break;
 	}
@@ -3818,7 +3818,7 @@ void Animation::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("track_get_key_count", "track_idx"), &Animation::track_get_key_count);
 	ClassDB::bind_method(D_METHOD("track_get_key_value", "track_idx", "key_idx"), &Animation::track_get_key_value);
 	ClassDB::bind_method(D_METHOD("track_get_key_time", "track_idx", "key_idx"), &Animation::track_get_key_time);
-	ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "exact"), &Animation::track_find_key, DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("track_find_key", "track_idx", "time", "find_mode"), &Animation::track_find_key, DEFVAL(FIND_MODE_NEAREST));
 
 	ClassDB::bind_method(D_METHOD("track_set_interpolation_type", "track_idx", "interpolation"), &Animation::track_set_interpolation_type);
 	ClassDB::bind_method(D_METHOD("track_get_interpolation_type", "track_idx"), &Animation::track_get_interpolation_type);
@@ -3905,6 +3905,10 @@ void Animation::_bind_methods() {
 	BIND_ENUM_CONSTANT(LOOPED_FLAG_NONE);
 	BIND_ENUM_CONSTANT(LOOPED_FLAG_END);
 	BIND_ENUM_CONSTANT(LOOPED_FLAG_START);
+
+	BIND_ENUM_CONSTANT(FIND_MODE_NEAREST);
+	BIND_ENUM_CONSTANT(FIND_MODE_APPROX);
+	BIND_ENUM_CONSTANT(FIND_MODE_EXACT);
 }
 
 void Animation::clear() {

+ 8 - 1
scene/resources/animation.h

@@ -79,6 +79,12 @@ public:
 		LOOPED_FLAG_START,
 	};
 
+	enum FindMode {
+		FIND_MODE_NEAREST,
+		FIND_MODE_APPROX,
+		FIND_MODE_EXACT,
+	};
+
 #ifdef TOOLS_ENABLED
 	enum HandleMode {
 		HANDLE_MODE_FREE,
@@ -392,7 +398,7 @@ public:
 	void track_set_key_transition(int p_track, int p_key_idx, real_t p_transition);
 	void track_set_key_value(int p_track, int p_key_idx, const Variant &p_value);
 	void track_set_key_time(int p_track, int p_key_idx, double p_time);
-	int track_find_key(int p_track, double p_time, bool p_exact = false) const;
+	int track_find_key(int p_track, double p_time, FindMode p_find_mode = FIND_MODE_NEAREST) const;
 	void track_remove_key(int p_track, int p_idx);
 	void track_remove_key_at_time(int p_track, double p_time);
 	int track_get_key_count(int p_track) const;
@@ -489,6 +495,7 @@ VARIANT_ENUM_CAST(Animation::InterpolationType);
 VARIANT_ENUM_CAST(Animation::UpdateMode);
 VARIANT_ENUM_CAST(Animation::LoopMode);
 VARIANT_ENUM_CAST(Animation::LoopedFlag);
+VARIANT_ENUM_CAST(Animation::FindMode);
 #ifdef TOOLS_ENABLED
 VARIANT_ENUM_CAST(Animation::HandleMode);
 VARIANT_ENUM_CAST(Animation::HandleSetMode);

Some files were not shown because too many files changed in this diff