Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export support for gdscript resource types #26162

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
43 changes: 36 additions & 7 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 @@ -2287,6 +2291,16 @@ void EditorPropertyResource::_update_menu_items() {
E = E->next();
}

List<StringName> global_classes;
ScriptServer::get_global_class_list(&global_classes);
E = global_classes.front();
while (E) {
if (EditorNode::get_editor_data().script_class_is_parent(E->get(), base_type)) {
valid_inheritors.insert(E->get());
}
E = E->next();
}

for (Set<String>::Element *F = valid_inheritors.front(); F; F = F->next()) {
String t = F->get();

Expand All @@ -2303,7 +2317,7 @@ void EditorPropertyResource::_update_menu_items() {
}
}

if (!is_custom_resource && !ClassDB::can_instance(t))
if (!is_custom_resource && !(ScriptServer::is_global_class(t) || ClassDB::can_instance(t)))
continue;

inheritors_array.push_back(t);
Expand Down Expand Up @@ -2569,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 @@ -2702,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 @@ -2754,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency/clarity, I would recommend making this be if (rscript.is_valid()) or something.

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 @@ -3217,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);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just clean whitespaces in empty line to pass clang-format in Travis

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