diff --git a/doc/classes/VisualShaderNodeIntParameter.xml b/doc/classes/VisualShaderNodeIntParameter.xml index ed72584b1b3..ba17da49a82 100644 --- a/doc/classes/VisualShaderNodeIntParameter.xml +++ b/doc/classes/VisualShaderNodeIntParameter.xml @@ -15,6 +15,9 @@ If [code]true[/code], the node will have a custom default value. + + The names used for the enum select in the editor. [member hint] must be [constant HINT_ENUM] for this to take effect. + Range hint of this node. Use it to customize valid parameter range. @@ -38,7 +41,10 @@ The parameter's value must be within the specified range, with the given [member step] between values. - + + The parameter uses an enum to associate preset values to names in the editor. + + Represents the size of the [enum Hint] enum. diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index 5f70a24fcd6..5e148c9276d 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -5368,6 +5368,20 @@ String VisualShaderNodeIntParameter::generate_global(Shader::Mode p_mode, Visual code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ")"; } else if (hint == HINT_RANGE_STEP) { code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_range(" + itos(hint_range_min) + ", " + itos(hint_range_max) + ", " + itos(hint_range_step) + ")"; + } else if (hint == HINT_ENUM) { + code += _get_qual_str() + "uniform int " + get_parameter_name() + " : hint_enum("; + + bool first = true; + for (const String &_name : hint_enum_names) { + if (first) { + first = false; + } else { + code += ", "; + } + code += "\"" + _name.c_escape() + "\""; + } + + code += ")"; } else { code += _get_qual_str() + "uniform int " + get_parameter_name(); } @@ -5439,6 +5453,18 @@ int VisualShaderNodeIntParameter::get_step() const { return hint_range_step; } +void VisualShaderNodeIntParameter::set_enum_names(const PackedStringArray &p_names) { + if (hint_enum_names == p_names) { + return; + } + hint_enum_names = p_names; + emit_changed(); +} + +PackedStringArray VisualShaderNodeIntParameter::get_enum_names() const { + return hint_enum_names; +} + void VisualShaderNodeIntParameter::set_default_value_enabled(bool p_default_value_enabled) { if (default_value_enabled == p_default_value_enabled) { return; @@ -5476,22 +5502,27 @@ void VisualShaderNodeIntParameter::_bind_methods() { ClassDB::bind_method(D_METHOD("set_step", "value"), &VisualShaderNodeIntParameter::set_step); ClassDB::bind_method(D_METHOD("get_step"), &VisualShaderNodeIntParameter::get_step); + ClassDB::bind_method(D_METHOD("set_enum_names", "names"), &VisualShaderNodeIntParameter::set_enum_names); + ClassDB::bind_method(D_METHOD("get_enum_names"), &VisualShaderNodeIntParameter::get_enum_names); + ClassDB::bind_method(D_METHOD("set_default_value_enabled", "enabled"), &VisualShaderNodeIntParameter::set_default_value_enabled); ClassDB::bind_method(D_METHOD("is_default_value_enabled"), &VisualShaderNodeIntParameter::is_default_value_enabled); ClassDB::bind_method(D_METHOD("set_default_value", "value"), &VisualShaderNodeIntParameter::set_default_value); ClassDB::bind_method(D_METHOD("get_default_value"), &VisualShaderNodeIntParameter::get_default_value); - ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step"), "set_hint", "get_hint"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,Range + Step,Enum"), "set_hint", "get_hint"); ADD_PROPERTY(PropertyInfo(Variant::INT, "min"), "set_min", "get_min"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max"), "set_max", "get_max"); ADD_PROPERTY(PropertyInfo(Variant::INT, "step"), "set_step", "get_step"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "enum_names"), "set_enum_names", "get_enum_names"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "default_value_enabled"), "set_default_value_enabled", "is_default_value_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "default_value"), "set_default_value", "get_default_value"); BIND_ENUM_CONSTANT(HINT_NONE); BIND_ENUM_CONSTANT(HINT_RANGE); BIND_ENUM_CONSTANT(HINT_RANGE_STEP); + BIND_ENUM_CONSTANT(HINT_ENUM); BIND_ENUM_CONSTANT(HINT_MAX); } @@ -5513,6 +5544,9 @@ Vector VisualShaderNodeIntParameter::get_editable_properties() const if (hint == HINT_RANGE_STEP) { props.push_back("step"); } + if (hint == HINT_ENUM) { + props.push_back("enum_names"); + } props.push_back("default_value_enabled"); if (default_value_enabled) { props.push_back("default_value"); diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h index 7a37ffa0e0d..279599ef9c7 100644 --- a/scene/resources/visual_shader_nodes.h +++ b/scene/resources/visual_shader_nodes.h @@ -2115,6 +2115,7 @@ public: HINT_NONE, HINT_RANGE, HINT_RANGE_STEP, + HINT_ENUM, HINT_MAX, }; @@ -2123,6 +2124,7 @@ private: int hint_range_min = 0; int hint_range_max = 100; int hint_range_step = 1; + PackedStringArray hint_enum_names; bool default_value_enabled = false; int default_value = 0; @@ -2158,6 +2160,9 @@ public: void set_step(int p_value); int get_step() const; + void set_enum_names(const PackedStringArray &p_names); + PackedStringArray get_enum_names() const; + void set_default_value_enabled(bool p_enabled); bool is_default_value_enabled() const; diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 1e545237751..f8d00ba7ecb 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -96,6 +96,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "FLOAT_CONSTANT", "INT_CONSTANT", "UINT_CONSTANT", + "STRING_CONSTANT", "TYPE_VOID", "TYPE_BOOL", "TYPE_BVEC2", @@ -212,6 +213,7 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "HINT_ANISOTROPY_TEXTURE", "HINT_SOURCE_COLOR", "HINT_RANGE", + "HINT_ENUM", "HINT_INSTANCE_INDEX", "HINT_SCREEN_TEXTURE", "HINT_NORMAL_ROUGHNESS_TEXTURE", @@ -365,6 +367,7 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = { { TK_HINT_SOURCE_COLOR, "source_color", CF_UNSPECIFIED, {}, {} }, { TK_HINT_RANGE, "hint_range", CF_UNSPECIFIED, {}, {} }, + { TK_HINT_ENUM, "hint_enum", CF_UNSPECIFIED, {}, {} }, { TK_HINT_INSTANCE_INDEX, "instance_index", CF_UNSPECIFIED, {}, {} }, // sampler hints @@ -512,7 +515,54 @@ ShaderLanguage::Token ShaderLanguage::_get_token() { return _make_token(TK_OP_NOT); } break; - //case '"' //string - no strings in shader + case '"': { + String _content = ""; + bool _previous_backslash = false; + + while (true) { + bool _ended = false; + char32_t c = GETCHAR(0); + if (c == 0) { + return _make_token(TK_ERROR, "EOF reached before string termination."); + } + switch (c) { + case '"': { + if (_previous_backslash) { + _content += '"'; + _previous_backslash = false; + } else { + _ended = true; + } + break; + } + case '\\': { + if (_previous_backslash) { + _content += '\\'; + } + _previous_backslash = !_previous_backslash; + break; + } + case '\n': { + return _make_token(TK_ERROR, "Unexpected end of string."); + } + default: { + if (!_previous_backslash) { + _content += c; + } else { + return _make_token(TK_ERROR, "Only \\\" and \\\\ escape characters supported."); + } + break; + } + } + + char_idx++; + if (_ended) { + break; + } + } + + return _make_token(TK_STRING_CONSTANT, _content); + } break; //case '\'' //string - no strings in shader case '{': return _make_token(TK_CURLY_BRACKET_OPEN); @@ -1127,6 +1177,9 @@ String ShaderLanguage::get_uniform_hint_name(ShaderNode::Uniform::Hint p_hint) { case ShaderNode::Uniform::HINT_RANGE: { result = "hint_range"; } break; + case ShaderNode::Uniform::HINT_ENUM: { + result = "hint_enum"; + } break; case ShaderNode::Uniform::HINT_SOURCE_COLOR: { result = "source_color"; } break; @@ -4146,6 +4199,11 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform if (p_uniform.array_size > 0) { pi.type = Variant::PACKED_INT32_ARRAY; // TODO: Handle range and encoding for for unsigned values. + } else if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_ENUM) { + pi.type = Variant::INT; + pi.hint = PROPERTY_HINT_ENUM; + String hint_string; + pi.hint_string = String(",").join(p_uniform.hint_enum_names); } else { pi.type = Variant::INT; pi.hint = PROPERTY_HINT_RANGE; @@ -8988,6 +9046,40 @@ Error ShaderLanguage::_parse_shader(const HashMap &p_f new_hint = ShaderNode::Uniform::HINT_RANGE; } break; + case TK_HINT_ENUM: { + if (type != TYPE_INT) { + _set_error(vformat(RTR("Enum hint is for '%s' only."), "int")); + return ERR_PARSE_ERROR; + } + + tk = _get_token(); + if (tk.type != TK_PARENTHESIS_OPEN) { + _set_expected_after_error("(", "hint_enum"); + return ERR_PARSE_ERROR; + } + + while (true) { + tk = _get_token(); + + if (tk.type != TK_STRING_CONSTANT) { + _set_error(RTR("Expected a string constant.")); + return ERR_PARSE_ERROR; + } + + uniform.hint_enum_names.push_back(tk.text); + + tk = _get_token(); + + if (tk.type == TK_PARENTHESIS_CLOSE) { + break; + } else if (tk.type != TK_COMMA) { + _set_error(RTR("Expected ',' or ')' after string constant.")); + return ERR_PARSE_ERROR; + } + } + + new_hint = ShaderNode::Uniform::HINT_ENUM; + } break; case TK_HINT_INSTANCE_INDEX: { if (custom_instance_index != -1) { _set_error(vformat(RTR("Can only specify '%s' once."), "instance_index")); @@ -9082,7 +9174,9 @@ Error ShaderLanguage::_parse_shader(const HashMap &p_f default: break; } - if (((new_filter != FILTER_DEFAULT || new_repeat != REPEAT_DEFAULT) || (new_hint != ShaderNode::Uniform::HINT_NONE && new_hint != ShaderNode::Uniform::HINT_SOURCE_COLOR && new_hint != ShaderNode::Uniform::HINT_RANGE)) && !is_sampler_type(type)) { + + bool is_sampler_hint = new_hint != ShaderNode::Uniform::HINT_NONE && new_hint != ShaderNode::Uniform::HINT_SOURCE_COLOR && new_hint != ShaderNode::Uniform::HINT_RANGE && new_hint != ShaderNode::Uniform::HINT_ENUM; + if (((new_filter != FILTER_DEFAULT || new_repeat != REPEAT_DEFAULT) || is_sampler_hint) && !is_sampler_type(type)) { _set_error(RTR("This hint is only for sampler types.")); return ERR_PARSE_ERROR; } @@ -10785,15 +10879,21 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ } } else if ((completion_base == DataType::TYPE_INT || completion_base == DataType::TYPE_FLOAT) && !completion_base_array) { if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { - ScriptLanguage::CodeCompletionOption option("hint_range", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + Vector options; if (completion_base == DataType::TYPE_INT) { - option.insert_text = "hint_range(0, 100, 1)"; + options.push_back("hint_range(0, 100, 1)"); + options.push_back("hint_enum(\"Zero\", \"One\", \"Two\")"); } else { - option.insert_text = "hint_range(0.0, 1.0, 0.1)"; + options.push_back("hint_range(0.0, 1.0, 0.1)"); } - r_options->push_back(option); + for (const String &option_text : options) { + String hint_name = option_text.substr(0, option_text.find_char(char32_t('('))); + ScriptLanguage::CodeCompletionOption option(hint_name, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + option.insert_text = option_text; + r_options->push_back(option); + } } } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) { Vector options; diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index 366cdd303f8..1b5df7e90f3 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -59,6 +59,7 @@ public: TK_FLOAT_CONSTANT, TK_INT_CONSTANT, TK_UINT_CONSTANT, + TK_STRING_CONSTANT, TK_TYPE_VOID, TK_TYPE_BOOL, TK_TYPE_BVEC2, @@ -175,6 +176,7 @@ public: TK_HINT_ANISOTROPY_TEXTURE, TK_HINT_SOURCE_COLOR, TK_HINT_RANGE, + TK_HINT_ENUM, TK_HINT_INSTANCE_INDEX, TK_HINT_SCREEN_TEXTURE, TK_HINT_NORMAL_ROUGHNESS_TEXTURE, @@ -623,6 +625,7 @@ public: enum Hint { HINT_NONE, HINT_RANGE, + HINT_ENUM, HINT_SOURCE_COLOR, HINT_NORMAL, HINT_ROUGHNESS_NORMAL, @@ -661,6 +664,7 @@ public: TextureFilter filter = FILTER_DEFAULT; TextureRepeat repeat = REPEAT_DEFAULT; float hint_range[3]; + PackedStringArray hint_enum_names; int instance_index = 0; String group; String subgroup;