mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-30 21:21:10 +00:00 
			
		
		
		
	Removal of obsolete editor plugins Fixes #64
Signed-off-by: Vinzenz Feenstra <evilissimo@gmail.com>
This commit is contained in:
		
							parent
							
								
									891b2bdb4f
								
							
						
					
					
						commit
						dba66e0286
					
				
					 5 changed files with 0 additions and 1939 deletions
				
			
		|  | @ -82,8 +82,6 @@ | |||
| #include "plugins/path_2d_editor_plugin.h" | ||||
| #include "plugins/particles_editor_plugin.h" | ||||
| #include "plugins/particles_2d_editor_plugin.h" | ||||
| #include "plugins/font_editor_plugin.h" | ||||
| #include "plugins/animation_editor_plugin.h" | ||||
| #include "plugins/animation_tree_editor_plugin.h" | ||||
| #include "plugins/tile_set_editor_plugin.h" | ||||
| #include "plugins/animation_player_editor_plugin.h" | ||||
|  | @ -3959,16 +3957,13 @@ EditorNode::EditorNode() { | |||
| 	add_editor_plugin( memnew( ScriptEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( EditorHelpPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( AnimationPlayerEditorPlugin(this) ) ); | ||||
| 	//add_editor_plugin( memnew( AnimationEditorPlugin(this) ) ); - not useful anymore
 | ||||
| 	add_editor_plugin( memnew( ShaderEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( CameraEditorPlugin(this) ) ); | ||||
| 	//add_editor_plugin( memnew( FontEditorPlugin(this) ) ); obsolete
 | ||||
| 	add_editor_plugin( memnew( SampleEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( SampleLibraryEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( ThemeEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( MultiMeshEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( AnimationTreeEditorPlugin(this) ) ); | ||||
| 	//add_editor_plugin( memnew( GridMapEditorPlugin(this) ) );
 | ||||
| 	add_editor_plugin( memnew( SamplePlayerEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( MeshLibraryEditorPlugin(this) ) ); | ||||
| 	add_editor_plugin( memnew( StreamEditorPlugin(this) ) ); | ||||
|  |  | |||
|  | @ -1,806 +0,0 @@ | |||
| /*************************************************************************/ | ||||
| /*  animation_editor_plugin.cpp                                          */ | ||||
| /*************************************************************************/ | ||||
| /*                       This file is part of:                           */ | ||||
| /*                           GODOT ENGINE                                */ | ||||
| /*                    http://www.godotengine.org                         */ | ||||
| /*************************************************************************/ | ||||
| /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */ | ||||
| /*                                                                       */ | ||||
| /* Permission is hereby granted, free of charge, to any person obtaining */ | ||||
| /* a copy of this software and associated documentation files (the       */ | ||||
| /* "Software"), to deal in the Software without restriction, including   */ | ||||
| /* without limitation the rights to use, copy, modify, merge, publish,   */ | ||||
| /* distribute, sublicense, and/or sell copies of the Software, and to    */ | ||||
| /* permit persons to whom the Software is furnished to do so, subject to */ | ||||
| /* the following conditions:                                             */ | ||||
| /*                                                                       */ | ||||
| /* The above copyright notice and this permission notice shall be        */ | ||||
| /* included in all copies or substantial portions of the Software.       */ | ||||
| /*                                                                       */ | ||||
| /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | ||||
| /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | ||||
| /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | ||||
| /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | ||||
| /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | ||||
| /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | ||||
| /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | ||||
| /*************************************************************************/ | ||||
| #include "animation_editor_plugin.h" | ||||
| #include "io/resource_loader.h" | ||||
| 
 | ||||
| 
 | ||||
| class AnimationEditor_TrackEditor : public Object { | ||||
| 
 | ||||
| 	OBJ_TYPE(AnimationEditor_TrackEditor,Object); | ||||
| 
 | ||||
| protected: | ||||
| 
 | ||||
| 	bool _set(const StringName& p_name, const Variant& p_value) { | ||||
| 
 | ||||
| 		if (anim.is_null()) | ||||
| 			return false; | ||||
| 		String name=p_name; | ||||
| 
 | ||||
| 		if (name=="track/interpolation") { | ||||
| 
 | ||||
| 			anim_editor->_internal_set_interpolation_type(track,Animation::InterpolationType(p_value.operator int())); | ||||
| 
 | ||||
| 		} else if (name.begins_with("keys/")) { | ||||
| 
 | ||||
| 			int key = name.get_slice("/",1).to_int(); | ||||
| 			ERR_FAIL_INDEX_V( key,anim->track_get_key_count(track), false ); | ||||
| 			String what = name.get_slice("/",2); | ||||
| 			float time = anim->track_get_key_time(track,key); | ||||
| 			float transition = anim->track_get_key_transition(track,key); | ||||
| 
 | ||||
| 			if (what=="time") { | ||||
| 				Variant v = anim->track_get_key_value(track,key); | ||||
| 				anim_editor->_internal_set_key(track,time,transition,v); | ||||
| 				return true; | ||||
| 			} | ||||
| 			if (what=="transition") { | ||||
| 				transition=p_value; | ||||
| 				Variant v = anim->track_get_key_value(track,key); | ||||
| 				anim_editor->_internal_set_key(track,time,transition,v); | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			switch(anim->track_get_type(track)) { | ||||
| 
 | ||||
| 				case Animation::TYPE_TRANSFORM: { | ||||
| 
 | ||||
| 					Vector3 scale,loc; | ||||
| 					Quat rot; | ||||
| 					anim->transform_track_get_key(track,key,&loc,&rot,&scale); | ||||
| 
 | ||||
| 
 | ||||
| 					if (what=="loc") { | ||||
| 						loc=p_value; | ||||
| 					} else if (what=="scale") { | ||||
| 						scale=p_value; | ||||
| 					} else if (what=="rot") { | ||||
| 						rot=p_value; | ||||
| 					} else { | ||||
| 						return false; //meh
 | ||||
| 					} | ||||
| 
 | ||||
| 					Dictionary k; | ||||
| 					k["rot"]=rot; | ||||
| 					k["loc"]=loc; | ||||
| 					k["scale"]=scale; | ||||
| 					anim_editor->_internal_set_key(track,time,transition,k); | ||||
| 
 | ||||
| 				} break; | ||||
| 				case Animation::TYPE_METHOD: { | ||||
| 
 | ||||
| 				} break; | ||||
| 				case Animation::TYPE_VALUE: { | ||||
| 
 | ||||
| 					if (what=="value") | ||||
| 						anim_editor->_internal_set_key(track,time,transition,p_value); | ||||
| 				} break; | ||||
| 				default: { return false; } | ||||
| 
 | ||||
| 			} | ||||
| 
 | ||||
| 		} else | ||||
| 			return false; | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 
 | ||||
| 	bool _get(const StringName& p_name,Variant &r_ret) const { | ||||
| 
 | ||||
| 		if (anim.is_null()) | ||||
| 			return false; | ||||
| 		String name=p_name; | ||||
| 
 | ||||
| 		if (name=="track/interpolation") { | ||||
| 
 | ||||
| 			r_ret=anim->track_get_interpolation_type(track); | ||||
| 
 | ||||
| 		} else if (name.begins_with("keys/")) { | ||||
| 
 | ||||
| 			int key = name.get_slice("/",1).to_int(); | ||||
| 			ERR_FAIL_INDEX_V( key,anim->track_get_key_count(track), Variant() ); | ||||
| 			String what = name.get_slice("/",2); | ||||
| 
 | ||||
| 			if (what=="time") { | ||||
| 				r_ret=anim->track_get_key_time(track,key); | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			if (what=="transition") { | ||||
| 				r_ret=anim->track_get_key_transition(track,key); | ||||
| 				return true; | ||||
| 			} | ||||
| 
 | ||||
| 			switch(anim->track_get_type(track)) { | ||||
| 
 | ||||
| 				case Animation::TYPE_TRANSFORM: { | ||||
| 
 | ||||
| 					Vector3 scale,loc; | ||||
| 					Quat rot; | ||||
| 					anim->transform_track_get_key(track,key,&loc,&rot,&scale); | ||||
| 
 | ||||
| 
 | ||||
| 					if (what=="loc") { | ||||
| 						r_ret= loc; | ||||
| 					} else if (what=="scale") { | ||||
| 						r_ret= scale; | ||||
| 					} else if (what=="rot") { | ||||
| 						r_ret= rot; | ||||
| 					} | ||||
| 
 | ||||
| 				} break; | ||||
| 				case Animation::TYPE_METHOD: { | ||||
| 
 | ||||
| 				} break; | ||||
| 				case Animation::TYPE_VALUE: { | ||||
| 
 | ||||
| 					if (what=="value") | ||||
| 						r_ret= anim->track_get_key_value(track,key); | ||||
| 				} break; | ||||
| 				default: { return false; } | ||||
| 
 | ||||
| 			} | ||||
| 		} else | ||||
| 			return false; | ||||
| 
 | ||||
| 		return true; | ||||
| 	} | ||||
| 	void _get_property_list( List<PropertyInfo> *p_list) const { | ||||
| 
 | ||||
| 		p_list->push_back(PropertyInfo(Variant::INT,"track/interpolation",PROPERTY_HINT_ENUM,"Nearest,Linear,Cubic") ); | ||||
| 
 | ||||
| 		if (anim.is_null()) | ||||
| 			return; | ||||
| 
 | ||||
| 		int keycount = anim->track_get_key_count(track); | ||||
| 
 | ||||
| 		for(int i=0;i<keycount;i++) { | ||||
| 
 | ||||
| 			p_list->push_back(PropertyInfo(Variant::REAL,"keys/"+itos(i)+"/time",PROPERTY_HINT_RANGE,"0,3600,0.001") ); | ||||
| 			p_list->push_back(PropertyInfo(Variant::REAL,"keys/"+itos(i)+"/transition",PROPERTY_HINT_EXP_EASING) ); | ||||
| 			switch(anim->track_get_type(track)) { | ||||
| 
 | ||||
| 				case Animation::TYPE_TRANSFORM: { | ||||
| 
 | ||||
| 					p_list->push_back(PropertyInfo(Variant::VECTOR3,"keys/"+itos(i)+"/loc" ) ); | ||||
| 					p_list->push_back(PropertyInfo(Variant::QUAT,"keys/"+itos(i)+"/rot" ) ); | ||||
| 					p_list->push_back(PropertyInfo(Variant::VECTOR3,"keys/"+itos(i)+"/scale" ) ); | ||||
| 
 | ||||
| 				} break; | ||||
| 				case Animation::TYPE_METHOD: { | ||||
| 
 | ||||
| 				} break; | ||||
| 				case Animation::TYPE_VALUE: { | ||||
| 
 | ||||
| 
 | ||||
| 					Variant v = anim->track_get_key_value(track,i); | ||||
| 
 | ||||
| 					PropertyHint hint=PROPERTY_HINT_NONE; | ||||
| 					String hint_string; | ||||
| 					if (v.get_type()==Variant::INT) { | ||||
| 						hint=PROPERTY_HINT_RANGE; | ||||
| 						hint_string="-16384,16384,1"; | ||||
| 					} else if (v.get_type()==Variant::REAL) { | ||||
| 						hint=PROPERTY_HINT_RANGE; | ||||
| 						hint_string="-16384,16384,0.001"; | ||||
| 					} else if (v.get_type()==Variant::OBJECT) { | ||||
| 						hint=PROPERTY_HINT_RESOURCE_TYPE; | ||||
| 						hint_string="Resource"; | ||||
| 					} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 					p_list->push_back(PropertyInfo(v.get_type(),"keys/"+itos(i)+"/value",hint,hint_string ) ); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 				} break; | ||||
| 
 | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
| 	AnimationEditor *anim_editor; | ||||
| 
 | ||||
| 	Ref<Animation> anim; | ||||
| 	int track; | ||||
| 	AnimationEditor_TrackEditor() { } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| void AnimationEditor::update_anim() { | ||||
| 
 | ||||
| 	tracks->clear(); | ||||
| 	key_editor->edit(NULL); | ||||
| 	TreeItem *root = tracks->create_item(NULL); | ||||
| 
 | ||||
| 	TreeItem *sel=NULL; | ||||
| 	int selected_track=-1; | ||||
| 	if (animation->has_meta("_anim_editor_selected_track_")) | ||||
| 		selected_track=animation->get_meta("_anim_editor_selected_track_"); | ||||
| 
 | ||||
| 	for(int i=0;i<animation->get_track_count();i++) { | ||||
| 
 | ||||
| 		String path = animation->track_get_path(i); | ||||
| 		TreeItem *track = tracks->create_item(root); | ||||
| 		track->set_text(0,itos(i)); | ||||
| 		track->set_editable(0,false); | ||||
| 		track->set_text(1,path); | ||||
| 		track->set_editable(1,true); | ||||
| 		track->set_metadata(0,i); | ||||
| 		if (selected_track==i) | ||||
| 			sel=track; | ||||
| 
 | ||||
| 		switch(animation->track_get_type(i)) { | ||||
| 
 | ||||
| 			case Animation::TYPE_TRANSFORM: { | ||||
| 
 | ||||
| 				track->set_icon(0,get_icon("Matrix","EditorIcons")); | ||||
| 
 | ||||
| 			} break; | ||||
| 			case Animation::TYPE_METHOD: { | ||||
| 				track->set_icon(0,get_icon("TrackMethod","EditorIcons")); | ||||
| 			} break; | ||||
| 			case Animation::TYPE_VALUE: { | ||||
| 
 | ||||
| 				track->set_icon(0,get_icon("TrackValue","EditorIcons")); | ||||
| 			} break; | ||||
| 
 | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	if (sel) { | ||||
| 		sel->select(1); | ||||
| 		_update_track_keys(); | ||||
| 	} else { | ||||
| 		selected_track=-1; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_update_track_keys()  { | ||||
| 	if (selected_track<0 || selected_track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 	track_editor->anim=animation; | ||||
| 	track_editor->track=selected_track; | ||||
| 	key_editor->edit(NULL); | ||||
| 	key_editor->edit(track_editor); | ||||
| 
 | ||||
| 	if (animation->track_get_type(selected_track)==Animation::TYPE_VALUE) { | ||||
| 		key_time->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,160); | ||||
| 		key_type->show(); | ||||
| 	} else { | ||||
| 		key_time->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,60); | ||||
| 		key_type->hide(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_track_path_changed() { | ||||
| 
 | ||||
| 	TreeItem *ti=tracks->get_edited(); | ||||
| 	int track=ti->get_metadata(0); | ||||
| 	String path=ti->get_text(1); | ||||
| 	if (track<0 || track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 	undo_redo->create_action("Create Anim Track"); | ||||
| 	undo_redo->add_do_method(animation.ptr(),"track_set_path",track,path); | ||||
| 	undo_redo->add_undo_method(animation.ptr(),"track_set_path",track,animation->track_get_path(track)); | ||||
| 	undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->commit_action(); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_track_selected() { | ||||
| 
 | ||||
| 	TreeItem *ti=tracks->get_selected(); | ||||
| 	if(!ti) | ||||
| 		return; | ||||
| 	selected_track=ti->get_metadata(0); | ||||
| 	animation->set_meta("_anim_editor_selected_track_",selected_track); | ||||
| 	_update_track_keys(); | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_track_added() { | ||||
| 
 | ||||
| 	undo_redo->create_action("Create Anim Track"); | ||||
| 	undo_redo->add_do_method(animation.ptr(),"add_track",track_type->get_selected(),-1); | ||||
| 	undo_redo->add_undo_method(animation.ptr(),"remove_track",animation->get_track_count()); | ||||
| 	undo_redo->add_do_method(this,"_internal_set_selected_track",animation->get_track_count(),animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_set_selected_track",selected_track,animation); | ||||
| 	undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->commit_action(); | ||||
| 	update_anim(); | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_track_removed() { | ||||
| 
 | ||||
| 	if (selected_track<0 || selected_track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 
 | ||||
| 	undo_redo->create_action("Remove Anim Track"); | ||||
| 	undo_redo->add_do_method(animation.ptr(),"remove_track",selected_track); | ||||
| 	undo_redo->add_undo_method(animation.ptr(),"add_track",animation->track_get_type(selected_track),selected_track); | ||||
| 	undo_redo->add_undo_method(animation.ptr(),"track_set_path",selected_track,animation->track_get_path(selected_track)); | ||||
| 	//todo interpolation
 | ||||
| 	for(int i=0;i<animation->track_get_key_count(selected_track);i++) { | ||||
| 
 | ||||
| 		Variant v = animation->track_get_key_value(selected_track,i); | ||||
| 		float time =  animation->track_get_key_time(selected_track,i); | ||||
| 
 | ||||
| 		undo_redo->add_undo_method(animation.ptr(),"track_insert_key",selected_track,time,v); | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	int old_track=selected_track; | ||||
| 	if (selected_track>0) | ||||
| 		selected_track--; | ||||
| 	if (selected_track<0 || selected_track>=(animation->get_track_count()-1)) | ||||
| 		selected_track=-1; | ||||
| 
 | ||||
| 	undo_redo->add_do_method(this,"_internal_set_selected_track",selected_track,animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_set_selected_track",old_track,animation); | ||||
| 
 | ||||
| 	undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->commit_action(); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_internal_set_interpolation_type(int p_track,Animation::InterpolationType p_type) { | ||||
| 
 | ||||
| 	undo_redo->create_action("Set Interpolation"); | ||||
| 	undo_redo->add_do_method(animation.ptr(),"track_set_interpolation_type",p_track,p_type); | ||||
| 	undo_redo->add_undo_method(animation.ptr(),"track_set_interpolation_type",p_track,animation->track_get_interpolation_type(p_track)); | ||||
| 	undo_redo->add_do_method(this,"_internal_set_selected_track",selected_track,animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_set_selected_track",selected_track,animation); | ||||
| 	undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->commit_action(); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_internal_set_selected_track(int p_which,const Ref<Animation>& p_anim) { | ||||
| 
 | ||||
| 	if (is_visible() && animation==p_anim) { | ||||
| 		selected_track=p_which; | ||||
| 		animation->set_meta("_anim_editor_selected_track_",selected_track); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_track_moved_up() { | ||||
| 
 | ||||
| 
 | ||||
| 	if (selected_track<0 || selected_track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 
 | ||||
| 	if (selected_track<(animation->get_track_count()-1)) { | ||||
| 		undo_redo->create_action("Move Up Track"); | ||||
| 		undo_redo->add_do_method(animation.ptr(),"track_move_up",selected_track); | ||||
| 		undo_redo->add_undo_method(animation.ptr(),"track_move_down",selected_track+1); | ||||
| 		undo_redo->add_do_method(this,"_internal_set_selected_track",selected_track+1,animation); | ||||
| 		undo_redo->add_undo_method(this,"_internal_set_selected_track",selected_track,animation); | ||||
| 		undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 		undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 		undo_redo->commit_action(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_track_moved_down() { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	if (selected_track<0 || selected_track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 	if (selected_track>0) { | ||||
| 		undo_redo->create_action("Move Down Track"); | ||||
| 		undo_redo->add_do_method(animation.ptr(),"track_move_down",selected_track); | ||||
| 		undo_redo->add_undo_method(animation.ptr(),"track_move_up",selected_track-1); | ||||
| 		undo_redo->add_do_method(this,"_internal_set_selected_track",selected_track-1,animation); | ||||
| 		undo_redo->add_undo_method(this,"_internal_set_selected_track",selected_track,animation); | ||||
| 		undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 		undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 		undo_redo->commit_action(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_key_added() { | ||||
| 
 | ||||
| 	if (selected_track<0 || selected_track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 
 | ||||
| 	bool need_variant= animation->track_get_type(selected_track)==Animation::TYPE_VALUE; | ||||
| 
 | ||||
| 	Variant v; | ||||
| 
 | ||||
| 	if (need_variant) { | ||||
| 
 | ||||
| 		switch(key_type->get_selected()) { | ||||
| 
 | ||||
| 			case Variant::NIL: v=Variant(); break; | ||||
| 			case Variant::BOOL: v=false; break; | ||||
| 			case Variant::INT: v=0; break; | ||||
| 			case Variant::REAL: v=0.0; break; | ||||
| 			case Variant::STRING: v=""; break; | ||||
| 			case Variant::VECTOR2: v=Vector2(); break;		// 5
 | ||||
| 			case Variant::RECT2: v=Rect2(); break; | ||||
| 			case Variant::VECTOR3: v=Vector3(); break; | ||||
| 			case Variant::PLANE: v=Plane(); break; | ||||
| 			case Variant::QUAT: v=Quat(); break; | ||||
| 			case Variant::_AABB: v=AABB(); break; //sorry naming convention fail :( not like it's used often // 10
 | ||||
| 			case Variant::MATRIX3: v=Matrix3(); break; | ||||
| 			case Variant::TRANSFORM: v=Transform(); break; | ||||
| 			case Variant::COLOR: v=Color(); break; | ||||
| 			case Variant::IMAGE: v=Image(); break; | ||||
| 			case Variant::NODE_PATH: v=NodePath(); break;		// 15
 | ||||
| 			case Variant::_RID: v=RID(); break; | ||||
| 			case Variant::OBJECT: v=Variant(); break; | ||||
| 			case Variant::INPUT_EVENT: v=InputEvent(); break; | ||||
| 			case Variant::DICTIONARY: v=Dictionary(); break;		// 20
 | ||||
| 			case Variant::ARRAY: v=Array(); break; | ||||
| 			case Variant::RAW_ARRAY: v=DVector<uint8_t>(); break; | ||||
| 			case Variant::INT_ARRAY: v=DVector<int>(); break; | ||||
| 			case Variant::REAL_ARRAY: v=DVector<real_t>(); break; | ||||
| 			case Variant::STRING_ARRAY: v=DVector<String>(); break;	//25
 | ||||
| 			case Variant::VECTOR3_ARRAY: v=DVector<Vector3>(); break; | ||||
| 			case Variant::COLOR_ARRAY: v=DVector<Color>(); break; | ||||
| 			default: v=Variant(); break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	float time = key_time->get_text().to_double(); | ||||
| 
 | ||||
| 	switch(animation->track_get_type(selected_track)) { | ||||
| 		case Animation::TYPE_TRANSFORM: { | ||||
| 
 | ||||
| 			Dictionary d; | ||||
| 			d["loc"]=Vector3(); | ||||
| 			d["rot"]=Quat(); | ||||
| 			d["scale"]=Vector3(1,1,1); | ||||
| 			v=d; | ||||
| 
 | ||||
| 		} break; | ||||
| 		case Animation::TYPE_VALUE: { | ||||
| 			//v=v
 | ||||
| 		} break; | ||||
| 		case Animation::TYPE_METHOD: { | ||||
| 
 | ||||
| 			return; //not do anything yet
 | ||||
| 		} break; | ||||
| 	} | ||||
| 
 | ||||
| 	_internal_set_key(selected_track,time,1.0,v); | ||||
| 
 | ||||
| 	_update_track_keys(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void AnimationEditor::_internal_check_update(Ref<Animation> p_anim) { | ||||
| 
 | ||||
| 	if (is_visible() && p_anim==animation)	{ | ||||
| 		update_anim(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_internal_set_key(int p_track, float p_time, float p_transition,const Variant& p_value) { | ||||
| 
 | ||||
| 	int prev = animation->track_find_key(p_track,p_time); | ||||
| 	bool existing = (prev>=0) && (animation->track_get_key_time(p_track,prev)==p_time); | ||||
| 
 | ||||
| 	undo_redo->create_action("Insert Key"); | ||||
| 
 | ||||
| 	undo_redo->add_do_method(animation.ptr(),"track_insert_key",p_track,p_time,p_value,p_transition); | ||||
| 	if (existing) | ||||
| 		undo_redo->add_undo_method(animation.ptr(),"track_insert_key",p_track,p_time,animation->track_get_key_value(p_track,existing),animation->track_get_key_transition(p_track,existing)); | ||||
| 	else | ||||
| 		undo_redo->add_undo_method(animation.ptr(),"track_remove_key",p_track,prev+1); | ||||
| 	undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 
 | ||||
| 	undo_redo->commit_action(); | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_key_removed() { | ||||
| 
 | ||||
| 	if (selected_track<0 || selected_track>=animation->get_track_count()) | ||||
| 		return; | ||||
| 
 | ||||
| 	String sel=key_editor->get_selected_path(); | ||||
| 	if (sel.get_slice_count("/")<2) | ||||
| 		return; | ||||
| 	if (sel.get_slice("/",0)!="keys") | ||||
| 		return; | ||||
| 	int key = sel.get_slice("/",1).to_int(); | ||||
| 	if (key<0 || key>=animation->track_get_key_count(selected_track)) | ||||
| 		return; | ||||
| 
 | ||||
| 
 | ||||
| 	undo_redo->create_action("Remove Key"); | ||||
| 
 | ||||
| 	Variant data = animation->track_get_key_value(selected_track,key); | ||||
| 	float time = animation->track_get_key_time(selected_track,key); | ||||
| 	undo_redo->add_do_method(animation.ptr(),"track_remove_key",selected_track,key); | ||||
| 	undo_redo->add_undo_method(animation.ptr(),"track_insert_key",selected_track,time,data); | ||||
| 	undo_redo->add_do_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->add_undo_method(this,"_internal_check_update",animation); | ||||
| 	undo_redo->commit_action(); | ||||
| 
 | ||||
| 	_update_track_keys(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void AnimationEditor::edit(const Ref<Animation>& p_animation) { | ||||
| 
 | ||||
| 
 | ||||
| 	animation=p_animation; | ||||
| 	if (!animation.is_null()) | ||||
| 		update_anim(); | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void AnimationEditor::_bind_methods() { | ||||
| 
 | ||||
| 	ObjectTypeDB::bind_method(_MD("_track_selected"),&AnimationEditor::_track_selected); | ||||
| 	ObjectTypeDB::bind_method(_MD("_track_added"),&AnimationEditor::_track_added); | ||||
| 	ObjectTypeDB::bind_method(_MD("_track_removed"),&AnimationEditor::_track_removed); | ||||
| 	ObjectTypeDB::bind_method(_MD("_track_moved_up"),&AnimationEditor::_track_moved_up); | ||||
| 	ObjectTypeDB::bind_method(_MD("_track_moved_down"),&AnimationEditor::_track_moved_down); | ||||
| 	ObjectTypeDB::bind_method(_MD("_track_path_changed"),&AnimationEditor::_track_path_changed); | ||||
| 	ObjectTypeDB::bind_method(_MD("_key_added"),&AnimationEditor::_key_added); | ||||
| 	ObjectTypeDB::bind_method(_MD("_key_removed"),&AnimationEditor::_key_removed); | ||||
| 	ObjectTypeDB::bind_method(_MD("_internal_check_update"),&AnimationEditor::_internal_check_update); | ||||
| 	ObjectTypeDB::bind_method(_MD("_internal_set_selected_track"),&AnimationEditor::_internal_set_selected_track); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void AnimationEditor::_notification(int p_what) { | ||||
| 
 | ||||
| 	switch(p_what) { | ||||
| 
 | ||||
| 		case NOTIFICATION_ENTER_SCENE: { | ||||
| 
 | ||||
| 			add_track->set_icon( get_icon("Add","EditorIcons") ); | ||||
| 			remove_track->set_icon( get_icon("Del","EditorIcons") ); | ||||
| 			move_up_track->set_icon( get_icon("Up","EditorIcons") ); | ||||
| 			move_down_track->set_icon( get_icon("Down","EditorIcons") ); | ||||
| 			time_icon->set_texture( get_icon("Time","EditorIcons") ); | ||||
| 
 | ||||
| 			add_key->set_icon( get_icon("Add","EditorIcons") ); | ||||
| 			remove_key->set_icon( get_icon("Del","EditorIcons") ); | ||||
| 
 | ||||
| 		} break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| AnimationEditor::AnimationEditor() { | ||||
| 
 | ||||
| 	panel = memnew( Panel ); | ||||
| 	add_child(panel); | ||||
| 	panel->set_area_as_parent_rect(); | ||||
| 
 | ||||
| 	Control *left_pane = memnew( Control ); | ||||
| 	panel->add_child(left_pane); | ||||
| 	left_pane->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_RATIO,0.5); | ||||
| 	left_pane->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0); | ||||
| 
 | ||||
| 	Label *l = memnew( Label ); | ||||
| 	l->set_text("Track List:"); | ||||
| 	l->set_pos(Point2(5,5)); | ||||
| 	left_pane->add_child(l); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	track_name = memnew( LineEdit ); | ||||
| 	track_name->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,10); | ||||
| 	track_name->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	track_name->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,80); | ||||
| 	track_name->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	left_pane->add_child(track_name); | ||||
| */ | ||||
| 
 | ||||
| 	track_type = memnew( OptionButton ); | ||||
| 	track_type->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,10); | ||||
| 	track_type->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	track_type->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,115); | ||||
| 	track_type->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	left_pane->add_child(track_type); | ||||
| 	track_type->add_item("Transform",Animation::TYPE_TRANSFORM); | ||||
| 	track_type->add_item("Value",Animation::TYPE_VALUE); | ||||
| 	track_type->add_item("Method",Animation::TYPE_METHOD); | ||||
| 
 | ||||
| 
 | ||||
| 	add_track = memnew( Button ); | ||||
| 	add_track->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,110); | ||||
| 	add_track->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	add_track->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,90); | ||||
| 	add_track->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	left_pane->add_child(add_track); | ||||
| 
 | ||||
| 	remove_track = memnew( Button ); | ||||
| 	remove_track->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,85); | ||||
| 	remove_track->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	remove_track->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,60); | ||||
| 	remove_track->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	left_pane->add_child(remove_track); | ||||
| 
 | ||||
| 	move_up_track = memnew( Button ); | ||||
| 	move_up_track->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,55); | ||||
| 	move_up_track->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	move_up_track->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,30); | ||||
| 	move_up_track->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	left_pane->add_child(move_up_track); | ||||
| 
 | ||||
| 	move_down_track = memnew( Button ); | ||||
| 	move_down_track->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,25); | ||||
| 	move_down_track->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	move_down_track->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0); | ||||
| 	move_down_track->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	left_pane->add_child(move_down_track); | ||||
| 
 | ||||
| 	tracks = memnew(Tree); | ||||
| 	tracks->set_columns(2); | ||||
| 	tracks->set_column_expand(0,false); | ||||
| 	tracks->set_column_min_width(0,55); | ||||
| 	tracks->set_column_expand(1,true); | ||||
| 	tracks->set_column_min_width(1,100); | ||||
| 	tracks->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,55); | ||||
| 	tracks->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0); | ||||
| 	tracks->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,10); | ||||
| 	tracks->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,5); | ||||
| 	tracks->set_hide_root(true); | ||||
| 	left_pane->add_child(tracks); | ||||
| 
 | ||||
| 
 | ||||
| 	Control *right_pane = memnew( Control ); | ||||
| 	panel->add_child(right_pane); | ||||
| 	right_pane->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_RATIO,0.5); | ||||
| 	right_pane->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,5); | ||||
| 	right_pane->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0); | ||||
| 
 | ||||
| 	l = memnew( Label ); | ||||
| 	l->set_text("Track Keys:"); | ||||
| 	l->set_pos(Point2(5,5)); | ||||
| 	right_pane->add_child(l); | ||||
| 
 | ||||
| 	time_icon = memnew(  TextureFrame ); | ||||
| 	time_icon->set_pos(Point2(8,28)); | ||||
| 	right_pane->add_child(time_icon); | ||||
| 
 | ||||
| 	key_time = memnew( LineEdit ); | ||||
| 	key_time->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,24); | ||||
| 	key_time->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,160); | ||||
| 	key_time->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	key_time->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	key_time->set_text("0.0"); | ||||
| 	right_pane->add_child(key_time); | ||||
| 
 | ||||
| 	key_type = memnew( OptionButton ); | ||||
| 	key_type->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,160); | ||||
| 	key_type->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,60); | ||||
| 	key_type->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	key_type->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	right_pane->add_child(key_type); | ||||
| 
 | ||||
| 	for(int i=0;i<Variant::VARIANT_MAX;i++) { | ||||
| 
 | ||||
| 		key_type->add_item(Variant::get_type_name(Variant::Type(i))); | ||||
| 	} | ||||
| 
 | ||||
| 	add_key = memnew( Button ); | ||||
| 	add_key->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,55); | ||||
| 	add_key->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,30); | ||||
| 	add_key->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	add_key->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	right_pane->add_child(add_key); | ||||
| 
 | ||||
| 	remove_key = memnew( Button ); | ||||
| 	remove_key->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,25); | ||||
| 	remove_key->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0); | ||||
| 	remove_key->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,25); | ||||
| 	remove_key->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_BEGIN,45); | ||||
| 	right_pane->add_child(remove_key); | ||||
| 
 | ||||
| 	key_editor = memnew(PropertyEditor); | ||||
| 	key_editor->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,55); | ||||
| 	key_editor->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0); | ||||
| 	key_editor->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,10); | ||||
| 	key_editor->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,5); | ||||
| 	key_editor->hide_top_label(); | ||||
| 
 | ||||
| 	right_pane->add_child(key_editor); | ||||
| 
 | ||||
| 	track_editor = memnew( AnimationEditor_TrackEditor ); | ||||
| 	track_editor->anim_editor=this; | ||||
| 	selected_track=-1; | ||||
| 
 | ||||
| 	add_track->connect("pressed", this,"_track_added"); | ||||
| 	remove_track->connect("pressed", this,"_track_removed"); | ||||
| 	move_up_track->connect("pressed", this,"_track_moved_up"); | ||||
| 	move_down_track->connect("pressed", this,"_track_moved_down"); | ||||
| 	tracks->connect("cell_selected", this,"_track_selected"); | ||||
| 	tracks->connect("item_edited", this,"_track_path_changed"); | ||||
| 	add_key->connect("pressed", this,"_key_added"); | ||||
| 	remove_key->connect("pressed", this,"_key_removed"); | ||||
| } | ||||
| 
 | ||||
| AnimationEditor::~AnimationEditor() { | ||||
| 
 | ||||
| 	memdelete(track_editor); | ||||
| } | ||||
| 
 | ||||
| void AnimationEditorPlugin::edit(Object *p_node) { | ||||
| 
 | ||||
| 	animation_editor->set_undo_redo(&get_undo_redo()); | ||||
| 	if (p_node && p_node->cast_to<Animation>()) { | ||||
| 		animation_editor->edit( p_node->cast_to<Animation>() ); | ||||
| 		animation_editor->show(); | ||||
| 	} else | ||||
| 		animation_editor->hide(); | ||||
| } | ||||
| 
 | ||||
| bool AnimationEditorPlugin::handles(Object *p_node) const{ | ||||
| 
 | ||||
| 	return p_node->is_type("Animation"); | ||||
| } | ||||
| 
 | ||||
| void AnimationEditorPlugin::make_visible(bool p_visible){ | ||||
| 
 | ||||
| 	if (p_visible) | ||||
| 		animation_editor->show(); | ||||
| 	else | ||||
| 		animation_editor->hide(); | ||||
| } | ||||
| 
 | ||||
| AnimationEditorPlugin::AnimationEditorPlugin(EditorNode *p_node) { | ||||
| 
 | ||||
| 	animation_editor = memnew( AnimationEditor ); | ||||
| 
 | ||||
| 	p_node->get_viewport()->add_child(animation_editor); | ||||
| 	animation_editor->set_area_as_parent_rect(); | ||||
| 	animation_editor->hide(); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1,123 +0,0 @@ | |||
| /*************************************************************************/ | ||||
| /*  animation_editor_plugin.h                                            */ | ||||
| /*************************************************************************/ | ||||
| /*                       This file is part of:                           */ | ||||
| /*                           GODOT ENGINE                                */ | ||||
| /*                    http://www.godotengine.org                         */ | ||||
| /*************************************************************************/ | ||||
| /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */ | ||||
| /*                                                                       */ | ||||
| /* Permission is hereby granted, free of charge, to any person obtaining */ | ||||
| /* a copy of this software and associated documentation files (the       */ | ||||
| /* "Software"), to deal in the Software without restriction, including   */ | ||||
| /* without limitation the rights to use, copy, modify, merge, publish,   */ | ||||
| /* distribute, sublicense, and/or sell copies of the Software, and to    */ | ||||
| /* permit persons to whom the Software is furnished to do so, subject to */ | ||||
| /* the following conditions:                                             */ | ||||
| /*                                                                       */ | ||||
| /* The above copyright notice and this permission notice shall be        */ | ||||
| /* included in all copies or substantial portions of the Software.       */ | ||||
| /*                                                                       */ | ||||
| /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | ||||
| /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | ||||
| /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | ||||
| /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | ||||
| /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | ||||
| /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | ||||
| /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | ||||
| /*************************************************************************/ | ||||
| #ifndef ANIMATION_EDITOR_PLUGIN_H | ||||
| #define ANIMATION_EDITOR_PLUGIN_H | ||||
| 
 | ||||
| #include "scene/resources/animation.h" | ||||
| #include "scene/gui/texture_frame.h" | ||||
| #include "scene/gui/option_button.h" | ||||
| #include "tools/editor/editor_node.h" | ||||
| #include "tools/editor/property_editor.h" | ||||
| #include "undo_redo.h" | ||||
| class AnimationEditor_TrackEditor; | ||||
| 
 | ||||
| class AnimationEditor : public Control { | ||||
| 
 | ||||
| 	OBJ_TYPE( AnimationEditor, Control ); | ||||
| 
 | ||||
| 	Panel *panel; | ||||
| 	Ref<Animation> animation; | ||||
| 
 | ||||
| 	Button *add_track; | ||||
| 	Button *remove_track; | ||||
| 	Button *move_up_track; | ||||
| 	Button *move_down_track; | ||||
| 
 | ||||
| 	Button *add_key; | ||||
| 	Button *remove_key; | ||||
| 
 | ||||
| 	LineEdit *key_time; | ||||
| 	OptionButton *track_type; | ||||
| 	OptionButton *key_type; | ||||
| 	TextureFrame *time_icon; | ||||
| 
 | ||||
| 	Tree *tracks; | ||||
| 	PropertyEditor *key_editor; | ||||
| 	AnimationEditor_TrackEditor *track_editor; | ||||
| 	int selected_track; | ||||
| 
 | ||||
| 	void _track_selected(); | ||||
| 	void _track_added(); | ||||
| 	void _track_removed(); | ||||
| 	void _track_moved_up(); | ||||
| 	void _track_moved_down(); | ||||
| 	void _track_path_changed(); | ||||
| 
 | ||||
| 	void _key_added(); | ||||
| 	void _key_removed(); | ||||
| 
 | ||||
| 
 | ||||
| 	void _update_track_keys(); | ||||
| 	void update_anim(); | ||||
| 
 | ||||
| 	UndoRedo *undo_redo; | ||||
| 
 | ||||
| 	void _internal_set_selected_track(int p_which,const Ref<Animation>& p_anim); | ||||
| 	void _internal_check_update(Ref<Animation> p_anim); | ||||
| 
 | ||||
| friend class AnimationEditor_TrackEditor; | ||||
| 	void _internal_set_key(int p_track, float p_time, float p_transition,const Variant& p_value); | ||||
| 	void _internal_set_interpolation_type(int p_track,Animation::InterpolationType p_type); | ||||
| 
 | ||||
| protected: | ||||
| 	void _notification(int p_what); | ||||
| 	static void _bind_methods(); | ||||
| public: | ||||
| 
 | ||||
| 	void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo=p_undo_redo; } | ||||
| 	void edit(const Ref<Animation>& p_animation); | ||||
| 
 | ||||
| 	AnimationEditor(); | ||||
| 	~AnimationEditor(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class AnimationEditorPlugin : public EditorPlugin { | ||||
| 
 | ||||
| 	OBJ_TYPE( AnimationEditorPlugin, EditorPlugin ); | ||||
| 
 | ||||
| 	AnimationEditor *animation_editor; | ||||
| 	EditorNode *editor; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
| 
 | ||||
| 	virtual String get_name() const { return "Animation"; } | ||||
| 	bool has_main_screen() const { return false; } | ||||
| 	virtual void edit(Object *p_node); | ||||
| 	virtual bool handles(Object *p_node) const; | ||||
| 	virtual void make_visible(bool p_visible); | ||||
| 
 | ||||
| 	AnimationEditorPlugin(EditorNode *p_node); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,905 +0,0 @@ | |||
| /*************************************************************************/ | ||||
| /*  font_editor_plugin.cpp                                               */ | ||||
| /*************************************************************************/ | ||||
| /*                       This file is part of:                           */ | ||||
| /*                           GODOT ENGINE                                */ | ||||
| /*                    http://www.godotengine.org                         */ | ||||
| /*************************************************************************/ | ||||
| /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */ | ||||
| /*                                                                       */ | ||||
| /* Permission is hereby granted, free of charge, to any person obtaining */ | ||||
| /* a copy of this software and associated documentation files (the       */ | ||||
| /* "Software"), to deal in the Software without restriction, including   */ | ||||
| /* without limitation the rights to use, copy, modify, merge, publish,   */ | ||||
| /* distribute, sublicense, and/or sell copies of the Software, and to    */ | ||||
| /* permit persons to whom the Software is furnished to do so, subject to */ | ||||
| /* the following conditions:                                             */ | ||||
| /*                                                                       */ | ||||
| /* The above copyright notice and this permission notice shall be        */ | ||||
| /* included in all copies or substantial portions of the Software.       */ | ||||
| /*                                                                       */ | ||||
| /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | ||||
| /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | ||||
| /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | ||||
| /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | ||||
| /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | ||||
| /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | ||||
| /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | ||||
| /*************************************************************************/ | ||||
| #include "font_editor_plugin.h" | ||||
| #include "os/file_access.h" | ||||
| #ifdef FREETYPE_ENABLED | ||||
| 
 | ||||
| #include <ft2build.h> | ||||
| #include FT_FREETYPE_H | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #include "core/io/resource_saver.h" | ||||
| 
 | ||||
| void FontEditor::edit(const Ref<Font>& p_font) { | ||||
| 
 | ||||
| 	font=p_font; | ||||
| 	label->add_font_override("font",font); | ||||
| } | ||||
| 
 | ||||
| void FontEditor::_preview_text_changed(const String& p_text) { | ||||
| 
 | ||||
| 	label->set_text(p_text); | ||||
| } | ||||
| struct FontData { | ||||
| 
 | ||||
| 	Vector<uint8_t> bitmap; | ||||
| 	int width,height; | ||||
| 	int ofs_x; //ofset to center, from ABOVE
 | ||||
| 	int ofs_y; //ofset to begining, from LEFT
 | ||||
| 	int valign; //vertical alignment
 | ||||
| 	int halign; | ||||
| 	int advance; | ||||
| 	int character; | ||||
| 	int glyph; | ||||
| 
 | ||||
| 	int texture; | ||||
| //	bool printable;
 | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| struct FontDataSort { | ||||
| 
 | ||||
| 	bool operator()(const FontData *p_A,const FontData *p_B)  const { | ||||
| 		return p_A->height > p_B->height; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| struct KerningKey { | ||||
| 
 | ||||
| 	CharType A,B; | ||||
| 	bool operator<(const KerningKey& p_k) const { return (A==p_k.A)?(B<p_k.B):(A<p_k.A); } | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| void FontEditor::_export_fnt(const String& p_name, Ref<Font> p_font) { | ||||
| 
 | ||||
| 	String fnt_name = p_name + ".fnt"; | ||||
| 	FileAccess* f = FileAccess::open(fnt_name, FileAccess::WRITE); | ||||
| 	ERR_FAIL_COND(!f); | ||||
| 
 | ||||
| 	f->store_string(String("info face=\"") + p_font->get_name() + "\" size=" + String::num_real(font->get_height()) + " bold=0 italic=0 charset=\"\" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=4,4\n"); | ||||
| 
 | ||||
| 	Vector2 size = p_font->get_texture(0)->get_size(); | ||||
| 	f->store_string(String("common lineHeight=") + String::num(font->get_height()) + " base=" + String::num(font->get_ascent()) + " scaleW=" + String::num(size.x) + " scaleH=" + String::num(size.y) + " pages="+String::num(p_font->get_texture_count()) + " packed=0\n"); | ||||
| 
 | ||||
| 	for (int i=0; i<p_font->get_texture_count(); i++) { | ||||
| 
 | ||||
| 		f->store_string(String("page id=")+String::num(i)+ " file=\""+ p_name.get_file() + "_" +String::num(i)+".png\"\n"); | ||||
| 	}; | ||||
| 
 | ||||
| 	f->store_string(String("chars count=")+String::num(p_font->get_character_count()) + "\n"); | ||||
| 
 | ||||
| 	Vector<CharType> keys = p_font->get_char_keys(); | ||||
| 	keys.sort(); | ||||
| 	for (int i=0; i<keys.size(); i++) { | ||||
| 
 | ||||
| 		Font::Character c = p_font->get_character(keys[i]); | ||||
| 		int width = c.rect.size.x; | ||||
| 		if (keys[i] == 32) { | ||||
| 			width = c.advance; | ||||
| 		}; | ||||
| 		f->store_string(String("char id=") + String::num(keys[i]) + " x=" + String::num(c.rect.pos.x) + " y=" + String::num(c.rect.pos.y) + | ||||
| 														" width=" + String::num(width) + " height=" + String::num(c.rect.size.y) + | ||||
| 														" xoffset=" + String::num(c.h_align) + " yoffset=" + String::num(c.v_align) + | ||||
| 														" xadvance=" + String::num(c.advance) + " page=" + String::num(c.texture_idx) + | ||||
| 														" chnl=0 letter=\"\"\n"); | ||||
| 	}; | ||||
| 
 | ||||
| 	f->close(); | ||||
| 
 | ||||
| 	for (int i=0; i<p_font->get_texture_count(); i++) { | ||||
| 
 | ||||
| 		ResourceSaver::save(p_name + "_" + String::num(i) + ".png", p_font->get_texture(i)); | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| void FontEditor::_import_fnt(const String& p_string) { | ||||
| 	//fnt format used by angelcode bmfont
 | ||||
| 	//http://www.angelcode.com/products/bmfont/
 | ||||
| 
 | ||||
| 	FileAccess *f = FileAccess::open(p_string,FileAccess::READ); | ||||
| 
 | ||||
| 	if (!f) { | ||||
| 
 | ||||
| 		ERR_EXPLAIN("Can't open font: "+p_string); | ||||
| 		ERR_FAIL(); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	font->clear(); | ||||
| 
 | ||||
| 	while(true) { | ||||
| 
 | ||||
| 		String line=f->get_line(); | ||||
| 
 | ||||
| 		int delimiter=line.find(" "); | ||||
| 		String type=line.substr(0,delimiter); | ||||
| 		int pos = delimiter+1; | ||||
| 		Map<String,String> keys; | ||||
| 
 | ||||
| 		while (pos < line.size() && line[pos]==' ') | ||||
| 			pos++; | ||||
| 
 | ||||
| 
 | ||||
| 		while(pos<line.size()) { | ||||
| 
 | ||||
| 			int eq = line.find("=",pos); | ||||
| 			if (eq==-1) | ||||
| 				break; | ||||
| 			String key=line.substr(pos,eq-pos); | ||||
| 			int end=-1; | ||||
| 			String value; | ||||
| 			if (line[eq+1]=='"') { | ||||
| 				end=line.find("\"",eq+2); | ||||
| 				if (end==-1) | ||||
| 					break; | ||||
| 				value=line.substr(eq+2,end-1-eq-1); | ||||
| 				pos=end+1; | ||||
| 			} else { | ||||
| 				end=line.find(" ",eq+1); | ||||
| 				if (end==-1) | ||||
| 					end=line.size(); | ||||
| 
 | ||||
| 				value=line.substr(eq+1,end-eq); | ||||
| 
 | ||||
| 				pos=end; | ||||
| 
 | ||||
| 			} | ||||
| 
 | ||||
| 			while (pos<line.size() && line[pos]==' ') | ||||
| 				pos++; | ||||
| 
 | ||||
| 
 | ||||
| 			keys[key]=value; | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		if (type=="info") { | ||||
| 
 | ||||
| 			if (keys.has("face")) | ||||
| 				font->set_name(keys["face"]); | ||||
| 			//if (keys.has("size"))
 | ||||
| 			//	font->set_height(keys["size"].to_int());
 | ||||
| 
 | ||||
| 		} else if (type=="common") { | ||||
| 
 | ||||
| 			if (keys.has("lineHeight")) | ||||
| 				font->set_height(keys["lineHeight"].to_int()); | ||||
| 			if (keys.has("base")) | ||||
| 				font->set_ascent(keys["base"].to_int()); | ||||
| 
 | ||||
| 		} else if (type=="page") { | ||||
| 
 | ||||
| 			if (keys.has("file")) { | ||||
| 
 | ||||
| 				String file = keys["file"]; | ||||
| 				file=p_string.get_base_dir()+"/"+file; | ||||
| 				Ref<Texture> tex = ResourceLoader::load(file); | ||||
| 				if (tex.is_null()) { | ||||
| 					ERR_PRINT("Can't load font texture!"); | ||||
| 				} else { | ||||
| 					font->add_texture(tex); | ||||
| 				} | ||||
| 			} | ||||
| 		} else if (type=="char") { | ||||
| 
 | ||||
| 			CharType idx=0; | ||||
| 			if (keys.has("id")) | ||||
| 				idx=keys["id"].to_int(); | ||||
| 
 | ||||
| 			Rect2 rect; | ||||
| 
 | ||||
| 			if (keys.has("x")) | ||||
| 				rect.pos.x=keys["x"].to_int(); | ||||
| 			if (keys.has("y")) | ||||
| 				rect.pos.y=keys["y"].to_int(); | ||||
| 			if (keys.has("width")) | ||||
| 				rect.size.width=keys["width"].to_int(); | ||||
| 			if (keys.has("height")) | ||||
| 				rect.size.height=keys["height"].to_int(); | ||||
| 
 | ||||
| 			Point2 ofs; | ||||
| 
 | ||||
| 			if (keys.has("xoffset")) | ||||
| 				ofs.x=keys["xoffset"].to_int(); | ||||
| 			if (keys.has("yoffset")) | ||||
| 				ofs.y=keys["yoffset"].to_int(); | ||||
| 
 | ||||
| 			int texture=0; | ||||
| 			if (keys.has("page")) | ||||
| 				texture=keys["page"].to_int(); | ||||
| 			int advance=-1; | ||||
| 			if (keys.has("xadvance")) | ||||
| 				advance=keys["xadvance"].to_int(); | ||||
| 
 | ||||
| 			font->add_char(idx,texture,rect,ofs,advance); | ||||
| 
 | ||||
| 		}  else if (type=="kerning") { | ||||
| 
 | ||||
| 			CharType first=0,second=0; | ||||
| 			int k=0; | ||||
| 
 | ||||
| 			if (keys.has("first")) | ||||
| 				first=keys["first"].to_int(); | ||||
| 			if (keys.has("second")) | ||||
| 				second=keys["second"].to_int(); | ||||
| 			if (keys.has("amount")) | ||||
| 				k=keys["amount"].to_int(); | ||||
| 
 | ||||
| 			font->add_kerning_pair(first,second,-k); | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 		if (f->eof_reached()) | ||||
| 			break; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	memdelete(f); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| void FontEditor::_import_ttf(const String& p_string) { | ||||
| 
 | ||||
| #ifdef FREETYPE_ENABLED | ||||
| 	FT_Library   library;   /* handle to library     */ | ||||
| 	FT_Face      face;      /* handle to face object */ | ||||
| 
 | ||||
| 	Vector<FontData*> font_data_list; | ||||
| 
 | ||||
| 	int error = FT_Init_FreeType( &library ); | ||||
| 
 | ||||
| 	ERR_EXPLAIN("Error initializing FreeType."); | ||||
| 	ERR_FAIL_COND( error !=0 ); | ||||
| 
 | ||||
| 
 | ||||
| 	error = FT_New_Face( library, p_string.utf8().get_data(),0,&face ); | ||||
| 
 | ||||
| 	if ( error == FT_Err_Unknown_File_Format ) { | ||||
| 		ERR_EXPLAIN("Unknown font format."); | ||||
| 		FT_Done_FreeType( library ); | ||||
| 	} else if ( error ) { | ||||
| 
 | ||||
| 		ERR_EXPLAIN("Error loading font."); | ||||
| 		FT_Done_FreeType( library ); | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	ERR_FAIL_COND(error); | ||||
| 
 | ||||
| 
 | ||||
| 	int height=0; | ||||
| 	int ascent=0; | ||||
| 	int font_spacing=0; | ||||
| 
 | ||||
| 	int size=font_size->get_text().to_int(); | ||||
| 
 | ||||
| 	error = FT_Set_Char_Size(face,0,64*size,512,512); | ||||
| 
 | ||||
| 	if ( error ) { | ||||
| 		FT_Done_FreeType( library ); | ||||
| 		ERR_EXPLAIN("Invalid font size. "); | ||||
| 		ERR_FAIL_COND( error ); | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	error = FT_Set_Pixel_Sizes(face,0,size); | ||||
| 
 | ||||
| 	FT_GlyphSlot slot = face->glyph; | ||||
| 
 | ||||
| //	error = FT_Set_Charmap(face,ft_encoding_unicode );   /* encoding..         */
 | ||||
| 
 | ||||
| 
 | ||||
| 	/* PRINT CHARACTERS TO INDIVIDUAL BITMAPS */ | ||||
| 
 | ||||
| 
 | ||||
| //	int space_size=5; //size for space, if none found.. 5!
 | ||||
| //	int min_valign=500; //some ridiculous number
 | ||||
| 
 | ||||
| 	FT_ULong  charcode; | ||||
| 	FT_UInt   gindex; | ||||
| 
 | ||||
| 	int max_up=-1324345; ///gibberish
 | ||||
| 	int max_down=124232; | ||||
| 
 | ||||
| 	Map<KerningKey,int> kerning_map; | ||||
| 
 | ||||
| 	charcode = FT_Get_First_Char( face, &gindex ); | ||||
| 
 | ||||
| 	int xsize=0; | ||||
| 	while ( gindex != 0 ) | ||||
| 	{ | ||||
| 
 | ||||
| 		bool skip=false; | ||||
| 		error = FT_Load_Char( face, charcode, FT_LOAD_RENDER ); | ||||
| 		if (error) skip=true; | ||||
| 		else error = FT_Render_Glyph( face->glyph, ft_render_mode_normal ); | ||||
| 		if (error) skip=true; | ||||
| 
 | ||||
| 
 | ||||
| 		if (!skip && (import_chars.has(charcode) && import_chars[charcode] != 0)) { | ||||
| 
 | ||||
| 			skip = false; | ||||
| 
 | ||||
| 		} else { | ||||
| 			if (import_option->get_selected() == 0 && charcode>127) | ||||
| 				skip=true; | ||||
| 			if (import_option->get_selected() == 1 && charcode>0xFE) | ||||
| 				skip=true; | ||||
| 		}; | ||||
| 
 | ||||
| 		if (charcode<=32) //
 | ||||
| 			skip=true; | ||||
| 
 | ||||
| 		if (skip) { | ||||
| 			charcode=FT_Get_Next_Char(face,charcode,&gindex); | ||||
| 			continue; | ||||
| 		} | ||||
| 
 | ||||
| 		FontData * fdata = memnew( FontData ); | ||||
| 		fdata->bitmap.resize( slot->bitmap.width*slot->bitmap.rows ); | ||||
| 		fdata->width=slot->bitmap.width; | ||||
| 		fdata->height=slot->bitmap.rows; | ||||
| 		fdata->character=charcode; | ||||
| 		fdata->glyph=FT_Get_Char_Index(face,charcode); | ||||
| 		if  (charcode=='x') | ||||
| 			xsize=slot->bitmap.width; | ||||
| 
 | ||||
| 
 | ||||
| 		if (charcode<127) { | ||||
| 			if (slot->bitmap_top>max_up) { | ||||
| 
 | ||||
| 				max_up=slot->bitmap_top; | ||||
| 			} | ||||
| 
 | ||||
| 
 | ||||
| 			if ( (slot->bitmap_top - fdata->height)<max_down ) { | ||||
| 
 | ||||
| 				max_down=slot->bitmap_top - fdata->height; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 
 | ||||
| 		fdata->valign=slot->bitmap_top; | ||||
| 		fdata->halign=slot->bitmap_left; | ||||
| 		fdata->advance=(slot->advance.x+(1<<5))>>6; | ||||
| 		fdata->advance+=font_spacing; | ||||
| 
 | ||||
| 		for (int i=0;i<slot->bitmap.width;i++) { | ||||
| 			for (int j=0;j<slot->bitmap.rows;j++) { | ||||
| 
 | ||||
| 				fdata->bitmap[j*slot->bitmap.width+i]=slot->bitmap.buffer[j*slot->bitmap.width+i]; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		font_data_list.push_back(fdata); | ||||
| 		charcode=FT_Get_Next_Char(face,charcode,&gindex); | ||||
| //                printf("reading char %i\n",charcode);
 | ||||
| 	} | ||||
| 
 | ||||
| 	/* SPACE */ | ||||
| 
 | ||||
| 	FontData *spd = memnew( FontData ); | ||||
| 	spd->advance=0; | ||||
| 	spd->character=' '; | ||||
| 	spd->halign=0; | ||||
| 	spd->valign=0; | ||||
| 	spd->width=0; | ||||
| 	spd->height=0; | ||||
| 	spd->ofs_x=0; | ||||
| 	spd->ofs_y=0; | ||||
| 
 | ||||
| 	if (!FT_Load_Char( face, ' ', FT_LOAD_RENDER ) && !FT_Render_Glyph( face->glyph, ft_render_mode_normal )) { | ||||
| 
 | ||||
| 		spd->advance = slot->advance.x>>6; | ||||
| 		spd->advance+=font_spacing; | ||||
| 	} else { | ||||
| 
 | ||||
| 		spd->advance=xsize; | ||||
| 		spd->advance+=font_spacing; | ||||
| 	} | ||||
| 
 | ||||
| 	font_data_list.push_back(spd); | ||||
| 
 | ||||
| 	Map<CharType, bool> exported; | ||||
| 	for (int i=0; i<font_data_list.size(); i++) { | ||||
| 		exported[font_data_list[i]->character] = true; | ||||
| 	}; | ||||
| 	int missing = 0; | ||||
| 	for(Map<CharType,int>::Element *E=import_chars.front();E;E=E->next()) { | ||||
| 		CharType c = E->key(); | ||||
| 		if (!exported.has(c)) { | ||||
| 			CharType str[2] = {c, 0}; | ||||
| 			printf("** Warning: character %i (%ls) not exported\n", (int)c, str); | ||||
| 			++missing; | ||||
| 		}; | ||||
| 	}; | ||||
| 	printf("total %i/%i\n", missing, import_chars.size()); | ||||
| 
 | ||||
| 	/* KERNING */ | ||||
| 
 | ||||
| 
 | ||||
| 	for(int i=0;i<font_data_list.size();i++) { | ||||
| 
 | ||||
| 		for(int j=0;j<font_data_list.size();j++) { | ||||
| 
 | ||||
| 			FT_Vector  delta; | ||||
| 			FT_Get_Kerning( face, font_data_list[i]->glyph,font_data_list[j]->glyph,  FT_KERNING_DEFAULT, &delta ); | ||||
| 
 | ||||
| 			if (delta.x!=0) { | ||||
| 
 | ||||
| 				KerningKey kpk; | ||||
| 				kpk.A = font_data_list[i]->character; | ||||
| 				kpk.B = font_data_list[j]->character; | ||||
| 				int kern = ((-delta.x)+(1<<5))>>6; | ||||
| 
 | ||||
| 				if (kern==0) | ||||
| 					continue; | ||||
| 				kerning_map[kpk]=kern; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	height=max_up-max_down; | ||||
| 	ascent=max_up; | ||||
| 
 | ||||
| 	/* FIND OUT WHAT THE FONT HEIGHT FOR THIS IS */ | ||||
| 
 | ||||
| 	/* ADJUST THE VALIGN FOR EACH CHARACTER */ | ||||
| 
 | ||||
| 	for (int i=0;i<(int)font_data_list.size();i++) { | ||||
| 
 | ||||
| 		font_data_list[i]->valign=max_up-font_data_list[i]->valign; | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 	/* ADD THE SPACEBAR CHARACTER */ | ||||
| /*
 | ||||
| 	FontData * fdata = new FontData; | ||||
| 
 | ||||
| 	fdata->character=32; | ||||
| 	fdata->bitmap=0; | ||||
| 	fdata->width=xsize; | ||||
| 	fdata->height=1; | ||||
| 	fdata->valign=0; | ||||
| 
 | ||||
| 	font_data_list.push_back(fdata); | ||||
| */ | ||||
| 	/* SORT BY HEIGHT, SO THEY FIT BETTER ON THE TEXTURE */ | ||||
| 
 | ||||
| 	font_data_list.sort_custom<FontDataSort>(); | ||||
| 
 | ||||
| 	int spacing=2; | ||||
| 
 | ||||
| 
 | ||||
| 	int use_width=256; | ||||
| 	int use_max_height=256; | ||||
| //	int surf_idx=-1;
 | ||||
| 
 | ||||
| 	List<Size2> tex_sizes; | ||||
| //	int current_texture=0;
 | ||||
| 
 | ||||
| 	Size2 first(use_width,nearest_power_of_2( font_data_list[0]->height + spacing )); | ||||
| 	Size2 *curtex=&tex_sizes.push_back(first)->get(); | ||||
| 
 | ||||
| 	Point2 tex_ofs; | ||||
| 
 | ||||
| 	/* FIT (NOT COPY YEY) FACES IN TEXTURES */ | ||||
| 
 | ||||
| 	int current_height=font_data_list[0]->height + spacing; | ||||
| 
 | ||||
| 	int font_margin=2; | ||||
| 
 | ||||
| 
 | ||||
| 	for(int i=0;i<font_data_list.size();i++) { | ||||
| 
 | ||||
| 		FontData *fd=font_data_list[i]; | ||||
| 
 | ||||
| 		if (tex_ofs.x+fd->width >= use_width) { | ||||
| 			//end of column, advance a row
 | ||||
| 			tex_ofs.x=0; | ||||
| 			tex_ofs.y+=current_height+font_margin; | ||||
| 			current_height=fd->height + spacing; | ||||
| 
 | ||||
| 			int new_tex_h = curtex->height; | ||||
| 
 | ||||
| 			while( tex_ofs.y+current_height > new_tex_h ) { | ||||
| 
 | ||||
| 				if (curtex->height * 2 > use_max_height) { | ||||
| 					//oops, can't use this texture anymore..
 | ||||
| 					Size2 newtex( use_width, nearest_power_of_2( fd->height + spacing )); | ||||
| 					new_tex_h=newtex.height;					 | ||||
| 					curtex=&tex_sizes.push_back(newtex)->get(); | ||||
| 					tex_ofs=Point2(0,0); | ||||
| 
 | ||||
| 				} else { | ||||
| 
 | ||||
| 					new_tex_h*=2; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			curtex->height=new_tex_h; | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 		fd->ofs_x=tex_ofs.x; | ||||
| 		fd->ofs_y=tex_ofs.y; | ||||
| 		fd->texture=tex_sizes.size()-1; | ||||
| 
 | ||||
| 		tex_ofs.x+=fd->width+font_margin; | ||||
| 
 | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	/* WRITE FACES IN TEXTURES */ | ||||
| 
 | ||||
| 	// create textures
 | ||||
| 
 | ||||
| 	Vector<DVector<uint8_t> >image_data; | ||||
| 	Vector<int> image_widths; | ||||
| 	Vector<DVector<uint8_t>::Write> image_ptrs; | ||||
| 	image_ptrs.resize(tex_sizes.size()); | ||||
| 
 | ||||
| 	for(int i=0;i<tex_sizes.size();i++) { | ||||
| 
 | ||||
| 		DVector<uint8_t> pixels; | ||||
| 		int texsize=tex_sizes[i].width * tex_sizes[i].height * 2; | ||||
| 		pixels.resize(texsize ); | ||||
| 
 | ||||
| 		image_data.push_back(pixels); | ||||
| 		image_widths.push_back( tex_sizes[i].width ); | ||||
| 		image_ptrs[i] = image_data[i].write(); | ||||
| 		for(int j=0;j<texsize;j++) { | ||||
| 
 | ||||
| 			image_ptrs[i][j]=0; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	//blit textures with fonts
 | ||||
| 	for(int i=0;i<font_data_list.size();i++) { | ||||
| 
 | ||||
| 		FontData *fd=font_data_list[i]; | ||||
| 
 | ||||
| 		uint8_t *pixels = image_ptrs[fd->texture].ptr(); | ||||
| 		int width = image_widths[fd->texture]; | ||||
| 
 | ||||
| 		for(int y=0;y<fd->height;y++) { | ||||
| 
 | ||||
| 			const uint8_t *src = &fd->bitmap[y*fd->width]; | ||||
| 			uint8_t *dst = &pixels[((fd->ofs_y+y)*width+fd->ofs_x)*2]; | ||||
| 
 | ||||
| 
 | ||||
| 			for(int x=0;x<fd->width;x++) { | ||||
| 
 | ||||
| 				dst[x<<1]=255; //white always
 | ||||
| 				dst[(x<<1) +1]=src[x]; | ||||
| 
 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	//unlock writing
 | ||||
| 	for(int i=0;i<image_ptrs.size();i++) | ||||
| 		image_ptrs[i]=DVector<uint8_t>::Write(); | ||||
| 
 | ||||
| 	/* CREATE FONT */ | ||||
| 
 | ||||
| 	font->clear(); | ||||
| 	font->set_height(height); | ||||
| 	font->set_ascent(ascent); | ||||
| 
 | ||||
| 	//register texures
 | ||||
| 	for(int i=0;i<tex_sizes.size();i++) { | ||||
| 		Image img(tex_sizes[i].width,tex_sizes[i].height,0,Image::FORMAT_GRAYSCALE_ALPHA,image_data[i]); | ||||
| 		Ref<ImageTexture> tex = memnew( ImageTexture ); | ||||
| 		tex->create_from_image(img,0); //no filter, no repeat
 | ||||
| 		font->add_texture(tex); | ||||
| 		//tframe->set_texture(tex);
 | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	//register characters
 | ||||
| 
 | ||||
| 	for(int i=0;i<font_data_list.size();i++) { | ||||
| 		FontData *fd=font_data_list[i]; | ||||
| 
 | ||||
| 		font->add_char(fd->character,fd->texture,Rect2( fd->ofs_x, fd->ofs_y, fd->width, fd->height),Point2(fd->halign,fd->valign), fd->advance); | ||||
| 		memdelete(fd); | ||||
| 	} | ||||
| 
 | ||||
| 	for(Map<KerningKey,int>::Element *E=kerning_map.front();E;E=E->next()) { | ||||
| 
 | ||||
| 		font->add_kerning_pair(E->key().A,E->key().B,E->get()); | ||||
| 	} | ||||
| 
 | ||||
| 	FT_Done_FreeType( library ); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void FontEditor::_add_source() { | ||||
| 
 | ||||
| 	_source_file->popup_centered_ratio(); | ||||
| }; | ||||
| 
 | ||||
| void FontEditor::_add_source_accept(const String& p_file) { | ||||
| 
 | ||||
| 	FileAccess* f = FileAccess::open(p_file, FileAccess::READ); | ||||
| 	ERR_FAIL_COND(!f); | ||||
| 
 | ||||
| 	String line; | ||||
| 	while ( !f->eof_reached() ) { | ||||
| 
 | ||||
| 		line = f->get_line(); | ||||
| 		for (int i=0; i<line.length(); i++) { | ||||
| 
 | ||||
| 			if (import_chars.has(line[i])) { | ||||
| 				import_chars[line[i]] = import_chars[line[i]] + 1; | ||||
| 			} else { | ||||
| 				import_chars[line[i]] = 1; | ||||
| 			}; | ||||
| 		}; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| void FontEditor::_export_fnt_pressed() { | ||||
| 
 | ||||
| 	_export_file->popup_centered_ratio(); | ||||
| }; | ||||
| 
 | ||||
| void FontEditor::_export_fnt_accept(const String& p_file) { | ||||
| 
 | ||||
| 	String name = p_file.replace(".fnt", ""); | ||||
| 	_export_fnt(name, font); | ||||
| }; | ||||
| 
 | ||||
| void FontEditor::_import_accept(const String& p_string) { | ||||
| 
 | ||||
| #ifdef FREETYPE_ENABLED | ||||
| 
 | ||||
| 	if (p_string.extension().nocasecmp_to("ttf")==0 || p_string.extension().nocasecmp_to("otf")==0) { | ||||
| 
 | ||||
| 		_import_ttf(p_string); | ||||
| 	} | ||||
| #endif | ||||
| 
 | ||||
| 	if (p_string.extension().nocasecmp_to("fnt")==0) { | ||||
| 
 | ||||
| 		_import_fnt(p_string); | ||||
| 	} | ||||
| 
 | ||||
| 	label->add_font_override("font",font); | ||||
| 	label->notification(Control::NOTIFICATION_THEME_CHANGED); | ||||
| 	label->update(); | ||||
| } | ||||
| 
 | ||||
| void FontEditor::_import() { | ||||
| 
 | ||||
| 
 | ||||
| 	file->popup_centered_ratio(); | ||||
| } | ||||
| 
 | ||||
| void FontEditor::_bind_methods() { | ||||
| 
 | ||||
| 	ObjectTypeDB::bind_method("_import",&FontEditor::_import); | ||||
| 	ObjectTypeDB::bind_method("_import_accept",&FontEditor::_import_accept); | ||||
| 	ObjectTypeDB::bind_method("_preview_text_changed",&FontEditor::_preview_text_changed); | ||||
| 	ObjectTypeDB::bind_method("_add_source",&FontEditor::_add_source); | ||||
| 	ObjectTypeDB::bind_method("_add_source_accept",&FontEditor::_add_source_accept); | ||||
| 	ObjectTypeDB::bind_method("_export_fnt_pressed",&FontEditor::_export_fnt_pressed); | ||||
| 	ObjectTypeDB::bind_method("_export_fnt_accept",&FontEditor::_export_fnt_accept); | ||||
| } | ||||
| 
 | ||||
| FontEditor::FontEditor() { | ||||
| 
 | ||||
| 	panel = memnew( Panel ); | ||||
| 	add_child(panel); | ||||
| 	panel->set_area_as_parent_rect(); | ||||
| 
 | ||||
| 	/*
 | ||||
| 	tframe = memnew( TextureFrame ); | ||||
| 
 | ||||
| 	tframe->set_anchor( MARGIN_RIGHT, ANCHOR_END ); | ||||
| 	tframe->set_anchor( MARGIN_BOTTOM, ANCHOR_END ); | ||||
| 
 | ||||
| 	tframe->set_begin( Point2(5, 40 ) ); | ||||
| 	tframe->set_end( Point2(5,55 ) ); | ||||
| 
 | ||||
| 	panel->add_child(tframe); | ||||
| */ | ||||
| 
 | ||||
| 	Label *l = memnew( Label ); | ||||
| 	l->set_pos( Point2(5,13 ) ); | ||||
| 	l->set_text("Import: "); | ||||
| 
 | ||||
| 	panel->add_child(l); | ||||
| 
 | ||||
| 	l = memnew( Label ); | ||||
| 	l->set_pos( Point2(25,37 ) ); | ||||
| 	l->set_text("Size: "); | ||||
| 
 | ||||
| 	panel->add_child(l); | ||||
| 
 | ||||
| 	font_size = memnew( LineEdit ); | ||||
| 	font_size->set_text("12"); | ||||
| 	font_size->set_pos( Point2(70,35 ) ); | ||||
| 	font_size->set_size( Size2(40,10 ) ); | ||||
| 	panel->add_child(font_size); | ||||
| 
 | ||||
| 	l = memnew( Label ); | ||||
| 	l->set_pos( Point2(140,37 ) ); | ||||
| 	l->set_text("Encoding: "); | ||||
| 
 | ||||
| 	panel->add_child(l); | ||||
| 
 | ||||
| 	import_option = memnew( OptionButton ); | ||||
| 	import_option->add_item("Ascii"); | ||||
| 	import_option->add_item("Latin"); | ||||
| 	import_option->add_item("Full Unicode"); | ||||
| 	import_option->select(1); | ||||
| 
 | ||||
| 	import_option->set_pos( Point2( 215,35 ) ); | ||||
| 	import_option->set_size( Point2( 100,12 ) ); | ||||
| 
 | ||||
| 	panel->add_child(import_option); | ||||
| 
 | ||||
| 	Button* import = memnew( Button ); | ||||
| 	import->set_text("Import:.."); | ||||
| 	import->set_begin( Point2(80,35) ); | ||||
| 	import->set_end( Point2(10,45) ); | ||||
| 
 | ||||
| 	import->set_anchor( MARGIN_LEFT, ANCHOR_END ); | ||||
| 	import->set_anchor( MARGIN_RIGHT, ANCHOR_END ); | ||||
| 
 | ||||
| 	panel->add_child(import); | ||||
| 
 | ||||
| 	Button* add_source = memnew( Button ); | ||||
| 	add_source->set_text("Add Source..."); | ||||
| 	add_source->set_begin( Point2(180,35) ); | ||||
| 	add_source->set_end( Point2(90,45) ); | ||||
| 	add_source->set_anchor( MARGIN_LEFT, ANCHOR_END ); | ||||
| 	add_source->set_anchor( MARGIN_RIGHT, ANCHOR_END ); | ||||
| 
 | ||||
| 	panel->add_child(add_source); | ||||
| 
 | ||||
| 	file = memnew( FileDialog ); | ||||
| 	file->set_access(FileDialog::ACCESS_FILESYSTEM); | ||||
| 
 | ||||
| 	_source_file = memnew( FileDialog ); | ||||
| 	_source_file->set_access(FileDialog::ACCESS_FILESYSTEM); | ||||
| 	_source_file->set_mode(FileDialog::MODE_OPEN_FILE); | ||||
| 	_source_file->connect("file_selected", this, "_add_source_accept"); | ||||
| 	panel->add_child( _source_file ); | ||||
| 
 | ||||
| 	Button* export_fnt = memnew(Button); | ||||
| 	export_fnt->set_text("Export fnt"); | ||||
| 	export_fnt->set_begin(Point2(80, 65)); | ||||
| 	export_fnt->set_end(Point2(10, 75)); | ||||
| 	export_fnt->set_anchor( MARGIN_LEFT, ANCHOR_END ); | ||||
| 	export_fnt->set_anchor( MARGIN_RIGHT, ANCHOR_END ); | ||||
| 	export_fnt->connect("pressed", this, "_export_fnt_pressed"); | ||||
| 	panel->add_child( export_fnt ); | ||||
| 
 | ||||
| 	_export_file = memnew(FileDialog); | ||||
| 	_export_file->set_access(FileDialog::ACCESS_FILESYSTEM); | ||||
| 	_export_file->set_mode(FileDialog::MODE_SAVE_FILE); | ||||
| 	_export_file->connect("file_selected", this, "_export_fnt_accept"); | ||||
| 	panel->add_child(_export_file); | ||||
| 
 | ||||
| 	l = memnew( Label ); | ||||
| 	l->set_pos( Point2(5,65 ) ); | ||||
| 	l->set_text("Preview Text: "); | ||||
| 
 | ||||
| 	panel->add_child(l); | ||||
| 
 | ||||
| 	preview_text = memnew( LineEdit ); | ||||
| 	preview_text->set_anchor( MARGIN_RIGHT, ANCHOR_END ); | ||||
| 	preview_text->set_begin( Point2(25,85 ) ); | ||||
| 	preview_text->set_end( Point2(10,95 ) ); | ||||
| 	panel->add_child(preview_text); | ||||
| 	preview_text->connect("text_changed", this,"_preview_text_changed"); | ||||
| 	preview_text->set_text("The quick brown fox jumped over the lazy dog."); | ||||
| 
 | ||||
| 	l = memnew( Label ); | ||||
| 	l->set_pos( Point2(5,115 ) ); | ||||
| 	l->set_text("Preview: "); | ||||
| 
 | ||||
| 	panel->add_child(l); | ||||
| 
 | ||||
| 	label = memnew( Label ); | ||||
| 	label->set_autowrap(true); | ||||
| 
 | ||||
| 	label->set_anchor( MARGIN_RIGHT, ANCHOR_END ); | ||||
| 	label->set_anchor( MARGIN_BOTTOM, ANCHOR_END ); | ||||
| 
 | ||||
| 	label->set_begin( Point2(5, 135 ) ); | ||||
| 	label->set_end( Point2(5,5 ) ); | ||||
| 
 | ||||
| 	label->set_text("The quick brown fox jumped over the lazy dog."); | ||||
| 	label->set_align( Label::ALIGN_CENTER ); | ||||
| 
 | ||||
| 	panel->add_child(label); | ||||
| 
 | ||||
| #ifdef FREETYPE_ENABLED | ||||
| 
 | ||||
| 	file->add_filter("*.ttf"); | ||||
| 	file->add_filter("*.otf"); | ||||
| #endif | ||||
| 	file->add_filter("*.fnt ; AngelCode BMFont"); | ||||
| 
 | ||||
| 	file->set_mode(FileDialog::MODE_OPEN_FILE); | ||||
| 	panel->add_child( file ); | ||||
| 
 | ||||
| 	import->connect("pressed", this,"_import"); | ||||
| 	file->connect("file_selected", this,"_import_accept"); | ||||
| 	add_source->connect("pressed", this, "_add_source"); | ||||
| } | ||||
| 
 | ||||
| void FontEditorPlugin::edit(Object *p_node) { | ||||
| 
 | ||||
| 	if (p_node && p_node->cast_to<Font>()) { | ||||
| 		font_editor->edit( p_node->cast_to<Font>() ); | ||||
| 		font_editor->show(); | ||||
| 	} else | ||||
| 		font_editor->hide(); | ||||
| } | ||||
| 
 | ||||
| bool FontEditorPlugin::handles(Object *p_node) const{ | ||||
| 
 | ||||
| 	return p_node->is_type("Font"); | ||||
| } | ||||
| 
 | ||||
| void FontEditorPlugin::make_visible(bool p_visible){ | ||||
| 
 | ||||
| 	if (p_visible) | ||||
| 		font_editor->show(); | ||||
| 	else | ||||
| 		font_editor->hide(); | ||||
| } | ||||
| 
 | ||||
| FontEditorPlugin::FontEditorPlugin(EditorNode *p_node) { | ||||
| 
 | ||||
| 	font_editor = memnew( FontEditor ); | ||||
| 
 | ||||
| 	p_node->get_viewport()->add_child(font_editor); | ||||
| 	font_editor->set_area_as_parent_rect(); | ||||
| 	font_editor->hide(); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | @ -1,100 +0,0 @@ | |||
| /*************************************************************************/ | ||||
| /*  font_editor_plugin.h                                                 */ | ||||
| /*************************************************************************/ | ||||
| /*                       This file is part of:                           */ | ||||
| /*                           GODOT ENGINE                                */ | ||||
| /*                    http://www.godotengine.org                         */ | ||||
| /*************************************************************************/ | ||||
| /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */ | ||||
| /*                                                                       */ | ||||
| /* Permission is hereby granted, free of charge, to any person obtaining */ | ||||
| /* a copy of this software and associated documentation files (the       */ | ||||
| /* "Software"), to deal in the Software without restriction, including   */ | ||||
| /* without limitation the rights to use, copy, modify, merge, publish,   */ | ||||
| /* distribute, sublicense, and/or sell copies of the Software, and to    */ | ||||
| /* permit persons to whom the Software is furnished to do so, subject to */ | ||||
| /* the following conditions:                                             */ | ||||
| /*                                                                       */ | ||||
| /* The above copyright notice and this permission notice shall be        */ | ||||
| /* included in all copies or substantial portions of the Software.       */ | ||||
| /*                                                                       */ | ||||
| /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */ | ||||
| /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */ | ||||
| /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ | ||||
| /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */ | ||||
| /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */ | ||||
| /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */ | ||||
| /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */ | ||||
| /*************************************************************************/ | ||||
| #ifndef FONT_EDITOR_PLUGIN_H | ||||
| #define FONT_EDITOR_PLUGIN_H | ||||
| 
 | ||||
| #include "scene/resources/font.h" | ||||
| #include "scene/gui/texture_frame.h" | ||||
| #include "scene/gui/option_button.h" | ||||
| #include "tools/editor/editor_node.h" | ||||
| 
 | ||||
| 
 | ||||
| class FontEditor : public Control { | ||||
| 
 | ||||
| 	OBJ_TYPE( FontEditor, Control ); | ||||
| 
 | ||||
| 	Panel *panel; | ||||
| 	LineEdit *font_size; | ||||
| 	//TextureFrame *tframe; //for debug
 | ||||
| 	Label *label; | ||||
| 	LineEdit *preview_text; | ||||
| 	FileDialog *file; | ||||
| 	FileDialog* _source_file; | ||||
| 	FileDialog* _export_file; | ||||
| 	OptionButton *import_option; | ||||
| 
 | ||||
| 	Ref<Font> font; | ||||
| 
 | ||||
| 	Map<CharType, int> import_chars; | ||||
| 
 | ||||
| 	void _export_fnt(const String& p_name, Ref<Font> p_font); | ||||
| 	void _export_fnt_pressed(); | ||||
| 	void _export_fnt_accept(const String& p_file); | ||||
| 
 | ||||
| 	void _import_ttf(const String& p_string); | ||||
| 	void _import_fnt(const String& p_string); | ||||
| 	void _preview_text_changed(const String& p_text); | ||||
| 
 | ||||
| 	void _add_source(); | ||||
| 	void _add_source_accept(const String& p_file); | ||||
| 
 | ||||
| 	void _import_accept(const String&); | ||||
| 	void _import(); | ||||
| protected: | ||||
| 	static void _bind_methods(); | ||||
| public: | ||||
| 
 | ||||
| 	void edit(const Ref<Font>& p_font); | ||||
| 
 | ||||
| 	FontEditor(); | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class FontEditorPlugin : public EditorPlugin { | ||||
| 
 | ||||
| 	OBJ_TYPE( FontEditorPlugin, EditorPlugin ); | ||||
| 
 | ||||
| 	FontEditor *font_editor; | ||||
| 	EditorNode *editor; | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
| 	virtual String get_name() const { return "Font"; } | ||||
| 	bool has_main_screen() const { return false; } | ||||
| 	virtual void edit(Object *p_node); | ||||
| 	virtual bool handles(Object *p_node) const; | ||||
| 	virtual void make_visible(bool p_visible); | ||||
| 
 | ||||
| 	FontEditorPlugin(EditorNode *p_node); | ||||
| 
 | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #endif // FONT_EDITOR_PLUGIN_H
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Feenstra
						Vinzenz Feenstra