diff --git a/core/object/object.h b/core/object/object.h index adb50268d2ad..16da1737ef8e 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -49,7 +49,7 @@ class TypedArray; enum PropertyHint { PROPERTY_HINT_NONE, ///< no hint provided. - PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_less][,hide_slider][,radians_as_degrees][,degrees][,exp][,suffix:] range. + PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_less][,hide_slider][,radians_as_degrees][,degrees][,exp][,display_unsigned][,suffix:] range. PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "positive_only" to exclude in-out and out-in. (ie: "attenuation,positive_only") diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 2cd3a5172241..4507b018307c 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2765,7 +2765,7 @@ Hints that an [int] or [float] property should be within a range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"or_greater"[/code] and/or [code]"or_less"[/code] to allow manual input going respectively above the max or below the min values. [b]Example:[/b] [code]"-360,360,1,or_greater,or_less"[/code]. - Additionally, other keywords can be included: [code]"exp"[/code] for exponential range editing, [code]"radians_as_degrees"[/code] for editing radian angles in degrees (the range values are also in degrees), [code]"degrees"[/code] to hint at an angle and [code]"hide_slider"[/code] to hide the slider. + Additionally, other keywords can be included: [code]"exp"[/code] for exponential range editing, [code]"radians_as_degrees"[/code] for editing radian angles in degrees (the range values are also in degrees), [code]"degrees"[/code] to hint at an angle and [code]"hide_slider"[/code] to hide the slider, and [code]"display_unsigned"[/code] to allow signed integers to be displayed as unsigned (this is especially useful with [PackedInt32Array]). Hints that an [int] or [String] property is an enumerated value to pick in a list specified via a hint string. diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index fdb4ec170b8d..ffeef7f13147 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1320,20 +1320,33 @@ void EditorPropertyInteger::_value_changed(int64_t val) { } void EditorPropertyInteger::update_property() { - int64_t val = get_edited_property_value(); - spin->set_value_no_signal(val); + if (display_unsigned) { + // Fix range to handle casting. + uint64_t val = (uint64_t)get_edited_property_value() % ((uint64_t)spin->get_max() + 1); + spin->set_value_no_signal(val); #ifdef DEBUG_ENABLED - // If spin (currently EditorSplinSlider : Range) is changed so that it can use int64_t, then the below warning wouldn't be a problem. - if (val != (int64_t)(double)(val)) { - WARN_PRINT("Cannot reliably represent '" + itos(val) + "' in the inspector, value is too large."); - } + // If spin (currently EditorSplinSlider : Range) is changed so that it can use uint64_t, then the below warning wouldn't be a problem. + if (val != (uint64_t)(double)(val)) { + WARN_PRINT("Cannot reliably represent '" + uitos(val) + "' in the inspector, value is too large."); + } #endif + } else { + int64_t val = get_edited_property_value(); + spin->set_value_no_signal(val); +#ifdef DEBUG_ENABLED + // If spin (currently EditorSplinSlider : Range) is changed so that it can use int64_t, then the below warning wouldn't be a problem. + if (val != (int64_t)(double)(val)) { + WARN_PRINT("Cannot reliably represent '" + itos(val) + "' in the inspector, value is too large."); + } +#endif + } } void EditorPropertyInteger::_bind_methods() { } -void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix) { +void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix, bool p_display_unsigned) { + display_unsigned = p_display_unsigned; spin->set_min(p_min); spin->set_max(p_max); spin->set_step(p_step); @@ -3453,6 +3466,7 @@ struct EditorPropertyRangeHint { bool exp_range = false; bool hide_slider = true; bool radians_as_degrees = false; + bool display_unsigned = false; }; static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const String &p_hint_text, double p_default_step, bool is_int = false) { @@ -3487,8 +3501,18 @@ static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const Stri hint.hide_slider = true; } else if (slice == "exp") { hint.exp_range = true; + } else if (slice == "display_unsigned") { + hint.display_unsigned = true; } } + + // display_unsigned requires a non-negative min and no or_less/or_greater. + ERR_FAIL_COND_V_MSG(hint.display_unsigned && hint.or_less, hint, + vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": display_unsigned cannot be combined with or_less.", p_hint_text)); + ERR_FAIL_COND_V_MSG(hint.display_unsigned && hint.or_greater, hint, + vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": display_unsigned cannot be combined with or_greater.", p_hint_text)); + ERR_FAIL_COND_V_MSG(hint.display_unsigned && hint.min < 0, hint, + vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": display_unsigned requires a non-negative minimum value.", p_hint_text)); } bool degrees = false; for (int i = 0; i < slices.size(); i++) { @@ -3587,7 +3611,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ EditorPropertyInteger *editor = memnew(EditorPropertyInteger); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, hint.or_greater, hint.or_less, hint.suffix); + editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, hint.or_greater, hint.or_less, hint.suffix, hint.display_unsigned); return editor; } @@ -3669,14 +3693,14 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ EditorPropertyVector2 *editor = memnew(EditorPropertyVector2(p_wide)); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, default_float_step); - editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix, false, hint.or_greater, hint.or_less); return editor; } break; case Variant::VECTOR2I: { EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i(p_wide)); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix, false, hint.or_greater, hint.or_less, hint.display_unsigned); return editor; } break; @@ -3696,28 +3720,28 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case Variant::VECTOR3: { EditorPropertyVector3 *editor = memnew(EditorPropertyVector3(p_wide)); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, default_float_step); - editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix, hint.radians_as_degrees); + editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix, hint.radians_as_degrees, hint.or_greater, hint.or_less); return editor; } break; case Variant::VECTOR3I: { EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i(p_wide)); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix, false, hint.or_greater, hint.or_less, hint.display_unsigned); return editor; } break; case Variant::VECTOR4: { EditorPropertyVector4 *editor = memnew(EditorPropertyVector4); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, default_float_step); - editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, p_hint == PROPERTY_HINT_LINK, hint.suffix, false, hint.or_greater, hint.or_less); return editor; } break; case Variant::VECTOR4I: { EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix, false, hint.or_greater, hint.or_less, hint.display_unsigned); return editor; } break; diff --git a/editor/editor_properties.h b/editor/editor_properties.h index e9e788ab7bd2..a4d2a98392bd 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -342,13 +342,15 @@ class EditorPropertyInteger : public EditorProperty { EditorSpinSlider *spin = nullptr; void _value_changed(int64_t p_val); + bool display_unsigned = false; + protected: virtual void _set_read_only(bool p_read_only) override; static void _bind_methods(); public: virtual void update_property() override; - void setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix = String()); + void setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_hide_slider, bool p_allow_greater, bool p_allow_lesser, const String &p_suffix = String(), bool p_display_unsigned = false); EditorPropertyInteger(); }; diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp index 365cbc6ec87e..d2e3f91906ab 100644 --- a/editor/editor_properties_vector.cpp +++ b/editor/editor_properties_vector.cpp @@ -73,7 +73,9 @@ void EditorPropertyVectorN::_value_changed(double val, const String &p_name) { Variant::construct(vector_type, v, nullptr, 0, cerror); for (int i = 0; i < component_count; i++) { - if (radians_as_degrees) { + if (display_unsigned) { + v.set(i, (int64_t)spin_sliders[i]->get_value()); + } else if (radians_as_degrees) { v.set(i, Math::deg_to_rad(spin_sliders[i]->get_value())); } else { v.set(i, spin_sliders[i]->get_value()); @@ -85,7 +87,10 @@ void EditorPropertyVectorN::_value_changed(double val, const String &p_name) { void EditorPropertyVectorN::update_property() { Variant val = get_edited_property_value(); for (int i = 0; i < component_count; i++) { - if (radians_as_degrees) { + if (display_unsigned) { + // Fix range to handle casting signed to unsigned. + spin_sliders[i]->set_value_no_signal((uint64_t)val.get(i) % ((uint64_t)spin_sliders[i]->get_max() + 1)); + } else if (radians_as_degrees) { spin_sliders[i]->set_value_no_signal(Math::rad_to_deg((real_t)val.get(i))); } else { spin_sliders[i]->set_value_no_signal(val.get(i)); @@ -151,16 +156,17 @@ void EditorPropertyVectorN::_notification(int p_what) { } } -void EditorPropertyVectorN::setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link, const String &p_suffix, bool p_radians_as_degrees) { +void EditorPropertyVectorN::setup(double p_min, double p_max, double p_step, bool p_hide_slider, bool p_link, const String &p_suffix, bool p_radians_as_degrees, bool p_allow_greater, bool p_allow_lesser, bool p_display_unsigned) { radians_as_degrees = p_radians_as_degrees; + display_unsigned = p_display_unsigned; for (EditorSpinSlider *spin : spin_sliders) { spin->set_min(p_min); spin->set_max(p_max); spin->set_step(p_step); spin->set_hide_slider(p_hide_slider); - spin->set_allow_greater(true); - spin->set_allow_lesser(true); + spin->set_allow_greater(p_allow_greater); + spin->set_allow_lesser(p_allow_lesser); spin->set_suffix(p_suffix); } diff --git a/editor/editor_properties_vector.h b/editor/editor_properties_vector.h index 009735ae3e7c..fe45859525e4 100644 --- a/editor/editor_properties_vector.h +++ b/editor/editor_properties_vector.h @@ -50,6 +50,7 @@ class EditorPropertyVectorN : public EditorProperty { bool is_grabbed = false; bool radians_as_degrees = false; + bool display_unsigned = false; void _update_ratio(); void _store_link(bool p_linked); @@ -62,7 +63,7 @@ class EditorPropertyVectorN : public EditorProperty { public: virtual void update_property() override; - void setup(double p_min, double p_max, double p_step = 1.0, bool p_hide_slider = true, bool p_link = false, const String &p_suffix = String(), bool p_radians_as_degrees = false); + void setup(double p_min, double p_max, double p_step = 1.0, bool p_hide_slider = true, bool p_link = false, const String &p_suffix = String(), bool p_radians_as_degrees = false, bool p_allow_greater = true, bool p_allow_lesser = true, bool p_display_unsigned = false); EditorPropertyVectorN(Variant::Type p_type, bool p_force_wide, bool p_horizontal); }; diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 6e7ac0dec966..d0514394e172 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -622,7 +622,7 @@ Export an [int], [float], [Array][lb][int][rb], [Array][lb][float][rb], [PackedByteArray], [PackedInt32Array], [PackedInt64Array], [PackedFloat32Array], or [PackedFloat64Array] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [member EditorSettings.interface/inspector/default_float_step] setting. If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget. - Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. + Hints also allow to indicate the units for the edited value. Using [code]"radians_as_degrees"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock (the range values are also in degrees). [code]"degrees"[/code] allows to add a degree sign as a unit suffix (the value is unchanged). [code]"display_unsigned"[/code] allows signed integers to be displayed as unsigned, this is especially useful with [PackedInt32Array]. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. See also [constant PROPERTY_HINT_RANGE]. [codeblock] @export_range(0, 20) var number diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 822fc412b448..0f772c478778 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -862,7 +862,7 @@ static void _get_directory_contents(EditorFileSystemDirectory *p_dir, HashMap &r_result) { if (p_annotation->name == SNAME("@export_range")) { - if (p_argument == 3 || p_argument == 4 || p_argument == 5) { + if (p_argument == 3 || p_argument == 4 || p_argument == 5 || p_argument == 6) { // Slider hint. ScriptLanguage::CodeCompletionOption slider1("or_greater", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider1.insert_text = slider1.display.quote(p_quote_style); @@ -873,6 +873,9 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a ScriptLanguage::CodeCompletionOption slider3("hide_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider3.insert_text = slider3.display.quote(p_quote_style); r_result.insert(slider3.display, slider3); + ScriptLanguage::CodeCompletionOption slider4("display_unsigned", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + slider4.insert_text = slider4.display.quote(p_quote_style); + r_result.insert(slider4.display, slider4); } } else if (p_annotation->name == SNAME("@export_exp_easing")) { if (p_argument == 0 || p_argument == 1) { diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 0235d72cfa07..0cb245929861 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -4138,16 +4138,21 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform case ShaderLanguage::TYPE_INT: { if (p_uniform.array_size > 0) { pi.type = Variant::PACKED_INT32_ARRAY; - // TODO: Handle range and encoding for for unsigned values. + pi.hint = PROPERTY_HINT_TYPE_STRING; + if (p_uniform.type == ShaderLanguage::TYPE_INT) { + pi.hint_string = vformat("%d/%d:%d,%d", Variant::INT, PROPERTY_HINT_RANGE, INT32_MIN, INT32_MAX); + } else { + pi.hint_string = vformat("%d/%d:0,%d,display_unsigned", Variant::INT, PROPERTY_HINT_RANGE, UINT32_MAX); + } } else { pi.type = Variant::INT; pi.hint = PROPERTY_HINT_RANGE; if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_RANGE) { pi.hint_string = rtos(p_uniform.hint_range[0]) + "," + rtos(p_uniform.hint_range[1]) + "," + rtos(p_uniform.hint_range[2]); - } else if (p_uniform.type == ShaderLanguage::TYPE_UINT) { - pi.hint_string = "0," + itos(UINT32_MAX); - } else { + } else if (p_uniform.type == ShaderLanguage::TYPE_INT) { pi.hint_string = itos(INT32_MIN) + "," + itos(INT32_MAX); + } else { + pi.hint_string = "0," + itos(UINT32_MAX) + ",display_unsigned"; } } } break; @@ -4156,8 +4161,20 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform if (p_uniform.array_size > 0) { pi.type = Variant::PACKED_INT32_ARRAY; // TODO: Handle vector pairs? + pi.hint = PROPERTY_HINT_TYPE_STRING; + if (p_uniform.type == ShaderLanguage::TYPE_VEC2) { + pi.hint_string = vformat("%d/%d:%d,%d", Variant::INT, PROPERTY_HINT_RANGE, INT32_MIN, INT32_MAX); + } else { + pi.hint_string = vformat("%d/%d:0,%d,display_unsigned", Variant::INT, PROPERTY_HINT_RANGE, UINT32_MAX); + } } else { pi.type = Variant::VECTOR2I; + pi.hint = PROPERTY_HINT_RANGE; + if (p_uniform.type == ShaderLanguage::TYPE_IVEC2) { + pi.hint_string = itos(INT32_MIN) + "," + itos(INT32_MAX); + } else { + pi.hint_string = "0," + itos(UINT32_MAX) + ",display_unsigned"; + } } } break; case ShaderLanguage::TYPE_UVEC3: @@ -4165,8 +4182,20 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform if (p_uniform.array_size > 0) { pi.type = Variant::PACKED_INT32_ARRAY; // TODO: Handle vector pairs? + pi.hint = PROPERTY_HINT_TYPE_STRING; + if (p_uniform.type == ShaderLanguage::TYPE_IVEC3) { + pi.hint_string = vformat("%d/%d:%d,%d", Variant::INT, PROPERTY_HINT_RANGE, INT32_MIN, INT32_MAX); + } else { + pi.hint_string = vformat("%d/%d:0,%d,display_unsigned", Variant::INT, PROPERTY_HINT_RANGE, UINT32_MAX); + } } else { pi.type = Variant::VECTOR3I; + pi.hint = PROPERTY_HINT_RANGE; + if (p_uniform.type == ShaderLanguage::TYPE_IVEC3) { + pi.hint_string = itos(INT32_MIN) + "," + itos(INT32_MAX); + } else { + pi.hint_string = "0," + itos(UINT32_MAX) + ",display_unsigned"; + } } } break; case ShaderLanguage::TYPE_UVEC4: @@ -4174,8 +4203,20 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform if (p_uniform.array_size > 0) { pi.type = Variant::PACKED_INT32_ARRAY; // TODO: Handle vector pairs? + pi.hint = PROPERTY_HINT_TYPE_STRING; + if (p_uniform.type == ShaderLanguage::TYPE_IVEC4) { + pi.hint_string = vformat("%d/%d:%d,%d", Variant::INT, PROPERTY_HINT_RANGE, INT32_MIN, INT32_MAX); + } else { + pi.hint_string = vformat("%d/%d:0,%d,display_unsigned", Variant::INT, PROPERTY_HINT_RANGE, UINT32_MAX); + } } else { pi.type = Variant::VECTOR4I; + pi.hint = PROPERTY_HINT_RANGE; + if (p_uniform.type == ShaderLanguage::TYPE_IVEC4) { + pi.hint_string = itos(INT32_MIN) + "," + itos(INT32_MAX); + } else { + pi.hint_string = "0," + itos(UINT32_MAX) + ",display_unsigned"; + } } } break; case ShaderLanguage::TYPE_FLOAT: {