summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/classes/RenderingDevice.xml2
-rw-r--r--doc/classes/TextServer.xml3
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp7
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp12
-rw-r--r--drivers/gles3/shaders/particles.glsl153
-rw-r--r--drivers/gles3/storage/particles_storage.cpp9
-rw-r--r--editor/editor_log.cpp5
-rw-r--r--editor/editor_properties_array_dict.cpp68
-rw-r--r--modules/text_server_adv/text_server_adv.cpp47
-rw-r--r--modules/text_server_adv/text_server_adv.h1
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--modules/webxr/doc_classes/WebXRInterface.xml24
-rw-r--r--modules/webxr/godot_webxr.h4
-rw-r--r--modules/webxr/native/library_godot_webxr.js50
-rw-r--r--modules/webxr/native/webxr.externs.js16
-rw-r--r--modules/webxr/webxr_interface.cpp4
-rw-r--r--modules/webxr/webxr_interface.h3
-rw-r--r--modules/webxr/webxr_interface_js.cpp24
-rw-r--r--modules/webxr/webxr_interface_js.h4
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/range.cpp23
-rw-r--r--scene/gui/range.h2
-rw-r--r--scene/gui/rich_text_label.cpp13
-rw-r--r--scene/gui/rich_text_label.h2
-rw-r--r--scene/gui/spin_box.cpp19
-rw-r--r--scene/gui/spin_box.h2
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/gui/tree.cpp4
-rw-r--r--scene/resources/packed_scene.cpp77
-rw-r--r--scene/resources/packed_scene.h2
-rw-r--r--servers/rendering/renderer_rd/storage_rd/particles_storage.cpp4
-rw-r--r--servers/rendering/rendering_device.cpp2
-rw-r--r--servers/rendering/shader_language.cpp64
-rw-r--r--servers/text_server.cpp3
-rw-r--r--servers/text_server.h9
35 files changed, 469 insertions, 199 deletions
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index 745926d4a8..f597cb7efc 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -113,7 +113,7 @@
</method>
<method name="compute_pipeline_is_valid">
<return type="bool" />
- <param index="0" name="compute_pieline" type="RID" />
+ <param index="0" name="compute_pipeline" type="RID" />
<description>
</description>
</method>
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 4f38785369..4118274732 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -1784,6 +1784,9 @@
<constant name="GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL" value="2048" enum="GraphemeFlag" is_bitfield="true">
It is safe to insert a U+0640 before this grapheme for elongation.
</constant>
+ <constant name="GRAPHEME_IS_EMBEDDED_OBJECT" value="4096" enum="GraphemeFlag" is_bitfield="true">
+ Grapheme is an object replacement character for the embedded object.
+ </constant>
<constant name="HINTING_NONE" value="0" enum="Hinting">
Disables font hinting (smoother but less crisp).
</constant>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 522685bf87..19f2cfd47d 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -1310,6 +1310,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
uint32_t instance_color_offset = 0;
bool instance_uses_color = false;
bool instance_uses_custom_data = false;
+ bool use_instancing = false;
if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_MESH) {
const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(state.canvas_instance_batches[p_index].command);
@@ -1336,6 +1337,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
instance_color_offset = mesh_storage->multimesh_get_color_offset(multimesh);
instance_uses_color = mesh_storage->multimesh_uses_colors(multimesh);
instance_uses_custom_data = mesh_storage->multimesh_uses_custom_data(multimesh);
+ use_instancing = true;
} else if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_PARTICLES) {
const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(state.canvas_instance_batches[p_index].command);
@@ -1362,6 +1364,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
instance_color_offset = 8; // 8 bytes for instance transform.
instance_uses_color = true;
instance_uses_custom_data = true;
+ use_instancing = true;
}
ERR_FAIL_COND(mesh.is_null());
@@ -1397,7 +1400,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
use_index_buffer = true;
}
- if (instance_count > 1) {
+ if (use_instancing) {
if (instance_buffer == 0) {
break;
}
@@ -1426,7 +1429,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
- if (instance_count > 1) {
+ if (use_instancing) {
glDisableVertexAttribArray(5);
glDisableVertexAttribArray(6);
glDisableVertexAttribArray(7);
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index b3d6b01c6c..3d8f7924a7 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -1948,7 +1948,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED;
- scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_OPAQUE;
+ scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_ALWAYS;
if (!fb_cleared) {
glClearDepth(1.0f);
@@ -1976,19 +1976,17 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
_render_list_template<PASS_MODE_COLOR>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size());
+ glDepthMask(GL_FALSE);
+ scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED;
+
if (draw_sky) {
RENDER_TIMESTAMP("Render Sky");
- if (scene_state.current_depth_test != GLES3::SceneShaderData::DEPTH_TEST_ENABLED) {
- glEnable(GL_DEPTH_TEST);
- scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED;
- }
+
glEnable(GL_DEPTH_TEST);
- glDepthMask(GL_FALSE);
glDisable(GL_BLEND);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED;
- scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED;
scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK;
_draw_sky(render_data.environment, render_data.cam_projection, render_data.cam_transform, sky_energy_multiplier, p_camera_data->view_count > 1, flip_y);
diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl
index f8741a22ab..40881a1808 100644
--- a/drivers/gles3/shaders/particles.glsl
+++ b/drivers/gles3/shaders/particles.glsl
@@ -300,28 +300,23 @@ void main() {
vec3 rel_vec = xform[3].xyz - attractors[i].transform[3].xyz;
vec3 local_pos = rel_vec * mat3(attractors[i].transform);
- switch (attractors[i].type) {
- case ATTRACTOR_TYPE_SPHERE: {
- dir = safe_normalize(rel_vec);
- float d = length(local_pos) / attractors[i].extents.x;
- if (d > 1.0) {
- continue;
- }
- amount = max(0.0, 1.0 - d);
- } break;
- case ATTRACTOR_TYPE_BOX: {
- dir = safe_normalize(rel_vec);
-
- vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz);
- float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
- if (d > 1.0) {
- continue;
- }
- amount = max(0.0, 1.0 - d);
-
- } break;
- case ATTRACTOR_TYPE_VECTOR_FIELD: {
- } break;
+ if (attractors[i].type == ATTRACTOR_TYPE_SPHERE) {
+ dir = safe_normalize(rel_vec);
+ float d = length(local_pos) / attractors[i].extents.x;
+ if (d > 1.0) {
+ continue;
+ }
+ amount = max(0.0, 1.0 - d);
+ } else if (attractors[i].type == ATTRACTOR_TYPE_BOX) {
+ dir = safe_normalize(rel_vec);
+
+ vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz);
+ float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z));
+ if (d > 1.0) {
+ continue;
+ }
+ amount = max(0.0, 1.0 - d);
+ } else if (attractors[i].type == ATTRACTOR_TYPE_VECTOR_FIELD) {
}
amount = pow(amount, attractors[i].attenuation);
dir = safe_normalize(mix(dir, attractors[i].transform[2].xyz, attractors[i].directionality));
@@ -383,80 +378,72 @@ void main() {
vec3 rel_vec = xform[3].xyz - colliders[i].transform[3].xyz;
vec3 local_pos = rel_vec * mat3(colliders[i].transform);
- switch (colliders[i].type) {
- case COLLIDER_TYPE_SPHERE: {
- float d = length(rel_vec) - (particle_size + colliders[i].extents.x);
+ if (colliders[i].type == COLLIDER_TYPE_SPHERE) {
+ float d = length(rel_vec) - (particle_size + colliders[i].extents.x);
- if (d < 0.0) {
- col = true;
- depth = -d;
- normal = normalize(rel_vec);
- }
+ if (d < 0.0) {
+ col = true;
+ depth = -d;
+ normal = normalize(rel_vec);
+ }
+ } else if (colliders[i].type == COLLIDER_TYPE_BOX) {
+ vec3 abs_pos = abs(local_pos);
+ vec3 sgn_pos = sign(local_pos);
- } break;
- case COLLIDER_TYPE_BOX: {
- vec3 abs_pos = abs(local_pos);
- vec3 sgn_pos = sign(local_pos);
-
- if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) {
- //point outside box
-
- vec3 closest = min(abs_pos, colliders[i].extents.xyz);
- vec3 rel = abs_pos - closest;
- depth = length(rel) - particle_size;
- if (depth < 0.0) {
- col = true;
- normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos);
- depth = -depth;
- }
- } else {
- //point inside box
- vec3 axis_len = colliders[i].extents.xyz - abs_pos;
- // there has to be a faster way to do this?
- if (all(lessThan(axis_len.xx, axis_len.yz))) {
- normal = vec3(1, 0, 0);
- } else if (all(lessThan(axis_len.yy, axis_len.xz))) {
- normal = vec3(0, 1, 0);
- } else {
- normal = vec3(0, 0, 1);
- }
+ if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) {
+ //point outside box
+ vec3 closest = min(abs_pos, colliders[i].extents.xyz);
+ vec3 rel = abs_pos - closest;
+ depth = length(rel) - particle_size;
+ if (depth < 0.0) {
col = true;
- depth = dot(normal * axis_len, vec3(1)) + particle_size;
- normal = mat3(colliders[i].transform) * (normal * sgn_pos);
+ normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos);
+ depth = -depth;
}
-
- } break;
- case COLLIDER_TYPE_SDF: {
- } break;
- case COLLIDER_TYPE_HEIGHT_FIELD: {
- vec3 local_pos_bottom = local_pos;
- local_pos_bottom.y -= particle_size;
-
- if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) {
- continue;
+ } else {
+ //point inside box
+ vec3 axis_len = colliders[i].extents.xyz - abs_pos;
+ // there has to be a faster way to do this?
+ if (all(lessThan(axis_len.xx, axis_len.yz))) {
+ normal = vec3(1, 0, 0);
+ } else if (all(lessThan(axis_len.yy, axis_len.xz))) {
+ normal = vec3(0, 1, 0);
+ } else {
+ normal = vec3(0, 0, 1);
}
- const float DELTA = 1.0 / 8192.0;
- vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5;
+ col = true;
+ depth = dot(normal * axis_len, vec3(1)) + particle_size;
+ normal = mat3(colliders[i].transform) * (normal * sgn_pos);
+ }
+ } else if (colliders[i].type == COLLIDER_TYPE_SDF) {
+ } else if (colliders[i].type == COLLIDER_TYPE_HEIGHT_FIELD) {
+ vec3 local_pos_bottom = local_pos;
+ local_pos_bottom.y -= particle_size;
- float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r;
+ if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) {
+ continue;
+ }
+ const float DELTA = 1.0 / 8192.0;
- if (y > uvw_pos.y) {
- //inside heightfield
+ vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5;
- vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
- vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
- vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz;
+ float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r;
- normal = normalize(cross(pos1 - pos2, pos1 - pos3));
- float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y;
+ if (y > uvw_pos.y) {
+ //inside heightfield
- col = true;
- depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
- }
+ vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
+ vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz;
+ vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz;
- } break;
+ normal = normalize(cross(pos1 - pos2, pos1 - pos3));
+ float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y;
+
+ col = true;
+ depth = dot(normal, pos1) - dot(normal, local_pos_bottom);
+ }
}
if (col) {
diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp
index 2b47271408..4b64e2aec1 100644
--- a/drivers/gles3/storage/particles_storage.cpp
+++ b/drivers/gles3/storage/particles_storage.cpp
@@ -919,7 +919,7 @@ void ParticlesStorage::_particles_update_instance_buffer(Particles *particles, c
glBeginTransformFeedback(GL_POINTS);
if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME) {
- uint32_t lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+ uint32_t lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
uint32_t stride = particles->process_buffer_stride_cache;
glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer);
@@ -1135,14 +1135,13 @@ void ParticlesStorage::_particles_reverse_lifetime_sort(Particles *particles) {
glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array);
#endif
- uint32_t lifetime_split = MIN(particles->amount * particles->sort_buffer_phase, particles->amount - 1);
-
+ uint32_t lifetime_split = (MIN(int(particles->amount * particles->sort_buffer_phase), particles->amount - 1) + 1) % particles->amount;
for (uint32_t i = 0; i < lifetime_split / 2; i++) {
- SWAP(particle_array[i], particle_array[lifetime_split - i]);
+ SWAP(particle_array[i], particle_array[lifetime_split - i - 1]);
}
for (uint32_t i = 0; i < (particles->amount - lifetime_split) / 2; i++) {
- SWAP(particle_array[lifetime_split + i + 1], particle_array[particles->amount - 1 - i]);
+ SWAP(particle_array[lifetime_split + i], particle_array[particles->amount - 1 - i]);
}
#ifndef __EMSCRIPTEN__
diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp
index 8d199ad989..1113a10e38 100644
--- a/editor/editor_log.cpp
+++ b/editor/editor_log.cpp
@@ -276,6 +276,11 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) {
return;
}
+ if (unlikely(log->is_updating())) {
+ // The new message arrived during log RTL text processing/redraw (invalid BiDi control characters / font error), ignore it to avoid RTL data corruption.
+ return;
+ }
+
// Only add the message to the log if it passes the filters.
bool filter_active = type_filter_map[p_message.type]->is_active();
String search_text = search_box->get_text();
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index f7c789a453..be66fbafac 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -38,34 +38,48 @@
#include "editor/gui/editor_spin_slider.h"
#include "editor/inspector_dock.h"
#include "scene/gui/button.h"
+#include "scene/resources/packed_scene.h"
bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) {
String name = p_name;
- if (name.begins_with("indices")) {
- int index = name.get_slicec('/', 1).to_int();
- array.set(index, p_value);
- return true;
+ if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) {
+ return false;
}
- return false;
+ int index;
+ if (name.begins_with("metadata/")) {
+ index = name.get_slice("/", 2).to_int();
+ } else {
+ index = name.get_slice("/", 1).to_int();
+ }
+
+ array.set(index, p_value);
+ return true;
}
bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const {
String name = p_name;
- if (name.begins_with("indices")) {
- int index = name.get_slicec('/', 1).to_int();
- bool valid;
- r_ret = array.get(index, &valid);
- if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
- r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
- }
+ if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) {
+ return false;
+ }
- return valid;
+ int index;
+ if (name.begins_with("metadata/")) {
+ index = name.get_slice("/", 2).to_int();
+ } else {
+ index = name.get_slice("/", 1).to_int();
}
- return false;
+ bool valid;
+ r_ret = array.get(index, &valid);
+
+ if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) {
+ r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id();
+ }
+
+ return valid;
}
void EditorPropertyArrayObject::set_array(const Variant &p_array) {
@@ -178,15 +192,22 @@ void EditorPropertyArray::initialize_array(Variant &p_array) {
}
void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) {
- if (p_property.begins_with("indices")) {
- int index = p_property.get_slice("/", 1).to_int();
-
- Variant array = object->get_array().duplicate();
- array.set(index, p_value);
+ if (!p_property.begins_with("indices") && !p_property.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) {
+ return;
+ }
- object->set_array(array);
- emit_changed(get_edited_property(), array, "", true);
+ int index;
+ if (p_property.begins_with("metadata/")) {
+ index = p_property.get_slice("/", 2).to_int();
+ } else {
+ index = p_property.get_slice("/", 1).to_int();
}
+
+ Array array;
+ array.assign(object->get_array().duplicate());
+ array.set(index, p_value);
+ object->set_array(array);
+ emit_changed(get_edited_property(), array, "", true);
}
void EditorPropertyArray::_change_type(Object *p_button, int p_index) {
@@ -690,11 +711,6 @@ EditorPropertyArray::EditorPropertyArray() {
add_child(edit);
add_focusable(edit);
- container = nullptr;
- property_vbox = nullptr;
- size_slider = nullptr;
- button_add_item = nullptr;
- paginator = nullptr;
change_type = memnew(PopupMenu);
add_child(change_type);
change_type->connect("id_pressed", callable_mp(this, &EditorPropertyArray::_change_type_menu));
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 43c7f49395..dfc820050f 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -4138,6 +4138,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start
new_sd->direction = sd->direction;
new_sd->custom_punct = sd->custom_punct;
new_sd->para_direction = sd->para_direction;
+ new_sd->base_para_direction = sd->base_para_direction;
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
new_sd->extra_spacing[i] = sd->extra_spacing[i];
}
@@ -4188,9 +4189,33 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
if (U_SUCCESS(err)) {
ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err);
if (U_FAILURE(err)) {
- ubidi_close(bidi_iter);
- bidi_iter = nullptr;
- ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err)));
+ // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead.
+ err = U_ZERO_ERROR;
+ const UChar *data = p_sd->utf16.get_data();
+ switch (static_cast<TextServer::Direction>(p_sd->bidi_override[ov].z)) {
+ case DIRECTION_LTR: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err);
+ } break;
+ case DIRECTION_RTL: {
+ ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
+ } break;
+ case DIRECTION_INHERITED: {
+ ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err);
+ } break;
+ case DIRECTION_AUTO: {
+ UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start);
+ if (direction != UBIDI_NEUTRAL) {
+ ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err);
+ } else {
+ ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err);
+ }
+ } break;
+ }
+ if (U_FAILURE(err)) {
+ ubidi_close(bidi_iter);
+ bidi_iter = nullptr;
+ ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err)));
+ }
}
} else {
bidi_iter = nullptr;
@@ -5619,25 +5644,25 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length()));
}
- int base_para_direction = UBIDI_DEFAULT_LTR;
+ sd->base_para_direction = UBIDI_DEFAULT_LTR;
switch (sd->direction) {
case DIRECTION_LTR: {
sd->para_direction = DIRECTION_LTR;
- base_para_direction = UBIDI_LTR;
+ sd->base_para_direction = UBIDI_LTR;
} break;
case DIRECTION_RTL: {
sd->para_direction = DIRECTION_RTL;
- base_para_direction = UBIDI_RTL;
+ sd->base_para_direction = UBIDI_RTL;
} break;
case DIRECTION_INHERITED:
case DIRECTION_AUTO: {
UBiDiDirection direction = ubidi_getBaseDirection(data, sd->utf16.length());
if (direction != UBIDI_NEUTRAL) {
sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR;
- base_para_direction = direction;
+ sd->base_para_direction = direction;
} else {
sd->para_direction = DIRECTION_LTR;
- base_para_direction = UBIDI_DEFAULT_LTR;
+ sd->base_para_direction = UBIDI_DEFAULT_LTR;
}
} break;
}
@@ -5666,14 +5691,14 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err);
} break;
case DIRECTION_INHERITED: {
- ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err);
+ ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err);
} break;
case DIRECTION_AUTO: {
UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start);
if (direction != UBIDI_NEUTRAL) {
ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err);
} else {
- ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err);
+ ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err);
}
} break;
}
@@ -5760,7 +5785,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
gl.start = span.start;
gl.end = span.end;
gl.count = 1;
- gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index af3a0c7876..4b8c3f7cd3 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -476,6 +476,7 @@ class TextServerAdvanced : public TextServerExtension {
/* Shaped data */
TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction.
+ int base_para_direction = UBIDI_DEFAULT_LTR;
bool valid = false; // String is shaped.
bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted).
bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string.
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 9095be46af..8b210bdc38 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -3670,7 +3670,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
gl.end = span.end;
gl.count = 1;
gl.index = 0;
- gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL;
+ gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
gl.advance = sd->objects[span.embedded_key].rect.size.x;
} else {
diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml
index 758c37e2ad..ed162b1da2 100644
--- a/modules/webxr/doc_classes/WebXRInterface.xml
+++ b/modules/webxr/doc_classes/WebXRInterface.xml
@@ -93,6 +93,18 @@
<link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link>
</tutorials>
<methods>
+ <method name="get_available_display_refresh_rates" qualifiers="const">
+ <return type="Array" />
+ <description>
+ Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the web browser and after the interface has been initialized.
+ </description>
+ </method>
+ <method name="get_display_refresh_rate" qualifiers="const">
+ <return type="float" />
+ <description>
+ Returns the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It may not report an accurate value until after using [method set_display_refresh_rate].
+ </description>
+ </method>
<method name="get_input_source_target_ray_mode" qualifiers="const">
<return type="int" enum="WebXRInterface.TargetRayMode" />
<param index="0" name="input_source_id" type="int" />
@@ -132,6 +144,13 @@
This method returns nothing, instead it emits the [signal session_supported] signal with the result.
</description>
</method>
+ <method name="set_display_refresh_rate">
+ <return type="void" />
+ <param index="0" name="refresh_rate" type="float" />
+ <description>
+ Sets the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It won't take effect right away until after [signal display_refresh_rate_changed] is emitted.
+ </description>
+ </method>
</methods>
<members>
<member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features">
@@ -167,6 +186,11 @@
</member>
</members>
<signals>
+ <signal name="display_refresh_rate_changed">
+ <description>
+ Emitted after the display's refresh rate has changed.
+ </description>
+ </signal>
<signal name="reference_space_reset">
<description>
Emitted to indicate that the reference space has been reset or reconfigured.
diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h
index c636be1576..554ca0398b 100644
--- a/modules/webxr/godot_webxr.h
+++ b/modules/webxr/godot_webxr.h
@@ -90,6 +90,10 @@ extern bool godot_webxr_update_input_source(
extern char *godot_webxr_get_visibility_state();
extern int godot_webxr_get_bounds_geometry(float **r_points);
+extern float godot_webxr_get_frame_rate();
+extern void godot_webxr_update_target_frame_rate(float p_frame_rate);
+extern int godot_webxr_get_supported_frame_rates(float **r_frame_rates);
+
#ifdef __cplusplus
}
#endif
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 5c01d88a30..fe47de02b0 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -42,6 +42,7 @@ const GodotWebXR = {
view_count: 1,
input_sources: new Array(16),
touches: new Array(5),
+ onsimpleevent: null,
// Monkey-patch the requestAnimationFrame() used by Emscripten for the main
// loop, so that we can swap it out for XRSession.requestAnimationFrame()
@@ -283,6 +284,9 @@ const GodotWebXR = {
GodotRuntime.free(c_str);
});
+ // Store onsimpleevent so we can use it later.
+ GodotWebXR.onsimpleevent = onsimpleevent;
+
const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef
const gl = GL.getContext(gl_context_handle).GLctx;
GodotWebXR.gl = gl;
@@ -368,6 +372,7 @@ const GodotWebXR = {
GodotWebXR.view_count = 1;
GodotWebXR.input_sources = new Array(16);
GodotWebXR.touches = new Array(5);
+ GodotWebXR.onsimpleevent = null;
// Disable the monkey-patched window.requestAnimationFrame() and
// pause/restart the main loop to activate it on all platforms.
@@ -594,6 +599,51 @@ const GodotWebXR = {
return point_count;
},
+
+ godot_webxr_get_frame_rate__proxy: 'sync',
+ godot_webxr_get_frame_rate__sig: 'i',
+ godot_webxr_get_frame_rate: function () {
+ if (!GodotWebXR.session || GodotWebXR.session.frameRate === undefined) {
+ return 0;
+ }
+ return GodotWebXR.session.frameRate;
+ },
+
+ godot_webxr_update_target_frame_rate__proxy: 'sync',
+ godot_webxr_update_target_frame_rate__sig: 'vi',
+ godot_webxr_update_target_frame_rate: function (p_frame_rate) {
+ if (!GodotWebXR.session || GodotWebXR.session.updateTargetFrameRate === undefined) {
+ return;
+ }
+
+ GodotWebXR.session.updateTargetFrameRate(p_frame_rate).then(() => {
+ const c_str = GodotRuntime.allocString('display_refresh_rate_changed');
+ GodotWebXR.onsimpleevent(c_str);
+ GodotRuntime.free(c_str);
+ });
+ },
+
+ godot_webxr_get_supported_frame_rates__proxy: 'sync',
+ godot_webxr_get_supported_frame_rates__sig: 'ii',
+ godot_webxr_get_supported_frame_rates: function (r_frame_rates) {
+ if (!GodotWebXR.session || GodotWebXR.session.supportedFrameRates === undefined) {
+ return 0;
+ }
+
+ const frame_rate_count = GodotWebXR.session.supportedFrameRates.length;
+ if (frame_rate_count === 0) {
+ return 0;
+ }
+
+ const buf = GodotRuntime.malloc(frame_rate_count * 4);
+ for (let i = 0; i < frame_rate_count; i++) {
+ GodotRuntime.setHeapValue(buf + (i * 4), GodotWebXR.session.supportedFrameRates[i], 'float');
+ }
+ GodotRuntime.setHeapValue(r_frame_rates, buf, 'i32');
+
+ return frame_rate_count;
+ },
+
};
autoAddDeps(GodotWebXR, '$GodotWebXR');
diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js
index 4b88820b19..7f7c297acc 100644
--- a/modules/webxr/native/webxr.externs.js
+++ b/modules/webxr/native/webxr.externs.js
@@ -68,6 +68,16 @@ XRSession.prototype.inputSources;
XRSession.prototype.visibilityState;
/**
+ * @type {?number}
+ */
+XRSession.prototype.frameRate;
+
+/**
+ * @type {?Float32Array}
+ */
+XRSession.prototype.supportedFrameRates;
+
+/**
* @type {?function (Event)}
*/
XRSession.prototype.onend;
@@ -142,6 +152,12 @@ XRSession.prototype.end = function () {};
XRSession.prototype.requestReferenceSpace = function (referenceSpaceType) {};
/**
+ * @param {number} rate
+ * @return {Promise<undefined>}
+ */
+XRSession.prototype.updateTargetFrameRate = function (rate) {};
+
+/**
* @typedef {function(number, XRFrame): undefined}
*/
var XRFrameRequestCallback;
diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp
index d7f4247768..5e30d0c996 100644
--- a/modules/webxr/webxr_interface.cpp
+++ b/modules/webxr/webxr_interface.cpp
@@ -46,6 +46,9 @@ void WebXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker);
ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode);
ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state);
+ ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &WebXRInterface::get_display_refresh_rate);
+ ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &WebXRInterface::set_display_refresh_rate);
+ ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &WebXRInterface::get_available_display_refresh_rates);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features");
@@ -68,6 +71,7 @@ void WebXRInterface::_bind_methods() {
ADD_SIGNAL(MethodInfo("visibility_state_changed"));
ADD_SIGNAL(MethodInfo("reference_space_reset"));
+ ADD_SIGNAL(MethodInfo("display_refresh_rate_changed"));
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN);
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE);
diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h
index f18b4b40e6..abaa8c01f8 100644
--- a/modules/webxr/webxr_interface.h
+++ b/modules/webxr/webxr_interface.h
@@ -66,6 +66,9 @@ public:
virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0;
virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0;
virtual String get_visibility_state() const = 0;
+ virtual float get_display_refresh_rate() const = 0;
+ virtual void set_display_refresh_rate(float p_refresh_rate) = 0;
+ virtual Array get_available_display_refresh_rates() const = 0;
};
VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode);
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index d3710bd0df..e1df2ea94e 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -202,6 +202,30 @@ PackedVector3Array WebXRInterfaceJS::get_play_area() const {
return ret;
}
+float WebXRInterfaceJS::get_display_refresh_rate() const {
+ return godot_webxr_get_frame_rate();
+}
+
+void WebXRInterfaceJS::set_display_refresh_rate(float p_refresh_rate) {
+ godot_webxr_update_target_frame_rate(p_refresh_rate);
+}
+
+Array WebXRInterfaceJS::get_available_display_refresh_rates() const {
+ Array ret;
+
+ float *rates;
+ int rate_count = godot_webxr_get_supported_frame_rates(&rates);
+ if (rate_count > 0) {
+ ret.resize(rate_count);
+ for (int i = 0; i < rate_count; i++) {
+ ret[i] = rates[i];
+ }
+ free(rates);
+ }
+
+ return ret;
+}
+
StringName WebXRInterfaceJS::get_name() const {
return "WebXR";
};
diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h
index 20516e89e2..d85eb8cad7 100644
--- a/modules/webxr/webxr_interface_js.h
+++ b/modules/webxr/webxr_interface_js.h
@@ -102,6 +102,10 @@ public:
virtual String get_visibility_state() const override;
virtual PackedVector3Array get_play_area() const override;
+ virtual float get_display_refresh_rate() const override;
+ virtual void set_display_refresh_rate(float p_refresh_rate) override;
+ virtual Array get_available_display_refresh_rates() const override;
+
virtual StringName get_name() const override;
virtual uint32_t get_capabilities() const override;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index dcd99f4374..d699236053 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -952,7 +952,7 @@ void LineEdit::_notification(int p_what) {
if (ceil(ofs.x) >= x_ofs && (ofs.x + glyphs[i].advance) <= ofs_max) {
if (glyphs[i].font_rid != RID()) {
TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color);
- } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color);
}
}
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
index bd081074ec..97a1483204 100644
--- a/scene/gui/range.cpp
+++ b/scene/gui/range.cpp
@@ -74,16 +74,26 @@ void Range::Shared::emit_changed(const char *p_what) {
}
}
+void Range::Shared::redraw_owners() {
+ for (Range *E : owners) {
+ Range *r = E;
+ if (!r->is_inside_tree()) {
+ continue;
+ }
+ r->queue_redraw();
+ }
+}
+
void Range::set_value(double p_val) {
double prev_val = shared->val;
- set_value_no_signal(p_val);
+ _set_value_no_signal(p_val);
if (shared->val != prev_val) {
shared->emit_value_changed();
}
}
-void Range::set_value_no_signal(double p_val) {
+void Range::_set_value_no_signal(double p_val) {
if (shared->step > 0) {
p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min;
}
@@ -107,6 +117,15 @@ void Range::set_value_no_signal(double p_val) {
shared->val = p_val;
}
+void Range::set_value_no_signal(double p_val) {
+ double prev_val = shared->val;
+ _set_value_no_signal(p_val);
+
+ if (shared->val != prev_val) {
+ shared->redraw_owners();
+ }
+}
+
void Range::set_min(double p_min) {
if (shared->min == p_min) {
return;
diff --git a/scene/gui/range.h b/scene/gui/range.h
index bf71fcc1c9..9b4f0707e6 100644
--- a/scene/gui/range.h
+++ b/scene/gui/range.h
@@ -48,6 +48,7 @@ class Range : public Control {
HashSet<Range *> owners;
void emit_value_changed();
void emit_changed(const char *p_what = "");
+ void redraw_owners();
};
Shared *shared = nullptr;
@@ -59,6 +60,7 @@ class Range : public Control {
void _value_changed_notify();
void _changed_notify(const char *p_what = "");
+ void _set_value_no_signal(double p_val);
protected:
virtual void _value_changed(double p_value);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 1ae24b6d70..43c68a8eca 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1323,7 +1323,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
if (!skip) {
if (frid != RID()) {
TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color);
- } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color);
}
}
@@ -2705,6 +2705,10 @@ bool RichTextLabel::is_ready() const {
return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size());
}
+bool RichTextLabel::is_updating() const {
+ return updating.load() || validating.load();
+}
+
void RichTextLabel::set_threaded(bool p_threaded) {
if (threaded != p_threaded) {
_stop_thread();
@@ -2729,6 +2733,7 @@ bool RichTextLabel::_validate_line_caches() {
if (updating.load()) {
return false;
}
+ validating.store(true);
if (main->first_invalid_line.load() == (int)main->lines.size()) {
MutexLock data_lock(data_mutex);
Rect2 text_rect = _get_text_rect();
@@ -2747,6 +2752,7 @@ bool RichTextLabel::_validate_line_caches() {
if (main->first_resized_line.load() == (int)main->lines.size()) {
vscroll->set_value(old_scroll);
+ validating.store(false);
return true;
}
@@ -2798,8 +2804,10 @@ bool RichTextLabel::_validate_line_caches() {
if (fit_content) {
update_minimum_size();
}
+ validating.store(false);
return true;
}
+ validating.store(false);
stop_thread.store(false);
if (threaded) {
updating.store(true);
@@ -2809,7 +2817,9 @@ bool RichTextLabel::_validate_line_caches() {
loading_started = OS::get_singleton()->get_ticks_msec();
return false;
} else {
+ updating.store(true);
_process_line_caches();
+ updating.store(false);
queue_redraw();
return true;
}
@@ -5895,6 +5905,7 @@ RichTextLabel::RichTextLabel(const String &p_text) {
set_text(p_text);
updating.store(false);
+ validating.store(false);
stop_thread.store(false);
set_clip_contents(true);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index fef34f7260..567528652c 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -377,6 +377,7 @@ private:
bool threaded = false;
std::atomic<bool> stop_thread;
std::atomic<bool> updating;
+ std::atomic<bool> validating;
std::atomic<double> loaded;
uint64_t loading_started = 0;
@@ -678,6 +679,7 @@ public:
void deselect();
bool is_ready() const;
+ bool is_updating() const;
void set_threaded(bool p_threaded);
bool is_threaded() const;
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
index f99b2edd54..e4c7be33f0 100644
--- a/scene/gui/spin_box.cpp
+++ b/scene/gui/spin_box.cpp
@@ -39,7 +39,7 @@ Size2 SpinBox::get_minimum_size() const {
return ms;
}
-void SpinBox::_value_changed(double p_value) {
+void SpinBox::_update_text() {
String value = String::num(get_value(), Math::range_step_decimals(get_step()));
if (is_localizing_numeral_system()) {
value = TS->format_number(value);
@@ -55,7 +55,6 @@ void SpinBox::_value_changed(double p_value) {
}
line_edit->set_text(value);
- Range::_value_changed(p_value);
}
void SpinBox::_text_submitted(const String &p_string) {
@@ -73,7 +72,7 @@ void SpinBox::_text_submitted(const String &p_string) {
if (value.get_type() != Variant::NIL) {
set_value(value);
}
- _value_changed(0);
+ _update_text();
}
void SpinBox::_text_changed(const String &p_string) {
@@ -192,7 +191,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
void SpinBox::_line_edit_focus_enter() {
int col = line_edit->get_caret_column();
- _value_changed(0); // Update the LineEdit's text.
+ _update_text();
line_edit->set_caret_column(col);
// LineEdit text might change and it clears any selection. Have to re-select here.
@@ -202,6 +201,10 @@ void SpinBox::_line_edit_focus_enter() {
}
void SpinBox::_line_edit_focus_exit() {
+ // Discontinue because the focus_exit was caused by left-clicking the arrows.
+ if (get_viewport()->gui_get_focus_owner() == get_line_edit()) {
+ return;
+ }
// Discontinue because the focus_exit was caused by right-click context menu.
if (line_edit->is_menu_visible()) {
return;
@@ -228,6 +231,7 @@ void SpinBox::_update_theme_item_cache() {
void SpinBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
+ _update_text();
_adjust_width_for_icon(theme_cache.updown_icon);
RID ci = get_canvas_item();
@@ -242,7 +246,7 @@ void SpinBox::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: {
_adjust_width_for_icon(theme_cache.updown_icon);
- _value_changed(0);
+ _update_text();
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -250,7 +254,6 @@ void SpinBox::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
- _value_changed(0);
queue_redraw();
} break;
@@ -279,7 +282,7 @@ void SpinBox::set_suffix(const String &p_suffix) {
}
suffix = p_suffix;
- _value_changed(0);
+ _update_text();
}
String SpinBox::get_suffix() const {
@@ -292,7 +295,7 @@ void SpinBox::set_prefix(const String &p_prefix) {
}
prefix = p_prefix;
- _value_changed(0);
+ _update_text();
}
String SpinBox::get_prefix() const {
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
index 0e9d424f68..29b278c50e 100644
--- a/scene/gui/spin_box.h
+++ b/scene/gui/spin_box.h
@@ -46,8 +46,8 @@ class SpinBox : public Range {
void _range_click_timeout();
void _release_mouse();
+ void _update_text();
void _text_submitted(const String &p_string);
- virtual void _value_changed(double p_value) override;
void _text_changed(const String &p_string);
String prefix;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 0839e4066d..db674c50fb 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1280,7 +1280,7 @@ void TextEdit::_notification(int p_what) {
if (glyphs[j].font_rid != RID()) {
TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
had_glyphs_drawn = true;
- } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+ } else if (((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color);
had_glyphs_drawn = true;
}
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e71c236dc7..e22890562f 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -4594,8 +4594,8 @@ int Tree::get_item_offset(TreeItem *p_item) const {
return ofs;
}
- ofs += compute_item_height(it);
- if (it != root || !hide_root) {
+ if ((it != root || !hide_root) && it->is_visible()) {
+ ofs += compute_item_height(it);
ofs += theme_cache.v_separation;
}
diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp
index 1e9038139e..a1e7558653 100644
--- a/scene/resources/packed_scene.cpp
+++ b/scene/resources/packed_scene.cpp
@@ -44,7 +44,9 @@
#include "scene/property_utils.h"
#define PACKED_SCENE_VERSION 3
-#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_"
+
+const String PackedScene::META_POINTER_PROPERTY_BASE = "metadata/_editor_prop_ptr_";
+
bool SceneState::can_instantiate() const {
return nodes.size() > 0;
}
@@ -239,15 +241,38 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) {
uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1);
ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr);
- if (Engine::get_singleton()->is_editor_hint()) {
- // If editor, just set the metadata and be it
- node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]);
+
+ const StringName &prop_name = snames[name_idx];
+ const Variant &prop_variant = props[nprops[j].value];
+
+ if (prop_variant.get_type() == Variant::ARRAY) {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // If editor, simply set the original array of NodePaths.
+ node->set(prop_name, prop_variant);
+ continue;
+ }
+
+ const Array &array = prop_variant;
+ for (int k = 0; k < array.size(); k++) {
+ DeferredNodePathProperties dnp;
+ dnp.path = array[k];
+ dnp.base = node;
+ // Use special property name to signify an array. This is only used in deferred_node_paths.
+ dnp.property = String(prop_name) + "/indices/" + itos(k);
+ deferred_node_paths.push_back(dnp);
+ }
+
} else {
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // If editor, just set the metadata and be it.
+ node->set(PackedScene::META_POINTER_PROPERTY_BASE + String(prop_name), prop_variant);
+ continue;
+ }
// Do an actual deferred sed of the property path.
DeferredNodePathProperties dnp;
- dnp.path = props[nprops[j].value];
+ dnp.path = prop_variant;
dnp.base = node;
- dnp.property = snames[name_idx];
+ dnp.property = prop_name;
deferred_node_paths.push_back(dnp);
}
continue;
@@ -415,8 +440,26 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const {
}
for (const DeferredNodePathProperties &dnp : deferred_node_paths) {
+ // Replace properties stored as NodePaths with actual Nodes.
Node *other = dnp.base->get_node_or_null(dnp.path);
- dnp.base->set(dnp.property, other);
+
+ const String string_property = dnp.property;
+ if (string_property.contains("/indices/")) {
+ // For properties with "/indices/", the replacement takes place inside an Array.
+ const String base_property = string_property.get_slice("/", 0);
+ const int index = string_property.get_slice("/", 2).to_int();
+
+ Array array = dnp.base->get(base_property);
+ if (array.size() >= index) {
+ array.push_back(other);
+ } else {
+ array.set(index, other);
+ }
+
+ dnp.base->set(base_property, array);
+ } else {
+ dnp.base->set(dnp.property, other);
+ }
}
for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) {
@@ -584,7 +627,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
if (E.name == META_PROPERTY_MISSING_RESOURCES) {
continue; // Ignore this property when packing.
}
- if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) {
+ if (E.name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE)) {
continue; // do not save.
}
@@ -600,7 +643,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
bool use_deferred_node_path_bit = false;
if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) {
- value = p_node->get(META_POINTER_PROPERTY_BASE + E.name);
+ value = p_node->get(PackedScene::META_POINTER_PROPERTY_BASE + E.name);
if (value.get_type() != Variant::NODE_PATH) {
continue; //was never set, ignore.
}
@@ -611,6 +654,20 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has
if (ures.is_null()) {
value = missing_resource_properties[E.name];
}
+ } else if (E.type == Variant::ARRAY && E.hint == PROPERTY_HINT_TYPE_STRING) {
+ int hint_subtype_separator = E.hint_string.find(":");
+ if (hint_subtype_separator >= 0) {
+ String subtype_string = E.hint_string.substr(0, hint_subtype_separator);
+ int slash_pos = subtype_string.find("/");
+ PropertyHint subtype_hint = PropertyHint::PROPERTY_HINT_NONE;
+ if (slash_pos >= 0) {
+ subtype_hint = PropertyHint(subtype_string.get_slice("/", 1).to_int());
+ subtype_string = subtype_string.substr(0, slash_pos);
+ }
+ Variant::Type subtype = Variant::Type(subtype_string.to_int());
+
+ use_deferred_node_path_bit = subtype == Variant::OBJECT && subtype_hint == PROPERTY_HINT_NODE_TYPE;
+ }
}
if (!pinned_props.has(name)) {
@@ -1736,7 +1793,7 @@ void SceneState::add_editable_instance(const NodePath &p_path) {
}
String SceneState::get_meta_pointer_property(const String &p_property) {
- return META_POINTER_PROPERTY_BASE + p_property;
+ return PackedScene::META_POINTER_PROPERTY_BASE + p_property;
}
Vector<String> SceneState::_get_node_groups(int p_idx) const {
diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h
index 5c53ffdb45..1967deab36 100644
--- a/scene/resources/packed_scene.h
+++ b/scene/resources/packed_scene.h
@@ -221,6 +221,8 @@ protected:
virtual void reset_state() override;
public:
+ static const String META_POINTER_PROPERTY_BASE;
+
enum GenEditState {
GEN_EDIT_STATE_DISABLED,
GEN_EDIT_STATE_INSTANCE,
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
index d8b78de526..b36e027f07 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
@@ -1202,7 +1202,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p
}
copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME);
- copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+ copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME;
copy_push_constant.frame_remainder = particles->interpolate ? particles->frame_remainder : 0.0;
@@ -1520,7 +1520,7 @@ void ParticlesStorage::update_particles() {
}
copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME);
- copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1);
+ copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount;
copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME;
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 286d1b683f..744fd051f5 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -752,7 +752,7 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("render_pipeline_is_valid", "render_pipeline"), &RenderingDevice::render_pipeline_is_valid);
ClassDB::bind_method(D_METHOD("compute_pipeline_create", "shader", "specialization_constants"), &RenderingDevice::_compute_pipeline_create, DEFVAL(TypedArray<RDPipelineSpecializationConstant>()));
- ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pieline"), &RenderingDevice::compute_pipeline_is_valid);
+ ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pipeline"), &RenderingDevice::compute_pipeline_is_valid);
ClassDB::bind_method(D_METHOD("screen_get_width", "screen"), &RenderingDevice::screen_get_width, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("screen_get_height", "screen"), &RenderingDevice::screen_get_height, DEFVAL(DisplayServer::MAIN_WINDOW_ID));
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 084fb64a53..12e99ba606 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -10529,36 +10529,42 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
r_options->push_back(option);
}
- } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT)) && !completion_base_array) {
+ } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) {
Vector<String> options;
- if (current_uniform_filter == FILTER_DEFAULT) {
- options.push_back("filter_linear");
- options.push_back("filter_linear_mipmap");
- options.push_back("filter_linear_mipmap_anisotropic");
- options.push_back("filter_nearest");
- options.push_back("filter_nearest_mipmap");
- options.push_back("filter_nearest_mipmap_anisotropic");
- }
- if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
- options.push_back("hint_anisotropy");
- options.push_back("hint_default_black");
- options.push_back("hint_default_white");
- options.push_back("hint_default_transparent");
- options.push_back("hint_normal");
- options.push_back("hint_roughness_a");
- options.push_back("hint_roughness_b");
- options.push_back("hint_roughness_g");
- options.push_back("hint_roughness_gray");
- options.push_back("hint_roughness_normal");
- options.push_back("hint_roughness_r");
- options.push_back("hint_screen_texture");
- options.push_back("hint_normal_roughness_texture");
- options.push_back("hint_depth_texture");
- options.push_back("source_color");
- }
- if (current_uniform_repeat == REPEAT_DEFAULT) {
- options.push_back("repeat_enable");
- options.push_back("repeat_disable");
+ if (completion_base_array) {
+ if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
+ options.push_back("source_color");
+ }
+ } else {
+ if (current_uniform_filter == FILTER_DEFAULT) {
+ options.push_back("filter_linear");
+ options.push_back("filter_linear_mipmap");
+ options.push_back("filter_linear_mipmap_anisotropic");
+ options.push_back("filter_nearest");
+ options.push_back("filter_nearest_mipmap");
+ options.push_back("filter_nearest_mipmap_anisotropic");
+ }
+ if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) {
+ options.push_back("hint_anisotropy");
+ options.push_back("hint_default_black");
+ options.push_back("hint_default_white");
+ options.push_back("hint_default_transparent");
+ options.push_back("hint_normal");
+ options.push_back("hint_roughness_a");
+ options.push_back("hint_roughness_b");
+ options.push_back("hint_roughness_g");
+ options.push_back("hint_roughness_gray");
+ options.push_back("hint_roughness_normal");
+ options.push_back("hint_roughness_r");
+ options.push_back("hint_screen_texture");
+ options.push_back("hint_normal_roughness_texture");
+ options.push_back("hint_depth_texture");
+ options.push_back("source_color");
+ }
+ if (current_uniform_repeat == REPEAT_DEFAULT) {
+ options.push_back("repeat_enable");
+ options.push_back("repeat_disable");
+ }
}
for (int i = 0; i < options.size(); i++) {
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 88f1e76811..36c3137472 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -547,6 +547,7 @@ void TextServer::_bind_methods() {
BIND_BITFIELD_FLAG(GRAPHEME_IS_UNDERSCORE);
BIND_BITFIELD_FLAG(GRAPHEME_IS_CONNECTED);
BIND_BITFIELD_FLAG(GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL);
+ BIND_BITFIELD_FLAG(GRAPHEME_IS_EMBEDDED_OBJECT);
/* Hinting */
BIND_ENUM_CONSTANT(HINTING_NONE);
@@ -1449,7 +1450,7 @@ void TextServer::shaped_text_draw(const RID &p_shaped, const RID &p_canvas, cons
if (glyphs[i].font_rid != RID()) {
font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
- } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) {
+ } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) != GRAPHEME_IS_EMBEDDED_OBJECT)) {
TextServer::draw_hex_code_box(p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
}
if (orientation == ORIENTATION_HORIZONTAL) {
diff --git a/servers/text_server.h b/servers/text_server.h
index e3c668bd5c..af6efb8c7d 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -125,18 +125,19 @@ public:
};
enum GraphemeFlag {
- GRAPHEME_IS_VALID = 1 << 0, // Glyph is valid.
- GRAPHEME_IS_RTL = 1 << 1, // Glyph is right-to-left.
- GRAPHEME_IS_VIRTUAL = 1 << 2, // Glyph is not part of source string (added by fit_to_width function, do not affect caret movement).
+ GRAPHEME_IS_VALID = 1 << 0, // Grapheme is valid.
+ GRAPHEME_IS_RTL = 1 << 1, // Grapheme is right-to-left.
+ GRAPHEME_IS_VIRTUAL = 1 << 2, // Grapheme is not part of source string (added by fit_to_width function, do not affect caret movement).
GRAPHEME_IS_SPACE = 1 << 3, // Is whitespace (for justification and word breaks).
GRAPHEME_IS_BREAK_HARD = 1 << 4, // Is line break (mandatory break, e.g. "\n").
GRAPHEME_IS_BREAK_SOFT = 1 << 5, // Is line break (optional break, e.g. space).
GRAPHEME_IS_TAB = 1 << 6, // Is tab or vertical tab.
- GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), glyph can be duplicated or truncated to fit line to width.
+ GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), grapheme can be duplicated or truncated to fit line to width.
GRAPHEME_IS_PUNCTUATION = 1 << 8, // Punctuation, except underscore (can be used as word break, but not line break or justifiction).
GRAPHEME_IS_UNDERSCORE = 1 << 9, // Underscore (can be used as word break).
GRAPHEME_IS_CONNECTED = 1 << 10, // Connected to previous grapheme.
GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL = 1 << 11, // It is safe to insert a U+0640 before this grapheme for elongation.
+ GRAPHEME_IS_EMBEDDED_OBJECT = 1 << 12, // Grapheme is an object replacement character for the embedded object.
};
enum Hinting {