1 /*************************************************************************/
2 /*  root_motion_editor_plugin.cpp                                        */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 
31 #include "root_motion_editor_plugin.h"
32 #include "editor/editor_node.h"
33 #include "scene/main/viewport.h"
34 
_confirmed()35 void EditorPropertyRootMotion::_confirmed() {
36 
37 	TreeItem *ti = filters->get_selected();
38 	if (!ti)
39 		return;
40 
41 	NodePath path = ti->get_metadata(0);
42 	emit_changed(get_edited_property(), path);
43 	update_property();
44 	filter_dialog->hide(); //may come from activated
45 }
46 
_node_assign()47 void EditorPropertyRootMotion::_node_assign() {
48 
49 	NodePath current = get_edited_object()->get(get_edited_property());
50 
51 	AnimationTree *atree = Object::cast_to<AnimationTree>(get_edited_object());
52 	if (!atree->has_node(atree->get_animation_player())) {
53 		EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer"));
54 		return;
55 	}
56 	AnimationPlayer *player = Object::cast_to<AnimationPlayer>(atree->get_node(atree->get_animation_player()));
57 	if (!player) {
58 		EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid"));
59 		return;
60 	}
61 
62 	Node *base = player->get_node(player->get_root());
63 
64 	if (!base) {
65 		EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
66 		return;
67 	}
68 
69 	Set<String> paths;
70 	{
71 		List<StringName> animations;
72 		player->get_animation_list(&animations);
73 
74 		for (List<StringName>::Element *E = animations.front(); E; E = E->next()) {
75 
76 			Ref<Animation> anim = player->get_animation(E->get());
77 			for (int i = 0; i < anim->get_track_count(); i++) {
78 				paths.insert(anim->track_get_path(i));
79 			}
80 		}
81 	}
82 
83 	filters->clear();
84 	TreeItem *root = filters->create_item();
85 
86 	Map<String, TreeItem *> parenthood;
87 
88 	for (Set<String>::Element *E = paths.front(); E; E = E->next()) {
89 
90 		NodePath path = E->get();
91 		TreeItem *ti = NULL;
92 		String accum;
93 		for (int i = 0; i < path.get_name_count(); i++) {
94 			String name = path.get_name(i);
95 			if (accum != String()) {
96 				accum += "/";
97 			}
98 			accum += name;
99 			if (!parenthood.has(accum)) {
100 				if (ti) {
101 					ti = filters->create_item(ti);
102 				} else {
103 					ti = filters->create_item(root);
104 				}
105 				parenthood[accum] = ti;
106 				ti->set_text(0, name);
107 				ti->set_selectable(0, false);
108 				ti->set_editable(0, false);
109 
110 				if (base->has_node(accum)) {
111 					Node *node = base->get_node(accum);
112 					ti->set_icon(0, EditorNode::get_singleton()->get_object_icon(node, "Node"));
113 				}
114 
115 			} else {
116 				ti = parenthood[accum];
117 			}
118 		}
119 
120 		Node *node = NULL;
121 		if (base->has_node(accum)) {
122 			node = base->get_node(accum);
123 		}
124 		if (!node)
125 			continue; //no node, can't edit
126 
127 		if (path.get_subname_count()) {
128 
129 			String concat = path.get_concatenated_subnames();
130 
131 			Skeleton *skeleton = Object::cast_to<Skeleton>(node);
132 			if (skeleton && skeleton->find_bone(concat) != -1) {
133 				//path in skeleton
134 				const String &bone = concat;
135 				int idx = skeleton->find_bone(bone);
136 				List<String> bone_path;
137 				while (idx != -1) {
138 					bone_path.push_front(skeleton->get_bone_name(idx));
139 					idx = skeleton->get_bone_parent(idx);
140 				}
141 
142 				accum += ":";
143 				for (List<String>::Element *F = bone_path.front(); F; F = F->next()) {
144 					if (F != bone_path.front()) {
145 						accum += "/";
146 					}
147 
148 					accum += F->get();
149 					if (!parenthood.has(accum)) {
150 						ti = filters->create_item(ti);
151 						parenthood[accum] = ti;
152 						ti->set_text(0, F->get());
153 						ti->set_selectable(0, true);
154 						ti->set_editable(0, false);
155 						ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
156 						ti->set_metadata(0, accum);
157 					} else {
158 						ti = parenthood[accum];
159 					}
160 				}
161 
162 				ti->set_selectable(0, true);
163 				ti->set_text(0, concat);
164 				ti->set_icon(0, get_icon("BoneAttachment", "EditorIcons"));
165 				ti->set_metadata(0, path);
166 				if (path == current) {
167 					ti->select(0);
168 				}
169 
170 			} else {
171 				//just a property
172 				ti = filters->create_item(ti);
173 				ti->set_text(0, concat);
174 				ti->set_selectable(0, true);
175 				ti->set_metadata(0, path);
176 				if (path == current) {
177 					ti->select(0);
178 				}
179 			}
180 		} else {
181 			if (ti) {
182 				//just a node, likely call or animation track
183 				ti->set_selectable(0, true);
184 				ti->set_metadata(0, path);
185 				if (path == current) {
186 					ti->select(0);
187 				}
188 			}
189 		}
190 	}
191 
192 	filters->ensure_cursor_is_visible();
193 	filter_dialog->popup_centered_ratio();
194 }
195 
_node_clear()196 void EditorPropertyRootMotion::_node_clear() {
197 
198 	emit_changed(get_edited_property(), NodePath());
199 	update_property();
200 }
201 
update_property()202 void EditorPropertyRootMotion::update_property() {
203 
204 	NodePath p = get_edited_object()->get(get_edited_property());
205 
206 	assign->set_tooltip(p);
207 	if (p == NodePath()) {
208 		assign->set_icon(Ref<Texture>());
209 		assign->set_text(TTR("Assign..."));
210 		assign->set_flat(false);
211 		return;
212 	}
213 	assign->set_flat(true);
214 
215 	Node *base_node = NULL;
216 	if (base_hint != NodePath()) {
217 		if (get_tree()->get_root()->has_node(base_hint)) {
218 			base_node = get_tree()->get_root()->get_node(base_hint);
219 		}
220 	} else {
221 		base_node = Object::cast_to<Node>(get_edited_object());
222 	}
223 
224 	if (!base_node || !base_node->has_node(p)) {
225 		assign->set_icon(Ref<Texture>());
226 		assign->set_text(p);
227 		return;
228 	}
229 
230 	Node *target_node = base_node->get_node(p);
231 	ERR_FAIL_COND(!target_node);
232 
233 	assign->set_text(target_node->get_name());
234 	assign->set_icon(EditorNode::get_singleton()->get_object_icon(target_node, "Node"));
235 }
236 
setup(const NodePath & p_base_hint)237 void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) {
238 
239 	base_hint = p_base_hint;
240 }
241 
_notification(int p_what)242 void EditorPropertyRootMotion::_notification(int p_what) {
243 
244 	if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) {
245 		Ref<Texture> t = get_icon("Clear", "EditorIcons");
246 		clear->set_icon(t);
247 	}
248 }
249 
_bind_methods()250 void EditorPropertyRootMotion::_bind_methods() {
251 
252 	ClassDB::bind_method(D_METHOD("_confirmed"), &EditorPropertyRootMotion::_confirmed);
253 	ClassDB::bind_method(D_METHOD("_node_assign"), &EditorPropertyRootMotion::_node_assign);
254 	ClassDB::bind_method(D_METHOD("_node_clear"), &EditorPropertyRootMotion::_node_clear);
255 }
256 
EditorPropertyRootMotion()257 EditorPropertyRootMotion::EditorPropertyRootMotion() {
258 
259 	HBoxContainer *hbc = memnew(HBoxContainer);
260 	add_child(hbc);
261 	assign = memnew(Button);
262 	assign->set_flat(true);
263 	assign->set_h_size_flags(SIZE_EXPAND_FILL);
264 	assign->set_clip_text(true);
265 	assign->connect("pressed", this, "_node_assign");
266 	hbc->add_child(assign);
267 
268 	clear = memnew(Button);
269 	clear->set_flat(true);
270 	clear->connect("pressed", this, "_node_clear");
271 	hbc->add_child(clear);
272 
273 	filter_dialog = memnew(ConfirmationDialog);
274 	add_child(filter_dialog);
275 	filter_dialog->set_title(TTR("Edit Filtered Tracks:"));
276 	filter_dialog->connect("confirmed", this, "_confirmed");
277 
278 	filters = memnew(Tree);
279 	filter_dialog->add_child(filters);
280 	filters->set_v_size_flags(SIZE_EXPAND_FILL);
281 	filters->set_hide_root(true);
282 	filters->connect("item_activated", this, "_confirmed");
283 	//filters->connect("item_edited", this, "_filter_edited");
284 }
285 //////////////////////////
286 
can_handle(Object * p_object)287 bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) {
288 	return true; //can handle everything
289 }
290 
parse_begin(Object * p_object)291 void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) {
292 	//do none
293 }
294 
parse_property(Object * p_object,Variant::Type p_type,const String & p_path,PropertyHint p_hint,const String & p_hint_text,int p_usage)295 bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage) {
296 
297 	if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) {
298 		EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion);
299 		if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) {
300 			editor->setup(p_hint_text);
301 		}
302 		add_property_editor(p_path, editor);
303 		return true;
304 	}
305 
306 	return false; //can be overridden, although it will most likely be last anyway
307 }
308 
parse_end()309 void EditorInspectorRootMotionPlugin::parse_end() {
310 	//do none
311 }
312