Browse Source

Merge pull request #63245 from V-Sekai/animation_editor_read_only

Rémi Verschelde 3 years ago
parent
commit
cf95056c91

+ 181 - 156
editor/animation_bezier_editor.cpp

@@ -328,6 +328,8 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
 					}
 					}
 				}
 				}
 
 
+				Color dc = get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"));
+
 				Ref<Texture2D> remove = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"));
 				Ref<Texture2D> remove = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"));
 				float remove_hpos = limit - hsep - remove->get_width();
 				float remove_hpos = limit - hsep - remove->get_width();
 
 
@@ -402,7 +404,11 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
 
 
 					float icon_start_height = vofs + rect.size.y / 2;
 					float icon_start_height = vofs + rect.size.y / 2;
 					Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height());
 					Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height());
-					draw_texture(remove, remove_rect.position);
+					if (read_only) {
+						draw_texture(remove, remove_rect.position, dc);
+					} else {
+						draw_texture(remove, remove_rect.position);
+					}
 
 
 					Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height());
 					Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height());
 					if (locked_tracks.has(current_track)) {
 					if (locked_tracks.has(current_track)) {
@@ -632,8 +638,9 @@ Ref<Animation> AnimationBezierTrackEdit::get_animation() const {
 	return animation;
 	return animation;
 }
 }
 
 
-void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) {
+void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only) {
 	animation = p_animation;
 	animation = p_animation;
+	read_only = p_read_only;
 	selected_track = p_track;
 	selected_track = p_track;
 	update();
 	update();
 }
 }
@@ -715,7 +722,7 @@ void AnimationBezierTrackEdit::set_filtered(bool p_filtered) {
 							continue; // Skip track due to not selected.
 							continue; // Skip track due to not selected.
 						}
 						}
 
 
-						set_animation_and_track(animation, i);
+						set_animation_and_track(animation, i, read_only);
 						break;
 						break;
 					}
 					}
 				}
 				}
@@ -819,12 +826,16 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 
 	if (p_event->is_pressed()) {
 	if (p_event->is_pressed()) {
 		if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
 		if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
-			duplicate_selection();
+			if (!read_only) {
+				duplicate_selection();
+			}
 			accept_event();
 			accept_event();
 		}
 		}
 
 
 		if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) {
 		if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) {
-			delete_selection();
+			if (!read_only) {
+				delete_selection();
+			}
 			accept_event();
 			accept_event();
 		}
 		}
 	}
 	}
@@ -917,26 +928,28 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 	if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
 	if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
 		menu_insert_key = mb->get_position();
 		menu_insert_key = mb->get_position();
 		if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) {
 		if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) {
-			Vector2 popup_pos = get_screen_position() + mb->get_position();
+			if (!read_only) {
+				Vector2 popup_pos = get_screen_position() + mb->get_position();
 
 
-			menu->clear();
-			if (!locked_tracks.has(selected_track) || locked_tracks.has(selected_track)) {
-				menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
-			}
-			if (selection.size()) {
-				menu->add_separator();
-				menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);
-				menu->add_separator();
-				menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);
-				menu->add_separator();
-				menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesFree"), SNAME("EditorIcons")), TTR("Make Handles Free"), MENU_KEY_SET_HANDLE_FREE);
-				menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED);
-			}
+				menu->clear();
+				if (!locked_tracks.has(selected_track) || locked_tracks.has(selected_track)) {
+					menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT);
+				}
+				if (selection.size()) {
+					menu->add_separator();
+					menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE);
+					menu->add_separator();
+					menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Delete Selected Key(s)"), MENU_KEY_DELETE);
+					menu->add_separator();
+					menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesFree"), SNAME("EditorIcons")), TTR("Make Handles Free"), MENU_KEY_SET_HANDLE_FREE);
+					menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED);
+				}
 
 
-			if (menu->get_item_count()) {
-				menu->reset_size();
-				menu->set_position(popup_pos);
-				menu->popup();
+				if (menu->get_item_count()) {
+					menu->reset_size();
+					menu->set_position(popup_pos);
+					menu->popup();
+				}
 			}
 			}
 		}
 		}
 	}
 	}
@@ -945,7 +958,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 		for (const KeyValue<int, Rect2> &E : subtracks) {
 		for (const KeyValue<int, Rect2> &E : subtracks) {
 			if (E.value.has_point(mb->get_position())) {
 			if (E.value.has_point(mb->get_position())) {
 				if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) {
 				if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) {
-					set_animation_and_track(animation, E.key);
+					set_animation_and_track(animation, E.key, read_only);
 					_clear_selection();
 					_clear_selection();
 				}
 				}
 				return;
 				return;
@@ -958,30 +971,32 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 			for (const KeyValue<int, Rect2> &I : track_icons) {
 			for (const KeyValue<int, Rect2> &I : track_icons) {
 				if (I.value.has_point(mb->get_position())) {
 				if (I.value.has_point(mb->get_position())) {
 					if (I.key == REMOVE_ICON) {
 					if (I.key == REMOVE_ICON) {
-						undo_redo->create_action("Remove Bezier Track");
-
-						undo_redo->add_do_method(this, "_update_locked_tracks_after", track);
-						undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);
-
-						undo_redo->add_do_method(animation.ptr(), "remove_track", track);
+						if (!read_only) {
+							undo_redo->create_action("Remove Bezier Track");
+
+							undo_redo->add_do_method(this, "_update_locked_tracks_after", track);
+							undo_redo->add_do_method(this, "_update_hidden_tracks_after", track);
+
+							undo_redo->add_do_method(animation.ptr(), "remove_track", track);
+
+							undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track);
+							undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));
+
+							for (int i = 0; i < animation->track_get_key_count(track); ++i) {
+								undo_redo->add_undo_method(
+										animation.ptr(),
+										"bezier_track_insert_key",
+										track, animation->track_get_key_time(track, i),
+										animation->bezier_track_get_key_value(track, i),
+										animation->bezier_track_get_key_in_handle(track, i),
+										animation->bezier_track_get_key_out_handle(track, i),
+										animation->bezier_track_get_key_handle_mode(track, i));
+							}
 
 
-						undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track);
-						undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track));
+							undo_redo->commit_action();
 
 
-						for (int i = 0; i < animation->track_get_key_count(track); ++i) {
-							undo_redo->add_undo_method(
-									animation.ptr(),
-									"bezier_track_insert_key",
-									track, animation->track_get_key_time(track, i),
-									animation->bezier_track_get_key_value(track, i),
-									animation->bezier_track_get_key_in_handle(track, i),
-									animation->bezier_track_get_key_out_handle(track, i),
-									animation->bezier_track_get_key_handle_mode(track, i));
+							selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1);
 						}
 						}
-
-						undo_redo->commit_action();
-
-						selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1);
 						return;
 						return;
 					} else if (I.key == LOCK_ICON) {
 					} else if (I.key == LOCK_ICON) {
 						if (locked_tracks.has(track)) {
 						if (locked_tracks.has(track)) {
@@ -991,7 +1006,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 							if (selected_track == track) {
 							if (selected_track == track) {
 								for (int i = 0; i < animation->get_track_count(); ++i) {
 								for (int i = 0; i < animation->get_track_count(); ++i) {
 									if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
 									if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
-										set_animation_and_track(animation, i);
+										set_animation_and_track(animation, i, read_only);
 										break;
 										break;
 									}
 									}
 								}
 								}
@@ -1007,7 +1022,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 							if (selected_track == track) {
 							if (selected_track == track) {
 								for (int i = 0; i < animation->get_track_count(); ++i) {
 								for (int i = 0; i < animation->get_track_count(); ++i) {
 									if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
 									if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) {
-										set_animation_and_track(animation, i);
+										set_animation_and_track(animation, i, read_only);
 										break;
 										break;
 									}
 									}
 								}
 								}
@@ -1046,7 +1061,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 								}
 								}
 							}
 							}
 
 
-							set_animation_and_track(animation, track);
+							set_animation_and_track(animation, track, read_only);
 							solo_track = track;
 							solo_track = track;
 						}
 						}
 						update();
 						update();
@@ -1087,7 +1102,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 						moving_selection_from_key = pair.second;
 						moving_selection_from_key = pair.second;
 						moving_selection_from_track = pair.first;
 						moving_selection_from_track = pair.first;
 						moving_selection_offset = Vector2();
 						moving_selection_offset = Vector2();
-						set_animation_and_track(animation, pair.first);
+						set_animation_and_track(animation, pair.first, read_only);
 						selection.clear();
 						selection.clear();
 						selection.insert(pair);
 						selection.insert(pair);
 						update();
 						update();
@@ -1096,24 +1111,26 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 				}
 				}
 			}
 			}
 
 
-			if (edit_points[i].in_rect.has_point(mb->get_position())) {
-				moving_handle = -1;
-				moving_handle_key = edit_points[i].key;
-				moving_handle_track = edit_points[i].track;
-				moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);
-				moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);
-				update();
-				return;
-			}
+			if (!read_only) {
+				if (edit_points[i].in_rect.has_point(mb->get_position())) {
+					moving_handle = -1;
+					moving_handle_key = edit_points[i].key;
+					moving_handle_track = edit_points[i].track;
+					moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);
+					moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);
+					update();
+					return;
+				}
 
 
-			if (edit_points[i].out_rect.has_point(mb->get_position())) {
-				moving_handle = 1;
-				moving_handle_key = edit_points[i].key;
-				moving_handle_track = edit_points[i].track;
-				moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);
-				moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);
-				update();
-				return;
+				if (edit_points[i].out_rect.has_point(mb->get_position())) {
+					moving_handle = 1;
+					moving_handle_key = edit_points[i].key;
+					moving_handle_track = edit_points[i].track;
+					moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key);
+					moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key);
+					update();
+					return;
+				}
 			}
 			}
 		}
 		}
 
 
@@ -1191,7 +1208,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 					selection.insert(IntPair(edit_points[i].track, edit_points[i].key));
 					selection.insert(IntPair(edit_points[i].track, edit_points[i].key));
 					if (!track_set) {
 					if (!track_set) {
 						track_set = true;
 						track_set = true;
-						set_animation_and_track(animation, edit_points[i].track);
+						set_animation_and_track(animation, edit_points[i].track, read_only);
 					}
 					}
 				}
 				}
 			}
 			}
@@ -1215,7 +1232,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 				float track_height = _bezier_h_to_pixel(track_h);
 				float track_height = _bezier_h_to_pixel(track_h);
 
 
 				if (abs(mb->get_position().y - track_height) < 10) {
 				if (abs(mb->get_position().y - track_height) < 10) {
-					set_animation_and_track(animation, i);
+					set_animation_and_track(animation, i, read_only);
 					break;
 					break;
 				}
 				}
 			}
 			}
@@ -1229,102 +1246,106 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 	}
 	}
 
 
 	if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
 	if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
-		undo_redo->create_action(TTR("Move Bezier Points"));
-		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left);
-		undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right);
-		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key));
-		undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key));
-		undo_redo->commit_action();
+		if (!read_only) {
+			undo_redo->create_action(TTR("Move Bezier Points"));
+			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left);
+			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right);
+			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key));
+			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key));
+			undo_redo->commit_action();
 
 
-		moving_handle = 0;
-		update();
+			moving_handle = 0;
+			update();
+		}
 	}
 	}
 
 
 	if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
 	if (moving_selection_attempt && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
-		if (moving_selection) {
-			//combit it
+		if (!read_only) {
+			if (moving_selection) {
+				//combit it
 
 
-			undo_redo->create_action(TTR("Move Bezier Points"));
-
-			List<AnimMoveRestore> to_restore;
-			// 1-remove the keys
-			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
-				undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);
-			}
-			// 2- remove overlapped keys
-			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
-				float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
+				undo_redo->create_action(TTR("Move Bezier Points"));
 
 
-				int idx = animation->track_find_key(E->get().first, newtime, true);
-				if (idx == -1) {
-					continue;
+				List<AnimMoveRestore> to_restore;
+				// 1-remove the keys
+				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+					undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second);
 				}
 				}
+				// 2- remove overlapped keys
+				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+					float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
 
 
-				if (selection.has(IntPair(E->get().first, idx))) {
-					continue; //already in selection, don't save
-				}
+					int idx = animation->track_find_key(E->get().first, newtime, true);
+					if (idx == -1) {
+						continue;
+					}
 
 
-				undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);
-				AnimMoveRestore amr;
+					if (selection.has(IntPair(E->get().first, idx))) {
+						continue; //already in selection, don't save
+					}
 
 
-				amr.key = animation->track_get_key_value(E->get().first, idx);
-				amr.track = E->get().first;
-				amr.time = newtime;
+					undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime);
+					AnimMoveRestore amr;
 
 
-				to_restore.push_back(amr);
-			}
+					amr.key = animation->track_get_key_value(E->get().first, idx);
+					amr.track = E->get().first;
+					amr.time = newtime;
 
 
-			// 3-move the keys (re insert them)
-			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
-				float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
-				Array key = animation->track_get_key_value(E->get().first, E->get().second);
-				float h = key[0];
-				h += moving_selection_offset.y;
-				key[0] = h;
-				undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1);
-			}
+					to_restore.push_back(amr);
+				}
 
 
-			// 4-(undo) remove inserted keys
-			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
-				float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
-				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);
-			}
+				// 3-move the keys (re insert them)
+				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+					float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
+					Array key = animation->track_get_key_value(E->get().first, E->get().second);
+					float h = key[0];
+					h += moving_selection_offset.y;
+					key[0] = h;
+					undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1);
+				}
 
 
-			// 5-(undo) reinsert keys
-			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
-				float oldpos = animation->track_get_key_time(E->get().first, E->get().second);
-				undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1);
-			}
+				// 4-(undo) remove inserted keys
+				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+					float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x);
+					undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos);
+				}
 
 
-			// 6-(undo) reinsert overlapped keys
-			for (const AnimMoveRestore &amr : to_restore) {
-				undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);
-			}
+				// 5-(undo) reinsert keys
+				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+					float oldpos = animation->track_get_key_time(E->get().first, E->get().second);
+					undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1);
+				}
 
 
-			undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
-			undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
+				// 6-(undo) reinsert overlapped keys
+				for (const AnimMoveRestore &amr : to_restore) {
+					undo_redo->add_undo_method(animation.ptr(), "track_insert_key", amr.track, amr.time, amr.key, 1);
+				}
 
 
-			// 7-reselect
+				undo_redo->add_do_method(this, "_clear_selection_for_anim", animation);
+				undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
 
 
-			for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
-				float oldpos = animation->track_get_key_time(E->get().first, E->get().second);
-				float newpos = editor->snap_time(oldpos + moving_selection_offset.x);
+				// 7-reselect
 
 
-				undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos);
-				undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos);
-			}
+				for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) {
+					float oldpos = animation->track_get_key_time(E->get().first, E->get().second);
+					float newpos = editor->snap_time(oldpos + moving_selection_offset.x);
 
 
-			undo_redo->commit_action();
+					undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos);
+					undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos);
+				}
 
 
-			moving_selection = false;
-		} else if (select_single_attempt != IntPair(-1, -1)) {
-			selection.clear();
-			selection.insert(select_single_attempt);
-			set_animation_and_track(animation, select_single_attempt.first);
-		}
+				undo_redo->commit_action();
 
 
-		moving_selection_attempt = false;
-		update();
+				moving_selection = false;
+			} else if (select_single_attempt != IntPair(-1, -1)) {
+				selection.clear();
+				selection.insert(select_single_attempt);
+				set_animation_and_track(animation, select_single_attempt.first, read_only);
+			}
+
+			moving_selection_attempt = false;
+			update();
+		}
 	}
 	}
 
 
 	Ref<InputEventMouseMotion> mm = p_event;
 	Ref<InputEventMouseMotion> mm = p_event;
@@ -1337,7 +1358,9 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 		float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;
 		float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll;
 		float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value());
 		float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value());
 
 
-		moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key));
+		if (!read_only) {
+			moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key));
+		}
 		update();
 		update();
 	}
 	}
 
 
@@ -1399,20 +1422,22 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 
 	bool is_finishing_key_handle_drag = moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT;
 	bool is_finishing_key_handle_drag = moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT;
 	if (is_finishing_key_handle_drag) {
 	if (is_finishing_key_handle_drag) {
-		undo_redo->create_action(TTR("Move Bezier Points"));
-		if (moving_handle == -1) {
-			double ratio = timeline->get_zoom_scale() * v_zoom;
-			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio);
-			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio);
-		} else if (moving_handle == 1) {
-			double ratio = timeline->get_zoom_scale() * v_zoom;
-			undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);
-			undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);
-		}
-		undo_redo->commit_action();
+		if (!read_only) {
+			undo_redo->create_action(TTR("Move Bezier Points"));
+			if (moving_handle == -1) {
+				double ratio = timeline->get_zoom_scale() * v_zoom;
+				undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio);
+				undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio);
+			} else if (moving_handle == 1) {
+				double ratio = timeline->get_zoom_scale() * v_zoom;
+				undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio);
+				undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio);
+			}
+			undo_redo->commit_action();
 
 
-		moving_handle = 0;
-		update();
+			moving_handle = 0;
+			update();
+		}
 	}
 	}
 }
 }
 
 

+ 2 - 1
editor/animation_bezier_editor.h

@@ -54,6 +54,7 @@ class AnimationBezierTrackEdit : public Control {
 	float play_position_pos = 0;
 	float play_position_pos = 0;
 
 
 	Ref<Animation> animation;
 	Ref<Animation> animation;
+	bool read_only = false;
 	int selected_track = 0;
 	int selected_track = 0;
 
 
 	Vector<Rect2> view_rects;
 	Vector<Rect2> view_rects;
@@ -176,7 +177,7 @@ public:
 
 
 	Ref<Animation> get_animation() const;
 	Ref<Animation> get_animation() const;
 
 
-	void set_animation_and_track(const Ref<Animation> &p_animation, int p_track);
+	void set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only);
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;
 
 
 	void set_undo_redo(UndoRedo *p_undo_redo);
 	void set_undo_redo(UndoRedo *p_undo_redo);

+ 203 - 124
editor/animation_track_editor.cpp

@@ -48,6 +48,7 @@ class AnimationTrackKeyEdit : public Object {
 
 
 public:
 public:
 	bool setting = false;
 	bool setting = false;
+	bool animation_read_only = false;
 
 
 	bool _hide_script_from_inspector() {
 	bool _hide_script_from_inspector() {
 		return true;
 		return true;
@@ -57,12 +58,17 @@ public:
 		return true;
 		return true;
 	}
 	}
 
 
+	bool _read_only() {
+		return animation_read_only;
+	}
+
 	static void _bind_methods() {
 	static void _bind_methods() {
 		ClassDB::bind_method("_update_obj", &AnimationTrackKeyEdit::_update_obj);
 		ClassDB::bind_method("_update_obj", &AnimationTrackKeyEdit::_update_obj);
 		ClassDB::bind_method("_key_ofs_changed", &AnimationTrackKeyEdit::_key_ofs_changed);
 		ClassDB::bind_method("_key_ofs_changed", &AnimationTrackKeyEdit::_key_ofs_changed);
 		ClassDB::bind_method("_hide_script_from_inspector", &AnimationTrackKeyEdit::_hide_script_from_inspector);
 		ClassDB::bind_method("_hide_script_from_inspector", &AnimationTrackKeyEdit::_hide_script_from_inspector);
 		ClassDB::bind_method("get_root_path", &AnimationTrackKeyEdit::get_root_path);
 		ClassDB::bind_method("get_root_path", &AnimationTrackKeyEdit::get_root_path);
 		ClassDB::bind_method("_dont_undo_redo", &AnimationTrackKeyEdit::_dont_undo_redo);
 		ClassDB::bind_method("_dont_undo_redo", &AnimationTrackKeyEdit::_dont_undo_redo);
+		ClassDB::bind_method("_read_only", &AnimationTrackKeyEdit::_read_only);
 	}
 	}
 
 
 	void _fix_node_path(Variant &value) {
 	void _fix_node_path(Variant &value) {
@@ -703,6 +709,7 @@ class AnimationMultiTrackKeyEdit : public Object {
 
 
 public:
 public:
 	bool setting = false;
 	bool setting = false;
+	bool animation_read_only = false;
 
 
 	bool _hide_script_from_inspector() {
 	bool _hide_script_from_inspector() {
 		return true;
 		return true;
@@ -712,12 +719,17 @@ public:
 		return true;
 		return true;
 	}
 	}
 
 
+	bool _read_only() {
+		return animation_read_only;
+	}
+
 	static void _bind_methods() {
 	static void _bind_methods() {
 		ClassDB::bind_method("_update_obj", &AnimationMultiTrackKeyEdit::_update_obj);
 		ClassDB::bind_method("_update_obj", &AnimationMultiTrackKeyEdit::_update_obj);
 		ClassDB::bind_method("_key_ofs_changed", &AnimationMultiTrackKeyEdit::_key_ofs_changed);
 		ClassDB::bind_method("_key_ofs_changed", &AnimationMultiTrackKeyEdit::_key_ofs_changed);
 		ClassDB::bind_method("_hide_script_from_inspector", &AnimationMultiTrackKeyEdit::_hide_script_from_inspector);
 		ClassDB::bind_method("_hide_script_from_inspector", &AnimationMultiTrackKeyEdit::_hide_script_from_inspector);
 		ClassDB::bind_method("get_root_path", &AnimationMultiTrackKeyEdit::get_root_path);
 		ClassDB::bind_method("get_root_path", &AnimationMultiTrackKeyEdit::get_root_path);
 		ClassDB::bind_method("_dont_undo_redo", &AnimationMultiTrackKeyEdit::_dont_undo_redo);
 		ClassDB::bind_method("_dont_undo_redo", &AnimationMultiTrackKeyEdit::_dont_undo_redo);
+		ClassDB::bind_method("_read_only", &AnimationMultiTrackKeyEdit::_read_only);
 	}
 	}
 
 
 	void _fix_node_path(Variant &value, NodePath &base) {
 	void _fix_node_path(Variant &value, NodePath &base) {
@@ -1416,22 +1428,32 @@ void AnimationTimelineEdit::_anim_length_changed(double p_new_len) {
 }
 }
 
 
 void AnimationTimelineEdit::_anim_loop_pressed() {
 void AnimationTimelineEdit::_anim_loop_pressed() {
-	undo_redo->create_action(TTR("Change Animation Loop"));
-	switch (animation->get_loop_mode()) {
-		case Animation::LOOP_NONE: {
-			undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_LINEAR);
-		} break;
-		case Animation::LOOP_LINEAR: {
-			undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_PINGPONG);
-		} break;
-		case Animation::LOOP_PINGPONG: {
-			undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_NONE);
-		} break;
-		default:
-			break;
+	if (!read_only) {
+		undo_redo->create_action(TTR("Change Animation Loop"));
+		switch (animation->get_loop_mode()) {
+			case Animation::LOOP_NONE: {
+				undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_LINEAR);
+			} break;
+			case Animation::LOOP_LINEAR: {
+				undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_PINGPONG);
+			} break;
+			case Animation::LOOP_PINGPONG: {
+				undo_redo->add_do_method(animation.ptr(), "set_loop_mode", Animation::LOOP_NONE);
+			} break;
+			default:
+				break;
+		}
+		undo_redo->add_undo_method(animation.ptr(), "set_loop_mode", animation->get_loop_mode());
+		undo_redo->commit_action();
+	} else {
+		String base_path = animation->get_path();
+		if (FileAccess::exists(base_path + ".import")) {
+			EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation instanced from imported scene."));
+		} else {
+			EditorNode::get_singleton()->show_warning(TTR("Can't change loop mode on animation embedded in another scene."));
+		}
+		update_values();
 	}
 	}
-	undo_redo->add_undo_method(animation.ptr(), "set_loop_mode", animation->get_loop_mode());
-	undo_redo->commit_action();
 }
 }
 
 
 int AnimationTimelineEdit::get_buttons_width() const {
 int AnimationTimelineEdit::get_buttons_width() const {
@@ -1656,11 +1678,17 @@ void AnimationTimelineEdit::_notification(int p_what) {
 	}
 	}
 }
 }
 
 
-void AnimationTimelineEdit::set_animation(const Ref<Animation> &p_animation) {
+void AnimationTimelineEdit::set_animation(const Ref<Animation> &p_animation, bool p_read_only) {
 	animation = p_animation;
 	animation = p_animation;
+	read_only = p_read_only;
+
 	if (animation.is_valid()) {
 	if (animation.is_valid()) {
 		len_hb->show();
 		len_hb->show();
-		add_track->show();
+		if (read_only) {
+			add_track->hide();
+		} else {
+			add_track->show();
+		}
 		play_position->show();
 		play_position->show();
 	} else {
 	} else {
 		len_hb->hide();
 		len_hb->hide();
@@ -1982,6 +2010,8 @@ void AnimationTrackEdit::_notification(int p_what) {
 			Color linecolor = color;
 			Color linecolor = color;
 			linecolor.a = 0.2;
 			linecolor.a = 0.2;
 
 
+			Color dc = get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"));
+
 			// NAMES AND ICONS //
 			// NAMES AND ICONS //
 
 
 			{
 			{
@@ -2131,14 +2161,18 @@ void AnimationTrackEdit::_notification(int p_what) {
 					ofs += update_icon->get_width() + hsep / 2;
 					ofs += update_icon->get_width() + hsep / 2;
 					update_mode_rect.size.x += hsep / 2;
 					update_mode_rect.size.x += hsep / 2;
 
 
-					if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
-						draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
-						update_mode_rect.size.x += down_icon->get_width();
-					} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
-						Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
-						update_mode_rect.size.x += down_icon->get_width();
+					if (!read_only) {
+						if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
+							draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
+							update_mode_rect.size.x += down_icon->get_width();
+						} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
+							Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
+							update_mode_rect.size.x += down_icon->get_width();
 
 
-						update_mode_rect = Rect2();
+							update_mode_rect = Rect2();
+						} else {
+							update_mode_rect = Rect2();
+						}
 					} else {
 					} else {
 						update_mode_rect = Rect2();
 						update_mode_rect = Rect2();
 					}
 					}
@@ -2169,7 +2203,7 @@ void AnimationTrackEdit::_notification(int p_what) {
 					ofs += icon->get_width() + hsep / 2;
 					ofs += icon->get_width() + hsep / 2;
 					interp_mode_rect.size.x += hsep / 2;
 					interp_mode_rect.size.x += hsep / 2;
 
 
-					if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
+					if (!read_only && !animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
 						draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
 						draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
 						interp_mode_rect.size.x += down_icon->get_width();
 						interp_mode_rect.size.x += down_icon->get_width();
 					} else {
 					} else {
@@ -2202,7 +2236,7 @@ void AnimationTrackEdit::_notification(int p_what) {
 					ofs += icon->get_width() + hsep / 2;
 					ofs += icon->get_width() + hsep / 2;
 					loop_wrap_rect.size.x += hsep / 2;
 					loop_wrap_rect.size.x += hsep / 2;
 
 
-					if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
+					if (!read_only && !animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) {
 						draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
 						draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
 						loop_wrap_rect.size.x += down_icon->get_width();
 						loop_wrap_rect.size.x += down_icon->get_width();
 					} else {
 					} else {
@@ -2223,7 +2257,11 @@ void AnimationTrackEdit::_notification(int p_what) {
 					remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;
 					remove_rect.position.y = int(get_size().height - icon->get_height()) / 2;
 					remove_rect.size = icon->get_size();
 					remove_rect.size = icon->get_size();
 
 
-					draw_texture(icon, remove_rect.position);
+					if (read_only) {
+						draw_texture(icon, remove_rect.position, dc);
+					} else {
+						draw_texture(icon, remove_rect.position);
+					}
 				}
 				}
 			}
 			}
 
 
@@ -2439,8 +2477,10 @@ Ref<Animation> AnimationTrackEdit::get_animation() const {
 	return animation;
 	return animation;
 }
 }
 
 
-void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) {
+void AnimationTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only) {
 	animation = p_animation;
 	animation = p_animation;
+	read_only = p_read_only;
+
 	track = p_track;
 	track = p_track;
 	update();
 	update();
 
 
@@ -2721,17 +2761,23 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 
 	if (p_event->is_pressed()) {
 	if (p_event->is_pressed()) {
 		if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
 		if (ED_GET_SHORTCUT("animation_editor/duplicate_selection")->matches_event(p_event)) {
-			emit_signal(SNAME("duplicate_request"));
+			if (!read_only) {
+				emit_signal(SNAME("duplicate_request"));
+			}
 			accept_event();
 			accept_event();
 		}
 		}
 
 
 		if (ED_GET_SHORTCUT("animation_editor/duplicate_selection_transposed")->matches_event(p_event)) {
 		if (ED_GET_SHORTCUT("animation_editor/duplicate_selection_transposed")->matches_event(p_event)) {
-			emit_signal(SNAME("duplicate_transpose_request"));
+			if (!read_only) {
+				emit_signal(SNAME("duplicate_transpose_request"));
+			}
 			accept_event();
 			accept_event();
 		}
 		}
 
 
 		if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) {
 		if (ED_GET_SHORTCUT("animation_editor/delete_selection")->matches_event(p_event)) {
-			emit_signal(SNAME("delete_request"));
+			if (!read_only) {
+				emit_signal(SNAME("delete_request"));
+			}
 			accept_event();
 			accept_event();
 		}
 		}
 	}
 	}
@@ -2740,79 +2786,81 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 	if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
 	if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
 		Point2 pos = mb->get_position();
 		Point2 pos = mb->get_position();
 
 
-		if (check_rect.has_point(pos)) {
-			undo_redo->create_action(TTR("Toggle Track Enabled"));
-			undo_redo->add_do_method(animation.ptr(), "track_set_enabled", track, !animation->track_is_enabled(track));
-			undo_redo->add_undo_method(animation.ptr(), "track_set_enabled", track, animation->track_is_enabled(track));
-			undo_redo->commit_action();
-			update();
-			accept_event();
-		}
+		if (!read_only) {
+			if (check_rect.has_point(pos)) {
+				undo_redo->create_action(TTR("Toggle Track Enabled"));
+				undo_redo->add_do_method(animation.ptr(), "track_set_enabled", track, !animation->track_is_enabled(track));
+				undo_redo->add_undo_method(animation.ptr(), "track_set_enabled", track, animation->track_is_enabled(track));
+				undo_redo->commit_action();
+				update();
+				accept_event();
+			}
 
 
-		// Don't overlap track keys if they start at 0.
-		if (path_rect.has_point(pos + Size2(type_icon->get_width(), 0))) {
-			clicking_on_name = true;
-			accept_event();
-		}
+			// Don't overlap track keys if they start at 0.
+			if (path_rect.has_point(pos + Size2(type_icon->get_width(), 0))) {
+				clicking_on_name = true;
+				accept_event();
+			}
 
 
-		if (update_mode_rect.has_point(pos)) {
-			if (!menu) {
-				menu = memnew(PopupMenu);
-				add_child(menu);
-				menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+			if (update_mode_rect.has_point(pos)) {
+				if (!menu) {
+					menu = memnew(PopupMenu);
+					add_child(menu);
+					menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+				}
+				menu->clear();
+				menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
+				menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
+				menu->add_icon_item(get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), TTR("Trigger"), MENU_CALL_MODE_TRIGGER);
+				menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+				menu->reset_size();
+
+				Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
+				menu->set_position(popup_pos);
+				menu->popup();
+				accept_event();
 			}
 			}
-			menu->clear();
-			menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
-			menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
-			menu->add_icon_item(get_theme_icon(SNAME("TrackTrigger"), SNAME("EditorIcons")), TTR("Trigger"), MENU_CALL_MODE_TRIGGER);
-			menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
-			menu->reset_size();
-
-			Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
-			menu->set_position(popup_pos);
-			menu->popup();
-			accept_event();
-		}
 
 
-		if (interp_mode_rect.has_point(pos)) {
-			if (!menu) {
-				menu = memnew(PopupMenu);
-				add_child(menu);
-				menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+			if (interp_mode_rect.has_point(pos)) {
+				if (!menu) {
+					menu = memnew(PopupMenu);
+					add_child(menu);
+					menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+				}
+				menu->clear();
+				menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
+				menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
+				menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
+				menu->reset_size();
+
+				Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height);
+				menu->set_position(popup_pos);
+				menu->popup();
+				accept_event();
 			}
 			}
-			menu->clear();
-			menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
-			menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
-			menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
-			menu->reset_size();
-
-			Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height);
-			menu->set_position(popup_pos);
-			menu->popup();
-			accept_event();
-		}
 
 
-		if (loop_wrap_rect.has_point(pos)) {
-			if (!menu) {
-				menu = memnew(PopupMenu);
-				add_child(menu);
-				menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+			if (loop_wrap_rect.has_point(pos)) {
+				if (!menu) {
+					menu = memnew(PopupMenu);
+					add_child(menu);
+					menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+				}
+				menu->clear();
+				menu->add_icon_item(get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP);
+				menu->add_icon_item(get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP);
+				menu->reset_size();
+
+				Vector2 popup_pos = get_screen_position() + loop_wrap_rect.position + Vector2(0, loop_wrap_rect.size.height);
+				menu->set_position(popup_pos);
+				menu->popup();
+				accept_event();
 			}
 			}
-			menu->clear();
-			menu->add_icon_item(get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")), TTR("Clamp Loop Interp"), MENU_LOOP_CLAMP);
-			menu->add_icon_item(get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")), TTR("Wrap Loop Interp"), MENU_LOOP_WRAP);
-			menu->reset_size();
-
-			Vector2 popup_pos = get_screen_position() + loop_wrap_rect.position + Vector2(0, loop_wrap_rect.size.height);
-			menu->set_position(popup_pos);
-			menu->popup();
-			accept_event();
-		}
 
 
-		if (remove_rect.has_point(pos)) {
-			emit_signal(SNAME("remove_request"), track);
-			accept_event();
-			return;
+			if (remove_rect.has_point(pos)) {
+				emit_signal(SNAME("remove_request"), track);
+				accept_event();
+				return;
+			}
 		}
 		}
 
 
 		// Check keyframes.
 		// Check keyframes.
@@ -2872,6 +2920,11 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 						moving_selection_attempt = true;
 						moving_selection_attempt = true;
 						moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
 						moving_selection_from_ofs = (mb->get_position().x - limit) / timeline->get_zoom_scale();
 					}
 					}
+
+					if (read_only) {
+						moving_selection_attempt = false;
+						moving_selection_from_ofs = 0.0f;
+					}
 					accept_event();
 					accept_event();
 				}
 				}
 			}
 			}
@@ -2883,33 +2936,35 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 		if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
 		if (pos.x >= timeline->get_name_limit() && pos.x <= get_size().width - timeline->get_buttons_width()) {
 			// Can do something with menu too! show insert key.
 			// Can do something with menu too! show insert key.
 			float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
 			float offset = (pos.x - timeline->get_name_limit()) / timeline->get_zoom_scale();
-			if (!menu) {
-				menu = memnew(PopupMenu);
-				add_child(menu);
-				menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
-			}
+			if (!read_only) {
+				if (!menu) {
+					menu = memnew(PopupMenu);
+					add_child(menu);
+					menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
+				}
 
 
-			menu->clear();
-			menu->add_icon_item(get_theme_icon(SNAME("Key"), SNAME("EditorIcons")), TTR("Insert Key"), MENU_KEY_INSERT);
-			if (editor->is_selection_active()) {
-				menu->add_separator();
-				menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE);
+				menu->clear();
+				menu->add_icon_item(get_theme_icon(SNAME("Key"), SNAME("EditorIcons")), TTR("Insert Key"), MENU_KEY_INSERT);
+				if (editor->is_selection_active()) {
+					menu->add_separator();
+					menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Key(s)"), MENU_KEY_DUPLICATE);
 
 
-				AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
-				if (!player->has_animation(SceneStringNames::get_singleton()->RESET) || animation != player->get_animation(SceneStringNames::get_singleton()->RESET)) {
-					menu->add_icon_item(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")), TTR("Add RESET Value(s)"), MENU_KEY_ADD_RESET);
-				}
+					AnimationPlayer *player = AnimationPlayerEditor::get_singleton()->get_player();
+					if (!player->has_animation(SceneStringNames::get_singleton()->RESET) || animation != player->get_animation(SceneStringNames::get_singleton()->RESET)) {
+						menu->add_icon_item(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons")), TTR("Add RESET Value(s)"), MENU_KEY_ADD_RESET);
+					}
 
 
-				menu->add_separator();
-				menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Delete Key(s)"), MENU_KEY_DELETE);
-			}
-			menu->reset_size();
+					menu->add_separator();
+					menu->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Delete Key(s)"), MENU_KEY_DELETE);
+				}
+				menu->reset_size();
 
 
-			menu->set_position(get_screen_position() + get_local_mouse_position());
-			menu->popup();
+				menu->set_position(get_screen_position() + get_local_mouse_position());
+				menu->popup();
 
 
-			insert_at_pos = offset + timeline->get_value();
-			accept_event();
+				insert_at_pos = offset + timeline->get_value();
+				accept_event();
+			}
 		}
 		}
 	}
 	}
 
 
@@ -3354,7 +3409,7 @@ void AnimationTrackEditor::remove_track_edit_plugin(const Ref<AnimationTrackEdit
 	track_edit_plugins.erase(p_plugin);
 	track_edit_plugins.erase(p_plugin);
 }
 }
 
 
-void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {
+void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_read_only) {
 	if (animation != p_anim && _get_track_selected() >= 0) {
 	if (animation != p_anim && _get_track_selected() >= 0) {
 		track_edits[_get_track_selected()]->release_focus();
 		track_edits[_get_track_selected()]->release_focus();
 	}
 	}
@@ -3363,7 +3418,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {
 		_clear_selection();
 		_clear_selection();
 	}
 	}
 	animation = p_anim;
 	animation = p_anim;
-	timeline->set_animation(p_anim);
+	read_only = p_read_only;
+	timeline->set_animation(p_anim, read_only);
 
 
 	_cancel_bezier_edit();
 	_cancel_bezier_edit();
 	_update_tracks();
 	_update_tracks();
@@ -3372,7 +3428,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) {
 		animation->connect("changed", callable_mp(this, &AnimationTrackEditor::_animation_changed));
 		animation->connect("changed", callable_mp(this, &AnimationTrackEditor::_animation_changed));
 
 
 		hscroll->show();
 		hscroll->show();
-		edit->set_disabled(false);
+		edit->set_disabled(read_only);
 		step->set_block_signals(true);
 		step->set_block_signals(true);
 
 
 		_update_step_spinbox();
 		_update_step_spinbox();
@@ -3501,7 +3557,7 @@ void AnimationTrackEditor::set_state(const Dictionary &p_state) {
 }
 }
 
 
 void AnimationTrackEditor::cleanup() {
 void AnimationTrackEditor::cleanup() {
-	set_animation(Ref<Animation>());
+	set_animation(Ref<Animation>(), read_only);
 }
 }
 
 
 void AnimationTrackEditor::_name_limit_changed() {
 void AnimationTrackEditor::_name_limit_changed() {
@@ -4378,6 +4434,27 @@ void AnimationTrackEditor::_update_tracks() {
 		return;
 		return;
 	}
 	}
 
 
+	bool read_only = false;
+	if (!animation->get_path().is_resource_file()) {
+		int srpos = animation->get_path().find("::");
+		if (srpos != -1) {
+			String base = animation->get_path().substr(0, srpos);
+			if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+				if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+					read_only = true;
+				}
+			} else {
+				if (FileAccess::exists(base + ".import")) {
+					read_only = true;
+				}
+			}
+		}
+	} else {
+		if (FileAccess::exists(animation->get_path() + ".import")) {
+			read_only = true;
+		}
+	}
+
 	RBMap<String, VBoxContainer *> group_sort;
 	RBMap<String, VBoxContainer *> group_sort;
 
 
 	bool use_grouping = !view_group->is_pressed();
 	bool use_grouping = !view_group->is_pressed();
@@ -4506,7 +4583,7 @@ void AnimationTrackEditor::_update_tracks() {
 		track_edit->set_undo_redo(undo_redo);
 		track_edit->set_undo_redo(undo_redo);
 		track_edit->set_timeline(timeline);
 		track_edit->set_timeline(timeline);
 		track_edit->set_root(root);
 		track_edit->set_root(root);
-		track_edit->set_animation_and_track(animation, i);
+		track_edit->set_animation_and_track(animation, i, read_only);
 		track_edit->set_play_position(timeline->get_play_position());
 		track_edit->set_play_position(timeline->get_play_position());
 		track_edit->set_editor(this);
 		track_edit->set_editor(this);
 
 
@@ -5178,6 +5255,7 @@ void AnimationTrackEditor::_update_key_edit() {
 	if (selection.size() == 1) {
 	if (selection.size() == 1) {
 		key_edit = memnew(AnimationTrackKeyEdit);
 		key_edit = memnew(AnimationTrackKeyEdit);
 		key_edit->animation = animation;
 		key_edit->animation = animation;
+		key_edit->animation_read_only = read_only;
 		key_edit->track = selection.front()->key().track;
 		key_edit->track = selection.front()->key().track;
 		key_edit->use_fps = timeline->is_using_fps();
 		key_edit->use_fps = timeline->is_using_fps();
 
 
@@ -5194,6 +5272,7 @@ void AnimationTrackEditor::_update_key_edit() {
 	} else if (selection.size() > 1) {
 	} else if (selection.size() > 1) {
 		multi_key_edit = memnew(AnimationMultiTrackKeyEdit);
 		multi_key_edit = memnew(AnimationMultiTrackKeyEdit);
 		multi_key_edit->animation = animation;
 		multi_key_edit->animation = animation;
+		multi_key_edit->animation_read_only = read_only;
 
 
 		RBMap<int, List<float>> key_ofs_map;
 		RBMap<int, List<float>> key_ofs_map;
 		RBMap<int, NodePath> base_map;
 		RBMap<int, NodePath> base_map;
@@ -5473,7 +5552,7 @@ void AnimationTrackEditor::_cancel_bezier_edit() {
 void AnimationTrackEditor::_bezier_edit(int p_for_track) {
 void AnimationTrackEditor::_bezier_edit(int p_for_track) {
 	_clear_selection(); // Bezier probably wants to use a separate selection mode.
 	_clear_selection(); // Bezier probably wants to use a separate selection mode.
 	bezier_edit->set_root(root);
 	bezier_edit->set_root(root);
-	bezier_edit->set_animation_and_track(animation, p_for_track);
+	bezier_edit->set_animation_and_track(animation, p_for_track, read_only);
 	scroll->hide();
 	scroll->hide();
 	bezier_edit->show();
 	bezier_edit->show();
 	// Search everything within the track and curve - edit it.
 	// Search everything within the track and curve - edit it.

+ 7 - 3
editor/animation_track_editor.h

@@ -56,6 +56,8 @@ class AnimationTimelineEdit : public Range {
 	GDCLASS(AnimationTimelineEdit, Range);
 	GDCLASS(AnimationTimelineEdit, Range);
 
 
 	Ref<Animation> animation;
 	Ref<Animation> animation;
+	bool read_only = false;
+
 	AnimationTrackEdit *track_edit = nullptr;
 	AnimationTrackEdit *track_edit = nullptr;
 	int name_limit = 0;
 	int name_limit = 0;
 	Range *zoom = nullptr;
 	Range *zoom = nullptr;
@@ -106,7 +108,7 @@ public:
 	float get_zoom_scale() const;
 	float get_zoom_scale() const;
 
 
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;
-	void set_animation(const Ref<Animation> &p_animation);
+	void set_animation(const Ref<Animation> &p_animation, bool p_read_only);
 	void set_track_edit(AnimationTrackEdit *p_track_edit);
 	void set_track_edit(AnimationTrackEdit *p_track_edit);
 	void set_zoom(Range *p_zoom);
 	void set_zoom(Range *p_zoom);
 	Range *get_zoom() const { return zoom; }
 	Range *get_zoom() const { return zoom; }
@@ -159,6 +161,7 @@ class AnimationTrackEdit : public Control {
 	NodePath node_path;
 	NodePath node_path;
 
 
 	Ref<Animation> animation;
 	Ref<Animation> animation;
+	bool read_only = false;
 	int track = 0;
 	int track = 0;
 
 
 	Rect2 check_rect;
 	Rect2 check_rect;
@@ -232,7 +235,7 @@ public:
 	AnimationTrackEditor *get_editor() const { return editor; }
 	AnimationTrackEditor *get_editor() const { return editor; }
 	UndoRedo *get_undo_redo() const { return undo_redo; }
 	UndoRedo *get_undo_redo() const { return undo_redo; }
 	NodePath get_path() const;
 	NodePath get_path() const;
-	void set_animation_and_track(const Ref<Animation> &p_animation, int p_track);
+	void set_animation_and_track(const Ref<Animation> &p_animation, int p_track, bool p_read_only);
 	virtual Size2 get_minimum_size() const override;
 	virtual Size2 get_minimum_size() const override;
 
 
 	void set_undo_redo(UndoRedo *p_undo_redo);
 	void set_undo_redo(UndoRedo *p_undo_redo);
@@ -290,6 +293,7 @@ class AnimationTrackEditor : public VBoxContainer {
 	GDCLASS(AnimationTrackEditor, VBoxContainer);
 	GDCLASS(AnimationTrackEditor, VBoxContainer);
 
 
 	Ref<Animation> animation;
 	Ref<Animation> animation;
+	bool read_only = false;
 	Node *root = nullptr;
 	Node *root = nullptr;
 
 
 	MenuButton *edit = nullptr;
 	MenuButton *edit = nullptr;
@@ -533,7 +537,7 @@ public:
 	void add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
 	void add_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
 	void remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
 	void remove_track_edit_plugin(const Ref<AnimationTrackEditPlugin> &p_plugin);
 
 
-	void set_animation(const Ref<Animation> &p_anim);
+	void set_animation(const Ref<Animation> &p_anim, bool p_read_only);
 	Ref<Animation> get_current_animation() const;
 	Ref<Animation> get_current_animation() const;
 	void set_root(Node *p_root);
 	void set_root(Node *p_root);
 	Node *get_root() const;
 	Node *get_root() const;

+ 116 - 21
editor/plugins/animation_library_editor.cpp

@@ -149,13 +149,35 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) {
 	}
 	}
 	switch (p_id) {
 	switch (p_id) {
 		case FILE_MENU_SAVE_LIBRARY: {
 		case FILE_MENU_SAVE_LIBRARY: {
-			if (al->get_path().is_resource_file()) {
+			if (al->get_path().is_resource_file() && !FileAccess::exists(al->get_path() + ".import")) {
 				EditorNode::get_singleton()->save_resource(al);
 				EditorNode::get_singleton()->save_resource(al);
 				break;
 				break;
 			}
 			}
 			[[fallthrough]];
 			[[fallthrough]];
 		}
 		}
 		case FILE_MENU_SAVE_AS_LIBRARY: {
 		case FILE_MENU_SAVE_AS_LIBRARY: {
+			// Check if we're allowed to save this
+			{
+				String al_path = al->get_path();
+				if (!al_path.is_resource_file()) {
+					int srpos = al_path.find("::");
+					if (srpos != -1) {
+						String base = al_path.substr(0, srpos);
+						if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+							error_dialog->set_text(TTR("This animation library can't be saved because it does not belong to the edited scene. Make it unique first."));
+							error_dialog->popup_centered();
+							return;
+						}
+					}
+				} else {
+					if (FileAccess::exists(al_path + ".import")) {
+						error_dialog->set_text(TTR("This animation library can't be saved because it was imported from another file. Make it unique first."));
+						error_dialog->popup_centered();
+						return;
+					}
+				}
+			}
+
 			file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
 			file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
 			file_dialog->set_title(TTR("Save Library"));
 			file_dialog->set_title(TTR("Save Library"));
 			if (al->get_path().is_resource_file()) {
 			if (al->get_path().is_resource_file()) {
@@ -178,6 +200,9 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) {
 
 
 			Ref<AnimationLibrary> ald = al->duplicate();
 			Ref<AnimationLibrary> ald = al->duplicate();
 
 
+			// TODO: should probably make all foreign animations assigned to this library
+			// unique too.
+
 			UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
 			UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo();
 			undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name));
 			undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name));
 			undo_redo->add_do_method(player, "remove_animation_library", lib_name);
 			undo_redo->add_do_method(player, "remove_animation_library", lib_name);
@@ -188,19 +213,43 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) {
 			undo_redo->add_undo_method(this, "_update_editor", player);
 			undo_redo->add_undo_method(this, "_update_editor", player);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 
 
+			update_tree();
+
 		} break;
 		} break;
 		case FILE_MENU_EDIT_LIBRARY: {
 		case FILE_MENU_EDIT_LIBRARY: {
 			EditorNode::get_singleton()->push_item(al.ptr());
 			EditorNode::get_singleton()->push_item(al.ptr());
 		} break;
 		} break;
 
 
 		case FILE_MENU_SAVE_ANIMATION: {
 		case FILE_MENU_SAVE_ANIMATION: {
-			if (anim->get_path().is_resource_file()) {
+			if (anim->get_path().is_resource_file() && !FileAccess::exists(anim->get_path() + ".import")) {
 				EditorNode::get_singleton()->save_resource(anim);
 				EditorNode::get_singleton()->save_resource(anim);
 				break;
 				break;
 			}
 			}
 			[[fallthrough]];
 			[[fallthrough]];
 		}
 		}
 		case FILE_MENU_SAVE_AS_ANIMATION: {
 		case FILE_MENU_SAVE_AS_ANIMATION: {
+			// Check if we're allowed to save this
+			{
+				String anim_path = al->get_path();
+				if (!anim_path.is_resource_file()) {
+					int srpos = anim_path.find("::");
+					if (srpos != -1) {
+						String base = anim_path.substr(0, srpos);
+						if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+							error_dialog->set_text(TTR("This animation can't be saved because it does not belong to the edited scene. Make it unique first."));
+							error_dialog->popup_centered();
+							return;
+						}
+					}
+				} else {
+					if (FileAccess::exists(anim_path + ".import")) {
+						error_dialog->set_text(TTR("This animation can't be saved because it was imported from another file. Make it unique first."));
+						error_dialog->popup_centered();
+						return;
+					}
+				}
+			}
+
 			file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
 			file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
 			file_dialog->set_title(TTR("Save Animation"));
 			file_dialog->set_title(TTR("Save Animation"));
 			if (anim->get_path().is_resource_file()) {
 			if (anim->get_path().is_resource_file()) {
@@ -232,6 +281,8 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) {
 			undo_redo->add_do_method(this, "_update_editor", player);
 			undo_redo->add_do_method(this, "_update_editor", player);
 			undo_redo->add_undo_method(this, "_update_editor", player);
 			undo_redo->add_undo_method(this, "_update_editor", player);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
+
+			update_tree();
 		} break;
 		} break;
 		case FILE_MENU_EDIT_ANIMATION: {
 		case FILE_MENU_EDIT_ANIMATION: {
 			EditorNode::get_singleton()->push_item(anim.ptr());
 			EditorNode::get_singleton()->push_item(anim.ptr());
@@ -577,19 +628,45 @@ void AnimationLibraryEditor::update_tree() {
 		} else {
 		} else {
 			libitem->set_suffix(0, "");
 			libitem->set_suffix(0, "");
 		}
 		}
-		libitem->set_editable(0, true);
-		libitem->set_metadata(0, K);
-		libitem->set_icon(0, get_theme_icon("AnimationLibrary", "EditorIcons"));
-		libitem->add_button(0, get_theme_icon("Add", "EditorIcons"), LIB_BUTTON_ADD, false, TTR("Add Animation to Library"));
-		libitem->add_button(0, get_theme_icon("Load", "EditorIcons"), LIB_BUTTON_LOAD, false, TTR("Load animation from file and add to library"));
-		libitem->add_button(0, get_theme_icon("ActionPaste", "EditorIcons"), LIB_BUTTON_PASTE, false, TTR("Paste Animation to Library from clipboard"));
+
 		Ref<AnimationLibrary> al = player->call("get_animation_library", K);
 		Ref<AnimationLibrary> al = player->call("get_animation_library", K);
-		if (al->get_path().is_resource_file()) {
-			libitem->set_text(1, al->get_path().get_file());
-			libitem->set_tooltip(1, al->get_path());
-		} else {
+		bool animation_library_is_foreign = false;
+		String al_path = al->get_path();
+		if (!al_path.is_resource_file()) {
 			libitem->set_text(1, TTR("[built-in]"));
 			libitem->set_text(1, TTR("[built-in]"));
+			libitem->set_tooltip(1, al_path);
+			int srpos = al_path.find("::");
+			if (srpos != -1) {
+				String base = al_path.substr(0, srpos);
+				if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+					if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+						animation_library_is_foreign = true;
+						libitem->set_text(1, TTR("[foreign]"));
+					}
+				} else {
+					if (FileAccess::exists(base + ".import")) {
+						animation_library_is_foreign = true;
+						libitem->set_text(1, TTR("[imported]"));
+					}
+				}
+			}
+		} else {
+			if (FileAccess::exists(al_path + ".import")) {
+				animation_library_is_foreign = true;
+				libitem->set_text(1, TTR("[imported]"));
+			} else {
+				libitem->set_text(1, al_path.get_file());
+			}
 		}
 		}
+
+		libitem->set_editable(0, !animation_library_is_foreign);
+		libitem->set_metadata(0, K);
+		libitem->set_icon(0, get_theme_icon("AnimationLibrary", "EditorIcons"));
+
+		libitem->add_button(0, get_theme_icon("Add", "EditorIcons"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add Animation to Library"));
+		libitem->add_button(0, get_theme_icon("Load", "EditorIcons"), LIB_BUTTON_LOAD, animation_library_is_foreign, TTR("Load animation from file and add to library"));
+		libitem->add_button(0, get_theme_icon("ActionPaste", "EditorIcons"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste Animation to Library from clipboard"));
+
 		libitem->add_button(1, get_theme_icon("Save", "EditorIcons"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk"));
 		libitem->add_button(1, get_theme_icon("Save", "EditorIcons"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk"));
 		libitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), LIB_BUTTON_DELETE, false, TTR("Remove animation library"));
 		libitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), LIB_BUTTON_DELETE, false, TTR("Remove animation library"));
 
 
@@ -600,20 +677,38 @@ void AnimationLibraryEditor::update_tree() {
 		for (const StringName &L : animations) {
 		for (const StringName &L : animations) {
 			TreeItem *anitem = tree->create_item(libitem);
 			TreeItem *anitem = tree->create_item(libitem);
 			anitem->set_text(0, L);
 			anitem->set_text(0, L);
-			anitem->set_editable(0, true);
+			anitem->set_editable(0, !animation_library_is_foreign);
 			anitem->set_metadata(0, L);
 			anitem->set_metadata(0, L);
 			anitem->set_icon(0, get_theme_icon("Animation", "EditorIcons"));
 			anitem->set_icon(0, get_theme_icon("Animation", "EditorIcons"));
-			anitem->add_button(0, get_theme_icon("ActionCopy", "EditorIcons"), ANIM_BUTTON_COPY, false, TTR("Copy animation to clipboard"));
-			Ref<Animation> anim = al->get_animation(L);
+			anitem->add_button(0, get_theme_icon("ActionCopy", "EditorIcons"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard"));
 
 
-			if (anim->get_path().is_resource_file()) {
-				anitem->set_text(1, anim->get_path().get_file());
-				anitem->set_tooltip(1, anim->get_path());
-			} else {
+			Ref<Animation> anim = al->get_animation(L);
+			String anim_path = anim->get_path();
+			if (!anim_path.is_resource_file()) {
 				anitem->set_text(1, TTR("[built-in]"));
 				anitem->set_text(1, TTR("[built-in]"));
+				anitem->set_tooltip(1, anim_path);
+				int srpos = anim_path.find("::");
+				if (srpos != -1) {
+					String base = anim_path.substr(0, srpos);
+					if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+						if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+							anitem->set_text(1, TTR("[foreign]"));
+						}
+					} else {
+						if (FileAccess::exists(base + ".import")) {
+							anitem->set_text(1, TTR("[imported]"));
+						}
+					}
+				}
+			} else {
+				if (FileAccess::exists(anim_path + ".import")) {
+					anitem->set_text(1, TTR("[imported]"));
+				} else {
+					anitem->set_text(1, anim_path.get_file());
+				}
 			}
 			}
-			anitem->add_button(1, get_theme_icon("Save", "EditorIcons"), ANIM_BUTTON_FILE, false, TTR("Save animation to resource on disk"));
-			anitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), ANIM_BUTTON_DELETE, false, TTR("Remove animation from Library"));
+			anitem->add_button(1, get_theme_icon("Save", "EditorIcons"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk"));
+			anitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library"));
 		}
 		}
 	}
 	}
 }
 }

+ 102 - 7
editor/plugins/animation_player_editor_plugin.cpp

@@ -55,7 +55,7 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) {
 
 
 		set_process(false);
 		set_process(false);
 
 
-		track_editor->set_animation(Ref<Animation>());
+		track_editor->set_animation(Ref<Animation>(), true);
 		track_editor->set_root(nullptr);
 		track_editor->set_root(nullptr);
 		track_editor->show_select_node_warning(true);
 		track_editor->show_select_node_warning(true);
 		_update_player();
 		_update_player();
@@ -283,7 +283,28 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
 
 
 		Ref<Animation> anim = player->get_animation(current);
 		Ref<Animation> anim = player->get_animation(current);
 		{
 		{
-			track_editor->set_animation(anim);
+			bool animation_library_is_foreign = false;
+			if (!anim->get_path().is_resource_file()) {
+				int srpos = anim->get_path().find("::");
+				if (srpos != -1) {
+					String base = anim->get_path().substr(0, srpos);
+					if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+						if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+							animation_library_is_foreign = true;
+						}
+					} else {
+						if (FileAccess::exists(base + ".import")) {
+							animation_library_is_foreign = true;
+						}
+					}
+				}
+			} else {
+				if (FileAccess::exists(anim->get_path() + ".import")) {
+					animation_library_is_foreign = true;
+				}
+			}
+
+			track_editor->set_animation(anim, animation_library_is_foreign);
 			Node *root = player->get_node(player->get_root());
 			Node *root = player->get_node(player->get_root());
 			if (root) {
 			if (root) {
 				track_editor->set_root(root);
 				track_editor->set_root(root);
@@ -292,7 +313,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
 		frame->set_max((double)anim->get_length());
 		frame->set_max((double)anim->get_length());
 
 
 	} else {
 	} else {
-		track_editor->set_animation(Ref<Animation>());
+		track_editor->set_animation(Ref<Animation>(), true);
 		track_editor->set_root(nullptr);
 		track_editor->set_root(nullptr);
 	}
 	}
 
 
@@ -751,14 +772,36 @@ void AnimationPlayerEditor::_animation_edit() {
 	String current = _get_current();
 	String current = _get_current();
 	if (current != String()) {
 	if (current != String()) {
 		Ref<Animation> anim = player->get_animation(current);
 		Ref<Animation> anim = player->get_animation(current);
-		track_editor->set_animation(anim);
+
+		bool animation_library_is_foreign = false;
+		if (!anim->get_path().is_resource_file()) {
+			int srpos = anim->get_path().find("::");
+			if (srpos != -1) {
+				String base = anim->get_path().substr(0, srpos);
+				if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+					if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+						animation_library_is_foreign = true;
+					}
+				} else {
+					if (FileAccess::exists(base + ".import")) {
+						animation_library_is_foreign = true;
+					}
+				}
+			}
+		} else {
+			if (FileAccess::exists(anim->get_path() + ".import")) {
+				animation_library_is_foreign = true;
+			}
+		}
+
+		track_editor->set_animation(anim, animation_library_is_foreign);
 
 
 		Node *root = player->get_node(player->get_root());
 		Node *root = player->get_node(player->get_root());
 		if (root) {
 		if (root) {
 			track_editor->set_root(root);
 			track_editor->set_root(root);
 		}
 		}
 	} else {
 	} else {
-		track_editor->set_animation(Ref<Animation>());
+		track_editor->set_animation(Ref<Animation>(), true);
 		track_editor->set_root(nullptr);
 		track_editor->set_root(nullptr);
 	}
 	}
 }
 }
@@ -812,13 +855,37 @@ void AnimationPlayerEditor::_update_player() {
 
 
 	int active_idx = -1;
 	int active_idx = -1;
 	bool no_anims_found = true;
 	bool no_anims_found = true;
+	bool foreign_global_anim_lib = false;
 
 
 	for (const StringName &K : libraries) {
 	for (const StringName &K : libraries) {
 		if (K != StringName()) {
 		if (K != StringName()) {
 			animation->add_separator(K);
 			animation->add_separator(K);
 		}
 		}
 
 
+		// Check if the global library is foreign since we want to disable options for adding/remove/renaming animations if it is.
 		Ref<AnimationLibrary> library = player->get_animation_library(K);
 		Ref<AnimationLibrary> library = player->get_animation_library(K);
+		if (K == "") {
+			if (!library->get_path().is_resource_file()) {
+				int srpos = library->get_path().find("::");
+				if (srpos != -1) {
+					String base = library->get_path().substr(0, srpos);
+					if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+						if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+							foreign_global_anim_lib = true;
+						}
+					} else {
+						if (FileAccess::exists(base + ".import")) {
+							foreign_global_anim_lib = true;
+						}
+					}
+				}
+			} else {
+				if (FileAccess::exists(library->get_path() + ".import")) {
+					foreign_global_anim_lib = true;
+				}
+			}
+		}
+
 		List<StringName> animlist;
 		List<StringName> animlist;
 		library->get_animation_list(&animlist);
 		library->get_animation_list(&animlist);
 
 
@@ -835,7 +902,13 @@ void AnimationPlayerEditor::_update_player() {
 			no_anims_found = false;
 			no_anims_found = false;
 		}
 		}
 	}
 	}
-#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), no_anims_found)
+#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), foreign_global_anim_lib)
+
+	ITEM_CHECK_DISABLED(TOOL_NEW_ANIM);
+
+#undef ITEM_CHECK_DISABLED
+
+#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), no_anims_found || foreign_global_anim_lib)
 
 
 	ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM);
 	ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM);
 	ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM);
 	ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM);
@@ -877,7 +950,29 @@ void AnimationPlayerEditor::_update_player() {
 	if (!no_anims_found) {
 	if (!no_anims_found) {
 		String current = animation->get_item_text(animation->get_selected());
 		String current = animation->get_item_text(animation->get_selected());
 		Ref<Animation> anim = player->get_animation(current);
 		Ref<Animation> anim = player->get_animation(current);
-		track_editor->set_animation(anim);
+
+		bool animation_library_is_foreign = false;
+		if (!anim->get_path().is_resource_file()) {
+			int srpos = anim->get_path().find("::");
+			if (srpos != -1) {
+				String base = anim->get_path().substr(0, srpos);
+				if (ResourceLoader::get_resource_type(base) == "PackedScene") {
+					if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) {
+						animation_library_is_foreign = true;
+					}
+				} else {
+					if (FileAccess::exists(base + ".import")) {
+						animation_library_is_foreign = true;
+					}
+				}
+			}
+		} else {
+			if (FileAccess::exists(anim->get_path() + ".import")) {
+				animation_library_is_foreign = true;
+			}
+		}
+
+		track_editor->set_animation(anim, animation_library_is_foreign);
 		Node *root = player->get_node(player->get_root());
 		Node *root = player->get_node(player->get_root());
 		if (root) {
 		if (root) {
 			track_editor->set_root(root);
 			track_editor->set_root(root);