Skip to content

Commit

Permalink
GDScript support for exporting script defined global classes
Browse files Browse the repository at this point in the history
  • Loading branch information
vixelz committed Mar 5, 2019
1 parent a6152db commit 2b7b64f
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 63 deletions.
8 changes: 8 additions & 0 deletions editor/editor_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,14 @@ void EditorData::get_plugin_window_layout(Ref<ConfigFile> p_layout) {
}
}

bool EditorData::class_equals_or_inherits(const String &p_class, const String &p_inherits) {
if (p_class == p_inherits)
return true;
if (ScriptServer::is_global_class(p_class))
return script_class_is_parent(p_class, p_inherits);
return ClassDB::is_parent_class(p_class, p_inherits);
}

bool EditorData::script_class_is_parent(const String &p_class, const String &p_inherits) {
if (!ScriptServer::is_global_class(p_class))
return false;
Expand Down
1 change: 1 addition & 0 deletions editor/editor_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ class EditorData {
void notify_edited_scene_changed();
void notify_resource_saved(const Ref<Resource> &p_resource);

bool class_equals_or_inherits(const String &p_class, const String &p_inherits);
bool script_class_is_parent(const String &p_class, const String &p_inherits);
StringName script_class_get_base(const String &p_class) const;
Object *script_class_instance(const String &p_class);
Expand Down
31 changes: 25 additions & 6 deletions editor/editor_properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1994,15 +1994,16 @@ void EditorPropertyResource::_file_selected(const String &p_path) {
if (!property_types.empty()) {
bool any_type_matches = false;
const Vector<String> split_property_types = property_types.split(",");
String res_class = _get_file_script_name_or_default(res);
for (int i = 0; i < split_property_types.size(); ++i) {
if (res->is_class(split_property_types[i])) {
if (EditorNode::get_editor_data().class_equals_or_inherits(res_class, split_property_types[i])) {
any_type_matches = true;
break;
}
}

if (!any_type_matches)
EditorNode::get_singleton()->show_warning(vformat(TTR("The selected resource (%s) does not match any type expected for this property (%s)."), res->get_class(), property_types));
EditorNode::get_singleton()->show_warning(vformat(TTR("The selected resource (%s) does not match any type expected for this property (%s)."), res_class, property_types));
}

emit_changed(get_edited_property(), res);
Expand All @@ -2022,6 +2023,9 @@ void EditorPropertyResource::_menu_option(int p_which) {
}
file->set_mode(EditorFileDialog::MODE_OPEN_FILE);
String type = base_type;
while (ScriptServer::is_global_class(type)) {
type = ScriptServer::get_global_class_base(type);
}

List<String> extensions;
for (int i = 0; i < type.get_slice_count(","); i++) {
Expand Down Expand Up @@ -2579,7 +2583,7 @@ void EditorPropertyResource::update_property() {
assign->set_text(res->get_path().get_file());
assign->set_tooltip(res->get_path());
} else {
assign->set_text(res->get_class());
assign->set_text(_get_file_script_name_or_default(res));
}

if (res->get_path().is_resource_file()) {
Expand Down Expand Up @@ -2712,10 +2716,12 @@ bool EditorPropertyResource::_is_drop_valid(const Dictionary &p_drag_data) const
String ftype = EditorFileSystem::get_singleton()->get_file_type(file);

if (ftype != "") {
RES res = ResourceLoader::load(file);
ftype = _get_file_script_name_or_default(res);

for (int i = 0; i < allowed_type.get_slice_count(","); i++) {
String at = allowed_type.get_slice(",", i).strip_edges();
if (ClassDB::is_parent_class(ftype, at)) {
if (EditorNode::get_editor_data().class_equals_or_inherits(ftype, at)) {
return true;
}
}
Expand Down Expand Up @@ -2764,6 +2770,20 @@ void EditorPropertyResource::set_use_sub_inspector(bool p_enable) {
use_sub_inspector = p_enable;
}

String EditorPropertyResource::_get_file_script_name_or_default(const RES &p_resource) const {
Ref<Script> rscript = p_resource->get_script();
if (rscript != 0) {
String rscript_path = rscript->get_path();
int script_index;
EditorFileSystemDirectory *fsdir = EditorFileSystem::get_singleton()->find_file(rscript_path, &script_index);
ERR_FAIL_COND_V(!fsdir, p_resource->get_class());
String file_script_name = fsdir->get_file_script_class_name(script_index);
if (!file_script_name.empty())
return file_script_name;
}
return p_resource->get_class();
}

void EditorPropertyResource::_bind_methods() {

ClassDB::bind_method(D_METHOD("_file_selected"), &EditorPropertyResource::_file_selected);
Expand Down Expand Up @@ -3227,8 +3247,7 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ
for (int j = 0; j < p_hint_text.get_slice_count(","); j++) {
String inherits = p_hint_text.get_slicec(',', j);

if (ClassDB::is_parent_class(inherits, type)) {

if (!ScriptServer::is_global_class(inherits) && ClassDB::is_parent_class(inherits, type)) {
editor->set_use_sub_inspector(false);
}
}
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,8 @@ class EditorPropertyResource : public EditorProperty {
void _button_input(const Ref<InputEvent> &p_event);
void _open_editor_pressed();
void _fold_other_editors(Object *p_self);

String _get_file_script_name_or_default(const RES &p_resource) const;

bool opened_editor;

Expand Down
148 changes: 91 additions & 57 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4345,71 +4345,81 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
parenthesis--;

if (subexpr->type != Node::TYPE_CONSTANT) {
current_export = PropertyInfo();
_set_error("Expected a constant expression.");
}
if (subexpr->type == Node::TYPE_IDENTIFIER) {
String type_identifier = static_cast<IdentifierNode *>(subexpr)->name;

Variant constant = static_cast<ConstantNode *>(subexpr)->value;
current_export.type = Variant::OBJECT;
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;

if (constant.get_type() == Variant::OBJECT) {
GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant);
current_export.hint_string = type_identifier;
current_export.class_name = type_identifier;
} else if (subexpr->type == Node::TYPE_CONSTANT) {

if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
current_export.type = Variant::OBJECT;
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
Variant constant = static_cast<ConstantNode *>(subexpr)->value;

current_export.hint_string = native_class->get_name();
current_export.class_name = native_class->get_name();
if (constant.get_type() == Variant::OBJECT) {
GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant);

} else {
current_export = PropertyInfo();
_set_error("Export hint not a resource type.");
}
} else if (constant.get_type() == Variant::DICTIONARY) {
// Enumeration
bool is_flags = false;
if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
current_export.type = Variant::OBJECT;
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;

if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();
current_export.hint_string = native_class->get_name();
current_export.class_name = native_class->get_name();

if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
is_flags = true;
tokenizer->advance();
} else {
current_export = PropertyInfo();
_set_error("Expected 'FLAGS' after comma.");
_set_error("Export hint not a resource type.");
}
}
} else if (constant.get_type() == Variant::DICTIONARY) {
// Enumeration
bool is_flags = false;

current_export.type = Variant::INT;
current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM;
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
Dictionary enum_values = constant;

List<Variant> keys;
enum_values.get_key_list(&keys);

bool first = true;
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (enum_values[E->get()].get_type() == Variant::INT) {
if (!first)
current_export.hint_string += ",";
else
first = false;

current_export.hint_string += E->get().operator String().camelcase_to_underscore(true).capitalize().xml_escape();
if (!is_flags) {
current_export.hint_string += ":";
current_export.hint_string += enum_values[E->get()].operator String().xml_escape();
if (tokenizer->get_token() == GDScriptTokenizer::TK_COMMA) {
tokenizer->advance();

if (tokenizer->get_token() == GDScriptTokenizer::TK_IDENTIFIER && tokenizer->get_token_identifier() == "FLAGS") {
is_flags = true;
tokenizer->advance();
} else {
current_export = PropertyInfo();
_set_error("Expected 'FLAGS' after comma.");
}
}

current_export.type = Variant::INT;
current_export.hint = is_flags ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM;
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
Dictionary enum_values = constant;

List<Variant> keys;
enum_values.get_key_list(&keys);

bool first = true;
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
if (enum_values[E->get()].get_type() == Variant::INT) {
if (!first)
current_export.hint_string += ",";
else
first = false;

current_export.hint_string += E->get().operator String().camelcase_to_underscore(true).capitalize().xml_escape();
if (!is_flags) {
current_export.hint_string += ":";
current_export.hint_string += enum_values[E->get()].operator String().xml_escape();
}
}
}
} else {
current_export = PropertyInfo();
_set_error("Expected type for export.");
return;
}
} else {
current_export = PropertyInfo();
_set_error("Expected type for export.");
return;
_set_error("Expected a constant or identifier expression.");
}
}

Expand Down Expand Up @@ -5440,6 +5450,12 @@ GDScriptParser::DataType GDScriptParser::_resolve_type(const DataType &p_source,

ClassNode *p = NULL;
if (name_part == 0) {
if (GDScriptLanguage::get_singleton()->get_global_map().has(id)) {
int idx = GDScriptLanguage::get_singleton()->get_global_map()[id];
Variant g = GDScriptLanguage::get_singleton()->get_global_array()[idx];
return _type_from_variant(g);
}

if (ScriptServer::is_global_class(id)) {
String script_path = ScriptServer::get_global_class_path(id);
if (script_path == self_path) {
Expand Down Expand Up @@ -7457,14 +7473,32 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
#endif
}

// Check export hint
if (v.data_type.has_type && v._export.type != Variant::NIL) {
DataType export_type = _type_from_property(v._export);
if (!_is_type_compatible(v.data_type, export_type, true)) {
_set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
v.data_type.to_string() + ").",
v.line);
return;
if (v._export.type != Variant::NIL) {
DataType export_type = DataType();
export_type.kind = DataType::UNRESOLVED;
export_type.native_type = v._export.class_name;
export_type.has_type = true;
export_type = _resolve_type(export_type, v.line);

if (export_type.kind == DataType::GDSCRIPT || export_type.kind == DataType::SCRIPT) {
String class_name = v._export.class_name;
while (ScriptServer::is_global_class(class_name)) {
class_name = ScriptServer::get_global_class_base(class_name);
}
if (class_name != "Resource") {
_set_error("Exported script-defined type(" + export_type.to_string() + ") must inherit from Resource.", v.line);
return;
}
}

// Check export hint
if (v.data_type.has_type) {
if (!_is_type_compatible(v.data_type, export_type, true)) {
_set_error("Export hint type (" + export_type.to_string() + ") doesn't match the variable's type (" +
v.data_type.to_string() + ").",
v.line);
return;
}
}
}

Expand Down

0 comments on commit 2b7b64f

Please sign in to comment.